Repeating a div on button press in AngularJS - angularjs

I am following a tutorial here: https://fourtonfish.com/blog/2014-01-dynamically-add-directives-in-angularjs-no-jquery/
to try and append information to a div when a button is pressed. I have got the button added to my home page, however when I click it I receive:
angular.js:14324 TypeError: element.bind is not a function
I'd love some advice on how to correct this, and potentially even a better approach if there is one. I feel like using element.bind is a jquery approach and there is probably a nicer way to do this with Angular?
main.html
<div id ="fullForm" ng-controller="MainCtrl" >
<button-add></button-add>
<div id="test">test</div>
</div>
main.js
'use strict';
/**
* #ngdoc function
* #name jsongeneratorApp.controller:MainCtrl
* #description
* # MainCtrl
* Controller of the jsongeneratorApp
*/
angular.module('jsongeneratorApp')
.controller('MainCtrl', function ($scope) {
.directive('buttonAdd',function(){
return{
restrict:'E',
templateUrl:'scripts/directives/addbutton.html'
}
})
.directive('addfields',function($compile){
console.log("directive called.");
return function(element,attrs){
element.bind("click",function(){
angular.element(document.getElementById('test')).append($compile("<div>Test</div>"))
})
}
})
addbutton.html
<div class="row rowmargin">
<div class="col-sm-9">
</div>
<div class="col-sm-3">
<button addfields class="btn"> Add New Variable</button>
</div>
</div>

Element is the second parameter, not the first one. The first one is the $scope object of the directive.
It might be possible that you were confused with the dependency injection of angular, but the link function of a directive doesn't work with dependency injection.
If you add the scope parameter in the signature it will work:
// You missed the first parameter 'scope'
return function(scope, element, attrs){
element.on("click",function(){
$compile("<div>Test</div>")(scope, function(compiled){
// callback
angular.element(document.getElementById('test')).append(compiled);
});
});
}
Please also note that the bind function is deprecated, so you should use on instead.

Related

Trigger ng-submit on ng-click

I am following this answer (and this fiddle) on how to accomplish this.
View:
<section>
<form style="display: none" id="addFriendForm" name="vm.form.addFriendForm" class="form-horizontal" submit-on="submitAddFriendForm" ng-submit="vm.save(vm.form.addFriendForm.$valid)" novalidate>
<input ng-model="vm.userInput" required>
</form>
<div class="list-group">
<a ng-repeat="user in vm.users" ng-click="vm.triggerSubmit(user)" class="list-group-item">
...
</a>
</div>
</section>
Directive:
function submitOn() {
return {
link: function postLink(scope, element, attrs) {
scope.$on(attrs.submitOn, function() {
setTimeout(function() {
// Error:
element.trigger('submit');
});
});
}
};
}
Controller: (triggerSubmit)
// Trigger submit of the form
function triggerSubmit(user) {
vm.userInput = user;
$scope.$broadcast('submitAddFriendForm');
}
When walking through this, I get the error:
TypeError: element.trigger is not a function
...on the line highlighted in the directive. Looking at docs, this seems correct. What's the reason for it failing?
ANSWER:
(if a jQuery answer is wanted, look at the answer below)
A pure angular way of doing it is:
angular.element(element).triggerHandler('submit');
Looks like trigger is not a part of JQLite which ships with angular.
You might have to load JQuery before loading angular.
<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
OR
As part of your build pipeline (grunt/gulp/webpack etc)
See the other answer for a jQuery approach. For a pure angular approach I used:
angular.element(element).triggerHandler('submit');

ng-repeat inside compiled html for directive

I have two directives:
window.app.directive('placeholder', function ($compile, $route, $rootScope) {
return {
restrict: 'AC',
link: function (scope, element, attr) {
// Store the placeholder element for later use
$rootScope["placeholder_" + attr.placeholder] = element[0];
// Clear the placeholder when navigating
$rootScope.$on('$routeChangeSuccess', function (e, a, b) {
element.html('');
});
}
};
});
window.app.directive('section', function ($compile, $route, $rootScope) {
return {
restrict: 'AC',
link: function (scope, element, attr) {
// Locate the placeholder element
var targetElement = $rootScope["placeholder_" + attr.section];
// Compile the template and bind it to the current scope, and inject it into the placeholder
$(targetElement).html($compile(element.html())(scope));
element.html('');
}
};
});
I use them to basically swap out one section with html in another.
If I have the following html:
<div placeholder="footer"></div>
<div section="footer">
<ul ng-model="items">
<li ng-repeat="item in items"> {{item.Description}}</li>
</ul>
</div>
The ng-repeat doesn't seem to be working. If I simply output {{items}} below the , it displays fine. Also, I know binding is working because I can change items and it will update.
Lastly, if I move the ul outside the section it works fine.
So, my question is why does this not work (compile ng-repeat inside directive).
Am I missing something?
EDIT:
What is confusing me, is I can do this:
<div section="footer">
<!-- This Works -->
{{items}}
<!-- This also works -->
<input type="text" ng-model="items[0].Description" />
<!-- This doesn't work -->
<ul ng-model="items">
<li ng-repeat="item in items"> {{item.Description}}</li>
</ul>
</div>
This isn't going to work. It can't evaluate something from another scope without having an exact copy of it in its scope. If you want two directives to communicate use require and setup a way for them to do that if they aren't in a parent child relationship.
A couple of things you should think about. Essentially what you are doing is called transclusion. Section directive would use ng-transclude to capture the client's defined code. Use transclusion and maybe you can evaluate the template into html in the scope of section then using directive communication allow it to pass the HTML block (already evaluated) to the other directive. The only problem is making sure this happens when things change through binding. You're probably going to need some $watches on variables in section in order for placeholder to be notified when things change.
You will probably need a 3rd directive so allow section and placeholder to communicate through. In this example say I have a 3rd directive called broadcaster. Then section and placeholder will require broadcaster (ie require: '^broadcaster'), and it will define some interface for each of the directives to send HTML from section -> placeholder. Something like this:
<div broadcaster>
<div placeholder="footer"></div>
<div section="footer">
<ul>...transcluded content</ul>
</div>
</div>

Why this doesnt fire ng-repeat?

My HTML
ng-app and ng-controller are specified in markup earlier
<div class="statusEntry" ng-repeat="statusInput in statusInputs">
<span class="userName"> a </span>
<span class="statusMsg"> b </span>
</div>
Controller
app.controller('globalCtrl', ['$scope', function($scope) {
//someWork
pubnub.subscribe({
channel: "statuses",
callback:
function (data) {
splitData = data.split(';');
prepData = '{'+splitData[0]+','+splitData[1]+'}';
statusInputs.push(prepData);
}
});
When I push the data no new object appears.
Your Controller has no name.
You haven't declare an ng-app or ng-controller in your markup anywhere.
data should be named $scope so Angular can appropriately inject the dependency.
It doesn't look like either statusInputs or your function are part of the $scope therefore there's no way for your view to access them.
Replace
statusInputs.push(prepData);
with
$scope.statusInputs.push(prepData);
This is how you enable your views to access them.

How do I get the clicked element's info (from the scope) in a given AngularJS directive?

Well, let me describe my original problem that's being aroused time to time. Consider the following product.html page:
<div data-ng-controller="productsCtrl" data-ng-init="getProducts()" class="row-fluid">
<div class="span12">
<div data-ng-repeat="section in sections">
<h2>{{section.name}}</h2>
<div class="products-container">
<div data-ng-repeat="product in section.products">
<img alt="{{product.name}}" class="product-img" data-zoomable-image="/path_to_large_img_or_empty_if_no_image_to_zoom" data-ng-src="/path_to_thumbnail_img">
<div class="product">
<!-- Product Info -->
</div>
</div>
</div>
</div>
</div>
</div>
The productsCtrl controller is obviously responsible to get the products information, one of which is the thumbnail / large image url of the product. The idea is to create a directive that controls the img element based on "data-zoomable" attribute on a given image. If the attribute's value is empty, there's no image to be displayed; Otherwise, a modal box should be opened to display the image.
So far, so good. So let me show the zoomableImage directive:
app.directive('zoomableImage', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var zoomIn = element.wrap('').click(function (e) {
var href = element.data('zoomableImage');
var modal = $('#zoomInModal');
//Here, I need to pass the "product's name" to the zoomInCtrl's scope.
//This can be done using the following 3 lines of code...
var s = modal.scope();
s.product = 'The clicked element\'s name obtained from the DOM "element"';
s.$apply();
//I believe that those 3 above lines suck, since I'm not supposed to
//set the scope of another controller in a directive. Right?
modal.prependTo('body').modal('show');
e.preventDefault();
});
$('<span class="label label-info">Zoom In</span>').insertAfter(zoomIn);
}
};
});
So, the question is that what's the correct way of passing the "clicked" product's Name, Part#, and Image URL to the dialog I'm going to open above? Please note that the ProductsCtrl returns an array of section, each section contains multiple products and so on...
Of course, I can put the name, part# and other related information in different data attributes of the img element, but I do believe this duplicates the information that's already there in the products controller.
Thanks.
You can pass the reference of your product objects(s) to the directive by adding this to the directive:
scope: {
product: '='
}
The object is then shared between your controller and your directive instance.
You have to add the attribute in your html too:
<img data-product="product" data-zoomable-image="..." ... >
EDIT:
Modal should be a service. Checkout the angular-bootstrap modal.

How can I set a form contained inside a ng-include to be prestine?

I have the following code:
<div modal="modal.shouldBeOpen" close="close()" options="opts">
<div class="modal-body">
<form novalidate name="itemForm" style="margin-bottom: 0px;">
Which is contained inside the included file modal.html
<div data-ng-controller="AdminController">
<ng-include src="'/Content/app/admin/partials/grid-subject.html'"></ng-include >
<ng-include src="'/Content/app/admin/partials/modal.html'"></ng-include>
</div>
In my AdminController controller I am trying to use the following code to reset the form to pristine:
$scope.itemForm.$setPristine();
When I do this it tells me that "itemForm" is undefined.
Is there a way I can set the contents of the form to pristine. I assume this is a scope problem but I am not sure how to fix it. I
tried the one solution of removing the second include and pasting the code in directly. This solution works.
However we want to be able to reuse code
so I would like to be able to do this with an include for modal.html
Note that the reason we would like to do this is because we have something like the following on our modal.html:
<button
class="btn float-right"
data-ng-disabled="itemForm.$pristine"
data-ng-click="modalReset()"
data-ng-show="modal.resetButton">
Reset</button>
</form>
So we are actually inside of the itemForm and would like to set it to $pristine from the button inside.
This answer will break all the rules (i.e., DOM traversal inside a controller), but here it is anyway...
.controller('AdminController', ['$scope','$element',
function($scope, $element) {
$scope.$on('$includeContentLoaded', function() {
var childFormController = $element.find('form').eq(0).controller('form');
console.log(childFormController);
childFormController.$setPristine();
});
}]);
We wait for the ng-included content to load, then from the $element where AdminController is defined, we look for form elements, pick the first one, then get its FormController.
Plunker
If you are only calling $setPristine() as a result of some user interaction, you won't need to look for the $includedContentLoaded event – I only had to do that because I didn't want to create any UI component to trigger the operation, and when the controller first runs, the form doesn't exist yet.
See also AngularJS: Access formController of a form placed inside transcluded directive from parent controller which deals with the similar problem of trying to access a child from a parent.
A cleaner solution: define a directive (use it on the ng-include element) and pass it an AdminController function as an attribute. In the directive's link function, call that method and pass the FormController as a parameter. Then the AdminController will have a reference to the desired FormController. (I did not bother coding this up, as I'm not sure you want a solution where you have to use a directive along with ng-include.)
Well, one way to do it is to broadcast an event, like so:
angular.module('myApp',[])
.controller('AdminCtrl',function($scope){
$scope.modalReset = function(){
$scope.$broadcast('modal-reset');
};
})
.controller('ModalCtrl', function($scope){
$scope.$on('modal-reset', function(){
$scope.itemForm.$setPristine();
});
});
This way you don't have to traverse the dom.
Do not break the rules :) Just define the variable (empty object) in the controller and use it while defining your form. Since angular JS uses scope prototypes under the hood, when form will try to access the inner scope (to bootstrap the variable), it will first go via scope chain and try to find the same variable in the parent's scope.
<!—- The vars should live in the controller. I placed them here for the example. -—>
<div ng-controller=“controllerName” ng-init="form={}; model={}" >
<div ng-include=“ ‘path-to-the-template’ ”></div>
</div>
<!—- Inside path-to-the-template -—>
<form name="form.createUser">
<input name="name" ng-model="model.name" />
<input name="email" ng-model="model.email" />
</form>
Link for reference http://blog.152.org/2014/07/angular-form-element-not-attaching-to.html
If you want to achieve this as the result of some user interaction, in my opinion a much more cleaner and 'angular' way of doing it would be to use a custom directive which will set the form to pristine (i.e. when the user wants to clear the form by pressing esc or clicking a button or whatever).
app.directive("formCleaner",
function () {
return {
restrict: 'E',
require: '^form',
scope: {
callback: '&',
defaultText:'#'
},
template: '<button type="button" ng-click="setFormToPristine()" class="btn btn-warning" >{{defaultText}}</button>',
link: function (scope, element, attrs, formCtrl) {
scope.setFormToPristine = function () {
formCtrl.$setPristine();
scope.callback();
};
}
};
});
and simply hook it up to some button in your form:
<form name="testForm">
<input type="text" ng-model="someModel" />
<hr/>
<input type="button" value="submit form" class="btn btn-primary" ng-disabled="testForm.$pristine"
ng-click=submitForm(testForm) />
<form-cleaner callback="resetFormCallback(testForm)" default-text="Clear Form"></form-cleaner>
</form>
And if you're looking to set the form to pristine directly from the controller, (not as a result of some user interaction) such as success response from a POST, then one way would be to assign a callback to the directive which will be responsible for clearing the form and then invoking that callback from the controller. In your view:
<form-cleaner callback="resetFormCallback(testForm)" default-text="Clear Form"></form-cleaner>
and the controller:
$scope.resetFormOnSubmitCallback=function(cb){
$log.warn("simulating $http POST call.....");
$timeout(function() {
cb();
$scope.someModel=null;
}, 3000)
}
and the directive:
return {
restrict: 'E',
require: '^form',
scope: {
callback: '&',
defaultText:'#',
ngDisabled:'='
},
template: '<button type="button" ng-disabled="ngDisabled" ng-click="submitForm()" class="btn btn-primary" >{{defaultText}}</button>',
link: function (scope, element, attrs, formCtrl) {
var setFormToPristine=function(){
$log.log("setting form to prsitine....");
formCtrl.$setPristine();
};
scope.submitForm = function () {
scope.callback({
onFormSubmittedCallback:setFormToPristine
});
};
}
};
See plunk

Resources