I am using autocomplete with angularjs.
Everytime I select a value from a dropdownlist the items that fill the autocomplete should change. But they does not change.
I see that the model change, but not the outcomplete.
Here the directive I am using for autocomplete.
.directive('autoComplete', function($timeout) {
return function(scope, iElement, iAttrs) {
iElement.autocomplete({
source: scope[iAttrs.uiItems],
select: function() {
$timeout(function() {
iElement.trigger('input');
}, 0);
}
});
};
})
And here how I use the autocomplete:
<input type="text" name="indirizzo" data-ng-model="input.indirizzo" auto-complete ui-items="indirizzi" data-ng-model-options="{ updateOn: 'mousedown blur' }" />
Everytime I change the dropdownlist the $scope.indirizzi change correctly but not for the autocomplete.
Perhaps I should use a watch or something similar?
Any suggestions?
Thank you
UPDATE
Perhaps I have not explained good.
I Have a dropdownlist that when a value is selected from, fill my autocomplete list (like some cascade dropdown).
Here the dropdownlist:
<select name="state" data-ng-options="s for s in states" data-ng-model="input.state" data-ng-change="fillAutoComplete(input.state)" />
The fillAutocomplete function is :
$scope.fillAutoComplete = function (state) {
$scope.indirizzi = ...
}
Now, everything I change the value in the dropdown, the $scope.indirizzi is correctly updated, but the autocomplete list is not updated.
Please check this example auto-complete using angular.
<div ng-app='MyModule'>
<div ng-controller='DefaultCtrl'>
<input auto-complete ui-items="names" ng-model="selected">
selected = {{selected}}
</div>
JsCode
function DefaultCtrl($scope) {
$scope.names = ["john", "bill", "charlie", "robert", "alban", "oscar", "marie", "celine", "brad", "drew", "rebecca", "michel", "francis", "jean", "paul", "pierre", "nicolas", "alfred", "gerard", "louis", "albert", "edouard", "benoit", "guillaume", "nicolas", "joseph"];
}
angular.module('MyModule', []).directive('autoComplete', function($timeout) {
return function(scope, iElement, iAttrs) {
iElement.autocomplete({
source: scope[iAttrs.uiItems],
select: function() {
$timeout(function() {
iElement.trigger('input');
}, 0);
}
});
};
});
JSFiddle Working
I have changed my directive as following:
.directive('autoComplete', function($timeout) {
return function(scope, iElement, iAttrs) {
iElement.autocomplete({
source: scope[iAttrs.uiItems],
select: function() {
$timeout(function() {
iElement.trigger('input');
}, 0);
}
});
scope.$watch(iAttrs.uiItems, function () {
iElement.autocomplete("option", "source", scope[iAttrs.uiItems]);
});
};
})
See the scope.$watch
Autocomplete is a jQuery plugin, I guess you have to call $scope.$apply() to let Angular know that you have changed something.
Related
I've been struggling with Angular's isolate scope for over 24hrs now. Here's my scenario: I have an ng-repeat iterating over an array of objects from which I want to use a custom directive to either generate a <select> or <input> based on the field_type property of the current object being iterated. This means I'll have to generate the template and $compile in the post-link function of the directive since I have no access to the iterated object in the template function.
Everything works as expected, apart from the actual binding of the generated template to the controller (vm) in my outer scope. I think my approach (adding this in the template string: ng-model="vm.prodAttribs.' + attr.attribute_code +'") may be wrong, and would appreciate pointers in the right direction. Thanks!
See sample code below:
directives:
directives.directive('productAttributeWrapper', ['$compile', function($compile){
//this directive exists solely to provide 'productAttribute' directive access to the parent scope
return {
restrict: 'A',
scope: false,
controller: function($scope, $element, $attrs){
this.compile = function (element) {
$compile(element)($scope);
console.log('$scope.prodAttribs in directive: ', $scope.prodAttribs);
};
}
}
}]);
directives.directive('productAttribute', ['$compile', function($compile){
return {
restrict: 'A',
require: '^productAttributeWrapper', //use the wrapper's controller
scope: {
attribModel: '=',
prodAttribute: '=productAttribute', //binding to the model being iterated by ng-repeat
},
link: function(scope, element, attrs, ctrl){
var template = '';
var attr = scope.prodAttribute;
if(!attr) return;
switch(attr.attribute_field_type.toLowerCase()){
case 'textfield':
template =
'<input type="text" id="'+attr.attribute_code+'" ng-model="vm.prodAttribs.' + attr.attribute_code +'">';
break;
case 'dropdown':
template = [
'<select class="cvl" id="'+attr.attribute_code+'" ng-model="vm.prodAttribs.' + attr.attribute_code +'">',
'#cvl_option_values',
'\n</select>'
].join('');
var options = '\n<option value="">Select One</option>';
for(var i=0; i<attr.cvl_option_values.length; i++) {
var optionVal = attr.cvl_option_values[i].value;
options += '\n<option value="'+optionVal+'">' + attr.cvl_option_values[i].value + '</option>';
}
template = template.replace('#cvl_option_values', options);
break;
}
element.html(template);
ctrl.compile(element.html()); //try to bind template to outer scope
}
}
}]);
html:
<div ng-controller="ProductController as vm">
<div product-attribute="attrib" ng-repeat="attrib in vm.all_attribs"></div>
</div>
controller:
app.controller('ProductDetailsController', function(){
var vm = this;
//also added the property to $scope to see if i could access it there
$scope.prodAttribs = vm.prodAttribs = {
name: '',
description: '',
price: [0.0],
condition: null
}
vm.all_attributes = [
{
"attribute_id": 1210,
"attribute_display_name": "Product Type",
"attribute_code": "product_type",
"attribute_field_type": "Textfield",
"cvl_option_values": [],
"validation_rules": {}
},
{
"attribute_id": 902,
"attribute_display_name": "VAT",
"attribute_code": "vat",
"attribute_field_type": "dropdown",
"cvl_option_values": [
{
"option_id": "5",
"value": "5%"
},
{
"option_id": "6",
"value": "Exempt"
}
],
"validation_rules": {}
}];
})
issue is probably here :
element.html(template);
ctrl.compile(element.html()); //try to bind template to outer scope
element.html() returns a html as a string, not the ACTUAL dom content, so what you inserted into your directive's element is never actually compiled by angular, explaining your (absence of) behaviour.
element.append(ctrl.compile(template));
should work way better.
For directive requiring parent controller, I would also change your ctrl.compile method (renamed to insertAndCompile here)
ctrl.insertAndCompile = function(content) {
$compile(content)($scope, function(clone) {
$element.append(clone);
}
}
You would just have to call it this way :
ctrl.insertAndCompile(template);
instead of the 2 lines I gave as first answer.
I would suggest to use templates instead of html compilation manually. The solution is much simpler:
Controller would contain data declaration:
app.controller('ProductDetailsController', function($scope) {
$scope.prodAttribs = {
name: '',
description: '',
price: [0.0],
condition: null
}
$scope.all_attribs = [{
"attribute_id": 1210,
"attribute_display_name": "Product Type",
"attribute_code": "product_type",
"attribute_field_type": "Textfield",
"cvl_option_values": [],
"validation_rules": {}
}, {
"attribute_id": 902,
"attribute_display_name": "VAT",
"attribute_code": "vat",
"attribute_field_type": "dropdown",
"cvl_option_values": [{
"option_id": "5",
"value": "5%"
}, {
"option_id": "6",
"value": "Exempt"
}],
"validation_rules": {}
}];
});
Your directive would be as simple as that:
app.directive('productAttribute', function() {
return {
restrict: 'A',
scope: {
attribModel: '=',
prodAttribute: '=productAttribute'
},
templateUrl: 'template.html',
controller: function($scope) {}
}
});
template.html would be:
<div>
<select ng-show="prodAttribute.attribute_field_type.toLowerCase() == 'dropdown'" class="cvl" id="" ng-model="prodAttribs.attribute_code">
<option value="">Select One</option>
<option ng-repeat="item in prodAttribute.cvl_option_values track by $index" value="{{item.value}}">{{item.value}}</option>
</select>
<input ng-show="prodAttribute.attribute_field_type.toLowerCase() == 'textfield'" type="text" id="{{prodAttribute.attribute_code}}" ng-model="prodAttribute.attribute_code">
</div>
And your html:
<div ng-controller="ProductController">
<div ng-repeat="attrib in all_attribs" product-attribute="attrib">{{attrib}}</div>
</div>
I'm trying to display html in UI-Grid that is sent from the server. An example is my column header containing a tooltip's HTML that was created server side and concatenated to the string (for some reason). I need that to display as HTML but regardless of SCE setting, it still displays the HTML encoded. Here is an example that replicates my issue:
var app = angular.module('app', ['ngTouch', 'ui.grid']);
app.config(function($sceProvider){
$sceProvider.enabled(false);
});
app.controller('MainCtrl', ['$scope', function ($scope) {
$scope.gridOptions = {
columnDefs: [
{
displayName: "First Name",
field: "firstName",
cellFilter: "exampleFilter:this"
},
{
displayName: "First Name",
field: "lastName",
},
{
displayName: "First Name",
field: "company",
},
{
displayName: "First Name",
field: "employed",
}
]
};
$scope.gridOptions.data = [
{
"firstName": "Cox",
"lastName": "Carney",
"company": "Enormo",
"employed": true
},
{
"firstName": "Lorraine",
"lastName": "Wise",
"company": "<span>Comveyer</span>",
"employed": false
},
{
"firstName": "Nancy",
"lastName": "Waters",
"company": "Fuelton",
"employed": false
}
];
}]);
app.filter('exampleFilter', function ($sce) {
console.log($sce.isEnabled());
return function (value) {
return $sce.trustAsHtml("<span>Here</span>");
//return (isNaN(parseFloat(value)) ? '0.0' : Number(value).toFixed(2)) + "%";
}
});
This is my plunker demo
You need to combine a cellTemplate and sce.
{
displayName: "First Name",
field: "company",
cellTemplate: '<div ng-bind-html="COL_FIELD | trusted"></div>'
}
COL_FIELD is replaced by ui-grid to contain the right field binding. The cellTemplate uses ng-bind-html and the following trusted filter to display the unescaped HTML:
app.filter('trusted', function ($sce) {
return function (value) {
return $sce.trustAsHtml(value);
}
});
http://plnkr.co/edit/rOWDQkFrQh24DWmIbhZx?p=preview
If you want to render the html of a filtered field, you can do this:
cellTemplate: '<div class="ui-grid-cell-contents" ng-bind-html="grid.getCellDisplayValue(row ,col)"></div>'
You may need to install ngSanitize plugin to ng-bind-html work.
I am trying to bind data from a web service and then use that data to pre-populate a form. All form controls are binding correctly except for a single multi-select element. If I manually select an option the model does update. Below is my controller:
myApp.controller('AdminVideosEditCtrl', [
'$scope',
'$http',
'$routeParams',
'$location',
function($scope, $http, $routeParams, $location) {
$http.get('/videos/' + $routeParams.videoId + '?embed=presenters').success(function(data) {
$scope.video = data.data
// Load providers
$http.get('/providers').success(function(data) {
$scope.providers = data.data;
// Load Presenters
$http.get('/presenters').success(function(data) {
$scope.presenters = data.data;
});
});
});
}
]);
Once the final request returns, my model looks like this (output via {{ video | json }}):
{
"id": "ca3ca05a-834e-47b1-aaa1-3dbe38338ca9",
"title": "Doloremque iure consequatur quam ea.",
"is_public": false,
"is_visible": true,
"url": "http://someurl.com/",
"provider_id": "1b4d18eb-d56c-41ae-9431-4c058a32d651",
"level_id": "38ed7286-da05-44b9-bfb9-e1278088d229",
"duration": "17:38",
"transcript_file": "rerum-sint-voluptatum.md",
"presenters": [
{
"id": "5111531d-5f2a-45f5-a0c4-4fa3027ff249",
"first_name": "First",
"last_name": "Last",
"full_name": "First Last"
}
],
"provider": {
"id": "1b4d18eb-d56c-41ae-9431-4c058a32d651",
"title": "You Tube"
}
}
Here is how the multi-select looks in my view:
<div class="form-group">
<label for="presenters" class="col-lg-2 control-label">Presenters</label>
<div class="col-lg-10">
<select multiple="multiple" class="form-control" id="presenters" ng-model="video.presenters" ng-options="presenter.full_name for ( id , presenter ) in presenters">
</select>
</div>
</div>
The select element populates correctly, and I would expect for it to default with the "First Last" element selected, however nothing is selected. I know my model is initialized correctly because if I manually select the element nothing in the model changes (if I select a different element it does, but still retains the same structure as it does on initial page load).
I tried adding a $scope.$apply call, and I also tried $scope.$root.$eval(), neither of which worked.
Update
The presenters model (containing all of the presenters) looks like this after it is fetched from the service (names have been changed to protect the innocent):
[
{
"id": "47b6e945-2d4b-44c2-b44b-adb96460864d",
"first_name": "First",
"last_name": "Last",
"full_name": "First Last"
},
{
"id": "5111531d-5f2a-45f5-a0c4-4fa3027ff249",
"first_name": "One",
"last_name": "Two",
"full_name": "One Two"
},
{
"id": "7cb1e44b-2806-4576-80b2-ae730ad356f7",
"first_name": "Three",
"last_name": "Four",
"full_name": "Three Four"
}
]
Solution
Just put this at the bottom of your controller
$scope.video.presenters.forEach(function(obj, idx){
$scope.presenters.forEach(function(presenter, jdx){
if (obj.id === presenter.id) {
$scope.video.presenters = [$scope.presenters[jdx]]
}
});
});
JSFIDDLE
More Robust Solution
This is more robust as you might want to preselect multiple options. This solution pushes each selected option into an array and then assigns it to $scope.video.presenters model
var selectedOptions = [];
$scope.video.presenters.forEach(function (obj, idx) {
$scope.presenters.forEach(function (presenter, idj) {
if (obj.id === presenter.id) {
selectedOptions.push($scope.presenters[idj]);
$scope.video.presenters = selectedOptions;
}
});
JSFIDDLE
Note: Ideally you should be using the id key as the unique for the objects.
This solution assumes and only caters for preselecting one option.
I'd like to populate a popup with the data of the element that was clicked. For example, I have a list of users with their name, position, etc. Then when I click 'view more', a popup shows up with the same user data that was in the list item.
At the moment nothing is get written in the popup.
Please see example here: http://jsfiddle.net/46LJ9/
JS/KO
//should be a json from server
var users = [
{
"name": "Yoshi",
"surname": "Kawasaki",
"position": "Developer"
},
{
"name": "Miu",
"surname": "Furinji",
"position": "Martial Artist"
},
{
"name": "Shigure",
"surname": "Kosaka",
"position": "Martial Artist Master"
},
{
"name": "Ore",
"surname": "Ore Ga",
"position": "Martial Artist and Developer"
}
];
(function($, ko, window) {
var UserModel = {
name: ko.observable(''),
surname: ko.observable(''),
position: ko.observable(''),
users: ko.observableArray(users),
userDetails: ko.observable({}),
showOverlay: function(o) {
UserModel.userDetails(o);
$('.overlay, .overlay-bg').fadeIn(400, function() {
$(this).removeClass('hide').addClass('show');
});
},
closeOverlay: function(o, e) {
$('.overlay, .overlay-bg').fadeOut(400, function() {
var $this = $(this);
$this
.removeClass('show')
.addClass('hide')
.removeAttr('style');
});
}
};
ko.applyBindings(UserModel);
}(jQuery, ko, window));
In your JS, you're updating UserModel.userDetails(o); but your html is binding to name etc.
<h1><span class="name" data-bind="text: name"></span></h1>
Change the binding to data-bind="text: userDetails().name" and it will work.
Either that, or update your name, surname and position observables.
See: http://jsfiddle.net/46LJ9/1/
EDIT
Obviously using a with binding makes more sense than the above:
<div class="overlay hide" data-bind="with: userDetails">
Seen here: http://jsfiddle.net/46LJ9/3/
I'm trying to figure out why I can't filter the content of the select when dynamically generated from an $http. In the plunker provided I can filter when I provide a test dataset, but when I retrieve the data from an $http request the select does not filter.
http://plnkr.co/edit/lmBRIvfZQogS4LTx2FYV?p=preview
Here is my controller:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $http) {
$http.get('http://graph.facebook.com/4')
.success(function(data) {
$scope.dataset = data;
})
.error(function() {
console.log('My name is Error, now eat it!');
});
// TEST DATASET
// $scope.dataset = [
// {name:'black', shade:'dark'},
// {name:'white', shade:'light'},
// {name:'red', shade:'dark'},
// {name:'blue', shade:'dark'},
// {name:'yellow', shade:'light'}
// ];
});
Here is my HTML:
<body ng-controller="MainCtrl">
Search:
<input type="search" ng-model="searchText" />
<BR>
<BR>
<select>
<option ng-repeat="data in dataset | filter: searchText">{{data}}</option>
</select>
</body>
Filter filters on items in an array. What is being returned from the service is a single object
{ "id": "4", "name": "Mark Zuckerberg", "first_name": "Mark", "last_name": "Zuckerberg", "link": "http://www.facebook.com/zuck", "username": "zuck", "gender": "male", "locale": "en_US" }
This is coming in dropdown because the dropdown also support object properties i think.