Get Specific Object from Array in MongoDB collection into handlebars table - javascript

I'm trying to build a table with values from an Array of objects from mongodb, but I only can get every value and only need the specific value.
So I made a query like this
router.get("/arquiveExpense", ensureAuthenticated, (req, res) => {
House.find({
userID: req.user.id,
expensesHouse: { $elemMatch: { status: "Private" } }
}).then(house => {
console.log(house);
res.render("houses/arquiveExpense", {
house: house
});
});
});
I want to retrieve the specific value from expensesHouse with status 'Private'.
And in handlebars I have this structure
<tbody>
<tr>
{{#each house}}
<td>{{expenseType}}</td>
<td>{{price}}€</td>
<td>{{payAt}}</td>
<td>{{formatDate date 'MMMM Do YYYY'}}</td>
<td>
<a href="/houses/showExpense/{{id}}" id="detailsExpense"
class="btn btn-outline-light mb-3"><i class="fas fa-eye mr-2"></i>Details
</a>
<td>
<a href="/houses/editExpense/{{id}}" id="editExpense" class="btn btn-outline-light mb-3"><i
class="fas fa-edit mr-2"></i>Edit
</td>
</tr>
{{else}}
<p>No expenses</p>
{{/each}}
</tbody>
And after this handlebars, this is the result
The Schema structure is the follow
So I want to show in the web page the value from expensesHouse with the Status 'Private'.
How can I change my code to retrieve only this value ?

Please update your code with the below, it resolves your issues with code & also with query :
router.get("/arquiveExpense", ensureAuthenticated, async (req, res) => {
try {
let house = await House.findOne({ // You can use findOne as if userID is unique
userID: req.user.id
}, { expensesHouse: { $elemMatch: { status: "Private" } } }) //$elemMatch in projection helps you to get what is needed
if (house) { // .findOne() returns null if no document found, else an object
console.log(house);
res.render("houses/arquiveExpense", {
house: house
});
} else { // No document found
console.log('No house found')
}
} catch (error) {
console.log('Error at DB call ::', error)
}
})

Related

How to update a row with contenteditable in Vue?

I'm trying to figure out how to get the current changes in a 'contenteditable' and update it in the row that it was changed.
<tbody>
<!-- Loop through the list get the each data -->
<tr v-for="item in filteredList" :key="item">
<td v-for="field in fields" :key="field">
<p contenteditable="true" >{{ item[field] }}</p>
</td>
<button class="btn btn-info btn-lg" #click="UpdateRow(item)">Update</button>
<button class="btn btn-danger btn-lg" #click="DelteRow(item.id)">Delete</button>
</tr>
</tbody>
Then in the script, I want to essentially update the changes in 'UpdateRow':
setup (props) {
const sort = ref(false)
const updatedList = ref([])
const searchQuery = ref('')
// a function to sort the table
const sortTable = (col) => {
sort.value = true
// Use of _.sortBy() method
updatedList.value = sortBy(props.tableData, col)
}
const sortedList = computed(() => {
if (sort.value) {
return updatedList.value
} else {
return props.tableData
}
})
// Filter Search
const filteredList = computed(() => {
return sortedList.value.filter((product) => {
return (
product.recipient.toLowerCase().indexOf(searchQuery.value.toLowerCase()) != -1
)
})
})
const DelteRow = (rowId) => {
console.log(rowId)
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowId}`, {
method: 'DELETE'
})
.then((response) => {
// Error handeling
if (!response.ok) {
throw new Error('Something went wrong')
} else {
// Alert pop-up
alert('Delete successfull')
console.log(response)
}
})
.then((result) => {
// Do something with the response
if (result === 'fail') {
throw new Error(result.message)
}
})
.catch((err) => {
alert(err)
})
}
const UpdateRow = (rowid) => {
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowid.id}`, {
method: 'PUT',
body: JSON.stringify({
id: rowid.id,
date: rowid.date,
recipient: rowid.recipient,
invoice: rowid.invoice,
total_ex: Number(rowid.total_ex),
total_incl: Number(rowid.total_incl),
duration: rowid.duration
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
})
})
}
return { sortedList, sortTable, searchQuery, filteredList, DelteRow, UpdateRow }
}
The commented lines work when I enter them manually:
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
Each cell has content editable, I'm not sure how to update the changed event
The way these run-time js frontend frameworks work could be summarized as "content is the function of data". What I mean is the html renders the data that you send it. If you want the data to be updated when the user changes it, you need to explicitly tell it to do so. Some frameworks (like react) require you to setup 1-way data binding, so you have to explicitly define the data that is displayed in the template, as well as defining the event. Vue has added some syntactic sugar to abstract this through v-model to achieve 2-way binding. v-model works differently based on whichever input type you chose, since they have slightly different behaviour that needs to be handled differently. If you were to use a text input or a textarea with a v-model="item[field]", then your internal model would get updated and it would work. However, there is no v-model for non-input tags like h1 or p, so you need to setup the interaction in a 1-way databinding setup, meaning you have to define the content/value as well as the event to update the model when the html tag content changes.
have a look at this example:
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1 contenteditable #input="({target})=>msg=target.innerHTML">{{ msg }}</h1>
<h2 contenteditable>{{ msg }}</h2>
<input v-model="msg">
</template>
If you change the h2 content, the model is not updated because vue is not tracking the changes. If you change through input or h1, the changes are tracked, which will also re-render the h2 and update its content.
TL;DR;
use this:
<p
contenteditable="true"
#input="({target})=>item[field]=target.innerHTML"
>{{ item[field] }}</p>

How To Display My Invoice Data In Invoice Template

I'm using Laravel 5.7 & VueJs 2.5.* ...
I have invoices table, i need to display specific invoice in a new component, so user can see whatever invoice he wants or print that invoice.
I don't know how to do that, i'm just playing around, if you could help me out, i'll be very grateful to you.
<router-link> to the component
<router-link to="/ct-invoice-view" #click="openInvoice(ctInvoice)">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Displaying Customer information here like this:
<div class="col-sm-4 invoice-col">
<address v-for="ctInvoice in ctInvoices" :key="ctInvoice.id">
<strong>Customer Info</strong><br>
Name: <span>{{ ctInvoice.customer.customer_name }}</span>
Invoice view component data() & method{}
data() {
return {
ctInvoices: {},
customers: null
};
},
methods: {
openInvoice(ctInvoice) {
axios
.get("api/ct-invoice/show/" + this.viewInvoice)
.then(({
data
}) => (this.ctInvoices = data.data));
},
Image for Better Understanding
You need to look at Dynamic Route matching: https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes
Then you need to use axios.get in invoice views beforeMount function where this.$route.params.id will hold the invoice ID you want to load if the link is applied like so:
<router-link :to="`/ct-invoice-view/${ctInvoice.id}`">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Alternatively...
I suggest not navigating away from the list, it can be irritating for users having filtered the list then returning to it to look at more invoices and having to filter again unless the filter options and current results are sticky
There are a number of ways of doing this and they are lengthy to example, Typically I would make proper use of a modal and the invoice view load the data on display but to get you started a basic in page solution to experiment with, then try adapting in a reusable modal component later:
<button #click="showInvoice = ctInvoice.id">
<i class="fas fa-eye fa-lg text-blue"></i>
</button>
data() {
return {
loading: false,
invoice: {},
customers: null
};
},
computed: {
showInvoice: {
get: function() {
return this.invoice.hasOwnProperty('id');
},
set: function(value) {
if(value === false) {
this.invoice = {};
return;
}
// could check a cache first and push the cached item into this.invoice else load it:
this.loading = true;
axios.get("api/ct-invoice/show/" + value).then(response => {
// you could push the invoice into a cache
this.invoice = response.data;
}).cache(error => {
// handle error
}).finally(() => {
this.loading = false;
});
}
}
}
In view-invoice component have a close button with bind #click="$emit('close')"
Check this article for how $emit works: https://v2.vuejs.org/v2/guide/components-custom-events.html
<div v-if="loading" class="loading-overlay"></div>
<view-invoice v-if="showInvoice" :invoice="invoice" #close="showInvoice = false" />
<table v-else>....</table>
Hide the table when displaying the invoice, experiment with using v-show instead of v-if upon loosing table content state.
Inside your invoice view, property called invoice will contain the invoice data.
Check this article for how to use props: https://v2.vuejs.org/v2/guide/components-props.html
Hint: The #close listens to the $emit('close')
Could also make use of when switching between table and invoice view.
https://v2.vuejs.org/v2/guide/transitions.html
#MarcNewton
I did something like this, it's working for me, can u just review it for me:
<router-link> to the Invoice View component
<router-link v-bind:to="{name: 'ctInvoiceView', params: {id: ctInvoice.id}}">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Getting Data of Specific Invoice ID Like This:
created: function() {
axios
.get("/api/ct-invoice/" + this.$route.params.id)
.then(({
data
}) => {
console.log(data);
this.form = new Form(data);
})
.catch(error => {
console.log(error.response);
});
},

Delete item from nested array in Schema using moongoose

I need help with removing this item from nested array. I tried used $http.delete but this method deleted whole ObjectID from database, and second problem is that I can't connect the click on the "removing" button with backend code.
My code:
var product = new Schema({
title: String,
price: String,
description: [ObjectID]
});
Index.html
<form name="editProduct.descriptionForm" ng-submit="editProduct.updateDescription(newDescription, editProduct.descriptionForm.description.$valid)" novalidate>
<div ng-class="{ 'has-success':(editProduct.descriptionForm.description.$valid && !editProduct.descriptionForm.description.$pristine), 'has-error':(!editProduct.descriptionForm.description.$valid && !editProduct.descriptionForm.description.$pristine) || (!descriptionForm.description.$valid && descriptionForm.$submitted) }">
<div class="entry input-group" ng-repeat="item in newDescription track by $index">
<strong><input ng-disabled="editProduct.disabled" class="form-control" type="text" name="description" ng-model="newDescription[$index]" required></strong>
<span class="input-group-btn">
<a ng-click="editProduct.deleteDescription(item);" class="btn btn-remove btn-danger">
<span class="glyphicon glyphicon-remove"></span>
</a>
</span>
</div>
</div>
<br>
<button ng-disabled="editProduct.disabled" class="btn btn-primary" type="submit">Save</button>
</form>
routes.js
router.put('/editProduct', function(req, res){
var editProduct = req.body._id;
var options = { multi: true };
Product.findOneAndUpdate({ _id: editProduct }, { $pull: { description: req.params.description }}, options, function(err, product){
if(err) throw err;
if(!product){
res.json({ success: false, message: 'Product no found' });
} else {
product.update(function(err){
if(err){
console.log(err);
} else {
res.json({ success: true, message: 'Description removed!'})
}
});
};
});
});
I also tried the following approach:
Product.findOne({'_id' : product.id}, function(err, me){
for(var i=0; i<=me.description.length; i++){
if (String(me.description[i])==String(uid)){
me.description.remove(uid);
me.save();
}
}
});
I think, the biggest problem is that I don't how to connect this function to the button.
Please try console.log(req.params.description) Before the Mongoose update query and check if the output is indeed a valid ObjectId.
If the console output is not showing the valid uid, then the problem is in the angular code. Most probably in editProduct.deleteDescription(item) function. Check if you are making Http Request by passing the correct Description Id as the parameter. Thats probably something like item.descriptionId or item.id. Debug thoroughly.

Handlebars won't show one out of 6 values

So I'm linking in some values to an .hbs-file and most of the values is returned. But for some reason, one of the values is not returning anything. The .hbs:
<nav>
<h2>Working Files</h2>
{{#if snippets }}
<ul>
{{#each snippets}}
<li class={{ this.active }}>{{ this.fileType }}</span>
</li>
{{/each}}
</ul>
{{/if}}
</nav>
And I'm sending them in like this:
router.route("/home/:id")
.get(restrict, function(req, res) {
User.findOne({ user: req.session.Auth.username }, function(error, data) {
Snippet.find({ postedBy: data._id}).exec()
.then(function(data) {
Snippet.find({ _id: req.params.id}).exec()
.then((snippetID) => {
// This is what I send it -------------------------------------------------
let context = {
snippets: data.map(function(snippet) { // Gets the snippet info for nav
return {
name: snippet.title,
fileType: snippet.fileName,
id: "/home/" + snippet._id
};
}),
text: snippetID[0].snippet[0].text, // Gets the snippet-text, and writes it
sessionId: snippetID[0]._id, // For the CRUD to get the id
active: "active"
};
res.render("../views/home", context);
// ------------------------------------
}).catch((err) => {console.log(err)})
}). catch(function(err) {console.log(err)});
});
});
It's the "this.active" that holds no value at all. I've been scratching my head over and over about this, and I can't understand why that value won't follow. All the other values do follow. I've even tried to set the "active"-key to the same value as "id" or "text", but no luck.
Do anyone know what the problem is?

Hide button and show data, upon button click, in Angular

It probably goes without saying that I'm quite new to angular as I'm trying to accomplish a relatively simple task, and I've come here for some help
I'm recreating our company's password vault using angular.
Here is what I am trying to accomplish.
The page loads with a list of accounts. Most the information is visible except for the password. I have a button that when clicked, hides the button, queries the database, logs who queried password, and displays the password to the user. The passwords are clear text because they aren't passwords for client accounts or anything sensitive, it exists for our employees to reference how/where to login to various websites we use for day to day business.
My HTML looks as follows:
<tr ng-repeat="account in resp.PasswordVaultAccounts">
<td>{{account.Name}}</td>
<td>{{account.Username}}</td>
<td><button ng-click="showPassword(account.AccountId);" class="btn btn-primary">View</button><span></span></td>
<td>{{account.Comments}}</td>
</tr>
My scope controller looks as follows
$scope.showPassword = function (accountId) {
passwordVaultData.getAccountPassword(accountId)
.$promise
.then(function (r) {
//success
}, function (r) {
//fail
});
}
My showPassword() method works and returns the correct password, but I can't figure out how to hide the button and display the password.
Using the ng-show and ng-hide directives against the password on the account object should suffice for modifying the UI
<tr ng-repeat="account in resp.PasswordVaultAccounts">
<td>{{account.Name}}</td>
<td>{{account.Username}}</td>
<td>
<button ng-hide="account.Password" ng-click="showPassword(account.AccountId);" class="btn btn-primary">View</button>
<span ng-show="account.Password">{{account.Password}}</span>
</td>
<td>{{account.Comments}}</td>
</tr>
As for the promise resolution, you want the getAccountPassword to return a promise, I will make an assumption about it's content below
function getAccountPassword(account) {
var deferred = $q.defer();
$http.get('api/vault/' + account.AccountId).then(function(r) {
deferred.resolve(r);
}, function(r) {
deferred.reject(r);
});
return deferred.promise;
}
$scope.showPassword = function (account) {
getAccountPassword(account.AccountId).then(function(password) {
account.Password = password;
}, function(password) {
account.Password = undefined; // some type of error handling here
});
}
Because the promise is executed in the context of an $http call, the digest cycle will run and the elements will be shown based on whether password is populated.
You can accomplish it by either ng-if or ng-show/hide:
Quick sample below:
<tr ng-repeat="account in resp.PasswordVaultAccounts">
<td>{{account.Name}}</td>
<td>{{account.Username}}</td>
<td>
<button ng-if="!account.password" ng-click="showPassword(account);" class="btn btn-primary">View</button><span></span></td>
<span ng-if="account.password">{{password}}</span>
<td>{{account.Comments}}</td>
</tr>
$scope.showPassword = function (account) {
account.password = passwordVaultData.getAccountPassword(account.AccountId)
.$promise
.then(function (r) {
//success
}, function (r) {
//fail
});
}
Please see demo below
var app = angular.module('app', []);
angular.module('app').
controller('firstCtrl', function($scope) {
$scope.resp = {
PasswordVaultAccounts: [{
AccountId: 1,
URL: "bbc.co.uk",
Username: "Jack",
Comments: "White"
}, {
AccountId: 2,
URL: "bbc.co.uk",
Username: "Mike",
Comments: "Green"
}, {
AccountId: 3,
URL: "bbc.co.uk",
Username: "Tim",
Comments: "Red"
}
]
}
$scope.showPassword = function(account) {
//call you backend and on sucess add that :
// passwordVaultData.getAccountPassword(account.accountId)
// .$promise
// .then(function (r) {
account.showpass = true;
account.pass = account.Username + " password is *****"
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="firstCtrl">
<table>
<tr ng-repeat="account in resp.PasswordVaultAccounts">
<td>{{account.Name}}
</td>
<td>{{account.Username}}</td>
<td>
<button ng-click="showPassword(account);" class="btn btn-primary" ng-hide="account.showpass">View</button>
<span ng-show="account.showpass">{{account.pass}}</span>
</td>
<td>{{account.Comments}}</td>
</tr>
</table>
</div>
</body>

Categories

Resources