Using jade to create an unordered list tree - node.js

I have an object called boundedArea which contains an array of boundedArea objects in a field children and I would like to create a tree of unordered lists.
I have the following code:
- for (var index = 0; index < rootAreas.length; index++) {
- var boundedArea = rootAreas[index];
div(class='panel panel-default')
div.panel-heading
| #{boundedArea.NAME}
div.panel-body
- printChildren(boundedArea, 0);
- }
-
- function printChildren(boundedArea, depth) {
- var children = boundedArea.children;
- if (children == null || children.length == 0) {
- return;
- }
ul
- for (var index = 0; index < children.length; index++) {
- var child = children[index];
li #{child.NAME}
- console.log("Printing %s child of %s", child.NAME, boundedArea.NAME);
- printChildren(child, depth + 1);
- }
- }
Now obviously this sort of works in that it prints out all the values. However because the ul and li tags are a fixed indentation in they do not nest and just ended up printing sequentially.
Is there any way to dynamically set the level of indent or to force these to nest. Or should I be using a completely different model of nesting altogether.
I tried cretaing a javascript variable indent filled with two spaces for each depth level and then tried to use #{indent} but that just ended up creating tags with spaces in which was not what I wanted. Though that implies that something around this idea could work as it must be resolved at some level before but the it is picked up as a token of some kind.

Try using a mixin instead of a function. Mixins respect/remember the level of indentation (not really sure why functions don't).
mixin printChildren(boundedArea, depth)
- var children = boundedArea.children;
- if (children == null || children.length == 0)
- return;
ul
- for (var index = 0; index < children.length; index++)
- var child = children[index];
li #{child.NAME}
+printChildren(child, depth + 1)
- for (var index = 0; index < rootAreas.length; index++)
- var boundedArea = rootAreas[index];
div(class='panel panel-default')
div.panel-heading
| #{boundedArea.NAME}
div.panel-body
+printChildren(boundedArea, 0)
I tweaked your code a bit. Mixins are invoked using a + instead of a - and they need to be defined before they are used.
I tested it with this sample data:
{
rootAreas: [
{
NAME: 'area1',
children: [
{ NAME: 'child1' },
{ NAME: 'child2' },
{
children: [
{ NAME: 'child3' },
{ NAME: 'child4' },
]
},
]
},
{
NAME: 'area2',
children: [
{ NAME: 'child5' },
{ NAME: 'child6' },
{ NAME: 'child7' },
]
}
]
}
And the template yielded this HTML code:
<div class="panel panel-default">
<div class="panel-heading">area1</div>
<div class="panel-body">
<ul>
<li>child1</li>
<li>child2</li>
<li>
<ul>
<li>child3</li>
<li>child4</li>
</ul>
</li>
</ul>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">area2</div>
<div class="panel-body">
<ul>
<li>child5</li>
<li>child6</li>
<li>child7</li>
</ul>
</div>
</div>
If I understood you correctly, this is what you're looking for.

Related

Loop through object with unkown levels of children in Preact

I'm trying to loop through an object with children, that can have children, that can have children.
Basically I won't know how many child elements and levels the object can contain. Is there an easy way to loop through these, and render out a component in a list view without having to call the Object.keys(myobject).map many times in side eachother. Inside the render function?
parent: {
term: "cats",
children: {
child: {
term: "kids",
otherdata: "dekme",
children: {
granddaughter: {term: "kids", otherdata: "dekme"},
grandson: {term: "mip", some other data: "si"}
}
},
}
}
And also the output should be something like:
<ul>
<li>
Parent
<ul>
<li>child</li>
<li>child
<ul>
<li>grandkid
</li>
</ul>
</li>
</ul>
</li>
</ul>
Great question! This class of problem is generally referred to as walking / tree walking, and there are probably some great libraries on npm that you could use to make it easier. We can write a simple recursive one by hand though. I'll assume your nested objects containing child elements look something like this:
var treeOfObjects = {
children: [
{
name: 'billy'
},
{
name: 'wanda',
children: [
{
name: 'stanley'
}
]
}
]
};
Here's a walk function that will crawl that tree, call an iterator function you supply on each child it finds, and collect up the results in an Array.
function walk(object, iterator, results) {
// no results array it means we're at the root.
// create the shared array, then map the root through our iterator.
if (!results) {
results = [iterator(object, null, object)];
}
if (object.children) {
for (let i=0; i<object.children.length; i++) {
// call the iterator on this child and append the result:
let value = object.children[i];
results.push(iterator(value, i, object));
// now "walk into" the child object (appending its children, etc):
walk(value, iterator, results);
}
}
return results;
}
Here's what it looks like in practice:
class Foo extends Component {
render({ someTreeOfObjects }) {
return (
<ul>
{walk(someTreeOfObjects, object => (
<li>{object.name}</li>
))}
</ul>
);
}
}
// render(<Foo someTreeOfObjects={treeOfObjects} />)
// .. produces:
// <ul>
// <li>billy</li>
// <li>wanda</li>
// <li>stanley</li>
// </ul>
Update - for nested output:
function nodeToListItem (object) {
// if we have children, we'll recurse into them
const children = object.children || [];
return (
<li>
{object.name}
{children.length>0 && (
<ul>
{children.map(nodeToListItem)}
<ul>
)}
</li>
);
}
class Foo extends Component {
render({ someTreeOfObjects }) {
return (
<ul>
{nodeToListItem(someTreeOfObjects)}
</ul>
);
}
}
Because the nesting from the original structure matches what we want to produce in the DOM, this ends up being a simple graph transformation.

Vue.js - Pagination

I have the following pagination component.
If users adds remove items dynamically i.e via some ajax call, how do i ensure the correct active or disabled classes are applied on the pagination links?
For example if the user is currently on the last page which only has 1 item, if the user deletes that item, the pagination links re-render but then i lose the active disable class becuase that page no longer exists. i.e. the links should update to move the user to previous page.
<div class="comment-pager ">
<div class="panel panel-default panel-highlight no-border ">
<div class="panel-body padded-5">
<nav v-if="totalItems > pageSize">
<ul class="pagination">
<li v-bind:class="[currentPage == 1 ? disabled : '']">
<a v-on:click.prevent="previous()" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li v-bind:class="[currentPage == pages ? active : '']" v-for="page in pages" v-on:click.prevent="changePage(page)">
<a>{{ page }}</a>
</li>
<li v-bind:class="[currentPage == pages.length ? disabled : '']">
<a v-on:click.prevent="next()" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['totalItems', 'pageSize']
data: function () {
return {
currentPage: 1,
pages: [],
}
},
watch: {
totalItems: function () {
var pagesCount = Math.ceil(this.totalItems / this.pageSize);
this.pages = [];
for (var i = 1; i <= pagesCount; i++)
this.pages.push(i);
}
},
methods: {
changePage: function (page){
this.currentPage = page;
this.$emit('pageChanged', page);
}
previous: function (){
if (this.currentPage == 1)
return;
this.currentPage--;
this.$emit('pageChanged', this.currentPage);
}
next: function () {
if (this.currentPage == this.pages.length)
return;
this.currentPage++;
this.$emit('pageChanged', this.currentPage);
}
},
}
</script>
<paginator v-bind:total-items="totalItems" v-bind:page-size="query.pageSize" v-on:pageChanged="onPageChange"></paginator>
There is no complete equivalent to ngOnChanges() in vue.
ngOnChanges() is a lifecycle hook which takes in an object that maps each changed property name to a SimpleChange object holding the current and previous property values.
If you want the lifecycle hook that gets invoked after every change in data and before re-rendering the virtual DOM then you should be using beforeUpdate() hook.
But as in ngOnChanges() you can't get the hold of which property is updated or what is it's oldvalue or newValue is.
As mklimek answered you can set up watcher on the properties you want to watch for changes.
In watcher you get what the oldValue is and what it's changed new value is
new Vue({
el:'#app',
data:{
prop1: '',
prop2: '' // property to watch changes for
},
watch:{
prop#(newValue, oldValue){
console.log(newValue);
console.log(oldValue);
}
}
});
EDIT
For your case you do not need a watcher. You can setup the pages[] property as a computed property:
computed:{
pages(){
var pageArray = [];
var pagesCount = Math.ceil(this.totalItems / this.pageSize);
for (var i = 1; i <= pagesCount; i++)
pages.push(i);
}
return pageArray;
}
computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed in your case the props
totalItems and pageSize
Now you can use the pages computed property as normal data property
You probably want to use watch property of a Vue instance.
var vm = new Vue({
data: {
count: 1
},
watch: {
count: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
}
})

Handlebars Partial is not working

I'm trying to generate a nested ul>li list mark-up by Handlebars. So, I'm using partials in handlebars.
But, somehow the following code is not working. Can you please mention, what am doing wrong.
var handlebars = require('handlebars');
var source = '<ul>{{> list}}</ul>';
handlebars.registerPartial('list',
'{{#children}}' +
' <li>' +
' {{name}}' +
' {{#children}}' +
' <ul>' +
' {{> list}}' +
' </ul>' +
' {{/children}}' +
' </li>' +
'{{/children}}');
var template = handlebars.compile(source);
var children = [
{
name: 'Abcd',
children: [
{
name: 'dfrt',
children: [
{
name: 'fgtd',
children: [
]
},
{
name: 'ghty',
children: [
]
}
]
}
]
}
];
var generatedString = template(children);
console.log(generatedString); // Only produces '<ul></ul>'
Unlike this mustache example your example resembles of, you can just use the Handlebars #each helper
Source:
var source = ' <ul>{{#each this}}{{> list this}} {{/each}}</ul>'
The helper:
<li>
{{name}}
{{if children}}
<ul>
{{#each children}}
{{> list this}}
{{/each}}
</ul>
{{/if}}
</li>
Result:
Abcd
dfrt
fgtd
ghty

Building Pagination in Reactjs

So the way I am building pagination in Reactjs is a bit odd, but it works for me, I, How ever would like to say show me the first 5 (1-5) on the 5th page show me 5-max. But I am unclear on how to do that.
this is what I currently have:
render: function() {
// Do we have more then one page?
if (this.props.maxPages > 0){
// We have to add one to the max pages that come back.
var pageLink = this.props.maxPages + 1;
var currentPage = this.props.currentPage;
var liElements = []
// Build [<<][<] for the user.
if (pageLink > 1) {
liElements.push(<li><<</li>);
liElements.push(<li><a href={this.pageSubtraction(currentPage, pageLink)}><</a></li>);
}
// Build the individual [x][y][z] links.
for (var i = 1; i <= pageLink; i++) {
liElements.push(<li key={i} id={i}><a href={"#posts?page="+i}>{i}</a></li>);
}
// Build the [>][>>] for the user.
if (pageLink > 1) {
liElements.push(<li><a href={this.pageAddition(currentPage, pageLink)}>></a></li>);
liElements.push(<li><a href={"#posts?page="+pageLink}>>></a></li>);
}
return (<ul className="pagination">{liElements}</ul>);
}else{
// Return nothing.
return ( <div></div> );
}
}
This will build me [<<][<][1][2][3] ... [>][>>] which is great but their is no limit on it.
At this time:
pageLink = 6 (the max number of pages - I know horrible variable name)
currentPage = 1 (the current page you are on)
So what I need is:
[<<][<][1][2][3][4][5][>][>>] Select Page 5 [<<][<][5][6][>][>>] But I am not sure if my current set up will allow me to do that.
This is a somewhat complicated algorithm (and not all of the details are provided). Rather than worrying about markup here, it might be simpler to start with a pure data structure representing what should be drawn.
Pagination = function(props){
var pages = props.maxPages + 1;
var current = props.currentPage;
var links = [];
// leading arrows
if (current > 0) {
links.push([0, "<<"]);
links.push([current - 1, "<"]);
}
for (var i=current-3; i<current+4; i++) {
if (i > 0 && i < pages) {
links.push([i, i]);
}
}
// tailing arrows
if (current < pages) {
links.push([current + 1, ">"]);
links.push([pages - 1, ">>"]);
}
return JSON.stringify(links, null, 4);
};
Now we get something like this (jsbin). You could also easily write unit tests to ensure this gives the correct results.
[
[
0,
"<<"
],
[
1,
"<"
],
[
1,
1
],
[
2,
2
],
[
3,
3
],
[
4,
4
],
[
5,
5
],
[
3,
">"
],
[
7,
">>"
]
]
Once you're getting the right data here, you can map that data through a presentation function.
function PageLink(i, char){
character = character || String(i);
return (
<li key={char}>
<a href={"#posts?page="+i}>{char}</a>
</li>
);
}
Pagination = function(props){
/* same code as before */
return links.map(function(x){
return PageLink(x[0], x[1]);
});;
};
P.s. when you do get it to match your requirements, please post an answer here so others can use it as a base for their pagination.
Below is the complete code for creating a paging option.Full post is available here.
var pager = React.createClass({
render : function(){
var li = [];
var pageCount = props.Size;
for(var i = 1; i <=pageCount; i++){
if(props.currentPage == i){
li.push(<li key={i} className="active">{i}</li>);
}
else{
li.push(<li key={i} ><a href="#" onClick={props.onPageChanged.bind(null,i)}>{i}</a></li>);
}
}
return (<ul className="pagination">{li}</ul>);
}
});
var dataGrid = React.createClass({
render : function(){
return (
<tr>
<td>{props.item.Name}</td>
<td>{props.item.Address}</td>
<td>...</td>
.....
</tr>
);
}
});
var EmployeeGridTable = React.createClass({
getInitialState : function(){
return {
Data : {
List : [],
totalPage : 0,
sortColumnName : null,
sortOrder : null,
currentPage : 1,
pageSize : 3
}
}
},
componentDidMount : function(){
this.populateData();
},
populateData: function(){
var params = {
pageSize : this.state.Data.pageSize,
currentPage : this.state.Data.currentPage
}
if(this.state.Data.sortColumnName){
params.sortColumnName = this.state.Data.sortColumnName;
}
if(this.state.Data.sortOrder){
params.sortOrder = this.state.Data.sortOrder;
}
$.ajax({
url : this.props.dataUrl,
type : 'GET',
data : params,
success : function(data){
if(this.isMounted()){
this.setState({
Data : data
});
}
}.bind(this),
error: function(err){
alert('Error');
}.bind(this)
});
},
pageChanged:function(pageNumber,e){
e.preventDefault();
this.state.Data.currentPage = pageNumber;
this.populateData();
},
sortChanged : function(sortColumnName, order , e){
e.preventDefault();
this.state.Data.sortColumnName = sortColumnName;
this.state.Data.currentPage = 1;
this.state.Data.sortOrder = order.toString().toLowerCase() == 'asc' ? 'desc':'asc';
this.populateData();
},
_sortClass : function(filterName){
return "fa fa-fw " + ((filterName == this.state.Data.sortColumnName) ? ("fa-sort-" + this.state.Data.sortOrder) : "fa-sort");
},
render : function(){
var rows = [];
this.state.Data.List.forEach(function(item){
rows.push(<dataGrid key={item.EmployeeID} item={item}/>);
});
return (
<div>
<table className="table table-responsive table-bordered">
<thead>
<tr>
<th onClick={this.sortChanged.bind(this,'FirstName',this.state.Data.sortOrder)}>First Name
<i className={this._sortClass('FirstName')}></i></th>
<th onClick={this.sortChanged.bind(this,'LastName',this.state.Data.sortOrder)}>
Last Name
<i className={this._sortClass('LastName')}></i></th>
<th onClick={this.sortChanged.bind(this,'EmailID',this.state.Data.sortOrder)}>
Email
<i className={this._sortClass('EmailID')}></i>
</th>
<th onClick={this.sortChanged.bind(this,'Country',this.state.Data.sortOrder)}>
Country
<i className={this._sortClass('Country')}></i>
</th>
<th onClick={this.sortChanged.bind(this,'City',this.state.Data.sortOrder)}>
City
<i className={this._sortClass('City')}></i>
</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
<pager Size={this.state.Data.totalPage} onPageChanged={this.pageChanged} currentPage={this.state.Data.currentPage}/>
</div>
);
}
});
ReactDOM.render(<EmployeeGridTable dataUrl="/home/getEmployeeList"/>, document.getElementById('griddata'));

How to efficiently do web scraping in Node.js?

I am trying to scrape some data from a shopping site Express.com. Here's 1 of many products that contains image, price, title, color(s).
<div class="cat-thu-product cat-thu-product-all item-1">
<div class="cat-thu-p-cont reg-thumb" id="p-50715" style="position: relative;"><img class="cat-thu-p-ima widget-app-quickview" src="http://t.express.com/com/scene7/s7d5/=/is/image/expressfashion/25_323_2516_900/i81?$dcat191$" alt="ROCCO SLIM FIT SKINNY LEG CORDUROY JEAN"><img id="widget-quickview-but" class="widget-ie6png glo-but-css-off2" src="/assets/images/but/cat/but-cat-quickview.png" alt="Express View" style="position: absolute; left: 50px;"></div>
<ul>
<li class="cat-cat-more-colors">
<div class="productId-50715">
<img class="js-swatchLinkQuickview" title="INK BLUE" src="http://t.express.com/com/scene7/s7d5/=/is/image/expressfashion/25_323_2516_900_s/i81?$swatch$" width="16" height="6" alt="INK BLUE">
<img class="js-swatchLinkQuickview" title="GRAPHITE" src="http://t.express.com/com/scene7/s7d5/=/is/image/expressfashion/25_323_2516_924_s/i81?$swatch$" width="16" height="6" alt="GRAPHITE">
<img class="js-swatchLinkQuickview" title="MERCURY GRAY" src="http://t.express.com/com/scene7/s7d5/=/is/image/expressfashion/25_323_2516_930_s/i81?$swatch$" width="16" height="6" alt="MERCURY GRAY">
<img class="js-swatchLinkQuickview" title="HARVARD RED" src="http://t.express.com/com/scene7/s7d5/=/is/image/expressfashion/25_323_2516_853_s/i81?$swatch$" width="16" height="6" alt="HARVARD RED">
</div>
</li>
<li class="cat-thu-name"><a href="/rocco-slim-fit-skinny-leg-corduroy-jean-50715-647/control/show/3/index.pro" onclick="var x=".tl(";s_objectID="http://www.express.com/rocco-slim-fit-skinny-leg-corduroy-jean-50715-647/control/show/3/index.pro_2";return this.s_oc?this.s_oc(e):true">ROCCO SLIM FIT SKINNY LEG CORDUROY JEAN
</a></li>
<li>
<strong>$88.00</strong>
</li>
<li class="cat-thu-promo-text"><font color="BLACK" style="font-weight:normal">Buy 1, Get 1 50% Off</font>
</li>
</ul>
The very naive and possibly error-prone approach I've done is to first to grab all prices, images, titles and colors:
var price_objects = $('.cat-thu-product li strong');
var image_objects = $('.cat-thu-p-ima');
var name_objects = $('.cat-thu-name a');
var color_objects = $('.cat-cat-more-colors div');
Next, I populate arrays with the data from DOM extracted using jsdom or cheerio scraping libraries for node.js. (Cheerio in this case).
// price info
for (var i = 0; i < price_objects.length; i++) {
prices.push(price_objects[i].children[0].data);
}
// image links
for (var i = 0; i < image_objects.length; i++) {
images.push(image_objects[i].attribs.src.slice(0, -10));
}
// name info
for (var i = 0; i < name_objects.length; i++) {
names.push(name_objects[i].children[0].data);
}
// color info
for (var i = 0; i < color_objects.length; i++) {
colors.push(color_objects[i].attribs.src);
}
Lastly, based on the assumption that price, title, image and colors will match up create a product object:
for (var i = 0; i < images.length; i++) {
items.push({
id: i,
name: names[i],
price: prices[i],
image: images[i],
colors: colors[i]
});
}
This method is slow, error-prone, and very anti-DRY. I was thinking it would be nice if we could grab $('.cat-thu-product') and using a single for-loop extract relevant information from a single product a time.
But have you ever tried traversing the DOM in jsdom or cheerio? I am not sure how anyone can even comprehend it. Could someone show how would I use this proposed method of scraping, by grabbing $('.cat-thu-product') div element containing all relevant information and then extract necessary data?
Or perhaps there is a better way to do this?
I would suggest still using jQuery (because it's easy, fast and secure) with one .each example:
var items = [];
$('div.cat-thu-product').each(function(index, productElement) {
var product = {
id: $('div.cat-thu-p-cont', productElement).attr('id'),
name: $('li.cat-thu-name a', productElement).text().trim(),
price: $('ul li strong', productElement).text(),
image: $('.cat-thu-p-ima', productElement).attr('src'),
colors: []
};
// Adding colors array
$('.cat-cat-more-colors div img', productElement).each(function(index, colorElement) {
product.colors.push({name: $(colorElement).attr('alt'), imageUrl: $(colorElement).attr('src')});
});
items.push(product);
});
console.log(items);
And to validate that you have all the required fields, you can write easilly validator or test. But if you are using different library, you still should loop through "div.cat-thu-product" elements.
Try node.io https://github.com/chriso/node.io/wiki
This will be a good approach of doing what you are trying to do.
using https://github.com/rc0x03/node-promise-parser
products = [];
pp('website.com/products')
.find('div.cat-thu-product')
.set({
'id': 'div.cat-thu-p-cont #id',
'name': 'li.cat-thu-name a',
'price': 'ul li strong',
'image': '.cat-thu-p-ima',
'colors[]': '.cat-cat-more-colors div img #alt',
})
.get(function(product) {
console.log(product);
products.push(product);
})

Resources