Angular JS + append items in ddl - javascript

I am trying to append an item inside my dll by targetting its ng-class after a save function like this but I can't seem to figure out what's wrong, I am new to AngularJs, please kindly advice:
My DDL:
<productbatchselectorcreate-dropdown ng-class="batch-{{item.productId}}" name="productBatch" id="{{item.productId}}" ng-model="item.productBatchId" ng-click="$event.stopPropagation();" class="ui-select2"></productbatchselectorcreate-dropdown>
Directive:
$scope.save = function () {
$scope.batch.productId = productId;
productService.addBatch($scope.batch)
.success(function (data) {
$('.batch-' + productId).append(new Option(data.name, data.productBatchId, false, false));
$modalInstance.dismiss('cancel');
})
};

I think you're going about this the wrong direction. Using jQuery to modify your DOM directly usually is not a good approach with Angular. Your drop down list options should be bound to some value on the scope (I'm not sure what that is though for your directive). Instead of modifying the DOM with jQuery, you want to add the new object to the scope value that the drop down items are bound.
I made a quick example showing how this works in a simple select http://plnkr.co/edit/jdCzqy3LZ5UXfskGdUMr?p=preview
Since you're using a custom directive though, I'm guessing you're creating the options using an ng-repeat in your directive template. If so, the same applies, whatever you're repeating over, the scope object for that should be modified, not the DOM directly.

Related

Check the availability of a javascript funcion in a page

What I want is very simple, I want the Expand All button to be auto clicked when I open this pluralsight course page. Its HTML is:
<a id="expandAll"
ng-click="expandAllModules()"
ng-hide="allModulesExpanded()">
Expand All
</a>
So it seems easy and we just need to call the function expandAllModules(). However I don't know why it give me undefined when I check its type:
typeof expandAllModules
=> "undefined"
Generally typeof a function should give me "function" like this:
function a(){}
=> undefined
typeof a
=> "function"
Since the function expandAllModules() is not available, I can't call it. Anyone can give me a hand on this issue?
Edit
Perhaps I need to elaborate on my question. I'm not the author of that page. I just want to make a simple greasemonkey or tempermonkey script and expand the modules automatically when I enter the page.
The Problem
The reason calling just expandAllModules() doesn't work is because this function belongs to one of Angular's scopes and isn't a method assigned to window. This function is defined in Plural Sight's table-of-contents-controller-v9.js like so:
"use strict";
pluralsightModule
.controller("TableOfContentsController", ['$scope', ..., function ($scope, ...) {
...
$scope.expandAllModules = function() {
_.each($scope.courseModules, function (module) { module.visible= true; });
};
...
}])
The Solution
In order for us to call this function ourselves, we have to go through this scope.
scope is an object that refers to the application model. It is an execution context for expressions. Scopes are arranged in hierarchical structure which mimic the DOM structure of the application. Scopes can watch expressions and propagate events. ā€“ AngularJS: Developer Guide
The scope is part of the element which triggers the function. We can access this particular scope by passing the element's id attribute into angular.element(), then calling scope() on that object:
angular.element('#expandAll').scope()
This will give us the following data, where we can see the expandAllModules() function:
Unfortunately AngularJS doesn't let us simply execute scope().expandAllModules(); instead we have to go through it's $apply and $eval methods:
var scope = angular.element('#expandAll').scope();
scope.$apply(function() {
scope.$eval(scope.expandAllModules())
});
We can now also collapse the modules as well by calling:
scope.$apply(function() {
scope.$eval(scope.collapseAllModules())
});
I apologize if I am off-base here. Are you trying to "link" into that page and 'force' the page to "expand all", or do you have access to the page, and want to trigger the click with some code on the page, by you inserting the code? Just doing something like this seems to work from commandline.
jQuery(function(){
jQuery('#expandAll').trigger('click');
});
Since I do not know your need, my thought is that this is a bit simplistic and not what you are looking for. From the responses of others, it appears you want to create your own directive to initiate the click?
I might have some typos -- but the idea is there.
angular.element(document.body).ready(function() {
var el = angular.element( document.getElementById('expandAll') );
var scope = el.scope();
scope.expandAllModules();
scope.$digest(); <--- might not be needed, but when i check your site, it needs to have this
});
updates
if it was just 'onclick' instead of 'ng-click', you do not need to get the scope; and just call the function directly.
updates
I have tried this on your site, you need to have scope.$digest(). When I tried it, i was using the developer console.
see the developer console below
I was playing with it on your site.

controller and link functions in directive do not have same scope

I have an issue with my angular.js directive.
It should be a kind of autocomplete, in directive's controller property I'm loading an array of values and inside link function compiling template to show the results.
But when I update scope inside link it doesn't reflect on controller and template, please take look at the example here - http://plnkr.co/edit/Lz3QGwklghPo3as2QTqU
Should I apply scope changes or smth similar?
Your code has two problems
Attach click event to document instead of body
Use $apply() inside bind
Below code will resolve your problem
$document.bind('click', function (e) {
scope.results = [];
scope.$apply();
});
I update your $body.bind('click',...) method to
$body.bind('change', function (e) {
scope.results = [];
});
and it seemed to work (I mean that after 0.5 sec I typed a letter, the list of name is re-displayed).

Can't get element inside controller in Angular

I am trying to get an element inside an AngularJS controller like this:
var imageInput = document.getElementById("myImage");
It doesn't work, the element is null. However if I move the statement to a function that I bound with ng-click it works. Why? and how do I fix it?
Because it's not rendered for that time. If you write all code, we'll suggest better alternative than working with dom in controller (it's no good for you :) )
Like Karaxuna said, it has not been rendered yet.
Depending on what you are trying to do, you could use a directive to add behavior to your element : http://docs.angularjs.org/guide/directive.
Add some code so we can give you a better answer.
Not only it may have not been rendered in your dom, but also you should inject the $window in your controller and then access the document like this:
var myCtrl = function($scope,$window) {
Var document = $window.document
...
}
But you should seek a better way to manipulate your DOM instead of doing so in your controller (e.g. Writing a directive)

Is there a way to watch attribute changes triggered from outside the AngularJS world?

Iā€™m trying to understand interactions between the Angular world and the non-Angular world.
Given a directive that one declares like this:
<dir1 id="d1" attr1="100"/>
If code outside angular changes the directive this way:
$("#d1").attr("attr1", 1000);
How can the directive know that one of its attribute has changed?
It would be best to make this change inside the directive instead. If, for whatever reason, that's not possible, then there are a couple of options.
Outside the app, get a reference to any DOM element within the app. Using that reference, you can then get a reference to its scope. You could use your element with id d1. For example:
var domElement = document.getElementById('d1');
var scope = angular.element(domElement).scope();
Here are a couple of options:
Option 1
Modify the model instead of making a direct change to the view. In the link function, store the initial attribute value in a scope variable like:
scope.myvalue = attrs.attr1;
Then you can change the value outside the app (using the above reference to scope) like:
scope.$apply(function(){
scope.myvalue = 1000;
console.log('attribute changed');
});
Here is a fiddle
Option 2
If the view is manipulated directly with jQuery, I don't know of any use of $observe, $watch, or an isolate scope binding to the attribute that will work, because they all bind to the attribute expression itself, just once, when the link function is first run. Changing the value will cause those bindings to fail. So you'd have to $watch the attribute on the DOM element itself (rather than through attrs):
scope.$watch(function(){
return $(el).attr('attr1'); // Set a watch on the actual DOM value
}, function(newVal){
scope.message = newVal;
});
Then you can change the value outside the app (using the above reference to scope) like:
scope.$apply(function(){
$("#d1").attr("attr1",1000);
});
Here is a fiddle
Use a Web Components library like x-tags by Mozilla or Polymer by Google. This option works without maunally calling $scope.$apply every time the attribute changes.
I use x-tags because of their wider browser support. While defining a new custom tag (directive) you can set the option lifecycle.attributeChanged to a callback function, which will fire every time an argument is changed.
The official docs aren't very helpful. But by trial and error and diving into the code I managed to find out how it works.
The callback function's context (the this object) is the element itself. The one whose attribute has changed. The callback can take three arguments:
name ā€“ the name of the attribute,
oldValue and
newValue ā€“ these speak for themselves.
So now, down to business:
The code
This will watch the attribute for changes:
xtag.register('dir1', {
lifecycle: {
attributeChanged: function (attribute, changedFrom, changedTo) {
// Find the element's scope
var scope = angular.element(this).scope();
// Update the scope if our attribute has changed
scope.$apply(function () {
if (attribute == 'attr1') scope.style = changedTo;
});
}
}
});
The attributeChanged callback only fires when the arguments' values actually change. To get their initial values you need to scan the lot manually. The easiest way seems to be while defining the directive:
myApp.directive('dir1', function () {
return {
... ,
link: function (scope, element, attributes) {
scope.attr1 = element[0].getAttribute('attr1');
}
};
});

AngularJS directive ng-click parameters not being passed

I'm trying to build a directive to output some HTML formatted code for a paging control (Twitter Bootstrap styled), this directive needs to take the current page and total pages from the scope of my controller and when a paging link is clicked trigger a function on my controller to change the page (builds a url and calls $location to change page).
I've watched many of the excellent YouTube angularjs videos (http://www.youtube.com/watch?v=nKJDHnXaKTY) but none seem to cover this particular complex scenario.
Any help would be great!
Here is jsfiddle that makes it clearer:
http://jsfiddle.net/philjones88/dVFDT/
What I can't get working is passing the parameter, I get:
changing page to: undefined
In your directive add the changePage call there (I know it's not where you want it). Have it call the parents scope changePage with the same parameter.
$scope.changePage = function(index){
console.log("running changePage");
$scope.$parent.changePage(index); //might want to check if the parent scope has this too
}
As another tip, in directives you shouldn't use the $ in front of the variables being sent in. In this case that would be $scope, $element, $attrs. The $ you see in front of scope in controllers (not linking functions) is there to let you know that it is being injected. It is not being injected in the linking controller. For instance, here:
app.directive("pager", function ($injector1, $injector2) {
This is where injected parameters would go, and you want to be able to distinguish the two of them. I realize this got a little off track and I hope the suggestion I have for the changePage is what you're looking for.
Edit: Updated fiddle http://jsfiddle.net/dVFDT/48/
Modified answer for future searchers: The function you were passing in via the click method like so:
..... click="changePage()".....
Needed to be changed to:
..... click="changePage".....
This means you're passing the function through and not the function call. This meant that in your directive when you wired up the changePage callback you were calling the function with the index like this:
changePage()(1)
and that's why you were getting undefined.
I dont understand completely, but at the end of your directive you want to execute a function of your controller?
Try:
<div class="pagination">
<pager current-page="currentPage" total-pages="totalPages" query="query" callback="changePage()"></pager>
</div>
I realize this question is a bit old, but there's actually another way to solve this that doesn't require recompiling or calling the parent scope. It does, however, require calling the method from within the directive in a slightly different way.
You can see the fiddle here: Fiddle
The line that's of most interest is in the template declaration. The call to onClick requires you pass it an object rather than just the value.
template:
"<div ng:repeat='i in [] | range:totalPages'> " +
"<a ng:click='onClick({page: i + 1})'>Page {{i + 1}}</a>" +
"</div>",
This also makes use of a filter from this answer from Gloopy in order to iterate n number of times in an ng:repeat. This allows the binding to all happen in the template.

Categories

Resources