ruby on rails vue and html.slim - ruby-on-rails

I've started a ruby on rails application. In this one, we can found a search bar. Besides, the result is return by json format. And the view is updated by vue.
In a first time my controller
def index
if params[:search].nil?
render 'index'
else
#organisations = Organisation.search(params[:search])
respond_to do |format|
format.json do
render json: {
organisations: #organisations
}, status: :ok
end
end
end
end
in a second times we check the result by a js file and here i integrate the vue
if (Object.keys(event.detail[0].organisations)[0] != undefined){
console.log ("results")
console.log (" here we display the result json " + event.detail[0].organisations )
document.querySelector('#results').classList.add("d-inline")
var app = new Vue({
el: '#app',
data: {
return{
search_results: event.detail[0].organisations
}
}
})
}else{
console.log ("not results")
document.querySelector('#no-result').classList.add("d-inline")
}
})
And the last file is the view where we indicate the results in vue. This view file is a .html.slim file.
.container.w-100.text-center
#app
.all_details
| {{ search_results }}
ul.list-group.list-group-flush
li.list-group-item v-for=("search_result in search_results")
.row
.col.mt-2
| {{ search_result.id }}
.col.mt-2
| {{ search_result.name }}
.col.mt-2
button#ok.btn.btn-primary type="button"
So, when i search an element in a first time, the search bar is working properly and this one generate the json expected.The result display is clear and vue displayed the json and the different elements. But when i use a second time the search bar the result keep the same and is not updated.
I've verify the search_results: event.detail[0].organisations is properly updated. But at the level on the view .html.slim file we didn't update the front page.
I've try to force the rerender of the view but without success.
To add an information, when i search different element (first time and second time we didn't have error in the server and the console of browser)
I would like know if something is wrong in my process, do you have already seen this issue ? or do you have an idea of the issue presented.
Thanks for your help.

You are initialising Vue on each ajax:success events, which works only the first time because Vue is hooking into the page and therefore deletes the template, and its ID, which makes Vue throwing an error on the next calls stating that the template couldn't be found.
Instead, initialise Vue on page loading, and then use Vue.set(object, key, value) after the ajax:success in order to update the Vue's data search_results attribute.

Related

Correct way to use coffee script in rails5

I am developing my first rails application and trying to show/hide a particular web element in my view by clicking an another link in my page. Below is my view,
%p
= link_to 'Show additional details', "#", id: "secondary-link"
%table{:id => "secondary"}
%tr
%th Key
%th Value
where "secondary-link" is the link which shows / hides my table. When the page is loaded first, the table is made to hide by the following line in my application.css.scss
#secondary{
display: none;
}
Also, i have added this following script in my assets/javascripts/application.coffee
#myFunction = (variable) ->
$(document).on "page:change", ->
$('#secondary-link').click ->
$('#secondary').toggle()
But, the table is not shown when i click the link in my page. However, in my console if i enter "myFunction" i am getting the function
ƒ (variable) {
return $(document).on("page:change", function() {
return $('#secondary-link').click(function() {
return $('#secondary').toggle();
});
});
}
and i am getting an error when i enter myFunction()
Uncaught TypeError: Cannot read property 'on' of undefined
at myFunction (application.self-4e74630fd8894bca1e22a8d66c1d7ebfaa39edf4f40e058aae03fc788a9f6d94.js?body=1:3)
at <anonymous>:1:1
Your help is appreciated.
I am not sure why this issue but i fixed it by using jquery directly in my application.js file and deleted the coffee file. My js file has the below coding to handle the event.
$(document).ready(function() {
$('#secondary-link').click(function(event){
return $('#secondary').toggle();
});
});
I have stopped and restarted the server and this worked.

Rails React Component Renders But Not Visible on Page

I just added the react-rails gem to my project and tried to add a react component to my index.html.erb file. I used the react component generator to make a simple message/text element, but it does not show up on my view after I deploy to Heroku, but it seems to render when I inspect the page. I have no idea why this isn't working.
Please look at the images I have attached. It looks like the javascript doesn't get compiled??
Body inspection part 2
index.html.erb code
<h2>Dashboard</h2>
<%= react_component('Message', text: 'hello') %>
<h3>Products TEST REACT </h3>
Message.jsx code
var React = require("react")
class Message extends React.Component {
render () {
return (
<div>
<div>Text: {this.props.text}</div>
</div>
);
}
}
Message.propTypes = {
text: React.PropTypes.string
};
module.exports = Message
HTML body inspection
I reverted to the git commit before installing react-rails and used the shakacode react-on-rails gem instead and got it to work

passing backbone collection to view

I'm just starting out with backbone / grails and i've been struggling to figure out how to get everything to work.
I'm building a pricing configurator where a user selects a product type from radio group A and radio group B containing the quantity / pricing / discount data will do an ajax call to the backend for updated pricing data. I do not want to expose my pricing algorithm to the front end, so I was thinking I would use backbone to handle my ajax request / template.
I do not want to fully rely on js to create my UI, so on the initial page load, I'll build the gsp view with grails. Only problem I've noticed was my gsp view was being replaced by my handlebars template on initial page load. I guess this is fine, except it does two identical queries which isn't optimal.
Anyhow my code that does not seem to be working.
<script id="priceTemplate" type="text/x-handlebars-template">
<tr>
<td><input type="radio" value="" name="quantity">{{quantity}}</td>
<td class="price"><span>{{price}}</span></td>
<td class="discount"><span>{{discount}}</span></td>
</tr>
</script>
<asset:javascript src="bb_product/config.js"/>
<script>
var prices = new models.PriceList([],{productId:${productInstance.id}});
var priceView = new PriceView({collection: prices});
prices.fetch();
</script>
Models
var models = {};
models.PriceModel = Backbone.Model.extend({
//Is the model automatically populated from the collections json response?
})
models.PriceList = Backbone.Collection.extend({
initialize: function(models, options) {
this.productId = options.productId;
},
model: models.PriceModel,
url: function() {
return '../product/pricing/' + this.productId + '.json'
}
});
View
var PriceView = Backbone.View.extend({
el: '#product-quantities',
template: Handlebars.compile($("#priceTemplate").html()),
initialize: function(){
this.render();
},
render: function() {
console.log('collection ' + this.collection.toJSON()) //comes back empty
this.$el.html( this.template(this.collection.toJSON()));
}
});
json returned from url
[{"id":1,"quantity":10,"price":"10","discount":"10"},{"id":2,"quantity":50,"price":"20","discount"
:"10"}]
To initially get this up and working, what am I missing to display all items in the json object?
I've also see this code around, not sure what it does this.listenTo(this.collection, 'reset', this.render);
The reason you don't see any items is that the items aren't actually in the collection until after the view is rendered. Look at these two lines of code:
var priceView = new PriceView({collection: prices});
prices.fetch();
The first line renders the view (since you're calling render from within initialize). However, at that time, the prices collection is empty. Then, the second line fetches the data from the server and loads it into the collection; but by that time, the view has been rendered.
That last line of code you posted is the key to fixing this:
this.listenTo(this.collection, 'reset', this.render);
Usually, you'll put this inside the initialize function in your view class. What this does is "listen" to the collection instance, and when the reset event occurs, it will call the this.render function. (Of course, the method this.listenTo can "listen" to other objects for other events; see more details in the Backbone documentation).
If you add that line to the view's initialize function, the view will re-render whenever a "reset" event happens on the collection.
HOWEVER, by default, the "reset" event happens when all the models in the collection are replaced with another set of models, and this doesn't happen by default when you call a collection's fetch method (instead, the collection will try to "smart-update"). To force a reset of the collection when using fetch, pass {reset: true} as a parameter:
prices.fetch({reset: true});

Rendering dynamic scss-files with ajax, rails

As the title suggests, my main objective is to render a dynamic scss(.erb) file after an ajax call.
assets/javascripts/header.js
// onChange of a checkbox, a database boolean field should be toggled via AJAX
$( document ).ready(function() {
$('input[class=collection_cb]').change(function() {
// get the id of the item
var collection_id = $(this).parent().attr("data-collection-id");
// show a loading animation
$("#coll-loading").removeClass("vhidden");
// AJAX call
$.ajax({
type : 'PUT',
url : "/collections/" + collection_id + "/toggle",
success : function() {
// removal of loading animation, a bit delayed, as it would be too fast otherwise
setTimeout(function() {
$("#coll_loading").addClass("vhidden");
}, 300);
},
});
});
});
controller/collections_controller.rb
def toggle
# safety measure to check if the user changes his collection
if current_user.id == Collection.find(params[:id]).user_id
collection = Collection.find(params[:id])
# toggle the collection
collection.toggle! :auto_add_item
else
# redirect the user to error page, alert page
end
render :nothing => true
end
All worked very smooth when I solely toggled the database object.
Now I wanted to add some extra spices and change the CSS of my 50+ li's accordingly to the currently selected collections of the user.
My desired CSS looks like this, it checks li elements if they belong to the collections and give them a border color if so.
ul#list > li[data-collections~='8'][data-collections~='2']
{
border-color: #ff2900;
}
I added this to my controller to generate the []-conditions:
def toggle
# .
# .
# toggle function
# return the currently selected collection ids in the [data-collections]-format
#active_collections = ""
c_ids = current_user.collections.where(:auto_add_item => true).pluck('collections.id')
if c_ids.size != 0
c_ids.each { |id| #active_collections += "[data-collections~='#{id}']" }
end
# this is what gets retrieved
# #active_collections => [data-collections~='8'][data-collections~='2']
end
now I need a way to put those brackets in a scss file that gets generated dynamically.
I tried adding:
respond_to do |format|
format.css
end
to my controller, having the file views/collections/toggle.css.erb
ul#list<%= raw active_collections %> > li<%= raw active_collections %> {
border-color: #ff2900;
}
It didn't work, another way was rendering the css file from my controller, and then passing it to a view as described by Manuel Meurer
Did I mess up with the file names? Like using css instead of scss? Do you have any ideas how I should proceed?
Thanks for your help!
Why dynamic CSS? - reasoning
I know that this should normally happen by adding classes via JavaScript. My reasoning to why I need a dynamic css is that when the user decides to change the selected collections, he does this very concentrated. Something like 4 calls in 3 seconds, then a 5 minutes pause, then 5 calls in 4 seconds. The JavaScript would simply take too long to loop through the 50+ li's after every call.
UPDATE
As it turns out, JavaScript was very fast at handling my "long" list... Thanks y'all for pointing out the errors in my thinking!
In my opinion, the problem you've got isn't to do with CSS; it's to do with how your system works
CSS is loaded static (from the http request), which means when the page is rendered, it will not update if you change the CSS files on the server
JS is client side and is designed to interact with rendered HTML elements (through the DOM). This means that JS by its nature is dynamic, and is why we can use it with technologies like Ajax to change parts of the page
Here's where I think your problem comes in...
Your JS call is not reloading the page, which means the CSS stays static. There is currently no way to reload the CSS and have them render without refreshing (sending an HTTP request). This means that any updating you do with JS will have to include per-loaded CSS
As per the comments to your OP, you should really look at updating the classes of your list elements. If you use something like this it should work instantaneously:
$('li').addClass('new');
Hope this helps?
If I understood your feature correctly, actually all you need can be realized by JavaScript simply, no need for any hack.
Let me organize your feature at first
Given an user visiting the page
When he checks a checkbox
He will see a loading sign which implies this is an interaction with server
When the loading sign stopped
He will see the row(or 'li") he checked has a border which implies his action has been accepted by server
Then comes the solution. For readability I will simplify your loading sign code into named functions instead of real code.
$(document).ready(function() {
$('input[class=collection_cb]').change(function() {
// Use a variable to store parent of current scope for using later
var $parent = $(this).parent();
// get the id of the item
var collection_id = $parent.attr("data-collection-id");
show_loading_sign();
// AJAX call
$.ajax({
type : 'PUT',
url : "/collections/" + collection_id + "/toggle",
success : function() {
// This is the effect you need.
$parent.addClass('green_color_border');
},
error: function() {
$parent.addClass('red_color_border');
},
complete: function() {
close_loading_sign(); /*Close the sign no matter success or error*/
}
});
});
});
Let me know if my understanding of feature is correct and if this could solve the problem.
What if, when the user toggles a collection selection, you use jquery change one class on the ul and then define static styles based on that?
For example, your original markup might be:
ul#list.no_selection
li.collection8.collection2
li.collection1
And your css would have, statically:
ul.collection1 li.collection1,
ul.collection2 li.collection2,
...
ul.collection8 li.collection8 {
border-color: #ff2900;
}
So by default, there wouldn't be a border. But if the user selects collection 8, your jquery would do:
$('ul#list').addClass('collection8')
and voila, border around the li that's in collection8-- without looping over all the lis in javascript and without loading a stylesheet dynamically.
What do you think, would this work in your case?

How to show images after uploading with Plupload without page refreshing?

Plupload/paperclip/rails 3.1.4/ruby 1.9.3
i successfully upload multiple images with Plupload, and as usual i've put in 'images/create.js.erb' code, which appends images to page.
nothing happens. even primitive alert has no visual response, but i hear how my hard disk grunts (actually i made endless cycle of alerts to hear that something happens inside :)
images_controller
def create
#image=Image.create(:photo => params[:file])
respond_to :js
end
views/images/create.js.erb
$('body').append('<%= escape_javascript(image_tag(#image.photo.url(:medium))) %>');
after each image creating, it must appear on page.
i found the answer.
views/images/create.js.erb
<%=#image.id %>:<%= #image.photo.url(:medium) %>
views/items/_form.js.erb
<script>
...
uploader.bind('FileUploaded', function (up, file, info) {
var response = info['response'].split(':');
var image_id= response[0];
var image_url= response[1];
...
use it as you want, my lord!..
...
});
...
</script>

Resources