AngularJS directive to update DOM - html

I want to write a directive that change an image depending on the attribute value "name". The image is shown but it doesn't update when the "name" attribute changes.
HTML:
<img test name="{{finder.name}}"
JS:
.directive("test", function() {
return {
restrict: "A",
scope: {
name: '#'
},
link: function(scope, element, attrs) {
scope.$watch("name", function(value) {
if (angular.isDefined(value))
var replaceName = value.replace(/[ \/]+/g, "_")
.toLowerCase();
var tag = '<img src="/images/banner_' +
replaceName + '.jpg" class="img-responsive"/>'
element.replaceWith(tag);
})
}}}
);
Thanks!

Here is what I did on my simple image directive.. to generate dynamic image source..
link: function(scope, element, attrs) {
// this will replace the value of your image source
var setImagrSRC = function() {
element.attr('src','some source value here..')
}
// this will observe if there is changes on your name directive
// and will trigger the function setImageSRC above
attrs.$observe('name',setImageSRC);
}}}

That's because you overwrite the element with the new element with that replaceWith method. Your directive will destroyed with it. Don't replace the element, just replace the attribute :
link: function(scope, element, attrs){
scope.$watch('name', function(value){
...
var imageUrl = value.replace(...);
element.attr('src', imageUrl);
});
}

Related

Separating ViewValue and ModelValue in Angular for a text input

I need to convert a custom element that takes account numbers as input like ABCD1234 and make it display as ****1234. While saving this value/submitting the form I want the value to go as ABCD1234 ie the true value. I have tried making use of $formatters but it seems to be changing the model value as well and saving the * in the value.
How do I separate the view value and model value so that the users see only last 4 digits but form saves true value entered.
The regex looks for all but last four digit and replaces with *
'use strict';
// #ngdoc directive
// #description
// directive for masking NPI(Non-public Information) inputs with asterisks.
angular.module('MyAPP').directive('npiMask', function() {
return {
require: 'ngModel',
link: function($scope, element, attrs, modelCtrl) {
modelCtrl.$formatters.push(function(inputValue) {
var transformedInput = inputValue.toString().replace(/.(?=.{4,}$)/g, '*');
if (transformedInput !== inputValue) {
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}
return transformedInput;
});
}
};
});
<input-text
name="accountNum"
label="{{'LOAN_REPAY.ADD_LOAN.ACCOUNT_NUM_LABEL' | translate}}"
ng-model="vm.model.formData.loanDetails.accountNum"
is-required="true"
maxlength="35"
locked="vm.isSummaryView"
size="4"
ui-mask
npi-mask
>
Change view value without change model value
^ this did not work for me.
Ended up using this:
'use strict';
// #ngdoc directive
// #description
// directive for masking NPI(Non-public Information) inputs with asterisks.
angular.module('myApp').directive('npiMask', 'constants', function(constants) {
return {
require: 'ngModel',
link: function($scope, element, attrs, modelCtrl) {
var temp = '';
element.on('focusin', function() {
element[0].querySelector('#' + modelCtrl.$name).value = temp;
});
element.on('focusout', function() {
temp = modelCtrl.$modelValue;
var transformedInput = temp.toString().replace(constants.patterns.spacesInTheEnd, '*');
element[0].querySelector('#' + modelCtrl.$name).value = transformedInput;
});
}
};
});

Ng-model input type=file [duplicate]

I tried to use ng-model on input tag with type file:
<input type="file" ng-model="vm.uploadme" />
But after selecting a file, in controller, $scope.vm.uploadme is still undefined.
How do I get the selected file in my controller?
I created a workaround with directive:
.directive("fileread", [function () {
return {
scope: {
fileread: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
var reader = new FileReader();
reader.onload = function (loadEvent) {
scope.$apply(function () {
scope.fileread = loadEvent.target.result;
});
}
reader.readAsDataURL(changeEvent.target.files[0]);
});
}
}
}]);
And the input tag becomes:
<input type="file" fileread="vm.uploadme" />
Or if just the file definition is needed:
.directive("fileread", [function () {
return {
scope: {
fileread: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
scope.$apply(function () {
scope.fileread = changeEvent.target.files[0];
// or all selected files:
// scope.fileread = changeEvent.target.files;
});
});
}
}
}]);
I use this directive:
angular.module('appFilereader', []).directive('appFilereader', function($q) {
var slice = Array.prototype.slice;
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, element, attrs, ngModel) {
if (!ngModel) return;
ngModel.$render = function() {};
element.bind('change', function(e) {
var element = e.target;
$q.all(slice.call(element.files, 0).map(readFile))
.then(function(values) {
if (element.multiple) ngModel.$setViewValue(values);
else ngModel.$setViewValue(values.length ? values[0] : null);
});
function readFile(file) {
var deferred = $q.defer();
var reader = new FileReader();
reader.onload = function(e) {
deferred.resolve(e.target.result);
};
reader.onerror = function(e) {
deferred.reject(e);
};
reader.readAsDataURL(file);
return deferred.promise;
}
}); //change
} //link
}; //return
});
and invoke it like this:
<input type="file" ng-model="editItem._attachments_uri.image" accept="image/*" app-filereader />
The property (editItem.editItem._attachments_uri.image) will be populated with the contents of the file you select as a data-uri (!).
Please do note that this script will not upload anything. It will only populate your model with the contents of your file encoded ad a data-uri (base64).
Check out a working demo here:
http://plnkr.co/CMiHKv2BEidM9SShm9Vv
How to enable <input type="file"> to work with ng-model
Working Demo of Directive that Works with ng-model
The core ng-model directive does not work with <input type="file"> out of the box.
This custom directive enables ng-model and has the added benefit of enabling the ng-change, ng-required, and ng-form directives to work with <input type="file">.
angular.module("app",[]);
angular.module("app").directive("selectNgFiles", function() {
return {
require: "ngModel",
link: function postLink(scope,elem,attrs,ngModel) {
elem.on("change", function(e) {
var files = elem[0].files;
ngModel.$setViewValue(files);
})
}
}
});
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
<h1>AngularJS Input `type=file` Demo</h1>
<input type="file" select-ng-files ng-model="fileArray" multiple>
<code><table ng-show="fileArray.length">
<tr><td>Name</td><td>Date</td><td>Size</td><td>Type</td><tr>
<tr ng-repeat="file in fileArray">
<td>{{file.name}}</td>
<td>{{file.lastModified | date : 'MMMdd,yyyy'}}</td>
<td>{{file.size}}</td>
<td>{{file.type}}</td>
</tr>
</table></code>
</body>
This is an addendum to #endy-tjahjono's solution.
I ended up not being able to get the value of uploadme from the scope. Even though uploadme in the HTML was visibly updated by the directive, I could still not access its value by $scope.uploadme. I was able to set its value from the scope, though. Mysterious, right..?
As it turned out, a child scope was created by the directive, and the child scope had its own uploadme.
The solution was to use an object rather than a primitive to hold the value of uploadme.
In the controller I have:
$scope.uploadme = {};
$scope.uploadme.src = "";
and in the HTML:
<input type="file" fileread="uploadme.src"/>
<input type="text" ng-model="uploadme.src"/>
There are no changes to the directive.
Now, it all works like expected. I can grab the value of uploadme.src from my controller using $scope.uploadme.
I create a directive and registered on bower.
This lib will help you modeling input file, not only return file data but also file dataurl or base 64.
{
"lastModified": 1438583972000,
"lastModifiedDate": "2015-08-03T06:39:32.000Z",
"name": "gitignore_global.txt",
"size": 236,
"type": "text/plain",
"data": "data:text/plain;base64,DQojaWdub3JlIHRodW1ibmFpbHMgY3JlYXRlZCBieSB3aW5kb3dz…xoDQoqLmJhaw0KKi5jYWNoZQ0KKi5pbGsNCioubG9nDQoqLmRsbA0KKi5saWINCiouc2JyDQo="
}
https://github.com/mistralworks/ng-file-model/
This is a slightly modified version that lets you specify the name of the attribute in the scope, just as you would do with ng-model, usage:
<myUpload key="file"></myUpload>
Directive:
.directive('myUpload', function() {
return {
link: function postLink(scope, element, attrs) {
element.find("input").bind("change", function(changeEvent) {
var reader = new FileReader();
reader.onload = function(loadEvent) {
scope.$apply(function() {
scope[attrs.key] = loadEvent.target.result;
});
}
if (typeof(changeEvent.target.files[0]) === 'object') {
reader.readAsDataURL(changeEvent.target.files[0]);
};
});
},
controller: 'FileUploadCtrl',
template:
'<span class="btn btn-success fileinput-button">' +
'<i class="glyphicon glyphicon-plus"></i>' +
'<span>Replace Image</span>' +
'<input type="file" accept="image/*" name="files[]" multiple="">' +
'</span>',
restrict: 'E'
};
});
For multiple files input using lodash or underscore:
.directive("fileread", [function () {
return {
scope: {
fileread: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
return _.map(changeEvent.target.files, function(file){
scope.fileread = [];
var reader = new FileReader();
reader.onload = function (loadEvent) {
scope.$apply(function () {
scope.fileread.push(loadEvent.target.result);
});
}
reader.readAsDataURL(file);
});
});
}
}
}]);
function filesModelDirective(){
return {
controller: function($parse, $element, $attrs, $scope){
var exp = $parse($attrs.filesModel);
$element.on('change', function(){
exp.assign($scope, this.files[0]);
$scope.$apply();
});
}
};
}
app.directive('filesModel', filesModelDirective);
I had to do same on multiple input, so i updated #Endy Tjahjono method.
It returns an array containing all readed files.
.directive("fileread", function () {
return {
scope: {
fileread: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
var readers = [] ,
files = changeEvent.target.files ,
datas = [] ;
for ( var i = 0 ; i < files.length ; i++ ) {
readers[ i ] = new FileReader();
readers[ i ].onload = function (loadEvent) {
datas.push( loadEvent.target.result );
if ( datas.length === files.length ){
scope.$apply(function () {
scope.fileread = datas;
});
}
}
readers[ i ].readAsDataURL( files[i] );
}
});
}
}
});
I had to modify Endy's directive so that I can get Last Modified, lastModifiedDate, name, size, type, and data as well as be able to get an array of files. For those of you that needed these extra features, here you go.
UPDATE:
I found a bug where if you select the file(s) and then go to select again but cancel instead, the files are never deselected like it appears. So I updated my code to fix that.
.directive("fileread", function () {
return {
scope: {
fileread: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
var readers = [] ,
files = changeEvent.target.files ,
datas = [] ;
if(!files.length){
scope.$apply(function () {
scope.fileread = [];
});
return;
}
for ( var i = 0 ; i < files.length ; i++ ) {
readers[ i ] = new FileReader();
readers[ i ].index = i;
readers[ i ].onload = function (loadEvent) {
var index = loadEvent.target.index;
datas.push({
lastModified: files[index].lastModified,
lastModifiedDate: files[index].lastModifiedDate,
name: files[index].name,
size: files[index].size,
type: files[index].type,
data: loadEvent.target.result
});
if ( datas.length === files.length ){
scope.$apply(function () {
scope.fileread = datas;
});
}
};
readers[ i ].readAsDataURL( files[i] );
}
});
}
}
});
If you want something a little more elegant/integrated, you can use a decorator to extend the input directive with support for type=file. The main caveat to keep in mind is that this method will not work in IE9 since IE9 didn't implement the File API. Using JavaScript to upload binary data regardless of type via XHR is simply not possible natively in IE9 or earlier (use of ActiveXObject to access the local filesystem doesn't count as using ActiveX is just asking for security troubles).
This exact method also requires AngularJS 1.4.x or later, but you may be able to adapt this to use $provide.decorator rather than angular.Module.decorator - I wrote this gist to demonstrate how to do it while conforming to John Papa's AngularJS style guide:
(function() {
'use strict';
/**
* #ngdoc input
* #name input[file]
*
* #description
* Adds very basic support for ngModel to `input[type=file]` fields.
*
* Requires AngularJS 1.4.x or later. Does not support Internet Explorer 9 - the browser's
* implementation of `HTMLInputElement` must have a `files` property for file inputs.
*
* #param {string} ngModel
* Assignable AngularJS expression to data-bind to. The data-bound object will be an instance
* of {#link https://developer.mozilla.org/en-US/docs/Web/API/FileList `FileList`}.
* #param {string=} name Property name of the form under which the control is published.
* #param {string=} ngChange
* AngularJS expression to be executed when input changes due to user interaction with the
* input element.
*/
angular
.module('yourModuleNameHere')
.decorator('inputDirective', myInputFileDecorator);
myInputFileDecorator.$inject = ['$delegate', '$browser', '$sniffer', '$filter', '$parse'];
function myInputFileDecorator($delegate, $browser, $sniffer, $filter, $parse) {
var inputDirective = $delegate[0],
preLink = inputDirective.link.pre;
inputDirective.link.pre = function (scope, element, attr, ctrl) {
if (ctrl[0]) {
if (angular.lowercase(attr.type) === 'file') {
fileInputType(
scope, element, attr, ctrl[0], $sniffer, $browser, $filter, $parse);
} else {
preLink.apply(this, arguments);
}
}
};
return $delegate;
}
function fileInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
element.on('change', function (ev) {
if (angular.isDefined(element[0].files)) {
ctrl.$setViewValue(element[0].files, ev && ev.type);
}
})
ctrl.$isEmpty = function (value) {
return !value || value.length === 0;
};
}
})();
Why wasn't this done in the first place? AngularJS support is intended to reach only as far back as IE9. If you disagree with this decision and think they should have just put this in anyway, then jump the wagon to Angular 2+ because better modern support is literally why Angular 2 exists.
The issue is (as was mentioned before) that without the file api
support doing this properly is unfeasible for the core given our
baseline being IE9 and polyfilling this stuff is out of the question
for core.
Additionally trying to handle this input in a way that is not
cross-browser compatible only makes it harder for 3rd party solutions,
which now have to fight/disable/workaround the core solution.
...
I'm going to close this just as we closed #1236. Angular 2 is being
build to support modern browsers and with that file support will
easily available.
Alternatively you could get the input and set the onchange function:
<input type="file" id="myFileInput" />
document.getElementById("myFileInput").onchange = function (event) {
console.log(event.target.files);
};
Try this,this is working for me in angular JS
let fileToUpload = `${documentLocation}/${documentType}.pdf`;
let absoluteFilePath = path.resolve(__dirname, fileToUpload);
console.log(`Uploading document ${absoluteFilePath}`);
element.all(by.css("input[type='file']")).sendKeys(absoluteFilePath);

Default background image with css and angularjs [duplicate]

I am adding background images to my div like this
ng-style="{'background-image' : 'url('+ myvariable.for.image +')'}">
where myvariable.for.image is a url like /examplesite/image/id
This works fine with one exception, if the image is not there it just doesnt do anything and my background looks too bla...If the image doesnt exist I want to be able to replace it with a default image.
But I cant seem to figure out how
Instead of ngStyle I'd use a custom directive for this. Such as the following. This checks to see if an attribute is provided, if so it attempts to load that image. If it loads an image then we set the background image to it, otherwise we use a default image.
myApp.directive('bgImage', function () {
return {
link: function(scope, element, attr) {
attr.$observe('bgImage', function() {
if (!attr.bgImage) {
// No attribute specified, so use default
element.css("background-image","url("+scope.defaultImage+")");
} else {
var image = new Image();
image.src = attr.bgImage;
image.onload = function() {
//Image loaded- set the background image to it
element.css("background-image","url("+attr.bgImage+")");
};
image.onerror = function() {
//Image failed to load- use default
element.css("background-image","url("+scope.defaultImage+")");
};
}
});
}
};
});
Used like this:
<div bg-image="{{person.src}}">
demo fiddle
<div err-bg-src='{{default_business_logo_wd}}' ng-style="{'background-image' : 'url('+ifNull(place.logo_wd,default_business_logo_wd)+')'}" id="perfilEstablecimiento-container10" class="place-header">
<div id="perfilEstablecimiento-container13" class="place-title-container">
<h4 id="perfilEstablecimiento-heading1" class="place-title">{{place.name}}</h4>
</div>
</div>
Using a $timeout inside that custom directive worked for me.
.directive
(
'errBgSrc',
function($timeout)
{
return {
link: function(scope, element, attrs)
{
$timeout
(
function()
{
if(window.getComputedStyle(document.getElementById(attrs.id)).backgroundImage=='none'||window.getComputedStyle(document.getElementById(attrs.id)).backgroundImage==null)
{
document.getElementById(attrs.id).style.backgroundImage='url('+attrs.errBgSrc+')';
}
else
{
var image = new Image();
var style=window.getComputedStyle(document.getElementById(attrs.id)).backgroundImage;
var url=style.slice(5,style.length-2);
image.src = url;
image.onerror = function()
{
document.getElementById(attrs.id).style.backgroundImage='url('+attrs.errBgSrc+')';
};
}
},
500
);
}
}
}
)

ng-repeat within pop-over angular directive

I am trying to use boot-strap pop-over within angular. I have created a directive & trying to attach the content dynamically using $compile. However $compile is not replacing my contents. Here is the fiddle.
http://jsfiddle.net/gurukashyap/4ajpyjyf/
customDirectives = angular.module('customDirectives', []);
function MyCtrl($scope) {
$scope.items = ['abc','dev','it'];
}
customDirectives.directive('custPopover', function ($compile) {
return {
scope : {
items : '=items'
},
restrict: 'A',
template: '<span>Label</span>',
link: function (scope, el, attrs) {
scope.label = attrs.popoverLabel;
var temp = '<ul><li ng-repeat="item in items">{{item}}</li></ul>';
var contents = $compile(temp)(scope);
console.log(scope);
$(el).popover({
trigger: 'click',
html: true,
content: contents,
placement: attrs.popoverPlacement
});
}
};
});
angular.module('CustomComponents', ['customDirectives']);
Any help appreicated
In HTML, you don't rename attribute from "newvar" to "items".
You just passed the parameter wrong. items: '=newvar'
http://jsfiddle.net/4ajpyjyf/2/

How get the text element before angular directive has changed it content

I have a simple span element with some text. I want paste in my html just tag "span" with any text and directive "text-type". Every 100 ms a new symbol will appear. So, first, i have to save span text in some variable and then show text with my directive:
<span data-text-type>Just a text</span>
And I want to change text in my directive
app.directive('textType', function() {
return {
restrict: 'A',
template: '{{newText}}',
scope:{},
link: function(scope, element) {
var text = element.text().split(''); /*will be '{{newText}}', but not 'Just a text'*/
scope.newText = '';
(function fn () {
if (text.length) {
scope.textStr += text.shift();
$timeout(fn, 100);
}
}());
}
}
});
So, how can I save text before the directive will change it?
I find that it was needed use compile function that return link function, and change "scope:{}" to "scope:true" to use isolate scope for each element.
return {
restrict: 'A',
scope:true,
compile: function(element, attrs) {
var string = element.text().split(''),
max = string.length;
element[0].innerHTML ='<span class="tt-text">{{textStr}}</span><span class="tt-heading">{{textHeading}}</span>';
return function(scope, element, attrs, controller) {
var dur = 200;
scope.textStr = '';
scope.textHeading = string[0];
(function fn() {
if (string.length) {
head.stop().fadeOut(0).fadeIn(dur, function() {
scope.textStr += string.shift();
scope.textHeading = string[0];
scope.$apply();
fn();
});
} else {
scope.textHeading = '';
}
}());
}
}
}