This is my scenario. I am adding a block of code dynamically using javascript. Since its dynamic I have to bind it to my angularjs scope, which is done. But I have one problem here. One of the text box has a directive for it which works. But on change of any text box other than the directive text box for first time the scope.$watch triggers, later on it does not. Here is my code
$('.addNew').click(function(){
var uniqid = Date.now();
var html= '';
html += '<section class="newItem" id="'+uniqid+'">';
html += '<h4 style="margin: 10px 22px 8px 22px;color:#FF9900;border-bottom:1px dotted black;padding:1%;" > Grocery: <em>{{gName}}</em></h4>';
html += '<div class="grosinput" style="width:0%;">-</div>';
html += '<div class="grosinput" style="width:50%;">';
html += '<lable style="color:#6699CC;font-size: 15px;">Name:</lable><input type="text" placeholder="Enter grocery item" name="name" ng-model="gName"/>';
html += '</div>';
html += '<div class="grosinput">';
html += '<lable style="color:#6699CC;font-size: 15px;">Cost:</lable><input type="text" placeholder="Enter Cost" value="30" name="cost" cost-check ng-model="cost"/></div></section>';
var $injector = angular.injector(['ng', 'grocery']);
$injector.invoke(function($rootScope, $compile) {alert('t');
$('.grocadd').hide().after($compile(html)($rootScope)).fadeIn(1500);
});
});
Here is the directive
app.directive('costCheck',function($compile,$rootScope){
$rootScope.gName= "What did i buy?";
return{
restrict: 'A',
link: function(scope,element,attrs){
scope.$watch('cost',function(oldval,newval){alert(attrs.name);
if(attrs.name === 'cost'){
alert(oldval+'--'+newval);
}
});
}
}
});
why is it triggering for other text box also
Please note Angular relies on dirty checking which means it triggers all the watches to check if anything is dirty in order to update the View.
Either you should put a if condition to check newVal and oldVal are different to go ahead as:
scope.$watch('cost',function(newVal, oldVal){
if (newVal !== oldVal){
alert(oldval+'--'+newval);
}
});
Or you can use attrs.$observe to observe the attributes as:
I changed the directive in the DOM from cost-check to cost-check="{{cost}}" and replace $watch with $observe as:
attrs.$observe('costCheck', function(val) {
console.log(scope.$eval(val));
});
Demo: http://jsfiddle.net/HB7LU/3018/
Related
I want to render a list of widgets on a page from my controller. Each widget has its own render function that returns a safe HTML string.
My first part of my ng-repeat (outside layer) looks like this:
$scope.renderWidgets = function() {
var html = "";
html += "<div ng-repeat='widget in widgets'>";
html += "<div ng-bind-html='widget.render()'></div>"
html += "</div>";
return $sce.trustAsHtml(html);
}
As you can see I have ng-bind-html inside my ng-repeat which is calling a function named .render() located inside widget:
this.render = function() {
var html = "";
html += "<div ng-repeat='choice in widget.choices'>";
html += "{{choice.name}}";
html += "</div>";
return $sce.trustAsHtml(html);
}
I also have a directive that I use to $compile the above HTML string into AngularJS.
If I run the above code the widget.render() function gets called just fine but the output on the page will look like this: {{choice.name}} and not the value inside choice.name.
As you can see I am trying to do another ng-repeat inside ng-bind-html with the widget object from my first ng-repeat.
Is this even possible what I am trying to do here (I am new to AngularJS)? If yes then what am I doing wrong? Or is there another way to resolve my problem?
I am using the latest AngularJS (v. 1.7.9).
UPDATE
I think I know the issue. When I call my first function $scope.renderWidgets() the returned HTML will be automatically compiled(the first ng-repeat) at which point the ng-bind-html gets triggered returning a non-compiled HTML string. Now I have to figure out a way to $compile the returned HTML from my ng-bind-html directive.
Am I on the right track?
The ng-bind-html directive only binds HTML. It does not compile HTML. This was a deliberate decision by the AngularJS team to avoid security problems.
You state that you have a directive that compiles HTML. Let's assume the directive is:1
app.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function(html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
Then use it in your nested code:
$scope.renderWidgets = function() {
var html = "";
html += "<div ng-repeat='widget in widgets'>";
̶h̶t̶m̶l̶ ̶+̶=̶ ̶"̶<̶d̶i̶v̶ ̶n̶g̶-̶b̶i̶n̶d̶-̶h̶t̶m̶l̶=̶'̶w̶i̶d̶g̶e̶t̶.̶r̶e̶n̶d̶e̶r̶(̶)̶'̶>̶<̶/̶d̶i̶v̶>̶"̶
html += "<div dynamic='widget.render()'></div>"
html += "</div>";
return $sce.trustAsHtml(html);
}
For more information, see
Compiling dynamic HTML strings from database — this answer
I am working on code where user selects an item from dropdown list and the UI elements are displayed according to that selected item. Example: if item1 is selected there will be 2 textboxes, 1 textarea and a checkbox. item2 will have 1 textbox. etc.
So, I currently have a function where I add html elements according to item selected. I get a JSON file where there is list of items and what UI elements are required for that item. When user selects an item I get the required information from that JSON file and call following code:
$scope.myHTML = "";
var aForm = (angular.element(document.getElementById('appType')));
aForm.html('');
dropdown.forEach(function(viewItem, index){
if(viewItem.control_type === "TextBox"){
$scope.myHTML += '<label class="label">'+ viewItem.label +'</label>'+
'<input type="text" ng-model = "item.value'+index + '" placeholder = "'+ viewItem.text +'">';
}
if(viewItem.control_type === "TextArea"){
$scope.myHTML += '<label class="label">'+ viewItem.label +'</label>'+
'<textarea type="text" rows="4" ng-model = "tests.value'+index + '" ></textarea>'';
}
}
aForm.append($scope.myHTML);
$compile(aForm)($scope);
So the ng-model will have item.value0,item.value1 and so on for each element. I have used $compile to add the html view to UI.
The problem is that I can get the values in my controller but I cannot set those values from controller.
dropdown.forEach(function(viewItem, index){
$scope.tests['value'+index] = "SomePresetValue"; //Does not update value on HTML
});
Does not set value to the HTML element. I tried the following after searching for alternate solutions but did not work.
$scope.$apply(function(){
dropdown.forEach(function(viewItem, index){
$scope.item['value'+index] = viewItem.value;
});
});
I have a simple directive using isolate scope which passes data to a scoped method from within the template:
app.directive("phone", function () {
return {
scope: {
dial: "&"
},
template: '<input type="text" ng-model="value">' +
'<br>' +
'<div class="button" ng-click="dial({message:value})">' +
'Call home!</div>',
controller: function($scope) {
console.log($scope);
}
};
});
Works fine. But I'd like to clear the input field after the alert has been completed. I'm having a hard time figuring out how I can access ng-model="value" on that input that is generated from within the directive. Any help?
Here's a plunk for you
change the template to this
template: '<input type="text" ng-model="value">' +
'<br>' +
'<div class="button" ng-click="dial({message:value}); value = \'\' ">' +
'Call home!</div>',
note that ng-click is changed to ng-click="dial({message:value}); value = \'\' "
this will sets the value to empty string after calling the dial() function.
here is the demo
or you can try something this, but seems like first one is the better one.
For a project I'm working on, I created a simplified version of the UI Bootstrap Calendar widget.
Plunker of my Simplified Calendar, and how I'm using it, is here.
One interesting aspect of the UI Bootstrap calendar, is that even though it goes onto a input[text], it still produces a date validation in the $error dictionary for a form controller, just as if I had specified an input[date] element in my DOM.
However, there's a catch with numerous sub-catches. One thing you'll notice right away in my plunker's DOM is that I've specified error spans for times when the given date fields are not actually dates (try entering something ridiculous like 'cat' for a value!) If you enter something that isn't a date, those should appear, but they don't.
I've tried a few things to expose the markup being created to the name field of the parent:
$transclude set to false, such that the <calendar></calendar> tags get replaced with the contents of the calendar directive's template, with a name attribute specified. This "works", except that said input is wrapped in a span that has a class necessary to look correct using the Bootstrap styling framework.
Directly creating a name attribute in the calendar directive's input field with a binding, like so*:
app.directive('mustPrecedeDate', [
function () {
return {
restrict: 'E',
template: '<input type="text" name="{{ someName }}" />',
scope: {},
controller: 'calendarCtrl',
link: function () {}
};
}
};
Writing link code to explicitly find the input that is a child of the calendar generated markup, and assign it a name attribute. Both 2 and 3 failed, because apparently that's not really something that can be done (I can't find the SO question that was the source of that discovery.)
This leads to my Question: in what way can I get a name down to the input element, such that validation results can be reported to the $error dictionary, so that I can give my users helpful validation messages?
*: Apparently, code blocks with the 'four spaces from the left' formatting don't behave well with numbered lists, so I had to use back-quote code notation to get the text to format halfway correctly. Please feel free to correct my formatting, if I haven't found a bug in the markdown setup SO uses.
The #3 thing needed to be tried a bit harder!
I was able to get a name on the input by adding the following code into my link function:
var inputElement = elem.find('input');
inputElement.attr('name', inputName);
...Where inputName is scraped from the attributes list. I was able to get the inputName down to the generated input[text] field by using a compile function as below.
app.directive('calendar', [
function() {
return {
restrict: 'E',
transclude: false,
scope: {},
template:
'<span class="input-group">'
+ '<input class="form-control" required '
+ 'type="text" placeholder="MM/dd/yyyy" '
+ 'data-ng-model="dt" data-ng-click="toggle($event)" '
+ 'data-ng-change="updateParentProperty()" '
+ 'datepicker-popup="MM/dd/yyyy" is-open="isOpen" />'
+ '<span class="input-group-btn">'
+ '<button type="button" class="btn btn-default" data-ng-click="toggle($event)">'
+ '<i class="fa fa-calendar"></i>'
+ '</button>'
+ '</span>'
+ '</span>',
controller: 'calendarCtrl',
compile: function(elem, attrs) {
var inputName = attrs.inputName;
var inputElement = elem.find('input');
inputElement.attr('name', inputName);
// Compile returns a Link function!
return function(scope, elem, attrs, ctrl) {
var modelName = attrs.ngModel;
scope.parentProperty = modelName;
scope.dt = scope.$parent[modelName];
};
}
};
}
]);
I transform FCKTAG to INPUT and attach dhtmlxcalendar. It works.
Directive works fine too.
But I need attach calendar to an input with an icon Initialization of dhtmlxCalendar Doc
Initialization of dhtmlxCalendar Doc: I must put in
<span><img id="calendar_icon" src="path.gif" border="0"></span>
In Angular Directive I put
template: '<input type="text" ng-model="g" ></input>' +
'<span><img src="http://clans.worldoftanks.ru/media/' +
'clans/emblems/cl_582/2582/emblem_24x24.png" border="0"></span>',
It's error. I need one root tag. I choose DIV:
template: '<div>' +
'<input type="text" ng-model="g" ></input>' +
<span><img src="http://clans.worldoftanks.ru/media/' +
'clans/emblems/cl_582/2582/emblem_24x24.png" border="0"></span>' +
'</div>',
Annnd... Calendar doesn't load.
I haven't any idea why it.
plunker without an icon
plunker with an icon DOESN'T WORK
In your link function you are get the input element using element[0]. Once you wrap all of it in a div tag it is no longer element[0]. Trying substituting the following code.
var input = element.find('input')[0];
if (myCalendar == null) {
myCalendar = new dhtmlXCalendarObject(input);
} else {
myCalendar.attachObj({input:input, button: input});
}
updated plunker