I am pretty new to AngularJS and I am stuck on a project for a client. I need to have multiple textareas on a page and have the last active one populate with text when I press a button. I found a plunker that does what I need with an input and a single textarea but when I add an extra textarea, I either end up populating both textareas or just one.
Plunker that I'm trying to edit:
var app = angular.module('plunker', []);
app.controller('MyCtrl', function($scope, $rootScope) {
$scope.items = [];
$scope.add = function() {
$scope.items.push($scope.someInput);
$rootScope.$broadcast('add', $scope.someInput);
}
});
app.directive('myText', ['$rootScope',
function($rootScope) {
return {
link: function(scope, element, attrs) {
$rootScope.$on('add', function(e, val) {
var domElement = element[0];
if (document.selection) {
domElement.focus();
var sel = document.selection.createRange();
sel.text = val;
domElement.focus();
} else if (domElement.selectionStart || domElement.selectionStart === 0) {
var startPos = domElement.selectionStart;
var endPos = domElement.selectionEnd;
var scrollTop = domElement.scrollTop;
domElement.value = domElement.value.substring(0, startPos) + val + domElement.value.substring(endPos, domElement.value.length);
domElement.focus();
domElement.selectionStart = startPos + val.length;
domElement.selectionEnd = startPos + val.length;
domElement.scrollTop = scrollTop;
} else {
domElement.value += val;
domElement.focus();
}
});
}
}
}
])
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>
document.write('<base href="' + document.location + '" />');
</script>
<link href="style.css" rel="stylesheet" />
<script data-semver="1.2.10" src="http://code.angularjs.org/1.2.10/angular.js" data-require="angular.js#1.2.x"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-controller="MyCtrl">
<input ng-model="someInput">
<button ng-click="add()">Add</button>
<p ng-repeat="item in items">Created {{ item }}</p>
</div>
<textarea my-text="">
</textarea>
</body>
</html>
I'm not sure if I need to keep the $on that's in the fiddle or use $watch instead. Also, I think I should be using document.activeElement somewhere in there or not. Any help or snippets explaining what to be done would be much appreciated
A quick prototype, if this match what you are looking for, please note that it is only a draft and should be refactored properly. I would refrain from using DOM element attribute too much in Angular and injecting rootScope just for one simple boardcasting is somewhat overkill.
<html>
<head>
</head>
<body ng-app="App" ng-controller="MyCtrl">
<input type="text" ng-model="input">
<span ng-bind="input"></span>
<button ng-click="populate()">Populate</button>
<textarea my-text></textarea>
<textarea my-text></textarea>
<textarea my-text></textarea>
<textarea my-text></textarea>
<script src="angular.js"></script>
<script>
(function(){
angular.module("App",[])
.value('TextManager', {
lastActive: -1,
texts: []
})
.controller('MyCtrl', function($scope, TextManager){
$scope.input = '';
$scope.populate = function(){
console.log(TextManager.texts[TextManager.lastActive]);
if (TextManager.texts[TextManager.lastActive])
TextManager.texts[TextManager.lastActive][0].value += $scope.input;
};
})
.directive("myText", function(TextManager){
return {
restrict: "A",
scope: [],
link: function(scope, element, attr){
TextManager.texts.push(element);
scope.index = TextManager.texts.length-1;
element.index = scope.index;
element.on('click', function(event){
TextManager.lastActive = scope.index;
});
}
};
});
})();
</script>
</body>
</html>
Related
We are trying to use a jquery time entry component in angularjs. We have a directive for the time entry component. When I try to change the ngModel from directive based on DOM value the model is not reflected. Please see the controller, index.html, app.js below.
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link rel="stylesheet" href="style.css" />
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="jquery.plugin.js"></script>
<script src="jquery.timeentry.js"></script>
<script data-require="angular.js#1.0.x" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js" data-semver="1.0.8"></script>
<script src="script.js"></script>
</head>
<body ng-controller="MainCtrl">
<p>Time {{orderPermitViews[0].permitDetails[0].effectiveTimeForDispaly}}!</p>
<div ng-repeat="permitDetail in orderPermitViews[0].permitDetails">
<input type="text" name="spinner" class="time-spinner"
time-spinner ng-model="permitDetail.effectiveTimeForDispaly" >
</div>
</body>
</html>
angular.module('plunker', [])
.directive('timeSpinner', function() {
return {
restrict: 'A',
scope: {timeModel: "=ngModel"},
link: function( scope, elem, attrs) {
$(elem).timeEntry({
spinnerImage: 'assets/img/spinnerUpDown.png',
spinnerSize: [15, 16, 0],
spinnerBigSize: [30, 32, 0],
spinnerIncDecOnly: true,
show24Hours: true
}).change(function () {
scope.timeModel = $(elem).timeEntry('getTime');
console.log("Time model:" + scope.timeModel);
});
scope.$watch('timeModel', function(newValue, oldValue) {
$(elem).timeEntry('setTime', newValue);
console.log("Set time:" + newValue);
});
}
}
})
.controller('MainCtrl', function($scope) {
var permitDetails = new Array();
$scope.orderPermitViews = new Array();
$scope.orderPermitViews[0] = {};
$scope.orderPermitViews[0].permitDetails = new Array();
$scope.orderPermitViews[0].permitDetails[0] = {};
$scope.orderPermitViews[0].permitDetails[0].effectiveTimeForDispaly = new Date();
});
When I change the text, I see the change function getting executed and update the model. However it is not reflected in the HTML {{orderPermitViews[0].permitDetails[0].effectiveTimeForDispaly}}. I need some help in figuring out what is going wrong here.
I want to write filter on array inside directive controller with validation message. I am working on file upload control where user can select multiple files, once user select files I need to show all files in a table with delete option.
I need few validations like I need to show message to user if user selects already selected attachment and file size validations.
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link href="style.css" rel="stylesheet" />
<script data-semver="1.4.3" src="https://code.angularjs.org/1.4.3/angular.js" data-require="angular.js#1.4.x"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<input type="file" ng-file-model="files" multiple />
<button type="button" ng-click="upload()">Upload</button>
<p ng-repeat="file in files">
{{file.name}}
<button type="button" class="btn btn-danger btn-xs" data="{{file.name}}" ng-click="remove(file.name)">
<span class="glyphicon glyphicon-trash">Remove</span>
</button>
</p>
</body>
</html>
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.files = [];
$scope.upload=function(){
alert($scope.files.length+" files selected ... Write your Upload Code");
};
$scope.remove=function(fileName){
alert(fileName);
for(var i=0;i<$scope.files.length;i++) {
if($scope.files[i].name === fileName) {
$scope.files.splice(i,1);
break;
}
}
// $scope.files.splice(0,1);
console.log($scope.files[0]._file);
console.log($scope.files.length);
};
});
app.directive('ngFileModel', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var model = $parse(attrs.ngFileModel);
var isMultiple = attrs.multiple;
var modelSetter = model.assign;
var values = [];
element.bind('change', function () {
angular.forEach(element[0].files, function (item) {
var value = {
// File Name
name: item.name,
//File Size
size: item.size,
//File URL to view
url: URL.createObjectURL(item),
// File Input Value
_file: item
};
values.push(value);
});
scope.$apply(function () {
if (isMultiple) {
modelSetter(scope, values);
} else {
modelSetter(scope, values[0]);
}
});
});
}
};
}]);
In above code I want to inject filter for duplicate checking when I am pushing file data to files object.
Please guide me best way to achieve this.
Thanks in advance
I'm going to answer your title question rather then your end goal, since it seems you are simply trying to get a $filter object into your directive. You can inject it like so:
app.directive('someDirectiveName',['$parse','$filter',function($parse,$filter) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$filter(filter)('yada','yada');
}
}
}]);
Quite straightforward, but I am fairly new to angular, and I think I have bitwise-sick, so I am kinda stuck :
// Checkboxes control existence of value in an array
var app = angular.module('myApp', []);
app.controller('MainController', function($scope) {
$scope.bitMaskFromDB = 5;
$scope.fruits = [{value:1, label: 'apple'}, {value:2, label:'orange'}, {value:4, label:'pear'}, {value:8, label:'naartjie'}];
});
app.directive('bitMask', function() {
return {
scope: {
bitMask: '=',
value: '#'
},
link: function(scope, elem, attrs ) {
var handler = function(setup) {
if (setup){
var checked = scope.bitMask&scope.value;
elem.prop('checked', checked);
}else{
var checked = elem.prop('checked');
if (!scope.bitMask&scope.value)
scope.bitMask |= scope.value
// elem.prop('checked', !checked);
scope.bitMask ^= scope.value
}
console.log ('bit = '+ scope.bitMask)
// console.log ('value = '+ scope.value)
// console.log ('checked ='+ checked)
};
var setupHandler = handler.bind(null, true);
var changeHandler = handler.bind(null, false);
elem.on('change', function() {
scope.$apply(changeHandler);
});
scope.$watch('bitMask', setupHandler, true);
}
};
});
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8">
<title>Custom Plunker</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js"></script>
<link rel="stylesheet" href="style.css">
<script>
document.write('<base href="' + document.location + '" />');
</script>
<script src="app.js"></script>
</head>
<body ng-app="myApp" ng-controller='MainController'>
Bitmask checkboxes <br>
<span ng-repeat="fruit in fruits">
<input type='checkbox' value="{{fruit.value}}" bit-mask='bitMaskFromDB' > {{fruit.label}}<br />
</span>
<div>Current value of bitmask: {{bitMaskFromDB }}</div>
</body>
</html>
I failed to
1/ change the bit mask value correctly to checkbox values and
2/ show/bind value back to controller
http://plnkr.co/edit/3M8KNyOxMBgPwj9jf01q
You are very close my friend.
Here is the fix for you. Well, I don't really remember what they said about that. But there are weird behavior binding primitive to scope
updated your plkr
// Checkboxes control existence of value in an array
var app = angular.module('myApp', []);
app.controller('MainController', function($scope) {
$scope.IforgetHowTheyExplainThis = {bitMaskFromDB:5};
$scope.fruits = [{value:1, label: 'apple'}, {value:2, label:'orange'}, {value:4, label:'pear'}, {value:8, label:'naartjie'}];
});
app.directive('bitMask', function() {
return {
scope: {
bitMask: '=',
value: '#'
},
link: function(scope, elem, attrs ) {
var handler = function(setup) {
if (setup){
console.log ('value1 = '+ scope.value)
var checked = scope.bitMask&scope.value;
elem.prop('checked', checked);
}else{
console.log ('value2 = '+ scope.value)
var checked = elem.prop('checked');
if (!scope.bitMask&scope.value)
scope.bitMask |= scope.value
else
scope.bitMask ^= scope.value
}
console.log ('bit = '+ scope.bitMask)
// console.log ('value = '+ scope.value)
// console.log ('checked ='+ checked)
};
var setupHandler = handler.bind(null, true);
var changeHandler = handler.bind(null, false);
elem.on('change', function() {
scope.$apply(changeHandler);
});
scope.$watch('bitMask', setupHandler, true);
}
};
});
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8">
<title>Custom Plunker</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js"></script>
<link rel="stylesheet" href="style.css">
<script>
document.write('<base href="' + document.location + '" />');
</script>
<script src="app.js"></script>
</head>
<body ng-app="myApp" ng-controller='MainController'>
Bitmask checkboxes <br>
<span ng-repeat="fruit in fruits">
<input type='checkbox' value="{{fruit.value}}" bit-mask='IforgetHowTheyExplainThis.bitMaskFromDB' > {{fruit.label}}<br />
</span>
<div>Current value of bitmask: {{IforgetHowTheyExplainThis.bitMaskFromDB }}</div>
</body>
</html>
I am not able to put the data into ng-model in view from an object in controller.
VIEW1 :
<input type="text" class="user-input" name="profile.firstname" ng-model="profile.firstname" ng-minlength="2" required pattern=".{2,}" placeholder="E.g. Anvika" title="Please enter atleast 2 characters">
When I click a button in VIEW2, it fires a function (say function 'test').
VIEW2
<input type="submit" ng-click="register.test()" ui-sref="doctorRegister" value="Profile">
CONTROLLER:
var app = angular.module('app');
app.controller('registerController', ['$scope', 'tempDataStorageService', function ($scope, tempDataStorageService) {
var register = this;
register.doctor = {};
register.test = function () {
register.refreshProfile = tempDataStorageService.get(register.doctor.profile);
//console.log(register.refreshProfile);
var a = register.refreshProfile.firstname;
console.log(a);
}
}
TempDataStorageService:
var app = angular.module('app');
app.factory('tempDataStorageService', function() {
var savedData = {};
function set(data) {
savedData = data;
}
function get() {
return savedData;
}
return {
set: set,
get: get
}
});
EDIT: I have tried to show the declaration of the controller as well, if that helps.
How can I make it so that when I click on the Profile button on VIEW2, it populates VIEW1 with the data?
The plunker:
Working example
The html:
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<script data-require="angular.js#1.4.x" src="https://code.angularjs.org/1.4.8/angular.js" data-semver="1.4.8"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl as mainCtrl">
<form>
<h2 class="text-primary">Get data into ng-Model from object in controller</h2>
<hr>
<div class="form-group">
<h3 ng-class="!mainCtrl.firstName? 'text-danger' : 'text-success'">Enter Name</h3>
<input type="text" class="form-control" ng-model="mainCtrl.firstName" placeholder="E.g. Anvika">
</div>
<hr>
<h3 class="text-info">This is what you are typing: {{mainCtrl.firstName}}</h3>
<button type="button" class="btn btn-success" ng-click="mainCtrl.test()">Save Name</button>
</form>
<hr>
<h3 class="text-info">This is what is stored</h3>
<h4>Doctor first name: {{mainCtrl.storedData.doctorFirstName}}</h4>
</body>
</html>
The JS:
var app = angular.module('plunker', []);
app.controller('MainCtrl', ['tempDataStorageService', function(tempDataStorageService) {
var register = this;
register.firstName = "";
register.storedData = tempDataStorageService;
register.test = function () {
tempDataStorageService.setName(register.firstName);
}
}]);
app.factory('tempDataStorageService', function() {
// The service object
var profile = {};
profile.doctorFirstName = "No doctor data stored";
//The functions
profile.setName = function(data) {
profile.doctorFirstName = data;
}
profile.getName = function() {
return profile.doctorFirstName;
}
// return the service object
return profile;
});
Recommendations:
Please properly format code when asking questions.
As good practice use a style guide. A good starting point is John
Papa's Angular Style Guide
I'm creating a my-validate directive that looks something like this
<input my-validate="customValidation" ng-model="model" />
What I want to do is to attach a sybling element to the directive like this
Error template:
<ul class"errors">
<li ng-repeat="for error in errors">{{error}} not valid</li>
</ul>
errors is defined in the scope of the directive.
I've added the error template in the compile function, but the problem I have is that the scope in the link function is not the same as the attached template.
Here is a plunker to illustrate the issue: http://plnkr.co/edit/ghdtdYruQaaO0Yxxlrt1?p=preview
'world' is seen in the directive template, but not on the added element :S.
That's because your div "2 hello" is outside the container where your scope is visible.
you can use element.append() instead of element.after() to have the scope available.
Directive
var app = angular.module('plunker', []);
app.directive('myValidate', function($compile) {
return {
template: '<span>1. Hello {{world}} my scope is {{$id}} (parent: {{$parent.$id}})<span/>',
replace: true,
restrict: 'A',
scope: true,
compile: function (element) {
element.append('<div>2. Hello {{ world }}, my scope is {{$id}} (parent: {{$parent.$id}})</div>');
return function(scope) {
scope.world = 'World';
//$compile()(scope);
};
}
};
});
HTML
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script data-require="angular.js#1.1.5" data-semver="1.1.5" src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="app.js"></script>
</head>
<body>
<input my-validate="" />
</body>
</html>
http://plnkr.co/edit/dU3holBCePKe0ZAwQKh1?p=preview
I was reading and checking the example because I was in the same situation to display validation messages but under the input field and the message can change according to what kind of validation is required.
So I came up with this solution
var app = angular.module('app', []);
app.controller('ctrl', function($scope, CONSTANTS) {
$scope.title = "title";
$scope.CONSTANTS = CONSTANTS;
});
app.constant('CONSTANTS', {
LENGHT_1: 3,
LENGHT_2: 4
});
app.directive('dir', function($compile) {
return {
scope: true,
restrict: 'A',
require: '?ngModel',
link: function(scope, elem, attrs, ngModel) {
scope.maxLength = false;
scope.required = false;
scope.max = scope.$eval(attrs['ngMaxlength']);
var tpl = '<div ng-if="maxLength" ng-include="\'length.tpl.html\'"></div>' +
'<div ng-if="required" ng-include="\'required.tpl.html\'"></div>';
var el = $compile(tpl)(scope);
elem.after(el);
scope.$watch(attrs['ngModel'], function(newValue, oldValue, scope) {
if (ngModel.$error !== null && ngModel.$error.maxlength) {
scope.maxLength = true;
} else {
scope.maxLength = false;
}
if (ngModel.$error !== null && ngModel.$error.required && ngModel.$dirty) {
scope.required = true;
} else {
scope.required = false;
}
});
}
}
});
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js#1.4.7" data-semver="1.4.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
<script type="text/ng-template" id="length.tpl.html">
max length {{max}}
</script>
<script type="text/ng-template" id="required.tpl.html">
required
</script>
</head>
<body ng-controller="ctrl">
<h1>Input Validation</h1> {{title}}
<br><br>
<form name="form" novalidate>
<input dir name="input_one" ng-model="bar" ng-maxlength="CONSTANTS.LENGHT_1" required>
<br>
input one: {{form.input_one.$error}}
<br>
<br>
<input dir name="input_two" ng-model="foo" ng-maxlength="CONSTANTS.LENGHT_2">
</form>
<br>
input two: {{form.input_two.$error}}
</body>
</html>
On Plunkr
Hope it helps.
I think you're on the right track by using the form errors to toggle display. That's exactly how it's recommended in the standard Angular documentation.
If you'd like to show multiple errors for a single input, however, and possible even control the error messages from there, I'd recommend utilizing a service, such as implemented at http://plnkr.co/edit/iNcNs2ErrOnYf9I7whdu?p=preview.
Right now you can have one message per token, but as many tokens as you want per input. If you want multiple messages per token, just use an array of messages instead of single string value (note, unset does become more complicated with that method).
Hope that helps,
Alex