Displaying a selected file from an <input> in AngularJS - javascript

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.

Related

How to use injected constants in html file in AngularJS

I have a constant file which looks like this
demo.constant.js
// Add detail constans here
(function () {
angular.module('myApp').constant('TYPE', {
DYNAMIC: 'dynamic',
STATIC: 'static'
});
}());
Now I have a controller file that looks similar to this.
demo.controller.js
(function() {
var DemoController = function(DEP1,DEP2, .... , TYPE)
console.log(TYPE.DYNAMIC); // works perfectly
var self = this;
self.type = '';
...
...
angular.module('myApp.controllers').controller('DemoController', DemoController);
})();
I am trying to access these constants in the HTML file like this:-
<div ng-controller="DemoController as self">
<span ng-if="(self.type === 'dynamic')"> <!--instead of 'dynamic' I want to use TYPE.DYNAMIC-->
...
...
...
</span>
</div>
Note:- {{self.type}} works but {{TYPE.DYNAMIC}} doesn't.
Another problem is that I want to use this constant as the value of radio buttons.
somewhat like this:-
<input type="radio" name="type" ng-model="self.inputType" value="dynamic"> <!-- Here I want to use TYPE.DYNAMIC -->
<input type="radio" name="type" ng-model="self.inputType" value="static"> <!-- Same as above -->
I have searched everywhere but nothing seems to work. Please Help!!
One approach is to assign the constant to a controller property:
function DemoController(DEP1,DEP2, /*.... ,*/ TYPE) {
console.log(TYPE.DYNAMIC); // works perfectly
this.TYPE = TYPE;
}
angular.module('myApp.controllers').controller('DemoController', DemoController)
Then use it in the template:
<div ng-controller="DemoController as $ctrl">
<span ng-if="$ctrl.type === $ctrl.TYPE.DYNAMIC">
...
</span>
</div>
Note: The ng-if directive uses creates a child scope. Consider instead using the ng-show directive which uses CSS and less resources.
You can use $rootScope and initilize it in run phase:
angular.module('app')
.run(function ($rootScope, TYPE) {
$rootScope.TYPE = TYPE
});
then you can use it directly in your HTML

How to add multiple files into a array and access files one by one in AngularJs

I am trying to add multiple files into a file array and later on access the each files in AngularJs.So far i have created a custom directive to read files looking at some posts.it was working fine with a single file.But my requirement is that i want to upload multiple files and add them into array and later on access the created file array when accessing the data.I am newbie to AngularJs please help me to achieve this.
HTML code snippet
<div class="input input-file ">
<span class="button"><input on-read-file="readFileContent($fileContent)"
type="file" id="attachments" name="file" data-ng-model="attachments"
onchange="this.parentNode.nextSibling.value = this.value">Browse</span>
<input type="text" placeholder="Attach some files" readonly="">
</div>
Custom directive for read input files
var mimeType,fileName;
testModule.directive('onReadFile', function ($parse) {
return {
restrict: 'A',
scope: false,
link: function(scope, element, attrs) {
var fn = $parse(attrs.onReadFile);
element.on('change', function(onChangeEvent) {
var reader = new FileReader();
reader.onload = function(onLoadEvent) {
scope.$apply(function() {
fn(scope, {$fileContent:onLoadEvent.target.result});
});
};
reader.readAsText((onChangeEvent.srcElement || onChangeEvent.target).files[0]);
//Get the Uploaded file mime type
mimeType=(onChangeEvent.srcElement || onChangeEvent.target).files[0].type;
fileName=(onChangeEvent.srcElement || onChangeEvent.target).files[0].name;
});
}
};
});
Read File Content Method
$scope.readFileContent = function($fileContent){
$scope.content = $fileContent;
};
I suggest to check this post on StackOverflow:
Upload multiple files in angular
One of the answers contains this working example:
http://plnkr.co/edit/B13t84j5IPzINMh1F862?p=preview
It uses a directive named ng-file-model:
<input type="file" ng-file-model="files" multiple />

Angular two-way binding

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>

Angularjs- adding/removing dynamic html elements (dropdown)

here is my code-
http://plnkr.co/edit/oTWXbLIKOxoGTd4U0goD?p=preview
why is the days dropdown does not data bind with scope.demoDays, it is always empty?
is this the correct way to add dropdown dynamically? If user adds 5 dropdown, how to get the results , will ng-model="selectedDay" create an array of selection? any suggestions?
Thank you
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $compile) {
var counter = 0;
$scope.fields = [];
$scope.days =['Day','Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
$scope.addField = function() {
$scope.fields.push({name:"test " + counter++});
};
});
app.directive('demoDisplay', function($compile){
return {
scope:{
demoDisplay:"=", //import referenced model to our directives scope
demoDays:"="
},
link:function (scope, elem, attr, ctrl)
{
scope.$watch('demoDisplay', function(){ // watch for when model changes
elem.html("") //remove all elements
angular.forEach(scope.demoDisplay, function(d){ //iterate list
var s = scope.$new(); //create a new scope
angular.extend(s,d); //copy data onto it
console.log(scope.demoDays);
var template = '<label class="item item-input"><div class="style-select"><select ng-model="selectedDay" ng-options="day for day in scope.demoDays"></select><br></div></label>';
elem.append($compile(template)(s)); // compile template & append
});
}, true) //look deep into object
}
}
})
html
<button ng-click="addField()">Add Field</button>
<div demo-display="fields" demo-days="days"></div>
There is no need for $watch in your link function - you have already established two-way binding by specifying = on your scope property. And you can use a plain template, without having to compile.
templateUrl: 'template.html',
where template.html is:
<label class="item item-input">
<div class="style-select">
<select ng-model="demoDisplay.selection" ng-options="day for day in demoDays"></select>
<br>
</div>
</label>
Notice that the select is bound to demoDisplay.selection, which will be created on each field and be accessible on the parent scope via two-way binding. Also, note that within ng-options, I changed scope.demoDays to just demoDays. In a directive's template you only need to use the property's name to access a scope value.
You can use the directive inside ng-repeat to create additional fields when the button is clicked:
<div ng-repeat="field in data.fields">
<div demo-display="field" demo-days="days"></div>
</div>
Here is a working plunker: http://plnkr.co/edit/pOY0l18W7wEbfSU7DKw2?p=preview
Any easy fix to get it working.
In your var template you have scope.demoDays.
Simply change this to demoDays. You are already in this scope so using it again isn't necessary.

AngularJS ng-model in template passed to directive controller

I've got a directive with a controller, that builds a form for posting comments to an API via CommentsService
My directive looks a bit lik this:
app.directive('appComments', function( CommentService ) {
return {
restrict: 'E',
scope: {
event: '='
},
controller: function( $rootScope, $scope, $element ) {
$scope.comments = [];
$scope.comment_text = '';
// load comments if event ID has changed
$scope.$watch( 'event', function() {
if( typeof $scope.event != 'undefined' ) {
CommentService.get( $scope.event ).then(
function( comments ) {
$scope.comments = comments;
}
);
}
});
// post comment to service
$scope.postComment = function() {
if( $scope.comment_text != '' ) {
CommentService.post(
$scope.event,
$scope.comment_text,
function() {
// code to reload comments
}
);
}
};
},
templateUrl: '/partials/comments.html'
};
});
This is my comments.html for the directive
<div class="event-comments">
<p ng-if="!comments.length">
<span>This event has no comments.</span>
</p>
<div
class="event-comment"
ng-repeat="comment in comments"
>
<div class="comment-text">{{comment.text}}</div>
</div>
</div>
<div class="insert-comment-container" ng-if="!loading">
<form ng-submit="postComment()">
<textarea
ng-model="comment_text"
></textarea>
<div
ng-tap="postComment()"
>Post</div>
</div>
</div>
This is how I'm placing it in my main view:
<app-comments event="event.id"></app-comments>
My comments are loading, and the event id is getting passed, but when I try and post a comment the comment_text is blank.
I think I'm getting my scopes mixed up or something, I'm still not completely clear on directives
** update **
I've just realised if I set
$scope.comment_text = 'Initial text'
in the directive, it appears when the template renders inside the textarea, and the if statement in the postComments() function fires. But if I change the text in the textarea, and tap the post button, the contents of $scope.comment_text is still "Initial text", it seems to be a one way binding.
Since you are using form i believe it creates a new scope scope. As per documentation
If the name attribute is specified, the form controller is published
onto the current scope under this name.
Try to give your form a name. Or else try to pass the property as object property like
<textarea
ng-model="comment.comment_text">
</textarea>
Ok so by changing comment_text to comment.text it solved the problem, as recommended by this SO answer:
angular-bootstrap (tabs): data binding works only one-way
Using an object instead of a primitive just uses the same reference to the object property, instead of copying the primitive into the new scope.

Categories

Resources