How to change image to next one in hbs handlebars, nodejs with multer - express-handlebars

I am building a classified ads website (like craigslist) with: handlebars (hbs), nodejs and multer for uploading images.
I have already created my own CRUD.
Users could post their ads for free, introducing their info for each ad:
user name
email
ad title
ad description
ad city
ad category
ad images (more than one if user needs)
I have a view, list.hbs, where ads show its information:
When users clicks on ad pictures, it will open like a modal box / pop up:
Everything perfect until here.
I save pictures in my ad.model, through multer like this:
image: {
imgName:{
type:String
},
imgPath:{
type:[String]
}
}
As you can see, I store path's pictures in an array to my mongodb database (managed by mongoose).
I.e:
"image" : {
"imgPath" : [
"uploads/e3c97fb10dd4c602054bedce194464b6",
"uploads/302fe7e147932d2b868a79b1799ba3f9"
]
}
The problem is here. I 've been trying to change image after clicking in each picture and it is impossible to do it.
I have tried via , but it looks like hbs doesn't work properly and don't read variables from handlebars:
I even tried passing through helper via onclick = myfunction({{#imageHelper}}{{/imageHelper}}), but it doesn't work...
Anybody knows how to handle this in handlebars? How to add js code to this view?
Note: my modal box / pop up is made it with pure html / css, no js or jquery in there.

I can't believe my mistake, how I didn't noticed before!
If you reach this question, or any question linked to handlebars - multer - javascript DOM, please, don't forget that template variables inside an onclick element MUST be quoted.
Mistake / Error
I passed through onclick element this:
<img onclick="myFun({{ad.reference}},{{ad.image.imgPath}})" class="imgAd" id="{{ad.reference}}" src="{{ad.image.imgPath.[0]}}"/>
And to manage the src and change images clicking everywhere on the image, I tried to do this:
...
<script>
let i = 0;
//next prev image
function myFun(ref,str){
const arr = str.split(',');
const len = arr.length;
if(i < len - 1){
i = i + 1;
document.getElementById(ref).src = arr[i]
}
else {
i = 0;
document.getElementById(ref).src = arr[i]
}
}
</script>
(This script above, just for change next picture, and so on)
Remember: if you pass variables like that, not quoted, js will throw errors, because it receives a "variable" like parameter, not a string.
In this case onclick received this:
uploads/b5e01da1707382ed915f314c0c77266c //variable, not string
Correct way:
<img onclick="myFun('{{ad.reference}}','{{ad.image.imgPath}}')" class="imgAd" id="{{ad.reference}}" display="block" src="{{ad.image.imgPath.[0]}}"/>
I passed a string, not a variable:
After I quoted each handlebar variable, I passed as string.
"uploads/b5e01da1707382ed915f314c0c77266c"
Now, the script will works. I get a string that I need to transform in an array to handle it:
<script>
let i = 0;
//next prev image
function myFun(ref,str){
const arr = str.split(',');
const len = arr.length;
if(i < len - 1){
i = i + 1;
document.getElementById(ref).src = arr[i]
}
else {
i = 0;
document.getElementById(ref).src = arr[i]
}
}
</script>
With this, I was able to manage DOM in each ad, changing images after clicking with nodejs, hbs and multer.

Related

How to Automatically Detects User's Location and Display in Wordpress Page

I have developed a Custom wordpress Theme and i want to detect the user's location and get inside dropdown?
To locate a users position you can use the HTML Geolocation API. Check out the documentation:
https://www.w3schools.com/html/html5_geolocation.asp
Like in the example of w3schools you only need something to trigger the javascript function. This can be your dropdown element, just give the attribute onclick="getLocation()" to it. Give the element where you want to output the location an ID, so you can put the result of the function in it.
Then you can put the following javascript in the footer.php file of your wordpress theme, or anywhere you think it fits your setting best.
Taken from: https://www.w3schools.com/html/tryit.asp?filename=tryhtml5_geolocation
<script>
var x = document.getElementById("your_dropwdown_id");
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
x.innerHTML = "Geolocation is not supported by this browser.";
}
}
function showPosition(position) {
x.innerHTML = "Latitude: " + position.coords.latitude +
"<br>Longitude: " + position.coords.longitude;
}
</script>
So on click of the element with the onclick="getLocation()" attribute, the getLocation() function is fired. The function gets the browser location data and outputs the result inside of the element with the id your_dropdown_id.
Hope this helps!

How do I create a paragraph break in Google Form help text?

I've looked on Google's product forums, and I can't find anything. The help text field is designed for brief text, but I want to insert a mulit-paragraph article. Without paragraph breaks, I wind up with a bunch of text that's difficult to read.
This has been bugging me for a long time and I've came up with a not so elegant but efficient solution based on Apps Script. Pavel Agarkov had the same idea! My version also works with multiple occurences and can be re-run if Google Forms removes the line breaks when you edit the text.
When editing a form, open the Script Editor from the main menu.
Create a new script, replace the content with the code below. Save it and return to your form.
Reload the page. You will notice a new option in the main menu, looking like this
That "Scripts" menu was added by our script. Don't use it for now, it won't do much.
When editing content, use fours spaces as a placeholder for line breaks.
Run the script from the Scripts menu. Now celebrate 👯‍♀️
Some things worth noting:
You will get a permission request the first time you run the script. It's ok, read the message and do what you have to do.
Once the line breaks are there, Google Forms, god bless its heart, will remove them every time you edit the field. Mildly infuriating. Just run the script again.
The script you need to use is:
// From https://stackoverflow.com/questions/22207368/
function onOpen() {
var ui = FormApp.getUi();
ui.createMenu('Scripts')
.addItem('Replace 4+ spaces with line breaks in Title and Description', 'addLineBreaks')
.addToUi();
}
function addLineBreaks() {
var theForm = FormApp.getActiveForm();
var theQuestions = theForm.getItems();
var thePlaceholder = new RegExp(/\s{4,99}|\n/, 'gi');
for (i = 0; i < theQuestions.length; i++) {
var theText = theQuestions[i].getHelpText();
if (theText.search(thePlaceholder) > 0 ) {
theQuestions[i].setHelpText(theText.replace(thePlaceholder,' \n'));
}
theText = theQuestions[i].getTitle();
if (theText.search(thePlaceholder) > 0 ) {
theQuestions[i].setTitle(theText.replace(thePlaceholder,' \n'));
}
}
}
I found that you can't do it through the editor but it is possible via the script.
Go to main menu -> script editor;
past the following code to the editor;
function addLineBreaks()
{
var form = FormApp.getActiveForm();
// find form items you need
var questions = form.getItems(FormApp.ItemType.MULTIPLE_CHOICE);
for(i = 0; i < questions.length; i++)
{
var title = questions[i].getTitle();
// if no replacement has been done yet
if(title.indexOf("\n") < 0)
{
// this will add line break after <double space> like in markdown
questions[i].setTitle(title.replace(" ", " \n"));
}
}
}
then set up trigger to start this method on form open.
I struggled with this question myself for too long!
However, when you know how its simple:
Go to "Add Item"
Choose "Section Header"
This option allows you to put paragraphed text into your Form.
As of June, 2018, the following work (but only the second option is documented):
Just put new lines in the description and it will be shown in the form - try using two for a paragraph.
If you want a bit more style - add a 'Title and Description' - see the second option in the tool bar showing 'Tᴛ'. The Title will always add extra space (even if it's empty) and will show any title as inverted, larger, text. You can disable the description if you just want a 'heading' followed by questions.
None of the above solutions worked for me, SO I added a unicode character https://www.compart.com/en/unicode/U+2002 pasted 4 to 5 times and this is how it looks
Sorry for the bad news, but this seems impossible to me.
I found the answer! While in the box into which you are entering text, go to Properties in the Developer tab. You will get a drop-down menu. At the bottom of the menu is "Plain Test Properties" with a check box for "Allow carriage returns (multiple paragraphs).
This is a better solution but based on the above. It allows you to edit the form which amazingly the above solutions don't:
// Version 2020-10-07a: by Dennis Bareis
// Handles "{nl}" in form & question descriptions
// Forms API: https://developers.google.com/apps-script/reference/forms
// Based on https://stackoverflow.com/questions/22207368/
// This code #: https://stackoverflow.com/a/64216993/3972414
// [0] ... -> Script Editor -> Create New Script
// [1] Paste into script editor
// [2] Run onOpen()
// [3] On first run authorise script
// [4] This adds 2 scripts under a new button in the edit form UI
// (to the left of the "Send" button)
// [5] Use "START" before re-editing form
// [6] Use "END" to publish the changes
// 5&6 required as otherwise you end up with "line1Line2Line3" etc
String.prototype.replaceAll = function(search, replacement)
{
var target = this;
return target.replace(new RegExp(search, 'g'), replacement);
};
//This doesn't perform the function on open, just adds it to the UI, you run when finished.
function onOpen()
{
var ui = FormApp.getUi();
ui.createMenu('Scripts')
.addItem('[1] Prepare for RE-EDITING this form (restore {nl})', 'editFormStart')
.addItem('[2] Publish the form (finished editing, remove {nl})', 'editFormEnd')
.addToUi();
}
function editFormStart()
{
swapLineBreaks("\n", "{nl}")
}
function editFormEnd()
{
swapLineBreaks("{nl}", "\n")
}
function swapLineBreaks(FromText, ToText)
{
var form = FormApp.getActiveForm();
// find form items you need
var oForm = FormApp.getActiveForm();
var questions = oForm.getItems();
// Fix the form's description
var formDesc = oForm.getDescription()
oForm.setDescription(formDesc.replaceAll(FromText, ToText))
// Work through each question
for(i = 0; i < questions.length; i++)
{
//var QTitle = questions[i].getTitle();
//questions[i].setTitle( QTitle.replaceAll(FromText, ToText));
var QText = questions[i].getHelpText();
questions[i].setHelpText(QText.replaceAll(FromText, ToText));
}
}

Excluding bootstrap from specific routes in Meteor

I was hoping anyone could give some input on this,
I'm creating a meteor app in which I would like to use bootstrap to creating the admin environment, but have the visitor facing side using custom css. When I add the bootstrap package to my app using meteor it's available on every page, is there a way to restrict the loading of bootstrap to routes that are in '/admin' ?
When you add bootstrap package it's not possible. You can, however, add bootstrap csses to public directory and then load them in a header subtemplate that will only be rendered when you're in the dashboard.
EDIT
But then how would you go about creating seperate head templates?
Easy:
<head>
...
{{> adminHeader}}
...
</head>
<template name="adminHeader">
{{#if adminPage}}
... // Put links to bootstrap here
{{/if}}
</template>
Template.adminHeader.adminPage = function() {
return Session.get('adminPage');
}
Meteor.router.add({
'/admin': function() {
Session.set('adminPage', true);
...
}
});
DISCLAIMER: I am unsure of a 'meteor way' to do this, so here is how I would do it with plain JS.
jQuery
$("link[href='bootstrap.css']").remove();
JS - Credit to javascriptkit
function removejscssfile(filename, filetype){
var targetelement=(filetype=="js")? "script" : (filetype=="css")? "link" : "none" //determine element type to create nodelist from
var targetattr=(filetype=="js")? "src" : (filetype=="css")? "href" : "none" //determine corresponding attribute to test for
var allsuspects=document.getElementsByTagName(targetelement)
for (var i=allsuspects.length; i>=0; i--){ //search backwards within nodelist for matching elements to remove
if (allsuspects[i] && allsuspects[i].getAttribute(targetattr)!=null && allsuspects[i].getAttribute(targetattr).indexOf(filename)!=-1)
allsuspects[i].parentNode.removeChild(allsuspects[i]) //remove element by calling parentNode.removeChild()
}
}
removejscssfile("bootstrap.css", "css")
However, doing that would complete remove it from the page. I am not sure whether meteor would then try to readd it when a user goes to another page. If that does not automatically get readded, then you have an issue of bootstrap not being included when someone goes from the admin section to the main site, which would break the look of the site.
The way I would get around that would be to disable and enable the stylesheets:
Meteor.autorun(function(){
if(Session.get('nobootstrap')){
$("link[href='bootstrap.css']").disabled = true;
}else{
$("link[href='bootstrap.css']").disabled = false;
}
});
There my be other bootstrap resources which may need to be removed, take a look at what your page is loading.
To use jQuery in the same way but for the javascript files, remember to change link to script and href to src
From my tests, Meteor does not automatically re-add the files once they have been removed so you would need to find some way of re-adding them dynamically if you want the same user to be able to go back and forth between the main site and the admin site. Or simply if the http referrer to the main site is from the admin, force reload the page and then the bootstrap resources will load and everything will look pretty.
P.s. make sure you get the href correct for the jQuery version
If somebody is interested in including any js/css files, I've written a helper for it:
if (Meteor.isClient) {
// dynamic js / css include helper from public folder
Handlebars.registerHelper("INCLUDE_FILES", function(files) {
if (files != undefined) {
var array = files.split(',');
array.forEach(function(entity){
var regex = /(?:\.([^.]+))?$/;
var extension = regex.exec(entity)[1];
if(extension == "js"){
$('head').append('<script src="' + entity + '" data-dynamicJsCss type="text/javascript" ></script>');
} else if (extension == "css"){
$('head').append('<link href="' + entity + '" data-dynamicJsCss type="text/css" rel="stylesheet" />');
};
});
}
});
Router.onStop(function(){
$("[data-dynamicJsCss]").remove();
});
}
Then simply use:
{{INCLUDE_FILES '/css/html5reset.css, /js/test.js'}}
in any of your loaded templates :)

How to print PDF or an image from a page

I have an ASP.NET app (it uses DevExpress v 10.2). There is a button called PRINT on a page. When the button is clicked the application should:
1. extract a file from its DB. The file is either PDF or JPEG (the application knows its type in runtime only)
2. TO PRINT OUT the file. Some ‘preview’ should be shown to user during this
The question is – how to implement this (the item ‘2’)? There is a well-known method to print out an image using JavaScript like the following:
function DisplayPrintPopup(html) {
var win = window.open('', 'popup', 'toolbar=no,menubar=no,width=500,height=500,scrollbars=yes');
self.focus();
win.document.open();
win.document.write('<head><style></style></head><body>' + html + '<style></style></head><body>');
win.document.close();
win.print();
win.close();
}
This could be Ok for me. But what to do when a file is PDF?
This just print an element from you page where strid=id of the element you want to print,
before the print is possible to view a preview:
function CallPrint(strid) {
var prtContent = document.getElementById(strid);
var WinPrint = window.open('', '', 'letf=0,top=0,width=800,height=600,toolbar=0,scrollbars=0,status=0');
WinPrint.document.write(prtContent.innerHTML);
WinPrint.document.close();
WinPrint.focus();
WinPrint.print();
WinPrint.close();
}
http://forums.asp.net/t/1034884.aspx/1

Reading documents CSS in Chrome Extension

I am trying to read the pages CSS using a chrome extension. This is what i have in my content script :
var allSheets = document.styleSheets;
for (var i = 0; i < allSheets.length; ++i) {
var sheet = allSheets[i];
var src = sheet.href;
var rules = sheet.cssRules || sheet.rules;
}
For some reason the rules are always empty. I do get all the CSS files used in the 'src' variable. But the rules always come as null.. Its working when I try it as a separate javascript on a HTML page. But fails when I put it up in the content script of my chrome extension. Can somebody lemme know why?
Well thats the Why, but for fun and interest (never done anything with style sheets before) I thought Id do a How....
manifest.json
{
"name": "Get all css rules in stylesheets",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js" : ["myscript.js"],
"run_at":"document_end"
}
],
"permissions": [
"tabs", "<all_urls>"
],
"version":"1.0"
}
myscript.js
// Create the div we use for communication
var comDiv = document.createElement('div');
comDiv.setAttribute("id", "myCustomEventDiv");
document.body.appendChild(comDiv);
// Utitlity function to insert some js into the page, execute it and then remove it
function exec(fn) {
var script = document.createElement('script');
script.setAttribute("type", "application/javascript");
script.textContent = '(' + fn + ')();';
document.body.appendChild(script); // run the script
document.body.removeChild(script); // clean up
}
// function that gets inserted into the page
// iterates through all style sheets and collects their rules
// then sticks them in the comDiv and dispatchs the event that the content script listens for
getCSS=function (){
var rules = '';
// Create the event that the content script listens for
var customEvent = document.createEvent('Event');
customEvent.initEvent('myCustomEvent', true, true);
var hiddenDiv = document.getElementById('myCustomEventDiv');
var rules ='';
var allSheets = document.styleSheets;
for (var i = 0; i < allSheets.length; ++i) {
var sheet = allSheets[i];
for (var z = 0; z <= sheet.cssRules.length-1; z++) {
rules = rules +'\n'+ sheet.cssRules[z].cssText;
}
}
hiddenDiv.innerText = rules;
hiddenDiv.dispatchEvent(customEvent);
}
// puts the rules back in the page in a style sheet that the content script can iterate through
// youd probably do most of this in the injected script normally and pass your results back through the comDiv....Im just having fun
document.getElementById('myCustomEventDiv').addEventListener('myCustomEvent', function() {
var eventData = document.getElementById('myCustomEventDiv').innerText;
document.getElementById('myCustomEventDiv').innerText='';
var style = document.createElement('style');
style.type = 'text/css';
style.innerText=eventData;
style = document.head.appendChild(style);
var sheet = document.styleSheets[document.styleSheets.length-1];
for (var z = 0; z <= sheet.cssRules.length-1; z++) {
console.log(sheet.cssRules[z].selectorText +' {\n');
for (var y = 0; y <= sheet.cssRules[z].style.length-1; y++) {
console.log(' '+sheet.cssRules[z].style[y] + ' : ' + sheet.cssRules[z].style.getPropertyValue(sheet.cssRules[z].style[y])+';\n');
};
console.log('}\n');
};
// Clean up
document.head.removeChild(style);
document.body.removeChild(document.getElementById('myCustomEventDiv'));
});
exec(getCSS);
In the case of this question Id prolly do most of the checks in the injected script and then pass the results back through the div and its event. But I wanted to see if I could use the dom methods in the content script to go through the css and this was the only way I could figure to do it. I dont like the idea of inserting the rules back into the page, but couldnt figure any other way of doing it.
Just a guess, but since chrome extensions are Javascript based, they may have cross domain issues. Chrome sets the rules and cssRules to null when programmatically trying to get a stylesheet from another domain.
For getting all external css and all internal css file, you can use devtools API. If you want to use it in chrome extension you need to hook devtool into you chrome extension. This code will work
chrome.devtools.panels.create(
'my chrome extension',
'icon.png',
'index.html',
function(panel) {
var initial_resources = {};
// collect our current resources
chrome.devtools.inspectedWindow.getResources(function(resources) {
for (var i = 0, c = resources.length; i < c; i++) {
if (resources[i].type == 'stylesheet') {
// use a self invoking function here to make sure the correct
// instance of `resource` is used in the callback
(function(resource) {
resource.getContent(function(content, encoding) {
initial_resources[resource.url] = content;
});
})(resources[i]);
}
}
});
}
);
Answer is late, but I think I can help. One method of accessing the cssRules of external sheets protected by CORs is to use Yahoo's YQL service. I've incorporated it into a developer tools extension for Chrome for capturing styles and markup for a page fragment. The extension is in the Chrome Web Store and is on Github.
Grab the source from Github and look at the content.js script to see how YQL is used. Basically, you'll make an AJAX call to YQL and it will fetch the CSS for you. You'll need to take the CSS content and either inject it into the page as an embedded style tag or parse the CSS using JavaScript (there are some libraries for that purpose). If you choose to inject them back into the document, make sure to set the new style blocks to disabled so that you don't screw up the rendering of the page.
The extension itself might be useful to you:

Resources