How to format <td> value in angular dynamic table - node.js

am trying to format displayed value in an angular table
this is my table right now
instead of showing the link am trying to format the displayed content link
instead of https://www.udemy.com/course/amazon-alexa-101-publishing-alexa-skills-without-coding/
am trying to show [amazon alexa 101 publishing alexa skills without coding]
i have tried in nodejs
var url = require('url');
var adr ='https://www.udemy.com/course/technology-strategy-success/?couponCode=05DIwC2320';
var q = url.parse(adr, true);
var data = q.pathname.replace("/course/", '');
var output = data.replace("/", '');
console.log(output);
this is my ts file
import {Component, OnDestroy, OnInit} from '#angular/core';
import {ApiService} from '../../services/api.service';
#Component({
selector: 'app-data',
templateUrl: './data.component.html',
styleUrls: ['./data.component.css']
})
export class DataComponent implements OnInit{
udemydata = [];
constructor(private apiService: ApiService) {
}
ngOnInit() {
this.apiService.getCoupen().subscribe((data: any[]) => {
this.udemydata = data;
console.log(data);
},
error => {
console.log('err', error);
}
);
}
}
this is my component.html file
<table datatable [dtOptions]="dtOptions" class="table align-items-center table-flush">
<thead>
<tr>
<th scope="col">S.No</th>
<th scope="col">course</th>
<th scope="col">Link</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of udemydata; let i = index;">
<td>{{i+1}}</td>
<td>{{data["courseidhref"]}}</td>
<td><a href ="{{data['courseidhref']}}" target="_blank"><button class="btn btn-icon btn-3 btn-primary" type="button">
<span class="btn-inner--icon"><i class="ni ni-send"></i></span>
<span class="btn-inner--text">Enroll Now</span>
</button></a></td>
</tbody>
</table>

First of all, note that Angular runs in user's browser, so you want to use the URL class, instead of Node's url module, as that is only available in Node runtime.
The, we can break down the process into a few steps:
const url = 'https://www.udemy.com/course/technology-strategy-success/?couponCode=05DIwC2320';
// first, convert to URL class so that it can do the parsing for us.
const parsedUrl = new URL(url);
// we want to ignore the host and query string, so we only need the pathname, as you have yourself discovered.
const pathName = parsedUrl.pathname;
// then, let's split the pathname on any "/" characters.
const urlParts = pathName.split('/');
// this produces some empty strings, so let's get rid of them
const nonEmptyParts = urlParts.filter(x => x !== '');
// now we are left with proper url parts.
// In our case: [ "course", "technology-strategy-success" ]
// Assuming that the last element is always the course name:
const coursePart = nonEmptyParts.pop(); // or, nonEmptyParts[nonEmptyParts.length - 1]
// Finally, let's get rid of the hyphens, replacing them with spaces.
// The regex has two parts:
// 1. /-/ stands for match hyphens
// 2. g stands for "global" - we want to replace ALL hyphens, not just the first one
const courseName = coursePart.replace(/-/g, ' ');
If you want to add a bit more sugar, you could also change the first letter of the course title to be a capital.
const title = courseName[0].toUpperCase() + courseName.slice(1);
// Technology strategy success
If you want to capitalize every word:
const title = courseName.split(' ')
.map(word => word[0].toUpperCase() + word.slice(1))
.join(' ');
// Technology Strategy Success
Now, to show it in Angular you could go about it in a few ways:
After you receive the data from your service, convert it so that the data also has the course title.
Create a method in your component that converts url to course title and call it in the template: <td>{{convertHrefToTitle(data.courseidhref)}}</td>
Create a courseTitle Pipe that transforms hrefs to titles. You would use it like so:
<td>{{data.courseidhref | courseTitle}}</td>
Option 1 is fairly simple. When we get the data, we transform it once.
Option 2 is very simple too, but the function will run VERY frequently, on every change detection cycle. This might affect performance if used without thinking.
Option 3 is slightly more complicated, but it will NOT run on change detection cycle unless the href itself has changed. Read more about pipes ion official docs.
Here's an example of how to use Option 1:
Given interfaces
interface OriginalData {
courseidhref: string;
}
interface TransformedData {
courseidhref: string;
title: string;
}
transformData(data: OriginalData ): TransformedData {
return {...data, title: this.hrefToTitle(data.courseidhref)}
}
hrefToTitle(href: string): string {
// paste the solution from above into here
}
ngOnInit() {
this.apiService.getCoupen().subscribe((data: any[]) => {
const transformedData = data.map(course => this.transformData(course));
this.udemydata = transformedData;
console.log(data, transformedData);
},
error => {
console.log('err', error);
}
);
}
Finally, in your template you can do
<td>{{data.title}}</td>
<td><a href ="{{data.courseidhref}}" target="_blank">...

Related

Cheerio how to remove DOM elements from selection

I am trying to write a bot to convert a bunch of HTML pages to markdown, in order to import them as Jekyll document. For this, I use puppeteer to get the HTML document, and cheerio to manipulate it.
The source HTML is pretty complex, and polluted with Google ADS tags, external scripts, etc. What I need to do, is to get the HTML content of a predefined selector, and then remove elements that match a predefined set of selectors from it in order to get a plain HTML with just the text and convert it to markdown.
Assume the source html is something like this:
<html>
<head />
<body>
<article class="post">
<h1>Title</h1>
<p>First paragraph.</p>
<script>That for some reason has been put here</script>
<p>Second paragraph.</p>
<ins>Google ADS</ins>
<p>Third paragraph.</p>
<div class="related">A block full of HTML and text</div>
<p>Forth paragraph.</p>
</article>
</body>
</html>
What I want to achieve is something like
<h1>Title</h1>
<p>First paragraph.</p>
<p>Second paragraph.</p>
<p>Third paragraph.</p>
<p>Forth paragraph.</p>
I defined an array of selectors that I want to strip from the source object:
stripFromText: ['.social-share', 'script', '.adv-in', '.postinfo', '.postauthor', '.widget', '.related', 'img', 'p:empty', 'div:empty', 'section:empty', 'ins'],
And wrote the following function:
const getHTMLContent = async ($, selector) => {
let value;
try {
let content = await $(selector);
for (const s of SELECTORS.stripFromText) {
// 1
content = await content.remove(s);
// 2
// await content.remove(s);
// 3
// content = await content.find(s).remove();
// 4
// await content.find(s).remove();
// 5
// const matches = await content.find(s);
// for (m of matches) {
// await m.remove();
// }
};
value = content.html();
} catch(e) {
console.log(`- [!] Unable to get ${selector}`);
}
console.log(value);
return value;
};
Where
$ is the cheerio object containing const $ = await cheerio.load(html);
selector is the dome selector for the container (in the example above it would be .post)
What I am unable to do, is to use cheerio to remove() the objects. I tried all the 5 versions I left commented in the code, but without success. Cheerio's documentation didn't help so far, and I just found this link but the proposed solution did not work for me.
I was wondering if someone more experienced with cheerio could point me in the right direction, or explain me what I am missing here.
I found a classical newby error in my code, I was missing an await before the .remove() call.
The working function now looks like this, and works:
const getHTMLContent = async ($, selector) => {
let value;
try {
let content = await $(selector);
for (const s of SELECTORS.stripFromText) {
console.log(`--- Stripping ${s}`);
await content.find(s).remove();
};
value = await content.html();
} catch(e) {
console.log(`- [!] Unable to get ${selector}`);
}
return value;
};
You can remove the elements with remove:
$('script,ins,div').remove()

not able to display fetched data on page ReactJS

in the code below i am fetching data from an API and want to display it on a page.
import React, { useState, useEffect } from "react";
import '../all.css';
import Axios from "axios";
const AllProduct = () => {
const [products, setProducts] = useState([]);
const fetchProducts = async () => {
const { data } = await Axios.get(
"http://localhost:8080/api/QueryAllProducts"
);
console.log(data.response);
setProducts(data.response);
console.log(products);
};
const display = () => {
return (products || []).map(product => (
<tr key={product.id}>
<th>{product.id}</th>
<th>{product.name}</th>
<th>{product.area}</th>
<th>{product.ownerName}</th>
<th>{product.cost}</th>
</tr>
) );
}
useEffect(() => {
fetchProducts();
}, []);
return (
<div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Area</th>
<th>Owner Name</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
{display()}
</tbody>
</table>
</div>
)
}
export default AllProduct;
i did almost every method which i found on stackoverflow but still can't resolve the error. In frontend i have used ReactJS and in Backend i am using NodeJS
here is the screenshot of the error i am getting
Earlier, when one wanted to assign a default value to a variable, a common pattern was to use the logical OR operator (||):
let foo;
// foo is never assigned any value so it is still undefined
let someDummyText = foo || 'Hello!';
However, due to || being a boolean logical operator, the left hand-side operand was coerced to a boolean for the evaluation and any falsy value (0, '', NaN, null, undefined) was not returned. This behavior may cause unexpected consequences if you consider 0, '', or NaN as valid values.
I recommend change your || operator to ?? like:
(products ?? [])
because this '??' operator will conditions if you receive nullish value.
if you want to know more check it out here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator
I think the challenge you are having is from your display function. You should return a Jsx element before mapping the products and enclose the mapping function in the curly ({}) bracket so React knows you are now writing javascript. So you might want to re-write your display function like below:
const display = () => {
return (<React.Fragment>
{(products || []).map(product => (
<tr key={product.id}>
<th>{product.id}</th>
<th>{product.name}</th>
<th>{product.area}</th>
<th>{product.ownerName}</th>
<th>{product.cost}</th>
</tr>}))
</React.Fragment>
);
}
Also I think you should use <td>{product.name}</td>... to return table data and not <th>{product.name}</th>

Display single item with Vue.js

I have a list of items where the title is a link to display a detailed view of the item. Click the title and it correctly goes to url + Id. In the Vue tolls the detail page retrieves the item with matching ID but as and array not an object and the template does not display any properties - what am I missing?
<script>
import axios from "axios";
export default {
name: "Report",
data() {
return {
report: {}
};
},
mounted: function() {
this.getReport();
},
methods: {
getReport() {
let uri = "http://localhost:5000/api/reports/" + this.$route.params.id;
axios.get(uri).then(response => {
this.report = response.data;
});
}
}
};
</script>
The template is so
<template>
<v-content>
<h1>report detail page</h1>
<p>content will go here</p>-
<h3>{{ report.month }}</h3>
<pre>{{ report._id }}</pre>
</v-content>
</template>
any comments appreciated
url + Id
It sounds like your issue is that you are receiving an array not an object.
You can pull out objects encapsulated inside arrays easily.
For example, if we had the following data:
var bus1 = {passengers:10, shift:1}
var busArr = [bus1]
which we can assert: busArr === [{passengers:10, shift:1}]
We could then pull out bus1 by referencing the index 0:
var bus1New = busArr[0]
If you want to avoid the data transformation and just output the structure you can consider a v-for in your template.
<p v-for="val in report">
_id: {{val._id}}
<br>
month: {{val.month}}
</p>

Get Dynamically created table Data in React Row

I am more familiar with NodeJs than react. I have build a react component that searches for user input and provides the output in a table format based on the value that the user has typed into the search input form. This is working as I want and the code for the module is below:
import React, { Component } from 'react';
import axios from 'axios';
import Suggestions from './Suggestions';
// API url
const API_URL = 'http://localhost:3000/api/file_infos'
class Search extends Component {
state = {
query: '',
results: []
}
getCount = () => {
axios.get(`${API_URL}count?filter[where][id][regexp]=/${this.state.query}/i`)
.then(count => {
this.setState({
results: count.data
})
})
}
// query loop back API for matching queries base on text input
getInfo = () => {
axios.get(`${API_URL}?filter[where][id][regexp]=/${this.state.query}/i&filter[limit]=20`)
.then(response => {
this.setState({
results: response.data
})
})
}
// check to see if input on the search bar has changed and update the search query accordingly
handleInputChange = () => {
this.setState({
query: this.search.value
}, () => {
if (this.state.query && this.state.query.length > 1) {
if (this.state.query) {
this.getInfo()
}
} else if (!this.state.query) {
}
})
}
// render form and pass results back to the home component
render() {
return (
<div>
<form>
<input
placeholder="Search for..."
ref={input => this.search = input}
onChange={this.handleInputChange}
/>
</form>
<Suggestions results={this.state.results} />
</div>
)
}
}
export default Search
The second module is the suggestions module that displays the output in the table format.
The next portion of the app I am building will open a file based on the table row that the user selected. I want that table data returned to a function so that I can make an http post request to my API that will in turn open the file using a NodeJS module.
I want the suggestions component to return the value of the data items in the table cells so that the data can be used to send to the API in order to open my files. The code I have come up with so far is only returning an undefined error.
Below is what I currently have:
import React from 'react';
// return results in a table format based on the text input entered
const Suggestions = (props) => {
const state = {
results: []
}
const handleFormOpen = () => {
this.setState({
results: this.results.value
},
console.log(this.state.results)
)
}
const options = props.results.map(r => (
<tr key={r.id} ref={tr => this.results = tr} onClick={handleFormOpen.bind(this)}>
<td>{r.id}</td>
<td>{r.OriginalPath}</td>
<td>{r.CreateDate}</td>
<td>{r.AccessDate}</td>
<td>{r.WriteDate}</td>
<td><i className="fas fa-book-open"></i></td>
</tr>
))
return <table className="striped responsive-table">
<thead>
<tr>
<th>File Name</th>
<th>Parent Directory</th>
<th>Creation Date</th>
<th>Access Date</th>
<th>Write Date</th>
<th>Open File</th>
</tr>
</thead>
<tbody>
{options}
</tbody>
</table>
}
export default Suggestions;
I am really unsure at this point if I am trying to tackle this issue in the correct way. I am thinking that maybe the suggestions component may need to be turned into a full class extending component but I am fairly lost at this point. Can someone please kindly point out my folly and get me going in the right direction?
UPDATE
As requested in the comments here is the error log from my browser:
Suggestions.js:10 Uncaught TypeError: Cannot read property 'results' of undefined
at Object.handleFormOpen (Suggestions.js:10)
at HTMLUnknownElement.callCallback (react-dom.development.js:145)
at Object.invokeGuardedCallbackDev (react-dom.development.js:195)
at invokeGuardedCallback (react-dom.development.js:248)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:262)
at executeDispatch (react-dom.development.js:593)
at executeDispatchesInOrder (react-dom.development.js:615)
at executeDispatchesAndRelease (react-dom.development.js:713)
at executeDispatchesAndReleaseTopLevel (react-dom.development.js:724)
at forEachAccumulated (react-dom.development.js:694)
at runEventsInBatch (react-dom.development.js:855)
at runExtractedEventsInBatch (react-dom.development.js:864)
at handleTopLevel (react-dom.development.js:4857)
at batchedUpdates$1 (react-dom.development.js:17498)
at batchedUpdates (react-dom.development.js:2189)
at dispatchEvent (react-dom.development.js:4936)
at interactiveUpdates$1 (react-dom.development.js:17553)
at interactiveUpdates (react-dom.development.js:2208)
at dispatchInteractiveEvent (react-dom.development.js:4913)
First thing Since your Suggestions component plays with state, I would recommend you to go with statefull component.
Stateless component is meant for getting props and returning jsx elements, there wont be any state mutations in stateless component. This is called pure function in javascript. Hope this makes clear.
Also since you declared handleFormOpen as an arrow function you no need to do binding. binding takes care automatically by arrow function. If you don't want to use arrow function and you want to bind it then do the binding always in constructor only but don't do binding anywhere in the component like you did in map.
PFB corrected Suggestions component code
import React, { Component } from 'react';
// return results in a table format based on the text input entered
export default class Suggestions extends Component {
constructor(props){
super(props);
this.state = {
results: [],
value: ""
}
}
handleFormOpen = (path, id) => {
console.log("id", id, path);//like wise pass value to this function in .map and get the value here
this.setState({
value: id
});
}
render(){
const { results } = this.props;
return (<div>
<table className="striped responsive-table">
<thead>
<tr>
<th>File Name</th>
<th>Parent Directory</th>
<th>Creation Date</th>
<th>Access Date</th>
<th>Write Date</th>
<th>Open File</th>
</tr>
</thead>
<tbody>
{Array.isArray(results) && results.length > 0 && results.map(r => (
<tr key={r.id} ref={tr => this.results = tr} onClick={() => this.handleFormOpen(r.OriginalPath, r.id)}>
<td>{r.id}</td>
<td>{r.OriginalPath}</td>
<td>{r.CreateDate}</td>
<td>{r.AccessDate}</td>
<td>{r.WriteDate}</td>
<td><i className="fas fa-book-open"></i></td>
</tr>
))}
</tbody>
</table>
</div>)
}
}
export default Suggestions;
You are using states in Functional Component, You need to use React Component
import React from 'react';
// return results in a table format based on the text input entered
class Suggestions extends React.Component {
constructor(props) {
super(props);
this.state = {
results: [],
}
}
handleFormOpen = () => {
this.setState({
results: this.results.value
},
console.log(this.state.results)
)
}
render () {
const options = this.props.results.map(r => (
<tr key={r.id} ref={tr => this.results = tr} onClick={handleFormOpen.bind(this)}>
<td>{r.id}</td>
<td>{r.OriginalPath}</td>
<td>{r.CreateDate}</td>
<td>{r.AccessDate}</td>
<td>{r.WriteDate}</td>
<td><i className="fas fa-book-open"></i></td>
</tr>
))
return (
<table className="striped responsive-table">
<thead>
<tr>
<th>File Name</th>
<th>Parent Directory</th>
<th>Creation Date</th>
<th>Access Date</th>
<th>Write Date</th>
<th>Open File</th>
</tr>
</thead>
<tbody>
{options}
</tbody>
</table>
)
}
}
export default Suggestions;

Show the item hit content only when the search box is not empty

I have this in my algolia file for my jekyll site.
<script>
const search = instantsearch({
appId: '{{ site.algolia.application_id }}',
apiKey: '{{ site.algolia.search_only_api_key }}',
indexName: '{{ site.algolia.index_name }}',
searchParameters: {
restrictSearchableAttributes: [
'title',
'content'
],
facetFilters: ['type:post']
},
});
const hitTemplate = function(hit) {
let date = '';
if (hit.date) {
date = moment.unix(hit.date).format('L');
// date = moment.unix(hit.date).format('MMM Do YY');
modifiedDate = moment.unix(hit.last_modified_at).format('MMM Do YY');
}
const url = hit.url;
const title = hit._highlightResult.title.value;
const content = hit._highlightResult.html.value;
return `
<div class="post-list">
<span class="post-date-list-wrap">
<span class="post-date-list">${date}
<span class="post-title"> ${title} </span>
</span>
${content}
</div>
`;
}
const hitTemplateTrans = function(hit) {
let date = '';
if (hit.date) {
date = moment.unix(hit.date).format('MMM DD YYYY');
}
const url = hit.url;
const title = hit._highlightResult.title.value;
const content = hit._highlightResult.html.value;
return `
<div class="post-list">
<span class="post-date-list-wrap">
<span class="post-date-list">${date}
<span class="post-title"> ${title}</span>
</span>
</span>
</div>
`;
}
search.addWidget(
instantsearch.widgets.searchBox({
container: '#search-searchbar',
placeholder: 'search notes',
autofocus: true
})
);
search.addWidget(
instantsearch.widgets.hits({
container: '#search-hits',
templates: {
empty: 'No results',
item: hitTemplate
},
})
);
search.start();
</script>
Without typing anything in the search box I have the list of articles
with the excerpt, the short introduction of the article.
That's because I have ${content} to show the highlights when someone
types the search term.
That's fine and everything is working but... I don't want to show the contents of each item when the search box is empty.
If the search box is empty I would like to keep only the title and the date
but if the search box is not empty just show the title/date and the excerpt as it's usual.
It seems like an easy task but I'm stuck right now, I tried removed the content tag and put in the hit transformation function, but it doesn't work.
The instantsearch.js library has a function hook, called searchFunction, you can define when instanciating the library. That is called right before any search is performed. You can use it to check if the current query is empty or not, and adapt your layout based on this knowledge.
Here is a slightly edited script (irrelevant parts removed) that should let you do what you're looking for:
let additionalClass = '';
const search = instantsearch({
[…]
searchFunction: function(helper) {
if (helper.getState().query === '') {
additionalClass = 'post-item__empty-query';
} else {
additionalClass = '';
}
helper.search()
}
});
[…]
const hitTemplate = function(hit) {
return
`<div class="post-item ${additionalClass}">
[…]
</div>`
;
}
.post-item__empty-query .post-snippet {
display: none;
}
What it does is defining a global variable (additionalClass) that will be used in the hit template (added alongside item-post, at the root level).
Right before everysearch, we check if the query is empty. If so, we set additionalClass to item-post__empty_query. We also defined in CSS that when this class is applied, the content should be hidden.
All of that together makes the title + date displayed when no search is performed, and the content displayed only when an actual keyword is searched for.
Hope that helps,

Resources