How do you pass props or data to the component when using the
javascript_pack_tag tag?
currently
/application.html.erb
<%= javascript_pack_tag 'flash_messages', data: {messages: flash_messages} %>
<%= javascript_pack_tag 'CropImages' %>
<%= content_tag :div,
id: "appointments_data",
data: #product.to_json,
front_image: url_for(#product.front_image),
back_image: url_for(#product.back_image) do %>
<% end %>
react component :
document.addEventListener('DOMContentLoaded', () => {
const node = document.getElementById('appointments_data')
const data = JSON.parse(node.getAttribute('data'))
const frontImage = node.getAttribute('front_image')
const backImage = node.getAttribute('back_image')
ReactDOM.render(
<CropImages data={data} front_image={frontImage} back_image={backImage} />,
document.body.appendChild(document.createElement('div')),
)
})
It could be done in several ways.
1.you could by pass props "by hand"
document.addEventListener('DOMContentLoaded', () => {
const node = document.getElementById('flash_messages_data')
const data = JSON.parse(node.getAttribute('data'))
ReactDOM.render(
<FlashMessages messages={data} />,
document.body.appendChild(document.createElement('div')),
)
})
Or you could you gem, which basically does almost the same:
https://github.com/renchap/webpacker-react
in you view
<%= react_component('FlashMessages', { messages: [] }) %>
<%= javascript_pack_tag 'components/flash_messages' %>
and in your component
import React, { Component } from 'react'
import WebpackerReact from 'webpacker-react'
class FlashMessages extends Component {
...
render() {}
}
WebpackerReact.setup({FlashMessages}) // ES6 shorthand for {Hello: Hello}
last line basically parses data attribute generated by the react_component helper, and passes to your component.
Related
I am dealing with a rails project where I am trying to upload multiple images and have them previewed right after image selection. I am using stimulus, ruby on rails, js6
The HTML is a form used to create a new product - I am also using cloudinary - to upload multiple pictures and simpleformfor
app/views/products/_form.html.erb
<%= simple_form_for product do |m| %>
<div class="d-flex justify-content-between" data-controller="upload">
<%= m.input :photos, as: :file, input_html: { multiple: true, class: 'hidden', id: 'photo-input',
data: {action: 'change->upload#displayPreview'} },
label_html: { class: 'upload-photo'}, label: ':camera: Upload a photo' %>
<% if #product.photos.attached? %>
<% #product.photos.each do |photo| %>
<%= cl_image_tag photo.key, height: 100, width: 200, crop: :fill, data: { target: 'upload.image'} %>
<% end %>
<% end %>
</div>
<%= m.button :submit, class: 'btn-primary' %>
<% end %>
the HTML form code for 1 image is as follow
<div data-controller="upload">
<label class="file optional upload-photo" for="photo-input">Upload photo</label>
<input class="form-control-file file optional hidden" id="photo-input" data-action="change->upload#displayPreview" type="file" name="product[photos][]">
<%= cl_image_tag "", height: 100, width: 200, crop: :fill, data: { 'upload-target': 'image', 'upload-index-value': 0 } %>
</div>
</div>
The code for 1 image is as follow and working fine if the form was just for 1 image, but how would i transform the following code to accommodate for multiple image uploading?
I tried several things using for loops and this.imageTargets.forEach((element) => {.. and even indexes but at no avail..
javascript/controllers/upload_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
static targets = ['image']
displayPreview(event) {
const input = event.target
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = (event) => {
this.imageTarget.src = event.currentTarget.result;
}
reader.readAsDataURL(input.files[0])
this.imageTarget.classList.remove('hidden');
}
}
}
I appreciate your comments and alternative solutions..
For file fields with multiple images, you need to use the for loop in your JS which will detect the input files length. For your reference you can update your js as like.
image_controller.js
import { Controller } from "#hotwired/stimulus"
export default class extends Controller {
static targets = ["input"]
preview() {
var input = this.inputTarget
var files = input.files
var imgLoc = document.getElementById("Img")
for (var i = 0; i < files.length; i++) {
let reader = new FileReader()
reader.onload = function() {
let image = document.createElement("img")
imgLoc.appendChild(image)
image.style.height = '100px'
image.src = reader.result
}
reader.readAsDataURL(files[i])
}
}
}
As you are displaying the output by replacing the preview.png, for this you don't need the image preview. This will create images.
_form.html.erb
<div class="mb-3" data-controller="image" id = "Img">
<%=f.label :images, class: "form-label" %>
<%= f.file_field :images, multiple: true, class: "form-control", accept: "image/png, image/jpeg, image/jpg", "data-image-target": "input", "data-action": "image#preview" %>
</div>
I having an issue with the Redux store in getting react_on_rails where I want to switch the page and it doesn't work correctly for me. I am losing the redux store if I change the page or reload it.
Also, I am sorry for a pile of code I put, but I really don't know anything else or ways to fix it, the official docs for react_on_rails and redux did not help me and it more likely confuse me how it should work. if anyone has a clue how to make it work, in advance, thank you
rootStore.js
import { combineReducers, applyMiddleware, createStore } from 'redux';
import middleware from 'redux-thunk';
import reducers from '../reducers';
export default (props, railsContext) => {
// eslint-disable-next-line no-param-reassign
delete props.prerender;
const combinedReducer = combineReducers(reducers);
const newProps = { ...props, railsContext };
return applyMiddleware(middleware)(createStore)(combinedReducer, newProps);
};
reducers/index.js
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import cartReducer from './cartReducer';
const rootReducer = combineReducers({
routing: routerReducer,
cart: cartReducer,
});
export default rootReducer;
the component that I am trying to do magic with
// #flow
import * as React from 'react';
import { connect } from 'react-redux';
import { addToCart} from '../actions/setCart';
import styles from './AddToCart.module.scss';
type Props = {
productId: number,
}
type State = {}
class AddToCart extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
}
onClick = () => {
console.log(this.props.productId);
this.props.addToCart(this.props.productId);
};
render() {
console.log('this.props.cart', this.props.cart);
return <button
onClick={this.onClick}
className={styles.button}
>Add To Cart</button>;
}
}
const AddToCartRedux = connect(
state => ({
cart: state.cart,
}),
dispatch => ({
addToCart: id => {
dispatch(addToCart(id));
},
}),
)(AddToCart);
export default AddToCartRedux;
my entry point
import ReactOnRails from 'react-on-rails';
import ProductDetailPage from '../pages/ProductDetailPage';
import rootStore from '../store/rootStore';
ReactOnRails.setOptions({
traceTurbolinks: true,
});
ReactOnRails.register({ProductDetailPage});
ReactOnRails.registerStore({rootStore});
my layout application
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= notice %>
<%= alert %>
<%= redux_store('rootStore', props: {}) %>
<%= react_component('ProductDetailPage', props: {product: #product.id}) %>
<%= yield %>
<%= redux_store_hydration_data %>
</body>
</html>
Since the Redux store is initialized when you load a page, navigating to a new page or reloading the page will cause all your JS assets to reload and re-initialize, causing your store to reset.
If you want to change the browser's location without a page reload, you would need to use something like react-router to handle navigation between React components instead of going through a full request/response cycle with the Rails server.
I have a react componente like this
var Task = React.createClass({
render: function () {
return (
<div className="task" id={ this.props.task.uid }>
<div className="header">
<span>task #{ this.props.task.uid }</span>
</div>
</div>
)
}
});
and when a task is created, I add this task to the task list inside an create.js.erb
<% if #task.errors.any? %>
$("#error-alert").removeClass('hidden')
<% else %>
$('#task-modal').modal('hide')
$('#tasks-list').prepend(React.renderToString(Task({ task: '<%= #task.to_react %>' })))
<% end %>
turns out that when the task component is prepended, he is prepended empty (just a box, without any text).
I tried <%= #task.to_react.to_json.html_safe %> and also had no success
My Task#to_react method:
def to_react
{
role: role,
need: need,
result: result,
uid: uid
}
end
Not that familiar with rails but unless it's doing some magic here you're embedding the object in a string.
Try
task: <%= ... %>
Instead of:
task: '<%= ... %>'
I would like to use the gmaps4rails gem to display a map of items in a fancybox.
I followed carefully the remarks on the wiki concerning ajax call, i.e. scripts have to be included manually in the application layout, maps have to be loaded in a javascript (see gem wiki).
But I still not succeed completely to make the map displayed in the box.
On the other hand as I hard code coordinates in the javascript it works fine, the map is displayed in the fancybox and the markers appear.
Let me recap.
In my index view, I have a ajax call to the items index action:
<%= link_to "Show Map", items_path(:format => :js, :show_map => true), :remote => true, :class => 'fancybox' %>
In the controller, I populate the map data:
def index
#items=Item.all
if params[:show_map]
#map= #items.to_gmaps4rails
end
end
in the index.js.erb file, I put
<% if params[:show_map] %>
var content = "<%= escape_javascript( gmaps({:last_map => false})) %>";
$.fancybox({
'content': content,
'padding' : 20
});
Gmaps.map = new Gmaps4RailsGoogle();
Gmaps.load_map = function() {
Gmaps.map.initialize();
Gmaps.map.markers = <%= #map %>;
Gmaps.map.create_markers();
Gmaps.map.adjustMapToBounds();
Gmaps.map.callback();
};
Gmaps.loadMaps();
<% else %>
// blablabla
<% end %>
Where the markers are provided in the map object.
This does not work and instead of my map I got in the fancybox the code itself appearing.
Something like:
var content = "\n
\n
<\/div>\n<\/div>\n"; $.fancybox({ 'content': content, 'padding' : 20 }); Gmaps.map = new Gmaps4RailsGoogle(); Gmaps.load_map = function() {Gmaps.map.initialize();
//Gmaps.map.markers = [{"lat":50.294,"lng":5.857},{"lat":50.294,"lng":5.857},{"lat":50.548,"lng":4.918},{"lat":50.384,"lng":3.649},{"lat":50.384,"lng":3.649},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.054,"lng":5.195}];
Gmaps.map.markers = [{"lat":50.8483059,"lng":4.351783999999999},{"lat":50.496,"lng":5.066},{"lat":50.11,"lng":5.003},{"lat":50.11,"lng":5.003},{"lat":50.162,"lng":5.871},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.08,"lng":4.5760000000000005}];
Gmaps.map.create_markers(); Gmaps.map.adjustMapToBounds(); Gmaps.map.callback(); }; Gmaps.loadMaps();
When instead of the erb <%= #map %>, I hard code the markers, for instance:
Gmaps.map.markers = [{"lat":50.294,"lng":5.857},"lat":50.294,"lng":5.857},{"lat":50.548,"lng":4.918}];
It works!
Seems like I'm missing something in the json data type conversion. But I'm not expert to find what is going wrong.
Thanks for your help!
Just successfully tried:
Open
<div id="test" style="display:none;width:300px;">
<%= gmaps markers: { data: #json } , last_map: false %>
</div>
<script type="text/javascript">
$(".fancybox").fancybox({
openEffect : 'none',
closeEffect : 'none',
afterLoad : function() { Gmaps.loadMaps(); }
});
</script>
Ok, I've got what was not going well. Thanks to the following answer https://stackoverflow.com/a/12219016/1100674.
As I use the following syntax:
Gmaps.map.markers = <%= #map %>;
I get the json rendered as this:
Gmaps.map.markers = [{"lat":50.8483059,"lng":4.351783999999999},{"lat":50.11,"lng":5.003},{"lat":50.11,"lng":5.003},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.413,"lng":4.371}];
Whereas I use the raw() method,
Gmaps.map.markers = <%= raw(#map) %>;
I get the correct format.
Gmaps.map.markers = [{"lat":50.8483059,"lng":4.351783999999999},{"lat":50.11,"lng":5.003},{"lat":50.11,"lng":5.003},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.413,"lng":4.371}];
I'm using Rails 3.0.9 version, and jquery.
I've been using this gem without a database. It is used only for map display, and display KML file on it. For this I used:
<div id='ajax_map'>
<% #kmlurl="http://mysite/file1.kml" %>
<%= gmaps( :kml => { :data => "[{ url: #{#kmlurl.inspect}}]" } ) %>
</div>
All great shows.
I want to do that after you change the links (# kmlurl), and click on the button, the map updated with this new KML file. I use a separate action js.erb with the following code:
$('#ajax_map').html('<%= #kmlurl="http://mysite/file2.kml" %>'+'<br />'+'<%= gmaps( :kml => { :data => "[{ url: #{#kmlurl.inspect}}]" } ) %>');
But he does not update the DIV. "js.erb" rendered normally, without using the method of gmaps () it normally returns # kmlurl. I tested this same code in the ". Html.erb" in the tags , it loads a new file, but, of course, just when the page loads.
How can I solve this problem?
Solved the problem as follows (in js.erb):
$('#ajax_map').html('<%= escape_javascript( gmaps({:last_map => false}) ) %>');
Gmaps.map = new Gmaps4RailsGoogle();
Gmaps.load_map = function() {
Gmaps.map.map_options.maxZoom = 15;
Gmaps.map.initialize();
Gmaps.map.kml = [{ url: '<%= "#{#kmlurl}" %>'}];
Gmaps.map.create_kml();
Gmaps.map.adjustMapToBounds();
Gmaps.map.callback();
};
Gmaps.loadMaps();
First I would refactor things just a bit.
Say that first bit of code were in your index page. I'd move the setting of #kmlurl into the corresponding controller action:
def index
#kmlurl = "http://mysite/file1.kml"
end
Then (assuming index?) your index view would be simply:
<div id="ajax_map">
<%= gmaps( :kml => { :data => "[{ url: #{#kmlurl}}]" } ) %>
</div>
Then to add a link that will update the map:
<%= link_to 'Other Map', '/othermap', :remote=>true %>
Now you'd create a route in routes.rb:
match '/othermap' => 'foo#othermap'
Then in foo_controller.rb:
def othermap
#kmlurl = "http://mysite/file2.kml"
end
Then create othermap.js.erb:
$('#ajax_map').html(
'<%=
escape_javascript(
gmaps( :kml => { :data => "[{ url: #{#kmlurl}}]" } )
)
%>'
)
That's a quick fix, but what I would REALLY do is strive to make your view code as simple as possible, and do all the real work in the controller. Ideally your view would just be:
<div id="ajax_map">
<%= gmaps( :kml => { :data => #mapdata } ) %>
</div>
set up #mapdata as appropriate in your controller. You've got too much stuff that really belongs in a controller embedded in your view code! Your othermap.js.erb should be equally simplified. i.e.
$('#ajax_map').html('<%= escape_javascript(gmaps( :kml => { :data => #mapdata } ))%>')