Morph label into input - angularjs

What is the proper way in Angular to switch data into "edit mode" where <label>'s get morphed into <input type='text'>'s? I'd like to create and destroy the DOM elements at runtime instead of rendering all the inputs first as hidden (where they are then shown, and labels hidden, when switched to edit mode).

Something along the lines of this jsfiddle should work for you. It is still hiding/showing but the input doesn't need to be in the DOM up front. There are probably a million alternative ways to handle this, but I thought this would at least demonstrate how to get the functionality into a directive.
HTML:
<label inline-edit>Edit me</label>
Directive:
'use strict';
var app = angular.module('myApp', []);
app.directive('inlineEdit', function() {
return{
restrict: 'A',
transclude: true,
template: '<label class="editing" data-ng-transclude></label>',
controller: ['$scope','$element','$transclude',function($scope, $element, $transclude) {
$transclude(function(clone) {
$scope.transcluded_content = clone[0].textContent;
});
$element.bind('click', function(){
$element.hide().after('<input type="text" value="'+$scope.transcluded_content+'" />');
$element.next().focus().blur(function (){
$scope.transcluded_content = $element.next().val();
$element.html($scope.transcluded_content);
$element.next().hide();
$element.show();
});
});
}]
};
});

Related

How can I use isolated scope with a component and a directive?

The goal here is to let MainCtrl know when there is an error(s) found in the directive. The error must be displayed here:
<div ng-if="errors[0]">Error 1: {{errors[0]}}</div>
How can I get isolated scope with a directive inside a component? The following application works if you uncomment the 2 lines mentioned below. As it is, I get error:
Multiple Directive Resource Contention
I can read the causes. I need to know how to fix this while still allowing the directive to have isolated scope. I may have 3-4 of these directives on a page and each one needs it's own unique of errors that is also available to the parent.
(working case example on codepen)
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
$scope.errors = [false, false];
$scope.text = "bobby";
});
app.directive('testDirective', function(){
return {
restrict: 'A',
scope: {
errors: '=',
text: '#'
},
link: function($scope, $element, $attr, ngModel) {
console.log('link fired');
console.log('errors: ', $scope.errors);
console.log('scope.text', $scope.text);
$attr.$observe('text', function (val) {
if($scope.text === 'bobby'){
$scope.errors[0] = true;
}else{
$scope.errors[0] = false;
}
});
},
template: '<p>text: {{ text }} </p>'
+ '<p>errors: {{errors}}</p>'
+ '<p><input type="text" ng-model="errors" /></p>'
};
});
app.component('panel', {
bindings: {
},
template: [
'<div>',
'</div>'
].join(''),
controller: function() {
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js"></script>
<section ng-app="app" ng-controller="MainCtrl">
<h3>Parent Scope</h3>
<p>errors: {{errors}}</p>
<input type="text" ng-model="text"></div>
<div ng-if="errors[0]">Error 1: {{errors[0]}}</div>
<div ng-if="errors[1]">Error 2: {{errors[1]}}</div>
<!-- UNCOMMENT THE FOLLOWING 2 LINES AND THIS APP WILL WORK
<h3>Directive by itself</h3>
<div test-directive text="{{text}}" errors="errors"><div>
-->
<h3>Directive in component</h3>
<panel test-directive text="{{text}}" errors="errors"></panel>
</section>
After researching, I noticed Angular only returns bool from $validators (as opposed to object). At this point I decided my approach was wrong. I decided to create a unique $valiators for each unique error message. Then use ng-message for the output.
In order to work with multiple components on the same page, I also have to check the ngModel.$error as part of validation. This blog covers the basic approach.

Using Angular, how do I bind a click event to an element and on click, slide a sibling element down and up?

I'm working with Angular and I'm having trouble doing something that I normally use jQuery for.
I want to bind a click event to an element and on click, slide a sibling element down and up.
This is what the jQuery would look like:
$('element').click(function() {
$(this).siblings('element').slideToggle();
});
Using Angular I have added an ng-click attribute with a function in my markup:
<div ng-click="events.displaySibling()"></div>
And this is what my controller looks like:
app.controller('myController', ['$scope', function($scope) {
$scope.events = {};
$scope.events.displaySibling = function() {
console.log('clicked');
}
}]);
So far this is working as expected but I don't know how to accomplish the slide. Any help is very much appreciated.
Update
I have replaced what I had with a directive.
My markup now looks like this:
<div class="wrapper padding myevent"></div>
I have removed what I had in my controller and have created a new directive.
app.directive('myevent', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
element.bind('click', function($event) {
element.parent().children('ul').slideToggle();
});
}
}
});
However, I still can't get the slide toggle to work. I don't believe slideToggle() is supported by Angular. Any suggestions?
I'm not sure exactly on the behaviour that you're talking about, but I would encourage you to think in a slightly different way. Less jQuery, more angular.
That is, have your html something like this:
<div ng-click="visible = !visible"></div>
<div ng-show="visible">I am the sibling!</div>
You can then use the build in ng-animate to make the sibling slide - yearofmoo has an excellent overview of how $animate works.
This example is simple enough that you can put the display logic in the html, but I would otherwise encourage you to as a rule to put it into the controller, like this:
<div ng-click="toggleSibling()"></div>
<div ng-show="visible"></div>
Controller:
app.controller('SiblingExample', function($scope){
$scope.visible = false;
$scope.toggleSibling = function(){
$scope.visible = !$scope.visible;
}
});
This kind of component is also a prime candidate for a directive, which would package it all up neatly.
app.directive('slideMySibling', [function(){
// Runs during compile
return {
// name: '',
// priority: 1,
// terminal: true,
// scope: {}, // {} = isolate, true = child, false/undefined = no change
// controller: function($scope, $element, $attrs, $transclude) {},
// require: 'ngModel', // Array = multiple requires, ? = optional, ^ = check parent elements
restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment
// template: '',
// templateUrl: '',
// replace: true,
// transclude: true,
// compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})),
link: function($scope, iElm, iAttrs, controller) {
iElm.bind("click", function(){
$(this).siblings('element').slideToggle();
})
}
};
}]);
Usage would be something like
<div slide-my-sibling><button>Some thing</button></div><div>Some sibling</div>
Note all the "code" above is just for the sake of example and hasn't been actually tested.
http://plnkr.co/edit/qd2CCXR3mjWavfZ1RHgA
Here's a sample Plnkr though as mentioned in the comments this isn't an ideal setup since it still has the javascript making assumptions about the structure of the view so ideally you would do this with a few directives where one requires the other or by using events (see $emit, $broadcast, $on).
You could also have the directive create the children "programmatically" via JavaScript and circumvent the issue of not knowing what context the directive is being used in. There are a lot of potential ways to solve these issues though none of the issues will stop it from functionally working they are worth noting for the sake of re-usability, stability, testing, etc.
As per this link : https://docs.angularjs.org/api/ng/function/angular.element
AngularJs element in your code snippet represents JQuery DOM object for related element. If you want to use JQuery functions, you should use JQuery library before angular loads. For more detail, please go through above link.
Best practice:
<div ng-if="view"></div>
$scope.view = true;
$scope.toggle = function(){
$scope.view = ($scope.view) ? false : true;
}

Pass form to directive

I want to encapsulate my form fields in a directive so I can simply do this:
<div ng-form='myForm'>
<my-input name='Email' type='email' label='Email Address' placeholder="Enter email" ng-model='model.email' required='false'></my-input>
</div>
How do I access the myForm in my directive so I can do validation checks, e.g. myForm.Email.$valid?
To access the FormController in a directive:
require: '^form',
Then it will be available as the 4th argument to your link function:
link: function(scope, element, attrs, formCtrl) {
console.log(formCtrl);
}
fiddle
You may only need access to the NgModelController though:
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
console.log(ngModelCtrl);
}
fiddle
If you need access to both:
require: ['^form','ngModel'],
link: function(scope, element, attrs, ctrls) {
console.log(ctrls);
}
fiddle
Here a complete example (styled using Bootstrap 3.1)
It contains a form with several inputs (name, email, age, and country).
Name, email and age are directives. Country is a "regular" input.
For each input is displayed an help message when the user does not enter a correct value.
The form contains a save button which is disabled if the form contains at least one error.
<!-- index.html -->
<body ng-controller="AppCtrl">
<script>
var app = angular.module('app', []);
app.controller('AppCtrl', function($scope) {
$scope.person = {};
});
</script>
<script src="inputName.js"></script>
<script src="InputNameCtrl.js"></script>
<!-- ... -->
<form name="myForm" class="form-horizontal" novalidate>
<div class="form-group">
<input-name ng-model='person.name' required></input-name>
</div>
<!-- ... -->
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<button class="btn btn-primary" ng-disabled="myForm.$invalid">
<span class="glyphicon glyphicon-cloud-upload"></span> Save
</button>
</div>
</div>
</form>
Person: <pre>{{person | json}}</pre>
Form $error: <pre>{{myForm.$error | json}}</pre>
<p>Is the form valid?: {{myForm.$valid}}</p>
<p>Is name valid?: {{myForm.name.$valid}}</p>
</body>
// inputName.js
app.directive('inputName', function() {
return {
restrict: 'E',
templateUrl: 'input-name.html',
replace: false,
controller: 'InputNameCtrl',
require: ['^form', 'ngModel'],
// See Isolating the Scope of a Directive http://docs.angularjs.org/guide/directive#isolating-the-scope-of-a-directive
scope: {},
link: function(scope, element, attrs, ctrls) {
scope.form = ctrls[0];
var ngModel = ctrls[1];
if (attrs.required !== undefined) {
// If attribute required exists
// ng-required takes a boolean
scope.required = true;
}
scope.$watch('name', function() {
ngModel.$setViewValue(scope.name);
});
}
};
});
// inputNameCtrl
app.controller('InputNameCtrl', ['$scope', function($scope) {
}]);
Edit 2: I'll leave my answer, as it might be helpful for other reasons, but the other answer from Mark Rajcok is what I originally wanted to do, but failed to get to work. Apparently the parent controller here would be form, not ngForm.
You can pass it in using an attribute on your directive, although that will get rather verbose.
Example
Here's a working, simplified jsFiddle.
Code
HTML:
<div ng-form="myForm">
<my-input form="myForm"></my-input>
</div>
Essential parts of the directive:
app.directive('myInput', function() {
return {
scope: {
form: '='
},
link: function(scope, element, attrs) {
console.log(scope.form);
}
};
});
What's happening
We've asked Angular to bind the scope value named in the form attribute to our isolated scope, by using an '='.
Doing it this way decouples the actual form from the input directive.
Note: I tried using require: "^ngForm", but the ngForm directive does not define a controller, and cannot be used in that manner (which is too bad).
All that being said, I think this is a very verbose and messy way to handle this. You might be better off adding a new directive to the form element, and use require to access that item. I'll see if I can put something together.
Edit: Using a parent directive
OK, here's the best I could figure out using a parent directive, I'll explain more in a second:
Working jsFiddle using parent directive
HTML:
<div ng-app="myApp">
<div ng-form="theForm">
<my-form form="theForm">
<my-input></my-input>
</my-form>
</div>
</div>
JS (partial):
app.directive('myForm', function() {
return {
restrict: 'E',
scope: {
form: '='
},
controller: ['$scope', function($scope) {
this.getForm = function() {
return $scope.form;
}
}]
}
});
app.directive('myInput', function() {
return {
require: '^myForm',
link: function(scope, element, attrs, myForm) {
console.log(myForm.getForm());
}
};
});
This stores the form in the parent directive scope (myForm), and allows child directives to access it by requiring the parent form (require: '^myForm'), and accessing the directive's controller in the linking function (myForm.getForm()).
Benefits:
You only need to identify the form in one place
You can use your parent controller to house common code
Negatives:
You need an extra node
You need to put the form name in twice
What I'd prefer
I was trying to get it to work using an attribute on the form element. If this worked, you'd only have to add the directive to the same element as ngForm.
However, I was getting some weird behavior with the scope, where the myFormName variable would be visible within $scope, but would be undefined when I tried to access it. That one has me confused.
Starting with AngularJS 1.5.0, there is much cleaner solution for this (as opposed to using the link function directly). If you want to access a form's FormController in your subcomponent's directive controller, you can simply slap the require attribute on the directive, like so:
return {
restrict : 'EA',
require : {
form : '^'
},
controller : MyDirectiveController,
controllerAs : 'vm',
bindToController : true,
...
};
Next, you'll be able to access it in your template or directive controller like you would any other scope variable, e.g.:
function MyDirectiveController() {
var vm = this;
console.log('Is the form valid? - %s', vm.form.$valid);
}
Note that for this to work, you also need to have the bindToController: true attribute set on your directive. See the documentation for $compile and this question for more information.
Relevant parts from the documentation:
require
Require another directive and inject its controller as the fourth argument to the linking function. The require property can be a string, an array or an object:
If the require property is an object and bindToController is truthy, then the required controllers are bound to the controller using the keys of the require property. If the name of the required controller is the same as the local name (the key), the name can be omitted. For example, {parentDir: '^parentDir'} is equivalent to {parentDir: '^'}.
Made your 'What I'd prefer' fiddle work!
For some reason you could see the "$scope.ngForm" string in a console.log, but logging it directly didn't work, resulting in undefined.
However, you can get it if you pass attributes to the controller function.
app.directive('myForm', function() {
return {
restrict: 'A',
controller: ['$scope','$element','$attrs', function($scope,$element,$attrs) {
this.getForm = function() {
return $scope[$attrs['ngForm']];
}
}]
}
});
http://jsfiddle.net/vZ6MD/20/

Binding the placeholder to the model causes ng-change to execute on load in IE

Using angularjs, if I bind the placeholder of an input to its model, the change event is fired when the document loads in IE. This does not appear to be correct and I'm not seeing this behavior in other browsers.
JS Fiddle
Html:
<div ng-app="angularjs-starter" data-ng-controller="MainCtrl">
<div data-ui-view="viewMain">
<input
placeholder="{{theValue}}"
data-ng-model="theValue"
data-ng-change="valueChanged(theValue)" />
</div>
Javascript:
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {
$scope.valueChanged = function(theValue) {
alert("Value Change Called On Load in IE.");
};
});
It's possible to use the built-in ng-attr-placeholder directive as well.
ng-attr-placeholder="{{theValue}}"
I know this is old but just in case anyone else runs in to this I created a small directive that goes around putting a dynamic value in the placeholder and instead have it assign when it changes:
.directive('dynamicPlaceholder',
function() {
return {
restrict: 'A',
link: function ($scope, element, attrs) {
attrs.$observe('dynamicPlaceholder', function(value) {
element.attr('placeholder', value);
});
}
};
});
Then to use this all you need to do is use dynamic-placeholder instead of placeholder:
<input ng-model='someValue' dynamic-placeholder='{{someDynamicPlaceholder}}' />
Not sure what is causing the problem in IE though

angularjs - need help for delayed search directive

fiddle here: http://jsfiddle.net/graphicsxp/QA4Fa/2/
I'm triying to create a directive for searching. Basically it's just a textbox that detects user input and after a 1 second delay, a search method is called.
It's not working yet and I'm having two issues.
First, why is the filterCriteria not updated in the span when user inputs text ?
Second, the watch on filterCriteria is triggered at page loading but not when text is entered in the textbox.
<div ng-app="myApp" ng-controller="myController">
<delayed-search ng-model="filterCriteria"></delayed-search>
<span>filter criteria is : {{filterCriteria}}</span>
</div>
angular.module('myApp', []).directive("delayedSearch", ['$timeout', function($timeout) {
return {
restrict: "E",
template: '<input type="text" />',
scope: {
filterCriteria : '='
},
link: function (scope, element, attrs) {
},
controller: function ($scope) {
var timer = false;
$scope.$watch('filterCriteria', function () {
if (timer) {
$timeout.cancel(timer);
}
timer = $timeout(function () {
alert('timeout expired');
}, 1000)
});
}
}
}]).controller('myController', function($scope){ });
You should NOT use a controller with a directive ( until you understand it ! ) .
A controller in a directive is meant for directive to directive communication (I wish they had named it something else!).
#Langdon got it right.. But here is another implementation of the same. Note that in both the answer's the controller is missing.
http://jsfiddle.net/QA4Fa/4/
First, why is the filterCriteria not updated in the span when user inputs text ?
Your scope is wrong, it should be scope: { ngModel : '=' },, and your template should be template: '<input type="text" ng-model="ngModel" />.
Second, the watch on filterCriteria is triggered at page loading but not when text is entered in the textbox.
Same as the first problem, you should be watching ngModel.
Also, you don't need the overhead of a controller for this, you can get away with just using the link function. Here's an updated fiddle: http://jsfiddle.net/QA4Fa/3/

Resources