Angular two-way binding - javascript

I have an Angular directive, which reads in the contents of a file <input> and populates a form. Currently, it seems to update the $scope variables within the controller, but the form elements bound to those $scope variables are not being updated in the view. Why is this?
The code snippets below are as follows:
my directive, which i use to read in from a file <input> and call the controller's $scope['modify_form'], which allows me to update the $scope variables corresponding to my form.
the HTML (view-side) for my directive
my controller logic for $scope['modify_form'], which calls a bunch of helper functions, one of which is shown below.
a snippet of the HTML of my view for my form; this is where the fundamental problem lies, since it does not get updated when i call $scope['modify_form']
Here is my directive, for reading in content:
app.directive('modifyBtn', function($compile) {
return {
restrict: 'E',
scope: {
modifyJSON:'&modifyJson',
},
link: function(scope, element, attrs) {
var fileElem = angular.element(element.find("#file_input2"));
console.log(fileElem);
var showUpload = function() {
// display file input element
var file = fileElem[0].files[0];
var reader = new FileReader();
reader.readAsText(file);
reader.onload = function(e) {
var uploadedJSON;
try {
uploadedJSON = JSON.parse(reader.result);
} catch(e) {
// should display help block (or warning)
return;
}
console.log(uploadedJSON); // #debug
for (var key in uploadedJSON) { // only one key
if (uploadedJSON.hasOwnProperty(key)) {
sessionStorage[MODEL] = key;
sessionStorage[NAMESPACE] = uploadedJSON[key][NAMESPACE]
var fields = [FIELDS, LINKS, PRIMARY_KEY, TABLE, SQL, VALIDATIONS];
fields.forEach(function(field) {
if (uploadedJSON[key].hasOwnProperty(field)) {
sessionStorage[field] = JSON.stringify(uploadedJSON[key][field]);
}
});
// trigger modification without reloading page
scope.modifyJSON();
}
}
}
};
$(fileElem).on('change', showUpload);
}
}
});
My view (HTML), for the directive is as follows:
<modify-btn modify-json="modifyForm()">
<li class="active">
<span class="btn creation-btn" style="background-color: #d9534f;" ng-click="fileshow = true">
Upload JSON
</span>
</li>
<li><input type="file" id="file_input2" ng-show="fileshow" /></li>
</modify-btn>
In my controller, here is where I update the $scope variables bound to the form:
$scope['modifyForm'] = function() { // filling in elements if reading in JSON
modify_data();
modify_fields();
modify_links();
modify_pkeys();
modify_table();
modify_sql();
modify_validations();
sessionStorage.clear();
}
function modify_data() {
var overall = [MODEL, NAMESPACE];
overall.forEach(function(elem) {
if (exist(sessionStorage[elem])) {
$scope['data'][elem] = sessionStorage[elem];
}
});
}
And, here, in the view is how my form elements are bound.
<div class="form-group">
<label class="col-sm-4">{{myConstants["model"]}}</label>
<div class="col-sm-6">
<input class="form-control" type="text" ng-model="data[myConstants['model']]" id="model" />
</div>
</div>
<div class="form-group">
<label class="col-sm-4">{{myConstants["namespace"]}}</label>
<div class="col-sm-6">
<input class="form-control" type="text" ng-model="data[myConstants['namespace']]" id="namespace" />
</div>
</div>

Related

Displaying a selected file from an <input> in AngularJS

I have a working example using standard Javascript, but I'd like to make this work more natively with AngularJS.
Specifically, I need to update the span with the filename of the file selected by the user.
Here's what I implemented using native Javascript:
<span>
<input ng-model="uploadDownloads" type="file" style="visibility:hidden; width: 1px;" id=uploadDownloads name=uploadDownloads onchange="$(this).parent().find('span').html($(this).val().replace('C:\\fakepath\\', ''))" /> <!-- Chrome security returns 'C:\fakepath\' -->
<input class="btn btn-primary" type="button" value="choose file" onclick="$(this).parent().find('input[type=file]').click();"/> <!-- on button click fire the file click event -->
<span class="badge badge-important" ></span>
</span>
The filereader function is in angular already :
$scope.add = function(valid){
if(valid){
$scope.data = 'none';
var f = document.getElementById('uploadDownloads').files[0];
var r = new FileReader();
r.onloadend = function(e){
$scope.data = e.target.result;
$scope.notPass = false;
$modalInstance.close({
'data':$scope.data,
'fileName':$scope.fileName,
'fileExplain':$scope.fileExplain
});
};
/*activate the onloadend to catch the file*/
r.readAsBinaryString(f);
} else {
$scope.notPass = true;
}
};
The problem is to activate the onclick and the onchange with Angular instead the JavaScript so that my <span> gets updated with the selected filename.
This question builds upon an existing question and answer. Specifically, however, I have modified the code from that answer to accomodate what appears to be the specific question here, which is how do you update a <span> to have the filename selected by a user in a way that's idiomatic to angularjs.
Here's a codepen with a working sample.
Here's the relevant part of the html file:
<body ng-controller="AppController">
<input ng-model="uploadDownloads" type="file" fd-input file-name="fileName"/>
<span class="badge badge-important">Output here: {{fileName}}</span>
</body>
What's key here is that you have a custom directive called fd-input that has a two-way binding to an attribute it defines called file-name. You can pass one of your $scope variables into that attribute and the directive will bind the filename to it. Here's the controller and the directive.
(function() {
'use strict';
angular.module('app', [])
.controller('AppController', AppController)
.directive('fdInput', fdInput);
function AppController($scope) {
$scope.fileName = '';
}
function fdInput() {
return {
scope: {
fileName: '='
},
link: function(scope, element, attrs) {
element.on('change', function(evt) {
var files = evt.target.files;
console.log(files[0].name);
console.log(files[0].size);
scope.fileName = files[0].name;
scope.$apply();
});
}
}
};
})();
As mentioned above, the directive is taken directly from another SO answer. I have modified it to add a scope that does a two way binding to a file-name attribute:
...
return {
scope: {
fileName: '='
},
...
I then assign files[0].name to the two-way binding:
...
scope.fileName = files[0].name;
scope.$apply();
...
Checkout the codepen. That should do it. You could just use the parent scope in the directive, but that's not a good idea as it limits you to using this directive once per controller. Also, if you want to list multiple files, you'll have to update this code to return an array of those files instead.
Hope this help.

How to pass focus to a new field as soon as it appears (angular 1.4)?

In the following example a new field is added (by adding a blank row to $scope) when the last field loses focus if it is not empty. The problem is that the new field is not added to the DOM in time to receive focus.
Is there a way to detect when angular has finished appending new field to the DOM and then pass focus to it?
Please, no "timer" solutions; the time it takes to change DOM is unknown and I need this focus switch to happen as fast as possible. We can do better!
JSFiddle
HTML
<div ng-app='a' ng-controller='b'>
<input type="text" ng-repeat="row in rows" ng-model="row.word" ng-model-options="{'updateOn': 'blur'}">
</div>
JS
angular.module('a', []).controller('b', function ($scope) {
$scope.rows = [{'word': ''}];
$scope.$watch('rows', function (n, o) {
var last = $scope.rows[$scope.rows.length - 1];
last.word && $scope.rows.push({'word': ''});
}, true);
});
This is a View-concern and so should be dealt with by using directives.
One way to do so, is to create a directive that grabs the focus when it's linked:
.directive("focus", function(){
return {
link: function(scope, element){
element[0].focus();
}
}
});
and use it like so:
<input type="text"
ng-repeat="row in rows"
ng-model="row.word"
focus>
Demo
Use $timeout without specifying a number of milliseconds. It will, by default, run after the DOM loads, as mentioned in the answer to this question.
angular.module('a', []).controller('b', function($scope, $timeout) {
$scope.rows = [{
'word': ''
}];
$scope.addRow = function() {
$scope.rows.push({
'word': ''
});
$timeout(function() {
//DOM has finished rendering
var inputs = document.querySelectorAll('input[type="text"]');
inputs[inputs.length - 1].focus();
});
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='a' ng-controller='b'>
<div ng-repeat="row in rows">
<input type="text" ng-model="row.word" ng-model-options="{'updateOn': 'blur'}"><br>
</div>
<input type="button" ng-click="addRow()" value="Add Row">
</div>

Persisting form data when user navigates away from page in Angular

I would like to persist the data entered in a form so that the information entered will still display in the respective fields if the user clicks the back button and then subsequently returns to the form. I've tried using this Stack Overflow answer as a model, but am not having any luck: https://stackoverflow.com/a/16806510/640508
I'm using the Controller As syntax.
Here's my adapted code:
Controller:
angular.module('myApp')
.controller('ContactFormCtrl', ['formMemory', '$http', function (formMemory, $http) {
var contactForm = this;
contactForm.contact=formMemory;
formMemory.set();
formMemory.get();
// . . .
}]);
Service:
angular.module('formMemory.fact', [])
.factory('formMemory', function () {
var contact = {};
return {
get: function () {
return contact;
},
set: function (value) {
contact = value;
},
reset: function () {
contact = {};
}
};
HTML:
<h1><small>ContactInformation</small></h1>
<form name="myForm" novalidate >
<div class="row">
<div class="col-sm-4 form-group">
<label class="control-label" for="first-name">First Name</label>
<input type="text" id="first-name" name="firstName" ng-model="contactForm.contact.firstName"
placeholder="First Name" class="form-control">
</div>
// . . .
app.js:
angular.module('myApp', [
'formMemory.fact',
//. . .
]);
The factory formMemory returns an anonymous object, with 3 functions attached. You aren't using the correct syntax for accessing these functions.
To access the saved data, you would want to set your controller variable to the return value of the get() function, like so:
contactForm.contact = formMemory.get();
and to save the data if you navigate away, you should be passing the contact in as a parameter to the set(value); most likely, you would do this in the $routeChangeStart.
$scope.$on('$routeChangeStart', function() {
//we are leaving the page, so let's save any data we have
formMemory.set(contactForm.contact);
}

angular: Validate multiple dependent fields

Let's say I have the following (very simple) data structure:
$scope.accounts = [{
percent: 30,
name: "Checking"},
{ percent: 70,
name: "Savings"}];
Then I have the following structure as part of a form:
<div ng-repeat="account in accounts">
<input type="number" max="100" min="0" ng-model="account.percent" />
<input type="text" ng-model="account.name" />
</div>
Now, I want to validate that the percents sum to 100 for each set of accounts, but most of the examples I have seen of custom directives only deal with validating an individual value. What is an idiomatic way to create a directive that would validate multiple dependent fields at once? There are a fair amount of solutions for this in jquery, but I haven't been able to find a good source for Angular.
EDIT: I came up with the following custom directive ("share" is a synonym for the original code's "percent").
The share-validate directive takes a map of the form "{group: accounts, id: $index}" as its value.
app.directive('shareValidate', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
params = angular.copy(scope.$eval(attr.shareValidate));
params.group.splice(params.id, 1);
var sum = +viewValue;
angular.forEach(params.group, function(entity, index) {
sum += +(entity.share);
});
ctrl.$setValidity('share', sum === 100);
return viewValue;
});
}
};
});
This ALMOST works, but can't handle the case in which a field is invalidated, but a subsequent change in another field makes it valid again. For example:
Field 1: 61
Field 2: 52
If I take Field 2 down to 39, Field 2 will now be valid, but Field 1 is still invalid. Ideas?
Ok, the following works (again, "share" is "percent"):
app.directive('shareValidate', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
scope.$watch(attr.shareValidate, function(newArr, oldArr) {
var sum = 0;
angular.forEach(newArr, function(entity, i) {
sum += entity.share;
});
if (sum === 100) {
ctrl.$setValidity('share', true);
scope.path.offers.invalidShares = false;
}
else {
ctrl.$setValidity('share', false);
scope.path.offers.invalidShares = true;
}
}, true); //enable deep dirty checking
}
};
});
In the HTML, set the attribute as "share-validate", and the value to the set of objects you want to watch.
You can check angularui library (ui-utility part). It has ui-validate directive.
One way you can implement it then is
<input type="number" name="accountNo" ng-model="account.percent"
ui-validate="{overflow : 'checkOverflow($value,account)' }">
On the controller create the method checkOverflow that return true or false based on account calculation.
I have not tried this myself but want to share the idea. Read the samples present on the site too.
I have a case where I have a dynamic form where I can have a variable number of input fields on my form and I needed to limit the number of input controls that are being added.
I couldn't easily restrict the adding of these input fields since they were generated by a combination of other factors, so I needed to invalidate the form if the number of input fields exceeded the limit. I did this by creating a reference to the form in my controller ctrl.myForm, and then each time the input controls are dynamically generated (in my controller code), I would do the limit check and then set the validity on the form like this: ctrl.myForm.$setValidity("maxCount", false);
This worked well since the validation wasn't determined by a specific input field, but the overall count of my inputs. This same approach could work if you have validation that needs to be done that is determined by the combination of multiple fields.
For my sanity
HTML
<form ng-submit="applyDefaultDays()" name="daysForm" ng-controller="DaysCtrl">
<div class="form-group">
<label for="startDate">Start Date</label>
<div class="input-group">
<input id="startDate"
ng-change="runAllValidators()"
ng-model="startDate"
type="text"
class="form-control"
name="startDate"
placeholder="mm/dd/yyyy"
ng-required
/>
</div>
</div>
<div class="form-group">
<label for="eEndDate">End Date</label>
<div class="input-group">
<input id="endDate"
ng-change="runAllValidators()"
ng-model="endDate"
type="text"
class="form-control"
name="endDate"
placeholder="mm/dd/yyyy"
ng-required
/>
</div>
</div>
<div class="text-right">
<button ng-disabled="daysForm.$invalid" type="submit" class="btn btn-default">Apply Default Dates</button>
</div>
JS
'use strict';
angular.module('myModule')
.controller('DaysCtrl', function($scope, $timeout) {
$scope.initDate = new Date();
$scope.startDate = angular.copy($scope.initDate);
$scope.endDate = angular.copy($scope.startDate);
$scope.endDate.setTime($scope.endDate.getTime() + 6*24*60*60*1000);
$scope.$watch("daysForm", function(){
//fields are only populated after controller is initialized
$timeout(function(){
//not all viewalues are set yet for somereason, timeout needed
$scope.daysForm.startDate.$validators.checkAgainst = function(){
$scope.daysForm.startDate.$setDirty();
return (new Date($scope.daysForm.startDate.$viewValue)).getTime() <=
(new Date($scope.daysForm.endDate.$viewValue)).getTime();
};
$scope.daysForm.endDate.$validators.checkAgainst = function(){
$scope.daysForm.endDate.$setDirty();
return (new Date($scope.daysForm.startDate.$viewValue)).getTime() <=
(new Date($scope.daysForm.endDate.$viewValue)).getTime();
};
});
});
$scope.runAllValidators = function(){
//need to run all validators on change
$scope.daysForm.startDate.$validate();
$scope.daysForm.endDate.$validate();
};
$scope.applyDefaultDays = function(){
//do stuff
}
});
You can define a single directive that is only responsible for this check.
<form>
<div ng-repeat="account in accounts">
<input type="number" max="100" min="0" ng-model="account.percent" />
<input type="text" ng-model="account.name" />
</div>
<!-- HERE IT IS -->
<sum-up-to-hundred accounts="accounts"></sum-up-to-hundred>
</form>
And here's the simple directive's code.
app.directive('sumUpToHundred', function() {
return {
scope: {
accounts: '<'
},
require: {
formCtrl: '^form'
},
bindToController: true,
controllerAs: '$ctrl',
controller: function() {
var vm = this;
vm.$doCheck = function(changes) {
var sum = vm.accounts.map((a)=> a.percent).reduce((total, n)=> total + n);
if (sum !== 100) {
vm.formCtrl.$setValidity('sumuptohundred', false);
} else {
vm.formCtrl.$setValidity('sumuptohundred', true);
}
};
}
};
});
Here's a plunker.

Sending POST with hidden <input> value does't work in AngularJs

In my web app, There are many form on a page. I want to submit it with AngularJS for specific form.
In each of form, it need unique ID with Hidden Value to submit. But value="UNIQUE_ID" seen doesn't work in hidden input box in AngularJS.
My HTML
<div ng-app>
<div ng-controller="SearchCtrl">
<form class="well form-search">
<input type="text" ng-model="keywords" name="qaq_id" value="UNIQUE_ID">
<pre ng-model="result">
{{result}}
</pre>
<form>
</div>
</div>
This is js script
function SearchCtrl($scope, $http) {
$scope.url = 'qa/vote_up'; // The url of our search
// The function that will be executed on button click (ng-click="search()")
$scope.search = function() {
// Create the http post request
// the data holds the keywords
// The request is a JSON request.
$http.post($scope.url, { "data" : $scope.keywords}).
success(function(data, status) {
$scope.status = status;
$scope.data = data;
$scope.result = data; // Show result from server in our <pre></pre> element
})
.
error(function(data, status) {
$scope.data = data || "Request failed";
$scope.status = status;
});
};
}
It may be that the only reason your code is not working is that $scope.keywords is a simple variable (with a text value) instead of an Object, which is required - see http://docs.angularjs.org/api/ng.$http#Usage
As angularJS works with variables within its own scope - its models, a form becomes just a way to interact with those models, wich can be sent via whatever method you want.
You can have a hidden field, yes, but in angularJS it isn't even necessary. You only need that information to be defined in the controller itself - randomly generated for each instance, or received from some other source.. Or you can define it yourself, upon the loading of the controller, for instance.
So (and only for sake of clarity) if you define a formData variable within your formCtrl:
Your HTML:
<div ng-app>
<div ng-controller="SearchCtrl">
<form class="well form-search">
<input type="text" ng-model="formData.title">
<input type="textarea" ng-model="formData.body">
<button ng-click="sendData()">Send!</button>
</form>
<pre ng-model="result">
{{result}}
</pre>
</div>
</div>
And your controller:
function SearchCtrl($scope, $http) {
$scope.url = 'qa/vote_up'; // The url of our search
// there is a formData object for each instance of
// SearchCtrl, with an id defined randomly
// Code from http://stackoverflow.com/a/1349426/1794563
function makeid()
{
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ )
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
$scope.formData = {
title: "",
text: ""
};
$scope.formData.id = makeid();
// The function that will be executed on button click (ng-click="sendData()")
$scope.sendData = function() {
// Create the http post request
// the data holds the keywords
// The request is a JSON request.
$http.post($scope.url, { "data" : $scope.formData}).
success(function(data, status) {
$scope.status = status;
$scope.data = data;
$scope.result = data; // Show result from server in our <pre></pre> element
})
.
error(function(data, status) {
$scope.data = data || "Request failed";
$scope.status = status;
});
};
}
Also: If you wanted to set the unique id on the html itself, you could add an input type="hidden" and set it's ng-model attribute to formData.id, and whichever value you set it to, the model would have it binded. using a hidden input won't work, as the value attribute doesn't update the angularJS Model assigned via ng-model. Use ng-init instead, to set up the value:
HTML with 2 forms:
<div ng-controller="SearchCtrl" ng-init="formData.id='FORM1'">
<form class="well form-search">
<input type="text" ng-model="formData.title">
<input type="textarea" ng-model="formData.body">
<button ng-click="sendData()">Send!</button>
</form>
</div>
<div ng-controller="SearchCtrl" ng-init="formData.id='FORM2'">
<form class="well form-search">
<input type="text" ng-model="formData.title">
<input type="textarea" ng-model="formData.body">
<button ng-click="sendData()">Send!</button>
</form>
</div>
You can add a hidden field, but it accomplishes nothing - the ng-init attribute does everything you need.

Categories

Resources