I'm currently using underscore templates to render a HTML list that displays a list of contacts.
The template looks something like this:
<li>
<span class="name">Name: <=%data.name%></span>
<span class="email">Name: <=%data.email%></span>
<img class="avatar" src="<=%data.avatar%>"></img>
</li>
The issue is, when I set the template data, the source of the image won't be known. Why? Because my data looks something like this:
contact = {
name: string, // i.e. 'John Doe'
email: string, // i.e 'john#doe.com'
avatar: string // i.e. '11a93150-14d4-11e3'
}
The avatar is actually not a URL, rather a link to a remote database that needs fetching. Something like:
function getAvatar(uuid, cb) { // uuid is something like 11a93150-14d4-11e3
window.db.getImageUrl(function(url) {
cb(url); // url is something like http://foo.com/avatar.png
});
}
Question is, is there a way to write my template so that instead of reading the avatar value of the contact object directly, I can embed a reference to a function like getAvatar that when the template is rendered, fetches the url to the image and sets the avatar image URL?
Thanks in advance
Here's an example to demonstrate how you can call JavaScript functions and asynchornously update src attribute of thumbnails. I've tried to simulate your DB call using setTimeout and the DB using associative array.
HTML:
<script type='text/html' id='contactTemplate'>
<li id="contact-<%= avatar %>">
<span class = "name"> Name: <%= name %> </span>
<span class="email">Name: <%= email %></span>
<img class = "avatar" data-populate-path="<% getPath( avatar ) %>" />
</li>
</script>
<ul id='contactList'></ul>
JavaScript:
var contacts = [
{name: 'John Doe', email: 'john#doe.com', avatar: '11a93150-14d4-11e3'},
{name: 'Hannah Smith', email: 'hannah#smith.com', avatar: '11a93150-14d4-1231' }
],
simulatedDB = [];
simulatedDB['11a93150-14d4-11e3'] = "path to avatar 1";
simulatedDB['11a93150-14d4-1231'] = "path to avatar 2";
$(document).ready(function () {
var compiled = _.template($("#contactTemplate ").html());
_.each(contacts, function (d, i) {
$("#contactList").append(compiled(d));
});
});
function getPath(target) {
setTimeout(updateAvatar, 1000, target);
}
function updateAvatar(target) {
$("#contact-"+target+" img").attr("src", simulatedDB[target]);
}
Related
I have a hbs view which is receiving and displaying some data that I am pulling from MongoDB.
It is displaying all my data correctly except for the binary data which I am using inside of an img element. If I copy the binary data from the MongoDB document and put it in the img element it displays in the browser. For this reason I feel that the variable I am referencing is incorrect?
I have also tried to use 'img.$binary' and that does not show any data at all.
This would seem to me the way to do it though with 'img' being an object?
But when I log 'img' to the console it seems to be the right one. When I log 'img.$binary' it is undefined. I am definitely getting some data in the img tag as it shows this when I run node js (with 'img' in the appropriate place in hbs view):
HTML:
My route:
router.get('/', async (req, res) => {
const products = await Product.find()
res.render('shop', {
title: 'Shop',
products: products
})
})
Mongoose Model:
const ProductSchema = new mongoose.Schema(
{
title: { type: String, required: true, unique: true },
desc: { type: String, required: true },
img: { type: Buffer },
categories: { type: Array },
size: { type: String },
colour: { type: String },
price: { type: Number, required: true },
},
{ timestamps: true }
)
MongoDB Collection:
{
"_id" : ObjectId("62dd1127884e20dcfbb09a6c"),
"title" : "'Northface tshirt'",
"desc" : "'test'",
"img" : { "$binary" : "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6CAIAAAAH...(shortened for viewability), "$type" : "00" },
"categories" : [],
"size" : "'XL'",
"colour" : "'White'",
"price" : 80,
"createdAt" : ISODate("2022-07-24T09:30:15.974Z"),
"updatedAt" : ISODate("2022-07-24T09:30:15.974Z"),
"__v" : 0
}
HBS view:
<body>
<div class="grid-wrapper">
{{!-- Object --}}
{{#each products}}
<div class="grid-number">
<img src="data:image/jpg;base64,{{img}}">
{{!-- String --}}
<h2>{{title}}</h2>
{{!-- Array --}}
{{#each categories}}
{{!-- Strings --}}
<p>Apparel Type: {{apparelType}}</p>
<p>Gender: {{gender}}</p>
{{/each}}
<p>Size: {{size}}</p>
<p>Price: {{price}}</p>
<p>Colour: {{colour}}</p>
</div>
{{/each}}
</div>
Any help would be greatly appreciated, I have searched for a couple hours now and seen some similar issues but nothing has helped so far. Hoping someone with more experience with data will know what is going wrong here. Thanks in advance.
Not exactly an answer for inserting the binary data straight into the img tag but I have now found a way to display the DB document images dynamically.
Firstly, I added a route to view the img for each document in the collection by it's id:
//VIEW PRODUCT IMG
router.get('/:id/img', async (req, res) => {
try {
const product = await Product.findById(req.params.id)
if (!product || !product.img) {
throw new Error()
}
res.set('Content-Type', 'image/jpg')
res.send(product.img)
} catch (e) {
res.status(404).send()
}
})
Then, modified my img src attribute to point towards the route + inserted the id's of the documents dynamically in the hbs template:
<div class="grid-wrapper">
{{!-- Object --}}
{{#each products}}
<div class="grid-number">
<img src="products/{{id}}</img">
{{!-- String --}}
<h2>{{title}}</h2>
{{!-- Array --}}
{{#each categories}}
{{!-- Strings --}}
<p>Apparel Type: {{apparelType}}</p>
<p>Gender: {{gender}}</p>
{{/each}}
<p>Size: {{size}}</p>
<p>Price: {{price}}</p>
<p>Colour: {{colour}}</p>
</div>
{{/each}}
</div>
I can now display the images as intended:
I'm working on an app were a parent user needs to create a student object that can be accessed by both the parent user and teacher users. Everything is working right except this one thing. When the parent user creates the student, it redirects to the user's dash where the newly created student should display. However, the student doesn't display until I log out and back in. I'm assuming that this is because my code doesn't run in the order I want it to, but I have no idea. I've been stuck here for a couple days. Help!
Here's the route handling for creating a student
EDIT:
I got it to work by refactoring things here almost entirely. I was creating a student document and saving it and then pushing that document into an array on the parent object. That was dumb for two reasons. One, it wasn't displaying properly in terms of timing. Two, if I ever wanted to update the student, I needed to update it in multiple instances. Here, I'm creating the document and adding a reference to an array on the parent object. Now when I edit/delete a student, I do it in one place, and the ObjectID is immediately accessible for the Student.findById() that runs in the "users/:id" route. Here's my updated code:
CREATE STUDENT
router.post("/users/:id/createStudent", middleware.isLoggedIn, function(req, res){
const firstName = req.body.firstName,
lastName = req.body.lastName,
age = req.body.age,
instrument = req.body.instrument,
parent = {
id: req.user._id,
username: req.user.username
},
newStudent = {
firstName: firstName,
lastName: lastName,
age: age,
instrument: instrument,
parent: parent
}
Student.create(newStudent, function(err){
if(err){
console.log(err);
req.flash("error", "error")
} else {
res.redirect("/users/:id")
}
})
});
And the Dashboard template:
<%- include ("../partials/header") %>
//
code for teachers exists here
//
<% } else if(user.isTeacher === false) {%>
<h1 class="ui huge header"><%= user.username%>'s Dashboard</h1>
<h2 class="ui big header">Here are your students:</h2>
<% if(students.length === 0) {%>
<p>Oops! Looks like you haven't registered a student. You can do that here. </p>
<% } else { %>
<div class="ui relaxed divided list">
<% students.forEach(function(student){ %>
<div class="item">
<div class="content">
<%= student.firstName %> <%= student.lastName %>
<ul>
<li>
<a class="ui secondary mini basic button"
id="assignments"
href="/student/<%= student._id%>">
<%= student.firstName %>'s Assignments
</a>
<form action="/student/<%= student._id %>?_method=DELETE" method="POST">
<button class="ui negative mini basic button" id="assignments">
Remove Student
</button>
</form>
</li>
</ul>
</div>
</div>
<% }) %>
<p>Add a new student.</p>
</div>
<% } %>
<%}%>
</div>
</div>
<%- include ("../partials/footer") %>
Here's the student model:
const mongoose = require("mongoose"),
Schema = mongoose.Schema;
const StudentSchema= new Schema({
firstName: String,
lastName: String,
age: String,
instrument: String,
assignments: [
{ id: {
type: mongoose.Schema.Types.ObjectId,
ref:"Assignment"
},
title: String
}
],
parent: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
teacher: [{
id: {
type: mongoose.Schema.Types.ObjectId,
ref:"User"
},
username: String
}]
});
const Student = mongoose.model("student", StudentSchema);
module.exports = Student;
[
{
title: "title of post"
author: "some guy"
},
{
title: "title of post",
author: "some guy"
}
]
When a user searches for an article, it might return something like the above. How could I make, using VanillaJS or jQuery, it return a page with a <ul> tag with <li> tags for the data inside for the title and author? I know this is a big question, but I don't need an exact answer, just an idea of what to do! I also tried foreach but couldn't figure it out.
Thanks,
Jude Wilson
As far as I understand your question, you're trying to create a <ul> tag that has many children <li> tags each containing the filtered data title, and author keys.
Well normally I recommend using something like vue.js to manage data bindings for your UI, but since you mentioned you want this accomplished using jQuery or vanilla JS, it's rather simple than you think.
Here is a complete HTML code example, using jQuery:
<html>
<body>
<!-- Posts List Container -->
<div>
<ul id="postsContainer"></ul>
</div>
<!-- Post Template (Just an example use-case) -->
<template id="postTemplate">
<li class="post-meta-data">
<h2 class="post-title"></h2>
<strong class="post-author"></strong>
<em class="post-tags"></em>
</li>
</template>
<!-- Include jQuery -->
<script
src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous"></script>
<!-- Our JS Logic -->
<script>
$(function () {
const
$container = $('#postsContainer'),
// Grabs the HTML content of the "tag" template
template = $('#postTemplate').html(),
// Here you can fetch/return your API's or filtered data
// NOTE: I modified the data to showcase the example.
getData = () => {
return [
{
title: "title of post #1",
author: "some guy",
tags: ['foo', 'bar', 'baz'],
},
{
title: "title of post #2",
author: "some other guy",
tags: ['watermelon', 'apple', 'banana'],
}
];
},
// Here you can call this function to update the UI
// based on the data returned by "getData()"
updateSearchTags = () => {
const postsArray = getData();
// Before adding any new elements to the container, we should empty it first.
$container.empty();
for (const post of postsArray) {
const
{ title, author, tags } = post,
$postEl = $(template);
// Hydrate the template with post data
$postEl.find('.post-title').text(title);
$postEl.find('.post-author').text(author);
$postEl.find('.post-tags').text(tags.join(', '));
// Add the post element to the list
$postEl.appendTo($container);
}
};
// Call the UI Update Function whenever necessary
updateSearchTags();
});
</script>
</body>
</html>
Not too sure what you mean by "return a page" but you could map over the data and set it to the ul with innerHTML
function printData(ele, data) {
ele.innerHTML = data.map(item => `<li>${item.title} - ${item.author}</li>`).join('')
}
const ulEle = document.querySelector('#target')
const data = [
{ title: "title of post", author: "some guy" },
{ title: "title of post", author: "some guy" }
]
printData(ulEle, data)
<ul id="target"></ul>
I have a default.js which stores my data like this:
{
id: 2
, title: 'testitem'
, url: 'http://www.alink.com/item=112920'
, dependsOn: [1]
},
I want in my default.html a link which is created from the url above.
I tried it like this:
<a data-bind="attr: { href: url }">
Testitem
</a>
But nothing appears. If I try something with the "title" attribute it works:
<h3 data-bind="text: title"></h3>
You can store your data object in a variable and you have to activate the knockout.js binding.
var data = {
id: 2,
title: "Hello world",
url: "http://www.google.com",
dependsOn: [1]
};
// Activates knockout.js
ko.applyBindings(data);
and your HTML
<h3 data-bind="text: title"></h3>
<a data-bind="attr: { href: url }">
Testitem
</a>
then everything will work fine. You just have to activate your knockout.js binding.
You can find the code here.
https://codepen.io/AElkhodary/pen/ZrxrqV
I have a list of products where the name is a link to the product's details view. The list of products is the "Results" view
Samsumg
iPhone
When the user clicks on a product, the "Details" template is shown, and the "Results" template is not shown; at least that is the behavior that I want.
I am using the following code to accomplish this behavior, and have the jsFiddle here http://jsfiddle.net/justinnafe/mLf5G/:
<div data-bind="template: displayMode"></div>
<script type="text/html" id="Result">
<ul data-bind="foreach: products">
<li></li>
</ul>
</script>
<script type="text/html" id="Details">
<p data-bind="text: name"></p>
<p data-bind="text: description"></p>
</script>
and the javascript:
var view = {
name: "Result"
};
var initialProducts = [{
name: "Samsumg",
description: "The best phone"
},{
name: "iPhone",
description: "The other best phone"
}];
var viewModel = (function (){
var products = ko.observableArray(initialProducts),
displayMode = ko.observable(view),
switchDisplayMode = function(item){
if (displayMode() == 'Result') {
displayMode({ name: "Details", data: item });
}
else {
displayMode({ name: "Result", data: item });
}
};
return {
products: products,
displayMode: displayMode,
switchDisplayMode: switchDisplayMode
};
})();
ko.applyBindings(viewModel);
I am trying to pass that product to the Details template, but have been unsuccessful. Any clues or tips would be helpful.
I am currently getting a "ReferenceError: products is not defined" error when I click on a link, but not sure how to fix it. Maybe if I fix that error, the switching views will behave as expected.
In your function to switch the template, you are forgetting that your displayMode observable is holding an object - not a string value.
So inside switchDisplayMode, displayMode() = { name: 'Result' }. Switching that to displayMode().name fixes the problem. See updated fiddle