I'm trying to integrate Chosen (jquery plugin) with AngularJS, but I can't get it to populate any options in the select.
I started following this tutorial:
http://onehungrymind.com/angularjs-chosen-plugin-awesome/
and created this simple example (with help from 'Chosen Angular directive doesn't get updated'):
http://plnkr.co/edit/BzLAdotxKVI15t5phNAA?p=preview
module.directive('chosen', function(){
var directive = {};
directive.restrict = 'A';
directive.link = function(scope, element, attrs) {
var list = attrs['chosen'];
scope.$watch(list, function () {
element.trigger('chosen:updated');
});
scope.$watch(attrs['ngModel'], function() {
element.trigger('chosen:updated');
});
element.chosen();
};
return directive;
});
which doesn't error but doesn't add anything to the list. If I remove the "chosen" attribute from the select, it works as expected so I know it's not a problem with the bindings.
I then tried using the angular-chosen wrapper:
https://github.com/localytics/angular-chosen
(referenced in this question - Angular.js Chosen integration and (probably) AngularJS Chosen not working/updating)
and created this:
http://plnkr.co/edit/NmQiDgU1xOK8l9MtTcjS?p=preview
Which has the same problem.
UPDATE
In response to Jose M - I looked at ui-select but it requires a lot of (not-intuitive) markup instead of just an attribute, so I ruled it out.
UPDATE
In response to **jd17* - * Working plunkr from the answer below:
http://plnkr.co/edit/2v3BWVL0xFgQVce0MPXR?p=preview
In your app.js, why did you redefine "chosen" directive since you are using the Angular Chosen plugin? I modified your app.js in your second plnkr as follow and things works well.
var app = angular.module('myApp',['localytics.directives']); // inject the chosen
app.controller('TestController', ['$scope','$http',
function TestController($scope,$http){
$scope.url = 'testValues.json';
$scope.wordList = [];
$scope.selectedWord = {};
$scope.loadWords = function(){
$http.get($scope.url).then(function(result){
$scope.wordList = result.data;
});
};
$scope.loadWords();
}
]);
Related
For some reason when using this function('testclickfn') as ng-click on dynamic elements, it doesn't invoke the function. Here is the angularjs file:
app.controller('testctrl',function($scope){
testfn($scope);
$scope.showelements = function(){
displayTestRows();
}
});
function testfn($scope){
$scope.testclickfn = function(){
alert('testing click fn');
};
}
function displayTestRows(){
for(var i=0; i < 5; i++){
$("#testdiv").append('<p ng-click="testclickfn()">click me</p><br>');
}
}
HTML page that calls angularjs controller 'testctrl':
<div id="testdiv" ng-controller="testctrl">
<button ng-click="showelements()">Show dynamic elements</button><br>
</div>
I'm assuming since the 'click me' tags are being generated after angular has loaded the page, it doesn't know of anything after page is generated so ng-click="testclickfn()" doesn't get registered with angularjs.
How do I get around this situation?
You're creating elements in a way angular has no idea about (pretty bad practice), but not to worry, you can let angular know!
Change the controller signature to
controller('testctrl', function($scope, $compile) {
Then run compile the new elements manually to get the ng-click directive activated
$scope.showelements = function(){
displayTestRows();
$compile($("#testdiv").contents())($scope);
}
If you cant tell, having to use jquery selectors inside your controller is bad, you should be using a directive and the link function to attach the element to the scope (ie, what if you have multiple testctrl elements?), but this'll get you running
As promised
The general rules are that no JS should be outside the angular functions, and that DOM manipulation, where appropriate should be handled by angular also.
Example 1: powerful
Have a look
<div ng-controller="ctrl">
<button ng-click="show('#here')">
create
</button>
<div id="here">
I'll create the clickables here.
</div>
</div>
use controllers for things that share stuff between a lot of different things
.controller('ctrl', ['$scope', '$compile', function($scope, $compile) {
$scope.sharedVariable = 'I am #';
$scope.show = function(where) {
where = $(where).html('');
//lets create a new directive, and even pass it a parameter!
for (var index = 0; index < 5; ++index)
$('<div>', {'test':index}).appendTo(where);
$compile(where.contents())($scope);
};
}])
use directives for non-unique elements that each have their own states
.directive('test', function() {
return {
//these too have their own controllers in case there are things they need to share with different things -inside them-
controller : ['$scope', function($scope) {
$scope.test = function() {
//see, no selectors, the scope already knows the element!
$scope.element.text(
//remember that parent controller? Just because we're in another one doesnt mean we lost the first!
$scope.$parent.sharedVariable +
$scope.index
);
}
}],
//no need to do things by hand, specify what each of these look like
template : '<p>click me</p>',
//the whole "angular way" thing. Basically no code should be outside angular functions.
//"how do I reference anything in the DOM, then?"; that's what the `link` is for: give the controller access using `scope`!
link : function(scope, element, attributes) {
//you can assign "ng-click" here, instead of putting it in the template
//not everything in angular has to be HTML
scope.element = $(element).click(scope.test);
//did you know you can accept parameters?
scope.index = Number.parseInt(attributes.test) + 1;
},
//just some set up, I'll let you look them up
replace : true,
restrict : 'A',
scope : {}
};
})
Example 2: Simple
But that is just a very generic and powerful way of doing things. It all depends on what you need to do. If this very simple example was indeed all you needed to do you can make a very simple, almost-all-html version:
<div ng-controller="ctrl">
<button ng-click="items = [1, 2, 3, 4, 5]">
create
</button>
<p ng-repeat="item in items" ng-click="test($event)">
<span>click me</span>
<span style="display:none">I am #{{item}}</span>
</p>
</div>
.controller('ctrl', ['$scope', function($scope) {
$scope.test = function($event) {
$($event.currentTarget).children().toggle();
};
}])
That's it, works the same almost
Here's the scenario.
In the app, you can add inline custom code (HTML attributes ex. style="", onclick="alert('Test')") in an element (ex. input texts, divs). The custom code is binded to the main model and loaded to the element using a custom directive I've created. I'm doing this to control dynamically generated fields that I want to hide and show based on different inputs.
This is my custom directive that loads inline attributes on the element:
app.directive('addCustomHtml', function() {
return {
scope: {
customHtml: "="
},
link: function(scope, element, attributes){
scope.$watch('customHtml', function(newVal, oldVal) {
if (newVal) {
var attrs = newVal.split('\n');
for (var i = 0; i < attrs.length; i++) {
var result = attrs[i].split('=');
var attr = result.splice(0,1);
attr.push(result.join('='));
if (attr[1]) {
element.attr(attr[0], attr[1].replace(/^"(.*)"$/, '$1'));
}
}
} else {
if (oldVal) {
var attrs = oldVal.split('\n');
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i].split('=');
if (attr[0]) {
element.removeAttr(attr[0]);
}
}
}
}
})
}
}
});
It is binded to the element like this:
<input type="checkbox" add-custom-html custom-html="checkbox1.customHtml">Yes
To see it in action, you can check the plunkr here: https://plnkr.co/edit/xjjMRPY3aE8IVLIeRZMp?p=preview
Now my problem is, when I try to add AngularJS directives (ex. ng-show, ng-if) using my custom directive, AngularJS doesn't seem to recognize them and the model scope I'm passing inside.
Another problem is when I try to add vanilla Javascript event functions (ex. onclick="", onchange=""), it does work but sometimes AngularJS does not read them especially when the element has an ng-change, ng-click attributes.
Again, I am doing this approach on the app because I have generic fields and I want to control some of them by adding this so called "custom codes".
Any help would be highly appreciated!!
If you want to add HTML code and compile it within current $scope, you should use the $compile service:
let someVar = $compile(yourHTML)($scope);
// you can now append someVar to any element and
// angular specific markup will work as expected
Being a service, you'll need to inject it into current controller (or pre/post link function) to be able to use it.
I am new to angualrjs , I am using jQuery DataTable directive with angularJS which is working fine. The problem I am facing is adding javascript function to "TR" dynamically with "ng-click" which is not working. It seem's like dynamically added elements are not recognized by angular.
Can somebody help me out solving this problem.
There are many questions in Stackoverflow and mentioned solution I already tried but not working actually I am using row.add method of jquery for popluating rows.
Please find the code which I am using
angular.module('myApp', [])
.controller('myCtrl', ['$scope','$filter','$http', '$compile', function($scope, $filter, $http,$compile) {
$scope.myFunc = function() {
$http.get('http://localhost:9999/Angular_web/rest/main_search/byaufnum/').
then(function(response) {
var mytable = $('#table1').DataTable();
response.data.map(function(item) {
mytable.row.add(['<span style=\'white-space: nowrap\'>'+item.auftragsNr+'</span>']);
$compile(mytable.row)($scope);
})
mytable.draw(false);
});
};
$scope.getBGPdata = function(searchValue) {
console.log('--> '+searchValue);
};
}]);
I know that that problem can be resolved by using $compile and $scope but not sure how to add this in above code.
Below is the HTML of redering table
<tr role="row" class="odd"><td class="sorting_1"><span style="white-space: nowrap">KRT-ZDK-PMN_NEU-WWT_loopback_SFV_1</span></td>
issue is compiling mytable which is not a DOM element (its a DataTable instance object)
so you need to do it like this
var table = $('#table1');
var mytable = table.DataTable();
response.data.forEach(function(item) {
mytable.row.add(['<span style=\'white-space: nowrap\'>' + item.auftragsNr + '</span>']);
})
mytable.draw(false);
$compile(table)($scope); // here table not mytable
working example JSFIDDLE
EDIT
DataTables will only render first page initially, so when you apply $compile(table)($scope); its only compiling first page rows, not all rows (since they are not yet rendered).
to fix this issue, you need to compile all rows before adding them to DataTable
success: function(data) {
var mytable = table.DataTable();
var tbody = $('<tbody></tbody>');
data.forEach(function(item) {
tbody.append('<tr><td><span style="white-space: nowrap">' + item.auftragsNr + '</span></td></tr>');
})
// compiling all rows under tbody
$compile(tbody[0])($scope)
// adding only trs from tbody to DataTable
mytable.rows.add(tbody[0].children).draw(false);
}
also here is an updated JSFIDDLE with paging
It is not advisable to do service calls and DOM manipulation in controllers.
For that you can use factory/service/proider and directive respectively.
I guess the issue is with mytable.row which is not at all holding the row instances that you've added.
It should mytable.row(index).
So Instead of compiling mytable.row add all the rows and compile the table once with $compile(angular.element('#table1'))($scope);.
angular.module('myApp', [])
.controller('myCtrl', ['$scope','$filter','$http', '$compile', function($scope, $filter, $http,$compile) {
$scope.myFunc = function() {
$http.get('http://localhost:9999/Angular_web/rest/main_search/byaufnum/').
then(function(response) {
var mytable = $('#table1').DataTable();
response.data.map(function(item) {
mytable.row.add(['<span style=\'white-space: nowrap\'>'+item.auftragsNr+'</span>']);
});
mytable.draw(false);
$compile(angular.element('#table1'))($scope);
});
};
$scope.getBGPdata = function(searchValue) {
console.log('--> '+searchValue);
};
}]);
I'm writing a directive wrapper around a typeahead input. This directive listens for changes on a link and get's new data + options for the typeahead.
I can simply simulate this behaviour with a $timeout and demonstrated it in this plnkr.co.
JS
app.controller('sample', function($scope, $timeout) {
$scope.options = ['1800', '1900', '2100'];
// Simulate some latency
$timeout(function () {
$scope.options.push('1850');
}, 4000);
});
HTML
<div>
<input type="text" ng-model="optionValue" typeahead="opt for opt in options | filter:$viewValue">
</div>
If you start typing '18' in the input field it shows 1800 as expected. But when 1850 get's added after an amount of time, the selectable options from typeahead are not being updated.
-- FYI my real live directive looks like this --
$scope.$watch($interpolate(url), function (newUrl) {
$http.get(newUrl).then(function (response) {
$scope.options = response;
});
});
I tried to use typeahead="opt for opt in getData()" but this doesn't work because the interpolated value is not yet up to date. It's always one value behind.
Seems like an issue to post on AngularUI Bootstrap website. Matches are getting selected on every keystroke but they don't get updated if you change the underlying data between keystrokes. I don't see any work-around for this, except maybe triggering the appropriate key event handler on the input manually (when you change the collection).
If someone interested in the solution, here is how I solved it at the moment. I'm not happy with the end result, please provide me some feedback :-).
Plunkr
Check out updated-bootstrap.js, I had to add the following in order to make it work:
A custom attribute that'll be use for the $watchCollection
var customOptions = attrs.typeaheadCustomOptions || '';
In the function where it gets the matches I've added a watch if customOptions is provided:
if (customOptions) {
originalScope.$watchCollection(customOptions, function (matches) {
resetMatches();
updateMatches(matches);
});
}
And that was basically it :-), the updateMatches is just an abstraction of existing code. It's not being used by me and the manual update.
var updateMatches = function(matches) {
for (var i = 0; i < matches.length; i++) {
locals[parserResult.itemName] = matches[i];
scope.matches.push({
id: getMatchId(i),
label: parserResult.viewMapper(scope, locals),
model: matches[i]
});
}
scope.query = modelCtrl.$viewValue;
};
Opened issue on github
I am new to angularjs.....I am trying to write a directive which adds some html before and after an element...html is as desired but data binding not happening ... please help
plunker link
my precompile function is as follows
var linkFunction = function(scope,element,attrs){
element.removeAttr("cs-options");
var html = getHTML(element);
element.replaceWith(html);
$compile(element.parent())(scope);
}
Here's a way simpler solution, I'm using transclude to have the contents of the element copied into the template.
app.directive('csOptions',["$compile",function($compile){
return{
restrict:'A',
transclude:true,
template:"<form><div ng-transclude></div></form>"
}
}])
http://plnkr.co/edit/fqHr6i
The data binding does not work because the getHTML() method not copying {{abc}} along with the element. You need to update the link method as:
var linkFunction = function(scope,element,attrs){
// do not miss {{abc}}
var $parent = element.parent();
element.removeAttr("cs-options");
var html = getHTML($parent);
// override the parent not the element otherwise
// there will be two instances of {{abc}}
$parent.html(html);
$compile($parent)(scope);
}
Demo: http://plnkr.co/edit/mckBVu1HfT4fp90Twvum