Using binary data from Mongo collection as image source - node.js

I have an express app, storing data in mongo, using Jade as the view engine. I have a simple route that gets the docs in a particular collection, each doc corresponding to a product. The image is base64 encoded. When I try and render as an image though it doesn't work
My route is
exports.index = function(req, res){
mongo.getProducts(function(data) {
res.render('consumer/index', {user: req.session.user, products: data});
});
};
The function that calls is
exports.getProducts = function(callback) {
Product.find().exec(function(err, products){
return callback(products);
});
};
and then my Jade file has the following code
each val in products
img(src="data:image/png;base64,'+#{val.image.data}+'", alt='Image', style="width: 20px; height: 20px")
Looking at the doc directly in Mongo (via robomongo) I get this
I don't know what I'm missing, because in another file I use jQuery datatables to show the documents, and the same approach there correctly renders the image, here is a snippet of the datatables code
"aoColumns": [
{"mData": "name"},
{"mData": "price"},
{"mData": "category"},
{"mData": "description"},
{"mData": "image.data", "mRender": function ( data, type, full ) {
return '<img src="data:image/png;base64,'+data+'", style="width: 20px; height: 20px"></>'}},
{"mData": "promoted"},
{"mData": null}
]

The problem is val.image.data doesn't provide a base64 string but a buffer. So, you have to convert it first. This is how I made it work:
Product.findById('559f6e08b090ca5c5ce6942b', function(err, result) {
if (err) throw (err);
var thumb = new Buffer(result.image.data).toString('base64');
res.render('index', { title: 'Express', img: thumb});
});
Also, there's a small issue on your frontend jade code, it should be:
img(src="data:image/jpeg;base64,#{img}") //No + and ''
Note: You could get away with this for small thumbnails or such but it is not the recommended approach due to a number of reasons (such as the 16MB limit). You are much better off using GridFS. More at http://docs.mongodb.org/manual/core/gridfs

Related

Scrape background-images using X-Ray-Scraper

I've been using X-Ray to scrape website which has been working really well. I can use it bring in images very easily. The one item I run into is I don't see an easy way to scrape a background image. Say I have a div where they are setting a style attribute on that dev and then setting the URL im not sure how to get the background-image url from this. I don't think I can just pass the featured image attribute the css property such as
.featured-image.attr('background-image');
const getWebsiteContent = async (blogURL, selector) => {
try {
return await x(blogURL, selector, [{
slug: 'a#href',
featuredImage: 'img#src'
}])
.paginate(`${pagi}#href`)
.limit(200)
.then((response) => {
spinner.succeed('Got the data');
return response;
})
} catch (error) {
throw new Error('Cannot get Data from website, try checking your URL');
}
};
For anyone that wants a solution to this with X-ray scraper what I ended up doing is pulling the attribute from the selector you pass into the object.Given the html looks like the following.
<div class="img" style="background-image: url('../path-to-img.jpg')"></div>
Instead of writing .img#src you could write .img#style and this would return to you the style attribute. From there you would need to use a regex to remove the rest of the un-needed data that is not the URL of the image.

How to display an image with <img> from Mongoose using React front-end

Ultimate goal: have the user upload pictures (less than 16mb so no need to worry about Grid FS), have that picture stored in my database which is Mongodb through Mongoose, and display the picture on the screen using the attribute.
To upload files I use Multer and add it to the database as follows:
newItem.picture.data = Buffer(fs.readFileSync(req.file.path), 'base64');
newItem.picture.contentType = 'image/png';
And it seems to be successfully added to the mongodb. Looks something like this:
how the image appears on mongodb
I'm able to send a get request from my front-end and, when I console.log it, this is what I'm getting: Data after being retreived from database. The question now is, how can I add it to an attribute and show the image on the screen. Thanks!
Edit: question has been marked as too broad by the moderators. Fair enough, I wasn't too sure how to approach it. Since I was able to solve it, this is what my front-end looks like.
componentDidMount() {
const PATH = "http://localhost:8080/apii/items/getitems";
axios.get(PATH)
.then(res => {
let picture64Bit = res.data[0].data.data
picture64Bit = new Buffer(x, 'binary').toString('base64');
this.setState({picture: picture64Bit})
})
.catch(err => console.log(err))
}
The key here is that, 1) res.data[0].data.data is equal to that random list of numbers. I take that convert it back to base64, so it appears exactly as it did in the first picture above from mongodb. Then, displaying it inline in an img attribute is very easy:
<img src = {`data:image/png;base64,${this.state.picture}`} />
There are a couple libraries you could use, but I will arbitrarily select Axios for a demonstration. It sounds good if the images are already in Mongo DB.
Your objective is to get photos from the server to the client, so you need a function to get them on demand. You could also investigate fetch or request.
Axios: https://www.npmjs.com/package/axios
In React, try something like this
async getPhotos() {
const res = await Axios.get('/photos')
console.log('RESPONSE', res)
const photos = res.data
console.log('IMAGES', photos)
this.setState({ photos })
}
Here is a more complete example
import React, { Component } from 'react'
import Axios from 'axios'
class List extends Component {
constructor(props) { // super props allows props to be available
super(props) // inside the constructor
this.state = {
photos : [], // Initialize empty list to assert existence as Array type
// and because we will retrieve a list of jpegs
error: '', // Initialize empty error display
}
}
componentDidMount() {
this.getPhotos() // Do network calls in componentDidMount
}
async getPhotos() {
try {
const res = await Axios.get('/photos')
console.log('RESPONSE', res)
const photos = res.data
console.log('IMAGES', photos)
this.setState({ photos, error: '' })
} catch (e) {
this.setState({ error: `BRUTAL FAILURE: ${e}` })
}
}
render() {
if (error.length) {
return (
<div>{this.state.error}</div>
)
}
if (!photos.length) {
return (
<div>No photos yet</div>
)
}
// Assuming shape { id: 0, caption: 'Cats again', src: 'http://www.com/win.jpg' }
// Make sure to include key prop when using map (for state management)
return (
<ul>
{this.state.photos.map(photo => (
<li key={photo.id} style={{ position: 'relative' }}>
<span>{photo.caption}</span>
<img src={photo.src}
<div
className="overlay"
style={{
position: 'absolute'
width: '100%',
height: '100%',
}}
/>
</li>
))}
</ul>
)
}
}
Citation: In React.js should I make my initial network request in componentWillMount or componentDidMount?
If you want to fetch one more photo after, you should try to think immutably and replace the this.state.photos Array with a duplicate of itself plus the new image pushed onto the end of the array. We will use the spread operator for this to do a shallow copy on the existing photos Array. This will allow React to diff against the two states and efficiently update for the new entry.
const res = await Axios.get('/photo?id=1337')
const photo = res.data
this.setState({
photos: [...photos, photo]
})
Note: the secret trick is to avoid ever doing this.state.photos.push(photo). You must place an illegal sign on setting state like that.
In React, try to consider a way you can get an Object or Array. Once you have it in your mind, throw it into a Component's state. As you progress into Redux, you will end up storing items sometimes in the Redux store. That is too complex and unnecessary to describe now. The photos would be available perhaps as this.props.photos via the Redux Connect Function.
For most other times, a Component's state field is an excellent place to store anything of interest to a Component.
You can imagine it like a holder at the top of the Component.

How to return the corresponding image by id in a helper with CollectionFS?

I had real problems writing the title of this question. I am using gridfs for my storage, so the files are stored in my MongoDb. Now I have the following situation:
var imageStore = new FS.Store.GridFS('images', {});
Images = new FS.Collection('images', {
stores: [imageStore]
});
Test = new Meteor.Collection('test');
The Images are stored in Images and the "posts" are stored in test. Now when I upload an image, as I can't reference the image in my Test collection, I save the ._id for that image.
FS.Utility.eachFile(event, function (file)
{
var image = Images.insert(file, function (err, fileObj)
{
});
Test.insert({name: 'Test!', userId: '123123', image: image._id});
});
Now in my template, I want to show the name and the image. But what do I return in my helper? My template for a single post looks like this:
<li>
<h2>{{name}}</h2>
<img src="{{file.url}}" alt="" class="thumbnail" />
</li>
And the Helper function:
Tests: function ()
{
return each-test-with-their-image;
}
I imagined something like this, but it looks stupid as hell:
Tests: function ()
{
var tests = Test.find().fetch();
for(var i = 0; i < tests.length; i++)
{
tests[i].file = Images.findOne({_id: tests[i].image});
}
return tests;
}
But I feel that there must be a better way.
Rather than iterate over an array of results you can just get the url in a helper as each document is displayed. This is cleaner and you don't have to recompute the whole array as docs are added/removed/changed.
html:
<li>
<h2>{{name}}</h2>
<img src="{{url}}" alt="" class="thumbnail" />
</li>
js:
Template.myTemplate.helpers({
url: function(){
var queryImage = Images.findOne({_id: this.image});
return queryImage && queryImage.url();
}
});

Image tag flickers then disappears - backbonejs

I am experiencing a strange behavior with backbone view,
On my view template, the image tag flickers and then disappears immediately when the view is rendered, no image is then displayed
this is my first backbonejs/nodejs application and I have spent quite some time trying to debug this,I hope I am clear enough, thanks.
Here is my code:
//The Backbone view
define(['text!templates/profile.html'],function(profileTemplate){
var profileView=Backbone.View.extend({
el:$('#content'),
initialize:function(){
this.model.bind('change',this.render,this);
},
viewTemplate: _.template(profileTemplate),
render:function(){
this.model.fetch();
this.$el.html(this.viewTemplate(this.model.toJSON()));
}
});
return profileView;
});
//HTML TEMPLATE (profile.html)
<img src="uploads/<%= photo%>" alt="image" />
//SCHEMA
var AccountSchema=new mongoose.Schema({
email:{type:String,unique:true},
password:{type:String},
name:{
first:{type:String},
last:{type:String},
},
photo:{type:String},
});
Directory Structure
/ParentDirectory
/Public
/templates
profile.html //this is the template being rendered
/uploads //contains images
//ROUTER
define(['views/profile'],function(ProfileView){
var router=Backbone.Router.extend({
currentView:null,
routes:{
"profile/:id":"profile"
},
//Calls render method on views
changeView:function(view){
if(null !=this.currentView){
this.currentView.undelegateEvents();
}
this.currentView=view;
this.currentView.render();
},
profile:function(id){
var model=new Account({id:id});
this.changeView(new ProfileView({model:model}));
},
return new router();
});
There is some issue here on the code:
initialize:function(){
this.model.bind('change',this.render,this);
},
render:function(){
this.model.fetch();
this.$el.html(this.viewTemplate(this.model.toJSON()));
}
You are fetching the model inside render function (which just feels wrong) the model get updated and trigger a change event that calls render which starts the same loop all over again.

nodejs handeling a select form element

I have a (probably very stupid) question.
I have a form I submit to my nodeJS server with Express.
This works perfectly with text inputs and radiobuttons, but now I have to add a select.
The server does not give an error but the select is not parsed properly.
my code:
<select id="chooselang">
<option value="nl" name="language">NL</option>
<option value="en" name="language">EN</option>
</select>
and my server looks like this:
app.post('/settings', function(req, res){
// Fill JSON array with new settings
var myData = {
,name : req.body.name
,mail : req.body.email
,language : req.body.language
,location: req.body.location
}
// Write to JSON file
fs.writeFile(configfilepath, JSON.stringify(myData, null, 4), function(err) {
if(err) {
res.send(500);
console.log(err);
} else {
setTimeout(function () {
res.redirect('back');
}, 2000)
}
});
});
Could someone please tell me what I'm doing wrong?
Probably you will need to add a name attribute in your select and use that to capture the values.
Also, only one of the option values will be sent to the server, so there is no point in assigning name to each one of the options, if that was your intention.

Resources