Can't render array elements from mongodb document with express-edge templating - node.js

I'm following this blog tutorial to learn nodejs backend along with mongodb, it seems a bit outdated(I've had to tweak some stuff to make it work) but also I'm not following it 100%, as I'm making my own front end instead of using a theme and I'm using my own database, which brings to the problem:
While rendering the post lists I want to render inside each post the list of it's tags, which in my database is an array of strings, but it doesnt work. When I try to access the first element of the array only, it return undefined.
This code doesnt render any <li>:
<div class="row" id="lista-posts">
#each(post in posts)
<div class="col-12">
<h4>{{post.titulo}}</h4>
<ul>
#each(tag in post.tags)
<li>{{tag}}</li>
#endeach
</ul>
<div class="post-conteudo">
{{post.conteudo}}
</div>
</div>
#endeach
</div>
This one here render one <li> (as expected) but it's written Undefined:
(...)
<h4>{{post.titulo}}</h4>
<ul>
<li>{{post.tags[0]}}</li>
</ul>
All the other elements like "titulo" and "conteudo" are rendered fine. For context, every post in my db has:
_id: IdObject
titulo: String
tags: Array of Strings
conteudo: String

Turns out it's because I didn't set up the tags array in my mongoose Schema.

Related

How can I render specific piece of data from my mongodb into my html?

It's my first project. I'm creating a website where administrator have the privilege to change home page texts. So I'm keeping all texts in a collection called "Texts" , each document has a text:value . When home page is rendered I use Texts.find() to return an array containing objects each has a "text" value. I link it to my home page using index.
Like this..
<h2>Texts[0].text</h2>, so if i have 100 texts I go all the way to <h2>Texts[100].text</h2>. and they are different texts and I need to put them in a specific order so I can't just throw them into my html.
I know that's so stupid , so I'm looking for some idea instead of this.
--------------Modification------------------>>>>
I tried using find method for arrays but it also is so tiring , so something simpler would be great , here's a portion of the code
<div class="card bg-dark text-white">
<img src="<%=imgsArr.find(x => x.name === 'main2').src%>" class="card-img" alt="...">
<div class="card-img-overlay ">
<h2 class="card-title"><%=textArr.find(x => x.id === 14).text%></h2>
<div class="triangle-up"></div>
<hr class="ml-0 ">
<div class="triangle-down"></div>
<p class="card-text "><%=textArr.find(x => x.id === 15).text%></p>
<div class="card-topic">
<h2 class="card-title" style=";"><%=textArr.find(x => x.id === 16).text%></h2>
<p class="card-text"><%=textArr.find(x => x.id === 17).text%></p>
</div>
<div class="card-topic">
<h2 class="card-title" style=""><%=textArr.find(x => x.id === 18).text%></h2>
<p class="card-text"><%=textArr.find(x => x.id === 19).text%></p>
</div>
<div class="btnOut ">
<button class="btn btn-lg shadow-lg ">MENU</button>
</div>
</div>
</div>
Good news is that for loops are always a good option. If you are using ejs or a similar rendering engine something like this should work, although you will have to tweak it a bit. Take a look are the EJS rendering engine documentation for an exact format, but it would look something like below and no matter how long the list gets it will render as much as it receives(if it is a lot you might want to consider using pagination)
<% for text in Texts.text %>
<% if text==="some value"%>
<h2>text</h2>
<% else %>
<p> text </p>
If your front-end is perhaps react.js or vue.js or similar the same prnciple would work but the format will be different.
No offense friend but you need to get some tutorials. But let me help you as best i can. Everything in a web application is somewhat defined. Meaning it falls into one predetermined category or another. When you get json for example you get something base on how that particular data is defined in the database. Hence you can get json data from your backend that looks
{ 'title':'sneaker', 'price':'200', 'quantity':'50' }
Now assuming it is a list of json objects what you can do is loop through this and assign them to tags based on their object key(because it gets converted to a javscript object). so again you code would look something like
<% for text in Texts.text %>
<h2>text.title</h2>
<div>
<p>text.price</p>
<p>text.quantity</p>
</div>
This would be how you would render data from your database(the format might not exactly be spot on). Dealing with forms is a whole other ball game. So get a book or some tutorial videos that discuss it. You will better understand how to handle it.
For your code you are being very rigid, at first glance there is already a pattern, work with that pattern, you code just needs a simple for loop from what i gather.
for(let i=0; let i>100; i++){
if (i%2===0){
console.log(<h2> i </h2>)
}else{
console.log(<p> i </p>
}
}
The reason i have resulted to pure javascript is so you can see what the inner workings of your render should look like. You can get the total number of ids in your database(that is what the integer 100 represents in this case), loop through one by one and produce what you want. It is still javascript, do not let the html throw you off

A way to render multiple root elements on VueJS with v-for directive

Right now, I'm trying to make a website that shows recent news posts which is supplied my NodeJS API.
I've tried the following:
HTML
<div id="news" class="media" v-for="item in posts">
<div>
<h4 class="media-heading">{{item.title}}</h4>
<p>{{item.msg}}</p>
</div>
</div>
JavaScript
const news = new Vue({
el: '#news',
data: {
posts: [
{title: 'My First News post', msg: 'This is your fist news!'},
{title: 'Cakes are great food', msg: 'Yummy Yummy Yummy'},
{title: 'How to learnVueJS', msg: 'Start Learning!'},
]
}
})
Apparently, the above didn't work because Vue can't render multiple root elements.
I've looked up the VueJS's official manual and couldn't come up with a solution.
After googling a while, I've understood that it was impossible to render multiple root element, however, I yet to have been able to come up with a solution.
The simplest way I've found of adding multiple root elements is to add a single <div> wrapper element and make it disappear with some CSS magic for the purposes of rendering.
For this we can use the "display: contents" CSS property. The effect is that it makes the container disappear, making the child elements children of the element the next level up in the DOM.
Therefore, in your Vue component template you can have something like this:
<template>
<div style="display: contents"> <!-- my wrapper div is rendered invisible -->
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
</div>
</template>
I can now use my component without the browser messing up formatting because the wrapping <div> root element will be ignored by the browser for display purposes:
<table>
<my-component></my-component> <!-- the wrapping div will be ignored -->
</table>
Note however, that although this should work in most browsers, you may want to check here to make sure it can handle your target browser.
You can have multiple root elements (or components) using render functions
A simple example is having a component which renders multiple <li> elements:
<template>
<li>Item</li>
<li>Item2</li>
... etc
</template>
However the above will throw an error. To solve this error the above template can be converted to:
export default {
functional: true,
render(createElement) {
return [
createElement('li', 'Item'),
createElement('li', 'Item2'),
]
}
}
But again as you probably noticed this can get very tedious if for example you want to display 50 li items. So, eventually, to dynamically display elements you can do:
export default {
functional: true,
props: ['listItems'], //this is an array of `<li>` names (e.g. ['Item', 'Item2'])
render(createElement, { props }) {
return props.listItems.map(name => {
return createElement('li', name)
})
}
}
INFO in those examples i have used the property functional: true but it is not required of course to use "render functions". Please consider learning more about functional componentshere
Define a custom directive:
Vue.directive('fragments', {
inserted: function(el) {
const children = Array.from(el.children)
const parent = el.parentElement
children.forEach((item) => { parent.appendChild(item) })
parent.removeChild(el)
}
});
then you can use it in root element of a component
<div v-fragments>
<tr v-for="post in posts">...</tr>
</div>
The root element will not be rendered in DOM, which is especially effective when rendering table.
Vue requires that there be a single root node. However, try changing your html to this:
<div id="news" >
<div class="media" v-for="item in posts">
<h4 class="media-heading">{{item.title}}</h4>
<p>{{item.msg}}</p>
</div>
</div>
This change allows for a single root node id="news" and yet still allows for rendering the lists of recent posts.
In Vue 3, this is supported as you were trying:
In 3.x, components now can have multiple root nodes! However, this does require developers to explicitly define where attributes should be distributed.
<!-- Layout.vue -->
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
Multiple root elements are not supported by Vue (which caused by your v-for directive, beacause it may render more than 1 elements). And is also very simple to solve, just wrap your HTML into another Element will do.
For example:
<div id="app">
<!-- your HTML code -->
</div>
and the js:
var app = new Vue({
el: '#app', // it must be a single root!
// ...
})

If element hasClass, add another class to its title value

I'm using slick carousel, and once a div is active I want to open the corresponding description.
Problem I'm having is with this code:
if ($('div').hasClass('active')) {
var title = $(this).attr('title');
$('ul li').removeClass('open');
$(title).addClass('open');
}
What I'm trying to achieve:
Once a div gets class 'active', I want to take its title value, and use it as a id link to list element I want to display(add class to).
Here is a FIDDLE.
Use event handling, not class monitoring.
The slick carousel API has events for this, I believe you want to use the afterChange event to act on the active element after it has been made visible.
Check out the docs and examples, especially the section titled "Events" on Slick page: http://kenwheeler.github.io/slick/
And I think you don't want to use title attribute for this because that is for tooltips. I recommend data-* attributes instead. And element IDs should generally start with a letter and not a number (was required in HTML4 and makes life easier when mapping IDs to JavaScript variables; though if you are using HTML5 I think this requirement is no longer in effect).
HTML
<div id="carousel">
<div data-content-id="content1">
Selector 1 </div>
<div data-content-id="content2">
Selector 2 </div>
<div data-content-id="content3">
Selector 3 </div>
</div>
<ul class="content">
<li id="content1">Content 1</li>
<li id="content2">Content 2</li>
<li id="content3">Content 3</li>
</ul>
JavaScript
$('#carousel').on('afterChange', function(event, slick, currentSlide) {
// get the associated content id
var contentId = $(slick.$slides.get(currentSlide)).data("content-id");
if(contentId && contentId.length)
{
var $content = $("#" + contentId);
$(".content>li").removeClass("open"); // hide other content
$content.addClass("open"); // show target content, or whatever...
}
});
I have found a solution:
$('.slider').on('afterChange', function(event, slick, currentSlide, nextSlide){
var contentId= $(slick.$slides.get(currentSlide)).data('content');
if(contentId)
{
$(".content li").removeClass('open');
$('#' + contentId).addClass('open');
}
});
Working fiddle

How to check type of object in Handlebars?

I am currently working on a search bar that lists three types of objects: users, records and locations. Each has its own model and such defined, with corresponding controllers. What I need to do is to check which type of object it is because I need the HTML that renders with it to be different.
{{#each mainUserSearchResults}}
{{#link-to 'user' this.userID}}
<div class="row mainListSeperator" {{action "getapplicantUserID" this target="view"}}>
<img class="applicantsIcon" src="">
<div class="applicantsName">
{{unbound this.firstName}} {{unbound this.lastName}}
</div>
<div class="applicantsTitle">
User
</div>
</div>
{{/link-to}}
{{/each}}
The only issue I am having is that I need it to print this.firstName & this.lastName if it is a user, but I cannot do that for records. For records, I would have to render another property - this.recordID - in the same manner as I did this.firstName. The way to do this would be an if conditional, but I cannot find anything in HandleBars that allows me to check whether the data coming in from mainUserSearchResults is a user or a record.
The mainUserSearchResults is a property in my controller that returns an array of objects: currently it return a concatenated array of user objects and record objects.
Handlebars is a logicless template, so you really can't perform computations, like type-checking. There are some pretty good reasons for this. If it were me, I'd push that responsibility elsewhere, probably all the way to your model, for two reasons:
Any conditionals you put in the DOM are SLOW. The problem only gets compounded when you're on slow devices (most notably, mobile).
Every time you add a new searchable thing, you're going to have to update your templates. Over time, you'll end up with:
{{#if ...}}
...
{{/if}}
{{#if ...}}
...
{{/if}}
{{#if ...}}
...
{{/if}}
{{#if ...}}
...
{{/if}}
Delegate instead. For example, in your User model, add
formattedName: (function() {
return [this.get('firstName'), this.get('lastName')].join(' ');
}).property('firstName', 'lastName')
Do the same for your records and locations.
Then, change your template from
{{unbound this.firstName}} {{unbound this.lastName}}
to
{{unbound this.formattedName}}
Now, if you add new types to your searches, you have a completely generic solution that only needs the model to implement the formattedName property.
Some would would say my suggestion is a bad one, because it mixes the business-logic responsibility of a model with display logic. In this case, I'm guessing you're displaying {{firstName}} {{lastName}} in more than one place, so I think the benefits of keeping your code DRY outweigh moving this to, e.g., a controller. Your call.
Assuming there is some way to easily distinguish between the 2+ types of objects, you can use an if statement and render a different template (or even use a different partial, http://emberjs.com/guides/templates/rendering-with-helpers/)
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each item in model}}
<li>
{{#if item.foo}}
{{render 'foo' item}}
{{/if}}
{{#if item.cow}}
{{render 'cow' item}}
{{/if}}
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="foo">
I'm a foo: {{foo}}
</script>
<script type="text/x-handlebars" data-template-name="cow">
I'm a cow: {{cow}}
</script>
http://emberjs.jsbin.com/qinilege/1/edit
Or if you need more advanced checking you can use an item controller and add the logic in there
{{#each item in model itemController='checker'}}
<li>
{{#if item.isFooBar}}
{{render 'foo' item.model}}
{{/if}}
{{#if item.isFooBaz}}
{{render 'fooz' item.model}}
{{/if}}
{{#if item.isCow}}
{{render 'cow' item.model}}
{{/if}}
</li>
{{/each}}
App.CheckerController = Em.ObjectController.extend({
isFooBar: Ember.computed.equal('foo', 'bar'),
isCow: Ember.computed.notEmpty('cow'),
isFooBaz: Ember.computed.equal('foo', 'baz')
});
http://emberjs.jsbin.com/qinilege/2/edit

Global search box in angular

I want to implement a search box that changes what it searches based on whichever controller is being used. If you are on the "posts" view it will search the posts api, if you are on the videos view, it searches the videos api. It seems the search box would need its own controller maybe. I'm pretty sure I need to inject a search service into all the model controllers but I'm not exactly sure how to change the url it searches or tie the input to the different controller scopes.
So any ideas how to have a global search box that changes where it searches based on whichever controller is making use of it and tying its state back into a changing view?
To make a resource call dynamic api i would first create two $resources that map to your two endpoints, posts and videos. Then put an ng-change event on your global search that calls a function in your base controller.
This function firsts need to figure out what api to search. Then make the appropriate api call. The important part is in the callback and i think this is what you are looking for.
In the callback you could $broadcast the resp data from your api query. Each of your controllers will be listening for an event with an $on function. The listeners will then populate the correct scope variable with the callback data.
Pseudo below.
Same html layout with ng-change
<html>
<body ng-controller="AppController">
<form>
<label>Search</label>
<input ng-model="global.search" ng-change="apiSearch()" type="text" class="form-control" />
</form>
<div ui-view="posts">
<div ng-controller="PostController">
<p ng-repeat="post in posts | filter: global.search">{{ post.name }}</p>
</div>
</div>
<div ui-view="videos">
<div ng-controller="VideoController">
<p ng-repeat="video in videos | filter: global.search">{{ video.name }}</p>
</div>
</div>
</body>
</html>
AppController
.controller('AppController', function ($scope, PostService, VideoService) {
$scope.apiSearch = function() {
// Determine what service to use. Could look at the current url. Could set value on scope
// every time a controller is hit to know what your current controller is. If you need
// help with this part let me know.
var service = VideoService, eventName = 'video';
if ($rootScope.currentController == 'PostController') {
service = PostService;
eventName = 'post';
}
// Make call to service, service is either PostService or VideoService, based on your logic above.
// This is pseudo, i dont know what your call needs to look like.
service.query({query: $scope.global.search}, function(resp) {
// this is the callback you need to $broadcast the data to the child controllers
$scope.$broadcast(eventName, resp);
});
}
})
Each of your child controllers that display the results.
.controller('PostController', function($scope) {
// anytime an event is broadcasted with "post" as the key, $scope.posts will be populated with the
// callback response from your search api.
$scope.$on('post', function(event, data) {
$scope.posts = data;
});
})
.controller('VideoController', function($scope) {
$scope.$on('video', function(event, data) {
$scope.videos = data;
});
})
Client side filtering.
If you are not looking for anything to crazy that can be achieved in a super simple way for global search. I didnt even know if this would work so i just did a quick test and it does. Obviously this could be solved in a much more detailed and controlled way using services and injecting them where they are needed. But since i don't know excatly what you are looking for i will provide this solution, if you like it, great accept it. If you don't i could probably help you with service injection solution
Quick solution is to have an app wide contoller with $rootScope ng-model. Lets call it global.search.
$rootScope.global = {
search: ''
};
For the app wide search input.
<form>
<label>Search</label>
<input ng-model="global.search" type="text" class="form-control" />
</form>
In separate partials you just need to filter data based on the global.search ng-model. Two examples
<p ng-repeat="post in posts | filter: global.search">{{ post.name }}</p>
Second template with different scope
<p ng-repeat="video in videos | filter: global.search">{{ video.name }}</p>
Note how they both implement | filter: global.search. Whenever global.search changes, any filters in the current view will be changed. So posts will be filtered on the posts view, and videos on the videos view. While still using the same global.search ng-model.
I tested this, it does work. If you need more detail explaining the setup and child controller hierarchy let me know. Here is a quick look at a full template
<html>
<body ng-controller="AppController">
<form>
<label>Search</label>
<input ng-model="global.search" type="text" class="form-control" />
</form>
<div ui-view="posts">
<div ng-controller="PostController">
<p ng-repeat="post in posts | filter: global.search">{{ post.name }}</p>
</div>
</div>
<div ui-view="videos">
<div ng-controller="VideoController">
<p ng-repeat="video in videos | filter: global.search">{{ video.name }}</p>
</div>
</div>
</body>
</html>

Resources