ngRoute in angularjs/multiple views - angularjs-routing

I am following a tutorial for learning angular. I am using ngRoute to marry 2 sets of views/controllers.
customers.html and cutomersController and
orders.html and ordersController.
When index.html loads it does show customers.html as desired. But when I click on 'View Orders' link, it does not show the order details.
The link for the code: http://plnkr.co/DW2rqiFIkxnhVPPisfLn
here is the code for index.html
<!DOCTYPE html>
<html ng-app="customersApp">
<head>
<title>Route 2</title>
</head>
<body>
<div ng-view></div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular-route.min.js"></script>
<script src="app.js"></script>
<script src="customersController.js"></script>
<script src="ordersController.js"></script>
</body>
</html>
here is the code for app.js
var app = angular.module('customersApp', ['ngRoute']);
app.config(function($routeProvider){
$routeProvider
.when('/', {
controller: 'CustomersController',
templateUrl: 'customers.html'
})
.when('/orders/:customerId', {
controller: 'OrdersController',
templateUrl: 'orders.html'
})
.otherwise( { redirectTo: '/' });
});
here is the code for customersController.js
app.controller('CustomersController', function ($scope){
$scope.sortBy= 'name';
$scope.reverse = false;
$scope.customers=[
{
id: 1,
joined: '2000-12-02',
name:'John',
city:'Sacramento',
orderTotal:7.554,
orders: [
{
id:1,
product: 'Shoes',
total: 7.554
}
]
},
{
id:2,
joined: '2012-12-07',
name:'Tom',
city:'Chandler',
orderTotal:19.99,
orders: [
{
id:2,
product: 'Baseball',
total: 9.995
},
{
id:3,
product: 'Bat',
total: 9.995
}
]
},
{
id: 3,
joined: '1997-05-02',
name:'Matt',
city:'Michigan',
orderTotal:19.993,
order: [
{
id:4,
product: 'Tiara',
total: 19.993
}
]
},
{
id: 4,
joined: '2001-10-08',
name:'Jane',
city:'New York',
orderTotal:112.954,
order: [
{
id:5,
product: 'Stereo',
total: 112.954
}
]
}
];
$scope.doSort = function(propName){
$scope.sortBy = propName;
$scope.reverse = !$scope.reverse;
};
});
here is the code for OrdersController.js
(function(){
var OrdersController = function ($scope, $routeParams){
var customerId = $routeParams.customerId;
$scope.orders = null;
function init() {
// Search for the customers for the customer id
for(var i=0, len=$scope.customers.length; i<len;i++){
if ($scope.customers[i].id === parseInt(customerId)) {
$scope.orders = $scope.customers[i].orders;
break;
}
}
}
$scope.customers=[
{
id: 1,
joined: '2000-12-02',
name:'John',
city:'Sacramento',
orderTotal:7.554,
orders: [
{
id:1,
product: 'Shoes',
total: 7.554
}
]
},
{
id:2,
joined: '2012-12-07',
name:'Tom',
city:'Chandler',
orderTotal:19.99,
orders: [
{
id:2,
product: 'Baseball',
total: 9.995
},
{
id:3,
product: 'Bat',
total: 9.995
}
]
},
{
id: 3,
joined: '1997-05-02',
name:'Matt',
city:'Michigan',
orderTotal:19.993,
order: [
{
id:4,
product: 'Tiara',
total: 19.993
}
]
},
{
id: 4,
joined: '2001-10-08',
name:'Jane',
city:'New York',
orderTotal:112.954,
order: [
{
id:5,
product: 'Stereo',
total: 112.954
}
]
}
];
init();
};
OrdersController.$inject = ['$scope', '$routeParams'];
angular.module('customersApp')
.controller('OrdersController', OrdersController);
}());
here is the code for customers.html
<h2>Customers</h2>
Filter: <input type="text" ng-model="customersFilter.name"/>
<br /><br />
<table>
<tr>
<th ng-click="doSort('name')">Name</th>
<th ng-click="doSort('city')">City</th>
<th ng-click="doSort('orderTotal')">Order Total</th>
<th ng-click="doSort('joined')">Joined</th>
</tr>
<tr data-ng-repeat="cust in customers | filter:customersFilter | orderBy:sortBy:reverse">
<td>{{cust.name}}</td>
<td>{{cust.city}}</td>
<td>{{cust.orderTotal | currency}}</td>
<td>{{cust.joined | date:'longDate'}}</td>
<td>View Orders</td>
</tr>
</table>
<br />
<span>Total customers: {{customers.length}}</span>
here is the code for orders.html
<h2>Orders</h2>
<table>
<tr>
<th>Product></th>
<th>Total</th>
</tr>
<tr ng-repeat="order in orders">
<td>{{order.product}}</td>
<td>{{order.total |currency}}</td>
</tr>
</table>
Thanks in advance!

so luckily this is a simple fix.
For some reason your JSON is not uniform.
Though these two customers have only one order in their order array, the order array needs to be renamed to orders, for Matt and Jane.
You just need to edit this hard-coded JSON in ordersController.js and it should work.
{
id: 3,
joined: '1997-05-02',
name:'Matt',
city:'Michigan',
orderTotal:19.993,
order: [
{
id:4,
product: 'Tiara',
total: 19.993
}
]
},
{
id: 4,
joined: '2001-10-08',
name:'Jane',
city:'New York',
orderTotal:112.954,
order: [
{
id:5,
product: 'Stereo',
total: 112.954
}
]
}
];
In the meantime you should edit this loop
for(var i=0, len=$scope.customers.length; i<len;i++){
if ($scope.customers[i].id === parseInt(customerId)) {
$scope.orders = $scope.customers[i].orders;
break;
}
}
}
This is a great place to use the forEach method.
$scope.customers.forEach(function(customer) {
if (customer.id === parseInt(customerId) {
$scope.orders = customer.orders;
})
});
It's a little more modern and a little easier to read.

Related

Property 'campaign' was accessed during render but is not defined on instance

These are the issues i'm getting
Here below is the code that produces the problems, this part in particular:
When ever i;m trying to filter campaigns using company_id and product_id with v-if the problem occurs. Almost the same exact code works a few lines above filtering products. I have no idea what to do next. I tried refs and putting the mocked that into reactive variable and computeing it with a function but it didn;t work out.
<script setup>
import CompanyItem from "./CompanyItem.vue";
import ProductItem from "./ProductItem.vue";
import CampaignItem from "./CampaignItem.vue";
import { useCurrentCompanyStore } from "../stores/currentCompanyStore.js"
import { useCurrentProductStore } from "../stores/currentProductStore.js"
const companyStore = useCurrentCompanyStore();
const productStore = useCurrentProductStore();
const companies =
[
{
company_id: 1,
name: 'Domain of Man',
fund_balance: 100000,
products_list: [
{
product_id: 1,
name: 'gate'
},
{
product_id: 2,
name: 'exploration ship'
},
{
product_id: 3,
name: 'artifacts'
}
]
},
{
company_id: 2,
name: 'Hegemony',
fund_balance: 200000,
products_list: [
{
product_id: 1,
name: 'toothbrash'
},
{
product_id: 2,
name: 'ore'
},
{
product_id: 3,
name: 'food'
}
]
},
];
const campaigns = [
{
campaign_id: 1,
company_id: 1,
product_id: 1,
campaign_name: "Gates for everyone",
keywords: [
"one for each",
"limited offer"
],
bid_amount: 25000,
status: true,
town: "Tarnow",
radius: "10"
},
{
campaign_id: 2,
company_id: 1,
product_id: 3,
campaign_name: "Get them while they last",
keywords: [
"rare",
"one for each",
"limited offer"
],
bid_amount: 25000,
status: false,
town: "Tarnow",
radius: "10"
},
{
campaign_id: 3,
company_id: 3,
product_id: 1,
campaign_name: "Let the shine power your ship",
keywords: [
"electricity",
"green technology",
],
bid_amount: 25000,
status: true,
town: "Tarnow",
radius: "10"
}
];
</script>
<template>
<div class="container">
<div class="companies" >
<CompanyItem v-for="company in companies" v-bind:key="company.company_id" :company-id="company.company_id">
<template #name>
{{ company.name }}
</template>
<template #budget>
{{ company.fund_balance }}
</template>
</CompanyItem>
</div>
<div class="products">
<template v-for="company in companies">
<ProductItem
v-for="product in company.products_list"
v-bind:key="product.product_id"
:id="company.company_id"
v-if="companyStore.companyId === company.company_id"
:product-id="product.product_id">
<template #name>
{{ product.name }}
</template>
</ProductItem>
</template>
</div>
<div class="campaigns">
<CampaignItem
v-for="campaign in campaigns"
v-if="companyStore.companyId === campaign.company_id"
v-bind:key="campaign.campaign_id"
:id="campaign.campaign_id"
>
<template #name>
{{campaign.campaign_name}}
</template>
</CampaignItem>
</div>
</div>
</template>
<style scoped>
.container {
width: 100%;
height: 100%;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: auto;
grid-template-areas:
"companies products campaigns";
}
.companies {
grid-area: companies;
display: flex;
flex-direction: column;
overflow: hidden;
}
.products {
grid-area: products;
}
.campaigns {
grid-area: campaigns;
}
</style>
Here are stores:
import { defineStore } from 'pinia'
export const useCurrentCompanyStore = defineStore({
id: 'currentComapny',
state: () => ({
companyId: -1
}),
getters: {
getCompanyId: (state) => state.companyId
},
actions: {
change(newCompanyId) {
this.companyId = newCompanyId;
}
}
})
import { defineStore } from 'pinia'
export const useCurrentProductStore = defineStore({
id: 'currentProduct',
state: () => ({
productId: -1
}),
getters: {
getCompanyId: (state) => state.productId
},
actions: {
change(newProductId) {
this.productId = newProductId;
}
}
})
Btw. if anybody wants to run it themself here is the git repo, its feature/frontend branch:
https://github.com/kuborek2/campaign_planer
You must not use v-if and v-for on the same element because v-if will always be evaluated first due to implicit precedence.
And exactly because of that, you are facing this error of undefined company_id as v-for is not executed yet and v-if is trying to access it.
Make the changes as suggested below and it should fix your error.
<CampaignItem
v-for="campaign in campaigns"
:key="campaign.campaign_id"
:id="campaign.campaign_id"
>
<template v-if="companyStore.companyId === campaign.company_id" #name>
{{campaign.campaign_name}}
</template>
</CampaignItem>
Click here for the reference

Standard "check-all" functionality in table

Here's a part of my grid (CRUD) component:
<template>
<table class="MyComponent table">
<thead>
<tr>
<th width="30px">
<b-form-checkbox v-model="allChecked" />
</th>
</tr>
</thead>
<tbody>
<tr v-for="(record, index) in records" :key="index">
<td width="30px">
<b-form-checkbox :value="record['id']" v-model="checkedRows" />
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "MyComponent",
components: {
},
props: ['config'],
data() {
return {
records: [{
id: 1
}, {
id: 2
}, {
id: 3
}, {
id: 4
}, {
id: 5
}, {
id: 6
}],
checkedRows: []
}
},
computed: {
allChecked: {
get() {
return this.records.length == this.checkedRows.length
},
set(v) {
if(v) {
this.checkedRows = [];
for(var i in this.records) {
this.checkedRows.push(this.records[i]['id'])
}
}
else {
this.checkedRows = [];
}
}
}
}
};
</script>
As you can see, I would like to achive a standard, widely used functionality: The user can check multiple rows and do some operation with the selected rows. The problem is with the "check all" checkbox on the top of the table. When I check all, then I remove the tick from only one checkbox below, it unchecks all the checkboxes on page.
I understand why its happening: When I remove a tick from on of the checkboxes below, the "this.records.length == this.checkedRows.length" condition will no longer be true, so the "allChecked" computed variable will be set to false, therefore the top checkbox will set to unchecked. The problem is: when the top checkbox will be unchecked, then all of the checkboxes will be unchecked as well, because of the "set" part of the computed variable.
Is there a clean way to solve this problem in Vue?
I'm not sure what you want to do with the checked rows, but maybe this will be better:
<b-form-checkbox :value="record['id']" v-model="record.checked" />
Then add to your objects in records a checked property.
records: [
{
id: 1,
checked: false
},
...
]
and if you need a list of checked records you might do a computed property:
computed: {
checkedRecords() {
return this.records.filter(record => record.checked);
}
}
and for checking-unchecking all you just iterate over all records:
<b-form-checkbox #change="clickedAll" />
methods: {
clickedAll(value) {
this.records = this.records.map(record => {
record.checked = value
return record
}
}
}
OK, meanwhile I solved the problem. Here's my solution. Thanks #Eggon for your help, you gave the idea to use the #change method.
<template>
<table class="MyComponent table">
<thead>
<tr>
<th width="30px">
<b-form-checkbox v-model="allChecked" #change="checkAll" />
</th>
</tr>
</thead>
<tbody>
<tr v-for="(record, index) in records" :key="index">
<td width="30px">
<b-form-checkbox :value="record['id']" v-model="checkedRows" />
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "MyComponent",
components: {
},
props: ['config'],
data() {
return {
records: [{
id: 1
}, {
id: 2
}, {
id: 3
}, {
id: 4
}, {
id: 5
}, {
id: 6
}],
checkedRows: []
}
},
methods: {
checkAll(value) {
if(!value) {
this.checkedRows = [];
return ;
}
var newCheckedRows = [];
for(var i in this.records) {
newCheckedRows.push(this.records[i].id)
}
this.checkedRows = newCheckedRows;
}
},
computed: {
allChecked: {
get() {
return this.records.length == this.checkedRows.length
},
set() {
}
}
}
};
</script>

<li> be moved to another <ul> by vue

I'm building a list that users can choose items.
It would be a nested list with at least 3 layers.
As can only offer one layer of sub-option, I would like to build it as <ul> and <li>.
But I can't figure out how to change my code with two <ul> and <li>.
Hope someone could give me some ideas.
Here are what I have
<div id="applyApp" class="container">
<div class="pool">
<ul>
<h3 #click.prevent="isShow = !isShow">Category</h3>
<li v-for="items in filterData" :value="items.id">
{{items.id}} {{items.ame}}
</li>
</ul>
<ul class="selected-item">
<li v-for="items in secondLayer" :value="items.id">
{{items.id}} {{items.name}}
</li>
</ul>
</div>
Vue
new Vue({
el: "#applyApp",
data: {
firstLayer: [
{
name: "name1",
id: "0101",
},
{
name: "name2",
id: "010101",
},
{
name: "name3",
id: "010101001B",
},
],
secondLayer: [],
firstLayerValue: [],
secondLayerValue: [],
},
methods: {
moveHelper(value, arrFrom, arrTo) {
const index = arrFrom.findIndex(function (el) {
return el.id == value;
});
const item = arrFrom[index];
arrFrom.splice(index, 1);
arrTo.push(item);
},
addItems() {
const selected = this.firstLayerValue.slice(0);
for (const i = 0; i < selected.length; ++i) {
this.moveHelper(selected[i], this.firstLayer, this.secondLayer);
}
},
removeItems() {
const selected = this.secondLayerValue.slice(0);
for (const i = 0; i < selected.length; ++i) {
this.moveHelper(selected[i], this.secondLayer, this.firstLayer);
}
}
},
});
If you want nested items in your list, you might have to use a recursion component, which means adding child data to your items. And calling the component inside of itself until it exhausts the list of children, by taking the data props of the child in every call.
Vue.component("listRecursion", {
props: ['listData'],
name: "listRecursion",
template: `
<div>
<ul v-for="item in listData">
<li>{{item.name}}</li>
<list-recursion v-if="item.children.length" :list-data="item.children"/>
</ul>
</div>`
})
new Vue({
el: "#app",
data: {
todos: [{
name: "name1",
id: "0101",
children: [
{
name: "sub item 1",
id: "0d201",
children: []
},
{
name: "sub item 2",
id: "020g1",
children: []
},
{
name: "sub item 3",
id: "20201",
children: [
{
name: "subsub item 1",
id: "40201",
children: [
{
name: "subsub item 1",
id: "40201",
children: [
{
name: "subsubsub item 1",
id: "40201",
children: []
}]
}]
},
{
name: "subsub item 2",
id: "50201",
children: []
},
]
},
]
},
{
name: "name2",
id: "010101",
children: []
},
{
name: "name3",
id: "010101001B",
children: []
},
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Recursive list:</h2>
<list-recursion :list-data="todos"/>
</div>
As you can see, this saves you from manually adding new levels, just add to the data to the child nodes
I see #procoib which solves your first case and for the li rearrange, you can do the same approach similar to 'select' which is shown below.
new Vue({
el: "#app",
data() {
return {
first: [1, 2, 3, 4, 5],
second: []
}
},
methods: {
alter: function (index, src, desc) {
this[desc].push(this[src].splice(index, 1)[0])
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<div id="app">
<h1>First</h1>
<ul id="firstList">
<li v-for="(_, i) in first" v-on:click="alter(i, 'first', 'second')">{{_}}</li>
</ul>
<h1>Second</h1>
<ul id="secondList">
<li v-for="(_, i) in second" v-on:click="alter(i, 'second', 'first')">{{_}}</li>
</ul>
</div>
If you click any number for a list, it is added to the other list.

jquery datatable does not get initialized with given response

I want to initialize data table with my generated response
my response looks like this
{data: Array(3), status: true}
data : Array(3)
0 : {countryId: 1, countryName: "sampleCountry", countryShortCode: "sampleCode", status: "yes"}
1 : {countryId: 2, countryName: "pakistan", countryShortCode: "pak", status: "yes"}
2 : {countryId: 3, countryName: "sample2", countryShortCode: "pak", status: "yes"}
please look at my html
<table class="table table-striped" id="countryTable">
<thead>
<tr>
<th>S.NO.</th>
<th>Country Name</th>
<th>Country Short Name</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
please look at my datatable initialization
$.ajax({
url : url,
type:"get",
contentType:'application/json; charset=utf-8',
dataType: 'json' ,
async: false,
success:function(response)
{
alert(response.data);
$('#countryTable').DataTable( {
"fnRowCallback" : function(nRow, aData, iDisplayIndex){
$("td:first", nRow).html(iDisplayIndex +1);
return nRow;
},
destroy: true,
mydata: response.data,
columns: [
{ mydata:'countryId'},
{ mydata:'countryName'},
{ mydata:'countryShortCode'}
]
} );
console.log(response);
}
});
after initialization data table shows as No data available in table but table gets initialized with datatable plugin .
data is not coming into table.
what went wrong in my code please help me.
The code looks fine, you just need to change mydata to data, like this:
var response = {
data: [{
countryId: 1,
countryName: "sampleCountry",
countryShortCode: "sampleCode",
status: "yes"
},
{
countryId: 2,
countryName: "pakistan",
countryShortCode: "pak",
status: "yes"
},
{
countryId: 3,
countryName: "sample2",
countryShortCode: "pak",
status: "yes"
}
],
status: true
}
$('#countryTable').DataTable({
"fnRowCallback": function(nRow, aData, iDisplayIndex) {
$("td:first", nRow).html(iDisplayIndex + 1);
return nRow;
},
destroy: true,
data: response.data,
columns: [{
data: 'countryId'
},
{
data: 'countryName'
},
{
data: 'countryShortCode'
}
]
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
<link href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css" rel="stylesheet" />
<table class="table table-striped" id="countryTable">
<thead>
<tr>
<th>S.NO.</th>
<th>Country Name</th>
<th>Country Short Name</th>
</tr>
</thead>
<tbody>
</tbody>
</table>

Keeping track of an array of components

I have a really simple Vue app:
<div id="app">
<item v-for="item in items" v-bind:title="item.title" v-bind:price="item.price"
#added="updateTotal(item)"></item>
<total v-bind:total="total"></total>
</div>
And a Vue instance:
Vue.component('item',{
'props' : ['title', 'price'],
'template' : "<div class='item'><div>{{ title }} – ${{total}} </div><button class='button' #click='add'>Add</button></div>",
'data' : function(){
return {
quantity : 0
}
},
'computed' : {
total : function(){
return (this.quantity * this.price).toFixed(2);
}
},
methods : {
add : function(){
this.quantity ++;
this.$emit('added');
}
}
});
Vue.component('total', {
'props' : ['total'],
'template' : "<div class='total'>Total: ${{ total }}</div>",
});
var app = new Vue({
'el' : '#app',
'data' : {
'total' : 0,
'items': [
{
'title': 'Item 1',
'price': 21
}, {
'title': 'Item 2',
'price': 7
}
],
},
methods : {
'updateTotal' : function(item){
console.log('updating');
this.total += item.price;
}
}
});
Demo link:
https://codepen.io/EightArmsHQ/pen/rmezQq?editors=1010
And what I'd like to do is update the <total> component as the various items are added to the cart. I have it working at the moment, however it doesn't seem very elegant.
Right now, I add the price of each item to a total. What I'd really like to do is have the total as a computed property, and then every time an item component is changed, loop through them all adding the quantity * price of each. Is there a way I can do this?
One option I have come up with just now is replacing my updateTotal method in the main app to the below:
methods : {
'updateTotal' : function(item){
item.quantity += 1;
}
},
computed : { total : function(){
var t = 0;
for(var i = 0; i < this.items.length; i ++){
t += this.items[i].quantity * this.items[i].price;
}
return t;
}
}
So, beginning to store the quantity of each item inside the Vue app, not the component. But it makes more sense to store the quantity of each item inside its own component... doesn't it? What is the best way of handling this?
Maybe counter-intuitively, the components only need their data as props. The items (as data objects) are defined in the parent; just define quantity there, too. Then use those data items in the components, but make changes via events to the parent.
With an array that includes the quantities, it's easy to create the computed total you want.
Vue.component('item', {
'props': ['item'],
'template': "<div class='item'><div>{{ item.title }} – ${{total}} </div><button class='button' #click='add'>Add</button></div>",
'computed': {
total: function() {
return (this.item.quantity * this.item.price).toFixed(2);
}
},
methods: {
add: function() {
this.$emit('added');
}
}
});
Vue.component('total', {
'props': ['total'],
'template': "<div class='total'>Total: ${{ total }}</div>",
});
var app = new Vue({
'el': '#app',
'data': {
'items': [{
'title': 'Item 1',
'price': 21,
'quantity': 0
}, {
'title': 'Item 2',
'price': 7,
'quantity': 0
}],
},
computed: {
total() {
return this.items.reduce((a, b) => a + (b.price * b.quantity), 0).toFixed(2);
}
},
methods: {
updateTotal(item) {
++item.quantity;
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<div id="app">
<item v-for="item in items" v-bind:item="item" #added="updateTotal(item)"></item>
<total v-bind:total="total"></total>
</div>