Writing EJS in JS script dynamically - javascript

i am trying to use Leaflet API and store my users saved points(markers,circles,polygons) into a MongoDB database.
Is there a more elegant and dynamic way of writing a JS script in a HTML page while getting the results from the database?
at the moment i am getting my data from Mongo and passing it through the Get request and using that on the HTML page, writing EJS inside the JS script. it works fine but im looking into a better solution.
many thanks for your time
// index.js
router.get('/index', ensureAuthenticated, (req, res) => {
var queryz = Points.find({ belongs_to: req.user._id })
queryz.exec(function (err, results) {
if (err) return handleError(err);
res.render('index', {
user: req.user,
points: results,
})
})
})
// index.ejs
// looping through the points from the database to dynamically add the points
// each time the map is called
<% if(typeof points != "undefined") { %> // making sure user is logged in
<% counter = 1 %>
<% if(points.length>0){ %>
<% points.forEach(p => {%>
<% if(p.type == "marker"){ %> // if point is a marker
marker<%=counter%> = L.marker([<%=p.coords%>], { icon: <%=p.icon%>, alt: '<%=p.popup_message%>' }).bindPopup('<%=p.popup_message%><form method="post" action="/delete_point" id="pointFORM"><input id="pointID" name="pointID" value="<%=p._id%>"><button id="submitBtn" type="submit">🚫</button></form> ').openPopup()
<% counter++%>
<% } %>
<% if(p.type == "circle"){ %> // if point is a circle
marker<%=counter%> = L.circle([<%=p.coords%>], { color: '<%=p.color%>', fillColor: '<%=p.fill_color%>', fillOpacity: <%=p.fill_opacity%>, radius: <%=p.radius%> }).bindPopup('<%=p.popup_message%>').openPopup()
<% counter++%>
<% } %>
<% if(p.type == "polygon"){ %> // if point is a polygon
marker<%=counter%> = L.polygon([<%=p.coords%>], ).bindPopup('<%=p.popup_message%>').openPopup()
<% counter++%>
<% } %>
<% });%>
<% } %>
<% } %>

I'm assuming the problem space here is that you want to dynamically fetch some data from your server and insert that into a web page using Javascript in the web page.
Given that, you have several options:
You can make an Ajax call to your server and have your server return fully formed HTML which your client-side Javascript can then insert directly into the page. That appears to be what you are illustrating already in your question.
You can make an Ajax call to your server and have your server return JSON which your client-side Javascript can then turn into HTML itself and insert that into the page. You can either manually code the conversion into HTML in Javascript or you can use client-side rendering with EJS to convert the data into HTML using a client-side EJS template.
You can reload the entire page and let the server render the whole new page, presumably including the latest data.
For incremental updates to a page, options 1 and 2 are generally preferable to option 3 and are usually more efficient for all.
The choice between 1 and 2 is really just an architectural preference. Some developers of high scale sites would rather offload as much processing to the client as possible and thus prefer letting the client do the rendering of the new HTML as in option 2, but that means you have to have both the EJS template and client-side EJS rendering in the web page which is more client-side weight. So, that's a tradeoff too.
There really aren't options that are simpler than these, so if what you have now (option 1) is working just fine you can stay with that.

Related

How to submit data then post on another page mongoDB/Node.js

I am needing to have 1 page of a website submit data to MongoDB then pull that same data and display it on a different page. I am using node.js, express, Mongoose, and MongoDB.
Currently, I have it so It gets submitted properly to the database, and I can see it with the correct layout, But I cannot seem to get it posted on the page.
How exactly do I go about doing this?
Can someone give a example of code of this?
I am really new to this stuff and still learning.
Thanks!
In the route of the page you want to load, use the Mongoose .find()
method.
You can use {} in the find() method to return all the data, or access
individual data based on the object key find({id:'value'}). Then when you
render the page, just pass in an object to the render, where the key is
what you access in the url page, in my example you would use
(mongs) to access the values within the url page (.ejs, etc). So in
your route definition file:
app.get('/', (req, res) => {
MongModel.find({}, (err, foundMongModel) => {
err ? console.log(err) : res.render('url/route', { mongs: foundMongModel });
});
});
Then if you're using .ejs file, you would need to use <%= %> to access
individual data, and <% %> to use a loop or something.
and use the mongs value. So if you imported all the data from the
database, you could loop through it using
<% mongs.forEach(mong =>{ %>
<div>mong.key<div>
<% }) %>
You can access the keys for each database object like above using
mong.key

Sharing Server Side data to a JS file

I've created a simple blog site, with posts, users and comments.
I'm using - MongoDB, NodeJS and Express with an EJS view.
I've encounterd a problem when I tried to create the comments sections.
I wanted to use JQuery with Ajax requests in order to make the comments section
so the page won't refresh when a user is posting a comment or edting one.
In order to get the comments for each post, I built an api route that look like this - www.domain.com/api/messages/:post_id
this url returns JSON that contains the comments for that post.
I didn't want to expose 'risky' data about the author of the comment so I fillterd the results with the Mongo query.
Now for the problem -
I want to add edit and delete buttons for each comment, that would show up only for the admin and the author of course.
When I use EJS its simple - I wrote something like this -
<% if (user !== null&& (JSON.stringify(user._id) === JSON.stringify(comments[i].author._id)
|| user.is_admin === true)) { %>
put the buttons here...
The thing is, when I'm using JQuery on my main scirpt file, I can't access the user (that's an object that was sent from Nodejs on the backend).
I'm not sure if I should 'send' this object to my js file from the EJS, I think it's not secure and not the right way.
And I can't also keep that EJS code I quoted a few lines ago because when the EJS file loads there are no comments on that page (the XHR request gets them and JQuery puts them on the page).
So I'm really not sure how to move on.
Edit - I think i have a solution: Maybe I should create an array of IDs on the EJS file that would contain the comments that need to have these buttons, and then I would send that array to the JS file somehow?
Thank you!
So I think I managed to overcome this problem, I'll post what I did so hopefully it will help someone in the future.
In the EJS file, I assigned an array that will hold the ID's of the comments that were published by the current user (The user was sent to the EJS file by the nodejd backend).
Then, I copied this array to a script tag variable in the EJS file, By doing that, I can now access it from the main.js file, and add the delete/edit buttons only to comments that have an ID that's in the array.
<% var arr = []; %>
<% for(var i = 0; i < comments.length ; i++){ %>
<% if (user !== null && (JSON.stringify(user._id) === JSON.stringify(comments[i].author._id) || user.is_admin === true)) { %>
<% arr.push(comments[i].comment_id); %> <% } %>
<% } %>
<script>
var exported_array = <%- JSON.stringify(arr) %>
</script>
Send the user and all comments to the main EJS file.
app.get('/', function (req, res) {
res.render('index', {
comments: [{ ... }, { ... }, { ... }],
user: { ... }
});
});
The comments can be rendered by including a EJS partial comment.ejs from the folder partials in the main EJS file.
<% comments.forEach(function(comment){ %>
<% include partials/comment %>
<% }) %>
In that partial you render the comment and buttons. The comment is past through to the include. The user already lives on the main EJS template.
<div class="comment-container" data-commentid="comment._id">
<p><% comment.content %></p>
<% if (user !== null&& (JSON.stringify(user._id) === JSON.stringify(comment.author._id) || user.is_admin === true)) { %>
<button class="button_delete" data-commentid="<% comments_id %>">delete</button>
<% } %>
</div>
Then create a route in your API like www.domain.com/api/messages/delete/:post_id.
app.get('/messages/delete/:post_id', function(req, res){
// logged on user lives on the server
// check if user may delete comments
// delete comment
// send result
res.send(true);
});
When you click the button use jQuery to send that id from the data attribute of the button to the server/api and from there I would Identify the user that's logged on. If the logged on user is authorized delete it and send back the result. If everything is okay use jQuery to delete the div containing the whole message.
$('.button_delete').click(function(){
// retrieve id from data attribute here
var commentId = $(this).data("commentid");
$.get('www.domain.com/api/messages/delete/' + commentId, {}, function(result){
if(result) {
// delete comment container from html using commentId or refresh
}
});
});

Rails efficient way to store JavaScript object to Model?

I am using the FileStack API and the file picker gem (https://github.com/Ink/filepicker-rails). I have an Attachment model that has a :title as a string. When a file is uploaded, the URL from the FilePicker API is stored as the :title. But the gem has a onchange method that returns an event variable as a JSON object that contains attributes of the file. I use JavaScript to access those attributes but I want to find a way in Rails to store those attributes, accessed via JavaScript, in a Model so that I can access it through the rest of the Rails app.
<%= filepicker_js_include_tag %>
<%= simple_form_for(#attachment) do |f| %>
<%= f.filepicker_field :title, onchange: 'onUpload(event)' %>
<%= f.submit %>
<% end %>
<script>
function onUpload(event) {
console.log(event);
var name = event.fpfile.filename;
console.log(name);
}
</script>
Update:
So after looking into your solution and googling around I am using ajax to send the data via routes to the controller. Below is my updated Javascript as well as the route and controller. When I render and inspect the #foo instance variable it is nil. So my data isn't getting passed properly. Furthermore, this whole process from the firing of the Javascript function to displaying the index view is now very very slow. I think I have the right idea after viewing your solution and doing more digging but I'm missing something and/or overcomplicating this. Any advice would be much appreciated.
<script>
function onUpload(event) {
var name = event.fpfile.filename;
jQuery.ajax({
data : { data_value: event },
type: 'post',
url: "/attachment/index"
});
}
</script>
Route
post 'attachments/' => 'attachment#index'
Controller
def index
#attachments = Attachment.all
#foo = params[:data_value]
end
View (returns nil)
<%= raise #foo.inspect %>
If you're using Postgres 9.3 or above you should consider using the hstore module and creating a JSON column. In a migration you can do:
add_column :your_model, :your_attribute, :json
And then you can just update YourModel.your_attribute => {'your': 'JSON here'}
Docs here: http://edgeguides.rubyonrails.org/active_record_postgresql.html#json
If you're using MySQL it's tricky, but doable. You have to create a text column and save the JSON as a string, and parse it every time you interact with it. Postgres is definitely better at handling JSON. I realize that this answer relies on an assumption, so if you're not using one of the two data stores mentioned, let me know and I'll pull it down.

Javascript variables in ruby code

In my RoR project I got html page, where I changing div's background image with javascript. Javascript function send me index, and I want to use this index for getting element of ruby array.
Look into my code
function drawNewProject (index){
console.log(<%= 'index' %>)
<% index = 'index' %>
<% #existProjects = Admin::Project.order('weight') %>
<% #existProject = #existProjects[index] %>
var image = document.getElementById('block_one')
image.style.backgroundImage="url('<%= #existProject.image(:large) %>')";
}
But this line
<% #existProject = #existProjects[index] %>
Gives me error
no implicit conversion of String into Integer
Do you know how to do it correct? Thnx.
You have to think that the <%= erb %> block is executed in the server before being sent to the browser, and once there, the javascript is run.
Your browser has no idea about ruby or php or whatever... it just receives the html and the js (regardless is a static file, or a dynamically generated bunch of js) and runs it.
That means that all the data must be known at rendering time. If your application depends on dynamic data, or on your user interaction, then you have to do an ajax request and deliver back a call with the right js to be run by the browser.

Rails- Pass information from view to Javascript

I want to start using websockets. I read all the docs and understand everything, but it leaves out something I need: How to pass information from view to JS?
I need to pass data from javascript to my controller. What I do not understand is, how do I get dynamically generated data in my view to the javascript to be sent?
Right now my view receives an instance variable on every HTTP request, it loops over every instance variable and makes a button which submits a hash with information extracted from that instance variable. I do not understand how to do the same thing with Javascript because Javascript will not understand Ruby classes.
This is what my code looks like now:
View/dashboards/_other_characters.html.erb
<% other_characters.each do |other_character| %>
<p><%= other_character.name %> is standing here (<%= other_character.power_level %>)</p>
<%= button_to "punch #{other_character.name}",
attacks_path(
target_type: other_character.class,
attack_type: :punch,
target_id: other_character,
target_name: other_character.name
) %>
<% end %>
This is what I would like to be able to do using JS
var task = {
name: 'Start taking advantage of WebSockets',
completed: false
}
var dispatcher = new WebSocketRails('localhost:3000/websocket');
dispatcher.trigger('tasks.create', task);
Try
<%= button_to "punch #{other_character.name}",
attacks_path(
target_type: other_character.class,
attack_type: :punch,
target_id: other_character,
target_name: other_character.name
), {id: '***', data: {name: '***', other_key: 'other_value'} } %>
Then you can access the data via jQuery data api.
When you need get dynamic data from view by client js, add data-attrs in your view dom then read it from dom API or other 3rd party js API.
In order to pass information from ruby to javascript you can use this gem: Gon, basically it transforms ruby variables and make them available to javascript on each view, take a look:
http://railscasts.com/episodes/324-passing-data-to-javascript
for any other alternative to achieve your purpose visit: https://www.ruby-toolbox.com/categories/javascript_tools#paloma

Categories