Passing argument to custom directive in AngularJS - javascript

I have a navbar that shows few country names and when you click on them respective map should show up.
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">Welcome to the world of directives!</a>
</div>
<ul class="nav navbar-nav">
<li ng-repeat="countryTab in countries" ng-clicked="itemClicked(countryTab)" style="cursor:pointer">
<a>{{countryTab.label}}</a></li>
</ul>
</div>
</nav>
And the array of countries for now is hard-coded .
var app = angular.module('app',[]);
app.controller('appCtrl',function($scope){
// Countries
$scope.countries = [{
id: 1,
label: 'Italy',
coords: '41.29246,12.5736108'
}, {
id: 2,
label: 'Japan',
coords: '37.4900318,136.4664008'
}, {
id: 3,
label: 'USA',
coords: '37.6,-95.665'
}, {
id: 4,
label: 'India',
coords: '20.5937,78.9629'
}];
});
The custom directive that should show the respective map for now is like :
<div>
<country-tab-bar></country-tab-bar>
</div>
And
app.directive('countryTabBar',function(){
return {
restrict: ';E',
template: '<div>'+
' <div>Italy</div>'+
' <br/>'+
' <img ng-src="https://maps.googleapis.com/maps/api/staticmap?center=41.29246,12.5736108&zoom=4&size=800x200"> '+
'</div>',
link : function(scope){
scope.itemClicked = function(value){
}
}
}
});
As it is hard coded coordinates , it only shows one map of Italy. But I want to make it to show respective maps passing coordinates .
Also the name in the div should change to reflect the current country .
How to achieve the same ?
Please provide a necessary explanation.

You can achieve the desired like below
you need to pass the country label and country coords from the html to directive first.
<country-tab-bar coords="countryTab.coords" country="countryTab.label"></country-tab-bar>
Receive the values in directive scope.
scope: {
coords: '=coords',
country: '=country',
}
in the link section use these scope members and update the template URL. append this to the element (this is the html tag where the directive is applied). and finally compile it.
var app = angular.module('app',[]);
app.controller('appCtrl',function($scope){
// Countries
$scope.countries = [{
id: 1,
label: 'Italy',
coords: '41.29246,12.5736108'
}, {
id: 2,
label: 'Japan',
coords: '37.4900318,136.4664008'
}, {
id: 3,
label: 'USA',
coords: '37.6,-95.665'
}, {
id: 4,
label: 'India',
coords: '20.5937,78.9629'
}];
});
app.directive('countryTabBar', function($compile){
return {
restrict: ';E',
scope: {
coords: '=coords',
country: '=country',
},
link : function(scope,element){
var template = '<div ng-click="show()">'+scope.country+'</div><img ng-src="https://maps.googleapis.com/maps/api/staticmap?center='+scope.coords+'&zoom=4&size=800x200">';
element.append(template);
$compile(element.contents())(scope);
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app" ng-controller="appCtrl">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">Welcome to the world of directives!</a>
</div>
<ul class="nav navbar-nav">
<li ng-repeat="countryTab in countries" style="cursor:pointer">
<country-tab-bar coords="countryTab.coords" country="countryTab.label"></country-tab-bar>
</li>
</ul>
</div>
</nav>
</body>

Related

How to filter an array by another array in vue

I have two arrays of object which one of them is called submodules which has a children array in it and I need to filter these childrens arrays by accessed array of objects
new Vue({
data: {
submodules: [
{
type: "something1",
children: [
{
name: "new_sth1",
value: "new_sth1"
},
{
name: "new_sth2",
value: "new_sth2"
}
]
},
{
type: "something2",
children: [
{
name: "new_sth3",
value: "new_sth3"
},
]
},
{
type: "something3",
children: [
{
name: "new_sth4",
value: "new_sth4"
},
{
name: "new_sth5",
value: "new_sth5"
},
{
name: "new_sth6",
value: "new_sth6"
},
]
},
],
accessed: [
{value: 'new_sth1'},
{value: 'new_sth2'},
{value: 'new_sth3'},
{value: 'new_sth4'},
]
}
})
<div class="row">
<div class="col-6 mt-3" v-for="item in submodules">
<div class="card" style="min-height: 260px">
<div class="card-header" style="background: #757575;color: white">
{{item.type}}
</div>
<div class="card-body">
<ul class="nav nav-pills nav-stacked mb-0 pb-0">
<li style="width: 100%;cursor: pointer" class="navbar my-0 py-0" v-for="child in item.children">
<a style="color: black" :href="'/'+child.value">
<span class="text-right navbar-text">{{child.name}}</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
I need to filter submodules array by accessed array values.
I tried many ways but I couldn't solve the problem.
If anyone has any idea please share it with me.
You can use computed property to filter the submodules array according to accessed.
computed properties in vue js
codesandbox: Code is not styled as I've not installed the bootstrap.
computed: {
getFilteredResult() {
return this.submodules.reduce((acc, curr) => {
const children = curr.children.filter(({ value }) => {
return this.accessed.find((x) => x.value === value);
});
acc.push({ ...curr, children });
return acc;
}, []);
},
},

Searching through multiple ng-repeats at once

i'm currently working on an application that is build with AngularJS as a base, and that obtains data through the prestashop webservice. All data obtained are JSON strings sorted through multiple files. Now i'm trying to create a searchbox that filters through some objects the moment the user fills in the searchbox. The easy way is ofcourse by using the ng-model and filter: combination like below:
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.names = [
'Jani',
'Carl',
'Margareth',
'Hege',
'Joe',
'Gustav',
'Birgit',
'Mary',
'Kai'
];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!DOCTYPE html>
<html>
<body>
<div ng-app="myApp" ng-controller="namesCtrl">
<p>Type a letter in the input field:</p>
<p><input type="text" ng-model="test"></p>
<ul>
<li ng-repeat="x in names | filter:test">
{{ x }}
</li>
</ul>
</div>
<p>The list will only consists of names matching the filter.</p>
</body>
</html>
But what if you're using two different sources? and two different ng-repeats?
So in my application some of the data is about customers. The data is obtained through two different $http.get() functions. One is for the customers basic information. The second one is the address information. Take a look below:
// Get the customers
$http.get('config/get/getCustomers.php', {cache: true}).then(function(response){
$scope.customers = response.data.customers.customer
});
// Get the addresses
$http.get('config/get/getAddress.php', {cache: true}).then(function (response) {
$scope.addresses = response.data.addresses.address
});
By using ng-repeat and ng-if i'm able to filter the information and connect it together. ng-if="customer.id == address.id_customer" ng-repeat=...
A full example below:
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.customers = [{
'id': 1,
'name': 'Jani'
},{
'id': 2,
'name': 'Carl'
},
{
'id': 3,
'name': 'Tim'
},
{
'id': 4,
'name': 'Tom'
}
];
$scope.addresses = [{
'id': 1,
'id_customer': 1,
'place': 'Street 12'
},{
'id': 2,
'id_customer': 2,
'place': 'Other street'
},
{
'id': 3,
'id_customer': 3,
'place': 'marioworld!'
},
{
'id': 4,
'id_customer': 4,
'place': 'Space!'
}
];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="namesCtrl">
<div ng-repeat="customer in customers">
<div ng-bind="customer.id + ' - ' + customer.name"></div>
<div ng-if="customer.id == address.id_customer" ng-repeat="address in addresses" ng-bind="address.place">
</div>
</div>
</div>
So as you can see i'm able to create the combination with the ng-if but now i would like to create a search input that's able to search through both fields. And that's where my issue starts. I'm able to create it for one ng-repeat. But what if i want to Search on the address and the customer? I would like to create the possibility of letting the user search by customer name, street address and ZIP code. But the ZIP code and address are from a different source.
I hope that someone has found a solution for this and if you have any questions please ask them in the comments.
As always, thanks in advance!
I'd suggest to map your customers array adding to each object it's own place this way:
$scope.customers.map( function addPlace(item) {
item.place = $scope.addresses.reduce(function(a,b){
return item.id === b.id_customer ? b.place : a;
}, '');
return item;
})
This way your template will be easier to read, and you will be able to use your previous search.
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.customers = [{
'id': 1,
'name': 'Jani'
},{
'id': 2,
'name': 'Carl'
},
{
'id': 3,
'name': 'Tim'
},
{
'id': 4,
'name': 'Tom'
}
];
$scope.addresses = [{
'id': 1,
'id_customer': 1,
'place': 'Street 12'
},{
'id': 2,
'id_customer': 2,
'place': 'Other street'
},
{
'id': 3,
'id_customer': 3,
'place': 'marioworld!'
},
{
'id': 4,
'id_customer': 4,
'place': 'Space!'
}
];
$scope.customers.map( function addPlace(item) {
item.place = $scope.addresses.reduce(function(a,b){
return item.id === b.id_customer ? b.place : a;
}, '');
return item;
})
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="namesCtrl">
<p><input type="text" ng-model="test"></p>
<div ng-repeat="customer in customers | filter:test">
{{ customer.id }} - {{ customer.name }}
<br>
{{ customer.place}}
</div>
</div>
</div>

Replace $scope with this by turning on ControllerAs

I thought to replace $scope with this keyword in my sample Angular code base and in turn to switch to using ControllerAs syntax.
But in turn this does not seem to work now.
I have a list of countries in my controller and in my custom directive whenever a country name is clicked , I show the map of the respective country.
<body ng-controller="appCtrl as vm">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">Welcome to the world of directives!</a>
</div>
<ul class="nav navbar-nav">
<li ng-repeat="countryTab in vm.countries" ng-click="vm.itemClicked(countryTab)" style="cursor:pointer">
<a>{{countryTab.label}}</a>
</li>
<br>
</ul>
</div>
</nav>
<data-country-tab-bar country="vm.selectedCountry" ng-if="vm.selectedCountry">
<strong><i>Check out our cool new directive!</i></strong>
</data-country-tab-bar>
<script>
var app = angular.module('app',[]);
app.controller('appCtrl',function($scope,$http){
this.countries = [{
id: 1,
label: 'Italy',
coords: '41.29246,12.5736108'
}, {
id: 2,
label: 'Japan',
coords: '37.4900318,136.4664008'
}, {
id: 3,
label: 'USA',
coords: '37.6,-95.665'
}, {
id: 4,
label: 'India',
coords: '20.5937,78.9629'
}];
this.itemClicked = function(value){
this.selectedCountry = value;
}
});
And in my directive , I just bind the country object that is the part of my DDO's isolated scope , to that of the controller's.
app.directive('countryTabBar',function(){
return {
restrict: 'E',
transclude:true,
replace:true,
$scope:{
country: '='
},
template: '<div>'+
' <div><strong>{{country.label }}</strong> : {{ country.coords}}</div>'+
' <br/>'+
' <img ng-src="https://maps.googleapis.com/maps/api/staticmap?center={{country.coords}}&zoom=4&size=800x200"> '+
' <br/><br/>'+
' <div ng-transclude></div>'+
'</div>',
}
});
</script>
</body>
But , I can see the transcluded string Check out our cool new directive! but I can't see the map.
There is no error as such in console.
Please help.
I think the problem is related to:
this.itemClicked = function(value){
this.selectedCountry = value;
}
this.selectedCountry in a function declared so in JavaScript will refer to the current function, not the controller (parent function) as you expect.
Solution (ES5):
var vm = this;
this.itemClicked = function(value){
vm.selectedCountry = value;
}
Solution (ES6):
this.itemClicked = value => this.selectedCountry = value;
Additionally, the directive scope syntax seems to be incorrect:
$scope:{
country: '='
},
Should be:
scope:{
country: '='
},
Hope this helps.

Dependent Select Angular JS

I have hierarchical data set. There is one fixed root unit.
What I want to do is to make this tree browsable with dependent selects.
I have created a simple plunkr example with a fixed dataset.
http://plnkr.co/edit/Bz5A1cbDLmcjoHbs5PID?p=preview
The data format in the example mimics the format I would get from a server request in "real" life.
This working fine in this simple first step. What is missing is, that when a user changes a selection somewhere in the middle, the select boxes and the ng-model binding below the new selection need to be destroyed.
So when I select Europe->France->Quimper and change "Europe" to "Asia" - then there should be "Asia" as the first select box and a second one the Asia countries.
Is there an "Angular" way to deal to deal with this? Any other hint is appreciated also ;)
<!DOCTYPE html>
<html ng-app="app">
<head>
<link data-require="bootstrap#3.3.5" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<script src="https://code.angularjs.org/1.3.17/angular.js" data-semver="1.3.17" data-require="angular.js#1.3.17"></script>
</head>
<body>
<div ng-controller="Ctrl">
<select ng-repeat="select in selects track by $index" ng-model="$parent.boxes[$index]">
<option ng-repeat="child in select.children" ng-click="expandSelects(child)">{{child.name}}</option>
</select>
<ul>
<li ng-repeat="item in boxes">{{ item }}</li>
</ul>
</div>
<script>
var app = angular.module('app', []);
app.controller('Ctrl', ['$scope', function($scope) {
var data = {
'europe': {
name: 'europe',
children: [{
name: 'france',
parent: 'europe'
}, {
name: 'italy',
parent: 'europe'
}],
},
'asia': {
name: 'asia',
children: [{
name: 'japan',
parent: 'asia'
}, {
name: 'china',
parent: 'asia'
}],
},
'france': {
name: 'france',
children: [{
name: 'paris',
parent: 'france'
}, {
name: 'quimper',
parent: 'france'
}]
}
};
var root = {
name: 'world',
children: [{
name: 'europe',
parent: 'world'
}, {
name: 'asia',
parent: 'world'
}, ]
};
$scope.selects = [root];
$scope.expandSelects = function(item) {
var select = data[item.name];
if (select) {
$scope.selects.push(select);
}
}
$scope.$watch('boxes', function(item, old) {
}, true);
}]);
</script>
</body>
</html>
This is a classic example of cascading dropdowns, with the added challenge of an unknown number of levels in the cascade. I combined the data set into one object for simplicity, added labels for the dropdowns, and simplified the select element.
This solution allows for any number of levels, so if you needed data below the city level, you could add it without changing any code, as illustrated by the "Street" example I added to Paris.
select {
display: block;
}
<!DOCTYPE html>
<html ng-app="app">
<head>
<link data-require="bootstrap#3.3.5" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<script src="https://code.angularjs.org/1.3.17/angular.js" data-semver="1.3.17" data-require="angular.js#1.3.17"></script>
</head>
<body>
<div ng-controller="Ctrl">
<div ng-repeat="select in selects track by $index" ng-if="select.children">
<label>{{ select.optionType }}</label>
<select ng-model="selects[$index + 1]" ng-options="child.name for child in select.children" ng-change="clearChildren($index)"></select>
<hr />
</div>
</div>
<script>
var app = angular.module('app', []);
app.controller('Ctrl', ['$scope', function($scope) {
var data = {
optionType: 'Continent',
name: 'World',
children: [
{
optionType: 'Country',
name: 'Europe',
children: [
{
optionType: 'City',
name: 'France',
children: [
{
optionType: 'Street',
name: 'Paris',
children: [
{
name: 'First'
},
{
name: 'Second'
}
]
},
{
name: 'Quimper'
}
]
},
{
name: 'Italy'
}
]
},
{
optionType: 'Country',
name: 'Asia',
children: [
{
name: 'Japan'
},
{
name: 'China'
}
]
}
]
};
$scope.selects = [data]
$scope.clearChildren = function (index) {
$scope.selects.length = index + 2;
};
}]);
</script>
</body>
</html>
To go to the children in your hierachy is not as hard as it may seem. If you set up your select with angular and let it do most of the selection for you (for example using ng-options instead of ng-repeating the tag itself), and tell it what options there are, then the list of children you are trying to render just becomes a standard ng-repeat of the children that were picked from the select above.
I modified your plunker to show you how you could accomplish that a slightly different way.
http://plnkr.co/edit/zByFaVKWqAqlR9ulxEBt?p=preview
Main points I changed were
$scope.expandSelects = function() {
var select = data[$scope.selected.name];
if (select) {
console.log('changed');
console.log(select);
$scope.chosen = select;
}
}
Here i just grab the chosen item which the will use. Then the ends up looking like.
<ul>
<li ng-repeat="item in chosen.children">{{ item.name }}</li>
</ul>
The only other set up that was really needed was setting up the with ng-options and giving it a model to bind to.
<select ng-options="child.name for child in selects.children"
ng-model="selected" ng-change="expandSelects()">
</select>
Use can use a filter on the second select to filter de options based on the previous selection.
For example, you can have a first selection to choose the continent:
<select ng-options="c for c in continents" ng-model="selectedContinent" ></select>
and a second selection for the coutries:
<select ng-options="c.name for c in countries | filter : {parent:selectedContinent}" ng-model="selectedCountry" ></select>
Made a fiddle with a simplified data structured just to show how the filter works: http://jsfiddle.net/marcosspn/oarL4n78/

How to debug AngularJS code?

I'm learning AngularJS.
I created a page index.html (code see below), which is supposed to display data of the product variable defined in the store module (file app.js below). It worked fine, until I added the Reviews section. Thereafter, placeholder substitution stopped to work (instead of {{product.name}}, the actual product name should be displayed).
But I can't figure out, what exactly is wrong now.
How can I debug this code (find out the reason, why all of a sudden no data are displayed) ?
app.js
(function(){
var app = angular.module('store', [ ]);
app.controller('StoreController', function(){
this.products = gems;
});
app.controller('PanelController', function(){
this.tab = 1;
this.selectTab = function(setTab) {
this.tab = setTab;
};
this.isSelected = function(checkTab) {
return this.tab === checkTab;
}
});
var gems = [
{
name: "Dodecahedron",
price: 2,
description: 'Some description',
canPurchase: true,
images : [
{
full: 'img01.jpg',
thumb: 'img01.jpg'
},
{
full: 'img02.jpg',
thumb: 'img02.jpg'
},
{
full: 'img03.jpg',
thumb: 'img03.jpg'
},
],
reviews = [
{
stars: 5,
body: "I love this product!",
author: "joe#thomas.com"
},
{
stars: 1,
body: "I hate this product!",
author: "mike#thomas.com"
},
]
},
{
name: "Pentagonal Gem",
price: 5.95,
description: 'Some description 2',
canPurchase: false,
images : [
{
full: 'img04.jpg',
thumb: 'img04.jpg'
},
{
full: 'img05.jpg',
thumb: 'img05.jpg'
},
{
full: 'img06.jpg',
thumb: 'img06.jpg'
},
]
}
]
})();
index.html
<!DOCTYPE html>
<html ng-app="store">
<head>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/>
</head>
<body ng-controller="StoreController as store">
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<div ng-repeat="product in store.products">
<h1>{{product.name}}</h1>
<section ng-init="tab = 1" ng-controller="PanelController as panel">
<ul class="nav nav-pills">
<li ng-class="{ active:panel.isSelected(1) }">
<a href ng-click="panel.selectTab(1)">Description</a>
</li>
<li ng-class="{ active:panel.isSelected(2) }">
<a href ng-click="panel.selectTab(2)">Specifications</a>
</li>
<li ng-class="{ active:panel.isSelected(3) }">
<a href ng-click="panel.selectTab(3)">Reviews</a>
</li>
</ul>
<div class="panel" ng-show="panel.isSelected(1)">
<h4>Description</h4>
<p>{{product.description}}</p>
</div>
<div class="panel" ng-show="panel.isSelected(2)">
<h4>Specifications</h4>
<blockquote>None yet</blockquote>
</div>
<div class="panel" ng-show="panel.isSelected(3)">
<h4>Reviews</h4>
<blockquote ng-repeat="review in product.reviews">
<b>Stars: {{review.stars}}</b>
{{review.body}}
<cite>by: {{review.author}}</cite>
None yet
</blockquote>
</div>
</section>
<h2>{{product.price | currency}}</h2>
<img ng-src="img/{{product.images[0].full}}"/>
<button ng-show="product.canPurchase">Add to cart</button>
</div>
</body>
</html>
Most of errors go to browser console. Also there are several techniques for custom errors handing, here is a good article. There is also other one, which describes several other code guards for common Angular pitfalls.
In your particular case definition of reviews = [ is not correct.
Sometimes I have this "problem" too with my AngularJS Apps, even when I have written much stuff, then suddenly it looks like your current result... :-)
Since now it was almost every time a missing ";" on end of a line, or a missing "{" or "}" brackets somewhere.... In your case I would take a look on something like this, before I change anything else...
Edit Found the Bug
Its like I said... Your Bug is inside your gems JSONArray.... I have replaced all ' with " and all = with :.... after that your app was back to live for me
var gems = [
{
name: "Dodecahedron",
price: 2,
description: "Some description",
canPurchase: true,
images : [
{
full: "img01.jpg",
thumb: "img01.jpg"
},
{
full: "img02.jpg",
thumb: "img02.jpg"
},
{
full: "img03.jpg",
thumb: "img03.jpg"
},
],
reviews : [
{
stars: 5,
body: "I love this product!",
author: "joe#thomas.com"
},
{
stars: 1,
body: "I hate this product!",
author: "mike#thomas.com"
},
]
},
{
name: "Pentagonal Gem",
price: 5.95,
description: "Some description 2",
canPurchase: false,
images : [
{
full: "img04.jpg",
thumb: "img04.jpg"
},
{
full: "img05.jpg",
thumb: "img05.jpg"
},
{
full: "img06.jpg",
thumb: "img06.jpg"
},
]
}
]

Categories