I have a pattern related question. I build a directive to "load" content into a bootstrap popover.
HTML:
<span data-ng-show="user.provider === 'local'" class="label label-warning" data-popover="" data-placement="bottom" data-content-selector="#passwordchangeform">change password</span>
<div id="passwordchangeform" style="display:none;">
<h3>Change password</h3>
<h4>(Not finished yet!)</h4>
<form>
Old password:
<input type="text" ng-model="username" name="text"/><br/>
New password:
<input type="text" ng-model="password1" name="text"/><br/>
New password again:
<input type="text" ng-model="password2" name="text"/><br/>
<input type="button" data-ng-click="login()" value="login"/>
</form>
</div>
Directive:
angular.module('app').directive('popover', function ($compile) {
return {
restrict: 'A',
link: function (scope, el, attrs) {
var content;
if (attrs.content) {
content = attrs.content;
} else {
content = $compile("<div>" + $(attrs.contentSelector).html() + "</div>")(scope);
}
$(el).popover({
trigger: 'click',
html: true,
content: content,
placement: attrs.placement
});
}
};
});
In my controller, I implemented the function login()
Let's say in the controller login() function I get a success response to a HTTP/POST
What would be the best-practice then on actually closing the popover?? I can think of "dirty hacks" to force the closure, but I guess it could be done in a more appropriate way...
The "problem" I may have maybe related to the fact I'm using $compile in the directive to tie the content to the scope of the controller so I'm unable to close it from the link function.
Thanks,
Sander
Related
I'm trying to create a input directive element with validation.
I'd like to manage error status in this directive.
There are 3 files
Index.html: uses this directive
textValid.js: contains directive code
textValid.html: contains the directive template
I create this directive
textValid.js
App.directive("textValid", function() {
return {
restrict: "E",
templateUrl: "tpl/textValid.html",
require: "?ngModel",
scope: {
name: "#",
element: "=",
model: "="
}
};
});
index.html
<form name="edit_form_ctrl.contract_edit_form" action="#" novalidate >
<div class="row">
<text-valid name="ncontract"model="edit_form_ctrl.contract.ncontract"
element="edit_form_ctrl.contract_edit_form.ncontract">
</text-valid>
</div>
</form>
and template textValid.html
<input type="text" name="name" ng-model="model" class="form-control" ng-required="true" value="{{model}}" />
<div>pristine: {{element.$pristine}}</div> <!--is always undefined-->
<div>Invalid: {{element.$error}}</div> <!--is always undefined-->
<span class="color-red" ng-if="element.$error.required &&!element.$pristine">
{{curLang.field_mandatory}}
</span>
I'm trying to get input control to check $error and $pristine value, but I cannot to achieve it.
I read all documentation and the book too, but with any results.
Does someone try to do that?
Thanks in advance
If you add {{double-curlies}} around the name attribute of the input element like so:
<input type="text" name="{{name}}" ng-model="model" class="form-control" ng-required="true" />
You should be able to access the ngModelController for it from inside text-valid with $scope.form[$scope.name]
A couple other notes:
using a value attribute on and element with ng-model attribute will not have an effect.
you have require: '?ngModel' in your directive definition, but there is no ng-model attribute on the text-valid element. This is ok, but you won't get any ngModelController being injected unless the text-valid element has an ng-model attribute.
Edit:
Because text-valid has isolate scope, to access the FormController, you would need to require it and bind it:
App.directive("textValid", function() {
return {
restrict: "E",
templateUrl: "tpl/textValid.html",
require: "^^form",
scope: {
name: "#",
element: "=",
model: "="
},
link: function($scope, elem, attr, FormCtrl){
$scope.form = FormCtrl;
// now you should be able to access $scope.form[$scope.name]
// (you *might* need to put any initialization that accesses it inside a $timeout to wait for it to be rendered/bound)
}
};
});
In template, you should then be able to access it like:
<div>pristine: {{form[name].$pristine}}</div>
<div>Invalid: {{form[name].$error}}</div>
Try this:
<input type="text" name="name" ng-model="model" class="form-control" ng-required="true" value="{{model}}" />
<div>pristine: {{form.name.$pristine}}</div> <!--is always undefined-->
<div>Invalid: {{form.name.$error}}</div> <!--is always undefined-->
<span class="color-red" ng-if="element.$error.required &&!element.$pristine">
{{curLang.field_mandatory}}
</span>
I have the following template:
<dynamic-field name="Name" type="input" ng-model="temp.product.name"></dynamic-field>
<dynamic-field name="Price" type="input" ng-model="temp.product.price"></dynamic-field>
<dynamic-field name="Qty" type="input" ng-model="temp.product.qty"></dynamic-field>
Custom directive code:
app.directive('dynamicField', function() {
return {
restrict: 'E',
templateUrl: getTemplate('templates/single-field.html'),
scope: {
ngModel: '='
},
link: function($scope, $element, $attrs) {
// console.log($scope);
}
};
});
In directive template, the input field is displayed:
<div class="field">
<input type="text" ng-model="ngModel" /> <!-- if $temp.product.post_text in parent scope is set to "Test", it's displayed -->
</div>
The problem is, when I modify something inside the input (isolated scope) changes are not applied to the parent scope. I think the problem is that I use primitive here:
<input type="text" ng-model="ngModel" />
... but I'm not sure how to resolve this. Any suggestions?
You must be doing something wrong. If it's two-way bound, it's two-way bound.
function MainController() {
this.name = "test";
this.logCtrlName = function() {
alert(this.name);
}
}
function dynamicField() {
return {
restrict: 'E',
template: `
<div class="field">
<input type="text" ng-model="ngModel" />
</div>
`,
scope: {
ngModel: '='
}
};
}
angular.module('app', []);
angular.module('app')
.controller('MainController', MainController)
.directive('dynamicField', dynamicField);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="MainController as ctrl">
<dynamic-field name="Name" type="input" ng-model="ctrl.name"></dynamic-field>
<a href ng-click="ctrl.logCtrlName();">Name inside parent controller</a>: {{ ctrl.name | json }}
</div>
</div>
Like you said since your model is primitive value, so whenever you change the value inside isolated scope will create its copy of scope variable.
A simple and quick way to fix that is create a function to update parent scope value $scope.$parent.ngModel directly then add ng-change to input element and call that function whenever input value is changed.
I have a directive that receives a data object and a function to it's isolate scope. In link function I'm declaring a scope method to trigger in a certain event (button click).
Problem is the value passed to the above mentioned method is available inside it, but the scope variable is still undefined.
Directive:
commentsModule.directive('commentsDirective', [ function() {
return {
restrict: 'E',
templateUrl: '/alarm-viewer-comments-template.html',
scope: {
alarmComments: "=value",
sendNewComment: "&sendNewComment"
},
link: function(scope, elems, attrs, ngModelCtrl) {
scope.sendComment = function(data) {
console.log(scope.newComment);//this newComment variable is undefined
scope.sendNewComment(data);//data is correct
scope.newComment = '';
};
}
}
}
]);
Here inside link function, data passed into scope.sendComment is available but yet scope.newComment gets undefined.
Template:
<h4>Comments</h4>
<div ng-repeat="comment in alarmComments.comments">
<p>{{comment.timestamp}} | <strong>{{comment.user}}</strong>: {{comment.commentType}} {{comment.comment}}</p>
</div>
<div ng-if="alarmComments.editPermission && alarmComments.isActiveAlarm">
<form name="commentsForm" role="form" track-form>
<input type="text" ng-model="newComment" pattern="/.{1,}" maxlength="4" required ng-enter="sendComment(newComment)"/>
<button type="button" class="btn btn-primary" ng-disabled="commentsForm.$invalid" ng-click="sendComment(newComment)">Send</button>
</form>
</div>
UI:
<comments-directive value="alarmComments" send-new-comment="addNewComment(comment)"></comments-directive>
Can someone help me out...?
edit: what I want is to clear the input text field after entering a comment.
Within directive scope you should map the newComment property also along with alarmComments. like below -
scope: {
alarmComments: "=value",
newComment: "=newComment",
sendNewComment: "&sendNewComment"
},
A good way to debug problems like this is render scope ids (scope.$id) and verify they are of the same id.
Could you verify during link, what is the scope.$id and render it in your template?
<h4>Comments</h4>
<div ng-repeat="comment in alarmComments.comments">
<p>{{comment.timestamp}} | <strong>{{comment.user}}</strong>: {{comment.commentType}} {{comment.comment}}</p>
</div>
<div ng-if="alarmComments.editPermission && alarmComments.isActiveAlarm">
<form name="commentsForm" role="form" track-form>
<input type="text" ng-model="newComment" pattern="/.{1,}" maxlength="4" required ng-enter="sendComment(newComment)"/>
<button type="button" class="btn btn-primary" ng-disabled="commentsForm.$invalid" ng-click="sendComment(newComment)">Send</button>
{{$id}}
</form>
</div>
Sometimes templates create its own scope and you might have to use $parent.newComment in your templates.
I have the following code:
<div class="col-md-10" data-ng-controller="type-controller">
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-success" ng-model="typeId" data-btn-radio="'1'">
Option 1
</label>
<label class="btn btn-success" ng-model="typeId" data-btn-radio="'2'">
Option 2
</label>
</div>
<input data-ng-model="typeId" name="typeId" type="hidden" data-start="2" />
</div>
My type-controller is empty so I'm omitting it - but I want to get the value of the attribute data-start from the last input inside the type-controller.
I'm not using jQuery.
IF the attribute data-start is significant because it is being used by some other 3rd party library, then you might consider simply using ng-init when you create this on the server:
<input data-ng-model="typeId" name="typeId" type="hidden" data-start="2"
ng-init='start = 2' />
This will essentially run any code you need, and doesn't involve you having to parse out data attributes from the DOM.
You could write a pretty trivial directive to pull in the value and publish using an expression. This will essentially accomplish the same thing, but is more difficult in my opinion:
angular.module('data-pluck', [])
.controller('fooController', function() {
this.name = 'Foo Controller';
})
.directive('pluckData', ['$parse',
function($parse) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
var expression = function() {};
expression.assign = function() {};
scope.$watch(attrs.placeData, function() {
expression = $parse(attrs.placeData);
});
scope.$watch(attrs.pluckData, function() {
expression.assign(scope, attrs[attrs.pluckData]);
});
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='data-pluck' ng-controller='fooController as ctrl'>
<h1>{{ctrl.name}}</h1>
<div data-my-val="I'm value one" pluck-data='myVal' place-data='ctrl.valueOne'>
<p>I'm a regular old <code><p></code> tag</p>
<input type='hidden' data-my-val="I'm the second value" pluck-data='myVal' place-data='ctrl.valueTwo' />
</div>
<h3>These Values Populated Dynamically</h3>
<ul>
<li>ctrl.valueOne = {{ctrl.valueOne}}</li>
<li>ctrl.valueTwo = {{ctrl.valueTwo}}</li>
</ul>
</div>
Angular comes with jqLite built in, which still has the attr() function. But it's not the Angular "way" to be manually fiddling around in the DOM from a controller. Your scope should be the interface between them.
I'm curious as to why you have a value in an attribute in your UI that isn't defined first in your model / scope? How does this value get changed? Is there a reason why you can't set it in the controller:
$scope.start = 2;
and then:
<input data-ng-model="typeId" name="typeId" type="hidden" data-start="{{start}}" />
Can you explain a little about what data-start is meant to do?
I have a radio button, which sets the value of True or False based on the value of transaction type
The demo can be found here
The problem is when I click on any of the radio button, the value of $scope.transaction.debit does not change
My javascript code is
var app = angular.module('myApp', []);
app.controller("MainCtrl", function($scope){
$scope.transaction = {};
$scope.transaction.debit=undefined;
console.log('controller initialized');
});
Please let me know what I am doing wrong.
Also, I do not want to use Angular-UI or AngularStrap for this purpose, unless no other option is available.
I modified dpineda's solution. You can use without removing bootsrap.js dependency. Also there is a working example here.
This is the flow:
Remove data-toggle="buttons" for preventing bootstrap execution.
Add some CSS for fixing the broken view (btn-radio css class)
Add some AngularJS logic for checked style effect.
html
<div class="btn-group col-lg-3">
<label class="btn btn-default btn-radio" ng-class="{'active': transaction.debit == '0'}">
<input type="radio" data-ng-model="transaction.debit" value="0"> Debit
</label>
<label class="btn btn-default btn-radio" ng-class="{'active': transaction.debit == '1'}">
<input type="radio" data-ng-model="transaction.debit" value="1"> Credit
</label>
</div>
<p>Transaction type: {{transaction.debit}}</p>
JavaScript
var app = angular.module('myApp', []);
app.controller("MainCtrl", function($scope) {
$scope.transaction = {
debit: 0
};
});
Style
.btn-radio > input[type=radio] {
position : absolute;
clip : rect(0, 0, 0, 0);
pointer-events : none;
}
I found the problem in bootstrap.js. Comment the line e.preventDefault(), it works.
// BUTTON DATA-API
// ===============
$(document)
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
var $btn = $(e.target)
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
Plugin.call($btn, 'toggle')
e.preventDefault() //Before
//e.preventDefault() //After
})
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
$(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
})
You have a large label stuck over the top of the radio buttons which prevents input to your radio buttons.
The html should read:
<input type="radio" data-ng-model="transaction.debit" value="True">Debit</input>
<input type="radio" data-ng-model="transaction.debit" value="False">Credit</input>
It then works, of course it may not look the way you want it to then.
if you remove de bootstrap code you can control the styles with conditionals
<label class="btn btn-default" ng-class="{'active': transaction.debit == 'some'}">
<input type="radio" data-ng-model="transaction.debit" name="debit" value="some"> Some
</label>
<label class="btn btn-default" ng-class="{'active': transaction.debit == 'other'}">
<input type="radio" data-ng-model="transaction.debit" name="debit" value="other"> Other
</label>
Here's a working version using a new directive:
html
<section ng-controller="MainCtrl">
<div class="form-group">
<label class="col-lg-2 control-label">Type</label>
<div class="btn-group col-lg-3" data-toggle="buttons">
<label class="btn btn-default" radio-button ng-model="transaction.debit" value="True">
Debit
</label>
<label class="btn btn-default" radio-button ng-model="transaction.debit" value="False">
Credit
</label>
</div>
<p>Transaction type: {{transaction.debit}}</p>
</div>
</section>
javascript
// Code goes here
var app = angular.module('myApp', []);
app.controller("MainCtrl", function($scope){
$scope.transaction = {};
$scope.transaction.debit=undefined;
console.log('controller initialized');
});
app.directive("radioButton", function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
element.bind('click', function () {
if (!element.hasClass('active')) {
scope.$apply(function () {
scope.transaction.debit = attrs.value;
});
}
});
}
};
})
Based on francisco.preller's answer I wrote two solutions trying to make it fit for generic use, without loosing the input tags:
html:
<label class="btn btn-info" radiobuttonlbl>
<input ng-model="query.gender" type="radio" value="0">male
</label>
solution #1:
.directive("radiobuttonlbl", function() {
return {
restrict: 'A',
link: function(scope, element, attrs, ctrl) {
element.bind('click', function () {
var input_elem = angular.element(element.find('input')[0]);
(function(o, s, v) {
s = s.replace(/\[(\w+)\]/g, '.$1');
s = s.replace(/^\./, '');
var a = s.split('.').reverse();
while(a.length>1) {
var k = a.pop();
o = o[k];
}
scope.$apply(function(){ o[a.pop()]=v;});
})(scope, input_elem.attr('ng-model'), input_elem.attr('value'));
});
}
};
})
Solution #2:
.directive("radiobuttonlbl", function() {
return {
restrict: 'A',
link: function(scope, element, attrs, ctrl) {
element.bind('click', function () {
var input_elem = angular.element(element.find('input')[0]);
input_elem.prop('checked',true);
input_elem.triggerHandler('click');
});
}
};
})
I have a feeling the first one is better because it make angular do the updating work.
If someone is still searching for an easy way to do this (I personally am hesitant to overload my code with directives), here is what I did:
You can set the value using ng-click on the label. Furthermore, notice the ng-init and active class on the label of the first radio item. This way, you can let bootstrap do its thing, and angular do its thing. The only drawback is you are not letting angular control this using ng-model.
<div class="btn-group col-lg-3" data-toggle="buttons">
<label class="btn btn-default active" ng-init="transaction.debit=true" ng-click="transaction.debit=true">
<input type="radio" checked> Debit
</label>
<label class="btn btn-default" ng-click="transaction.debit=false">
<input type="radio"> Credit
</label>
</div>
I had the same problem. Use ng-click on your labels and it will work fine with bootstrap
<label class="btn btn-default" ng-click="transaction.debit = 'debit'">
Here it's working in plunker
I have the same problem, in my case, the default style change and can't use angular ng-model inside any radio or checkbox button. So i read some articles and found that sometimes if you load JQuery after Bootstrap it overwrites any other instance of jQuery, and it prevent default styles and components to be loaded as bootstrap components, this also happens if you load angularJS after jQuery or viceversa.
PS.- My answer: Check your load script stack, play with it and find which order works for you. (first jquery, then angularJs, finally bootstrap). Usually you require to jQuery to be the first option, Angular and almost every new framework works on top of it. Cheers.