Related
I am trying to display embedded videos with ActionText on Rails 6, both in the WYSIWYG Trix, and in the rendered content. But the ActionText renderer filters all raw html code and forces me to use JS to display the iframes in the rendered content, which doesnt work in Trix.
I followed the instructions given here by one of Basecamp's dev : https://github.com/rails/actiontext/issues/37#issuecomment-451627370. Step 1 through 3 work, but when ActionText renders my partial it filters the iframe.
The form creating the WYSIYWG
= form_for(article, url: url, method: method) do |a|
= a.label :content
= a.rich_text_area :content, data: { controller: "articles", target: "articles.field", embeds_path: editorial_publication_embeds_path(#publication, format: :json) }
= a.submit submit_text, class:"btn full"
The Stimulus controller adding the embed functionality (in dire need of a refactor)
import { Controller } from "stimulus";
import Trix from "trix";
$.ajaxSetup({
headers: {
"X-CSRF-Token": $('meta[name="csrf-token"]').attr("content"),
},
});
export default class extends Controller {
static targets = ["field"];
connect() {
this.editor = this.fieldTarget.editor;
const buttonHTML =
'<button type="button" class="trix-button" data-trix-attribute="embed" data-trix-action="embed" title="Embed" tabindex="-1">Media</button>';
const buttonGroup = this.fieldTarget.toolbarElement.querySelector(
".trix-button-group--block-tools"
);
const dialogHml = `<div class="trix-dialog trix-dialog--link" data-trix-dialog="embed" data-trix-dialog-attribute="embed">
<div class="trix-dialog__link-fields">
<input type="text" name="embed" class="trix-input trix-input--dialog" placeholder="Paste your video or sound url" aria-label="embed code" required="" data-trix-input="" disabled="disabled">
<div class="trix-button-group">
<input type="button" class="trix-button trix-button--dialog" data-trix-custom="add-embed" value="Add">
</div>
</div>
</div>`;
const dialogGroup = this.fieldTarget.toolbarElement.querySelector(
".trix-dialogs"
);
buttonGroup.insertAdjacentHTML("beforeend", buttonHTML);
dialogGroup.insertAdjacentHTML("beforeend", dialogHml);
document
.querySelector('[data-trix-action="embed"]')
.addEventListener("click", event => {
const dialog = document.querySelector('[data-trix-dialog="embed"]');
const embedInput = document.querySelector('[name="embed"]');
if (event.target.classList.contains("trix-active")) {
event.target.classList.remove("trix-active");
dialog.classList.remove("trix-active");
delete dialog.dataset.trixActive;
embedInput.setAttribute("disabled", "disabled");
} else {
event.target.classList.add("trix-active");
dialog.classList.add("trix-active");
dialog.dataset.trixActive = "";
embedInput.removeAttribute("disabled");
embedInput.focus();
}
});
document
.querySelector('[data-trix-custom="add-embed"]')
.addEventListener("click", event => {
const content = document.querySelector('[name="embed"]').value;
if (content) {
$.ajax({
method: "POST",
url: document.querySelector("[data-embeds-path]").dataset
.embedsPath,
data: {
embed: {
content,
},
},
success: ({ content, sgid }) => {
const attachment = new Trix.Attachment({
content,
sgid,
});
this.editor.insertAttachment(attachment);
this.editor.insertLineBreak();
},
});
}
});
}
}
The Embed model
class Embed < ApplicationRecord
include ActionText::Attachable
validates :content, presence: true
after_validation :fetch_oembed_data
def to_partial_path
"editorial/embeds/embed"
end
def fetch_oembed_data
url =
case content
when /youtube/
"https://www.youtube.com/oembed?url=#{content}&format=json"
when /soundcloud/
"https://soundcloud.com/oembed?url=#{content}&format=json"
when /twitter/
"https://publish.twitter.com/oembed?url=#{content}"
end
res = RestClient.get url
json = JSON.parse(res.body, object_class: OpenStruct)
self.height = json.height
self.author_url = json.author_url
self.thumbnail_url = json.thumbnail_url
self.width = json.width
self.author_name = json.author_name
self.thumbnail_height = json.thumbnail_height
self.title = json.title
self.version = json.version
self.provider_url = json.provider_url
self.thumbnail_width = json.thumbnail_width
self.embed_type = json.type
self.provider_name = json.provider_name
self.html = json.html
end
end
The controller creating the Embed
def create
#embed = Embed.create!(params.require(:embed).permit(:content))
respond_to do |format|
format.json
end
end
The jbuilder view responding to the ajax call to create the Embed
json.extract! #embed, :content
json.sgid #embed.attachable_sgid
json.content render(partial: "editorial/embeds/embed", locals: { embed: #embed }, formats: [:html])
The Embed HTML partial (slim)
.youtube-embed.embed
.content
= image_tag(embed.thumbnail_url) if embed.thumbnail_url.present?
p = "Embed from #{embed.provider_name} (#{embed.content})"
p.embed-html = embed.html
And finally the JS code displaying the iframes when the Article's content with Embeds inside is displayed
$(document).ready(() => {
$(".embed").each(function(i, embed) {
const $embed = $(embed);
const p = $embed
.find(".content")
.replaceWith($embed.find(".embed-html").text());
});
});
If I change the Embed partial to
== embed.html
It displays properly in the WYSIWYG but not in the rendered view.
You need to add iframe to allowed_tags, add the following code in application.rb:
config.to_prepare do
ActionText::ContentHelper.allowed_tags << "iframe"
end
It looks like you need to whitelist the script that generates the iframe.
A quick test you can do is on the show page add the relevant JS for the content providers (I was testing Instagram attachments, so added <script async src="//www.instagram.com/embed.js"></script>).
It would be unwise to whitelist all <script> tags in ActionText views, but you can manage the script loading yourself.
I couldn't test your complex example, but if you want to put ActionText's HTML, it would be helpful.
Please try this:
<%= raw your_action_text_object.to_plain_text %>
I basically want to create an alert when clicking on a button, withour reloading the entire page.
Into my view :
:javascript
function ajax_test1(field)
{
var xmlhttp;
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp = new XMLHttpRequest();
}
else
{// code for IE6, IE5
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.open("GET", "test_ajax.xml?code=" + field.value, false);
xmlhttp.send();
xmlDoc=xmlhttp.responseXML;
alert(xmlDoc.getElementsByTagName("message")[0].childNodes[0]);
}
and
= button_to 'test ajax', :remote => true, :onclick => "ajax_test1(this);"
Into my controller
def test_ajax
#test = {message:'Hello there!'}
render :xml => #test
end
When I click on the button, the page is reloading with the following URL:
http://localhost:4242/test?onclick=ajax_test1%28this%29%3B&remote=true
How can I fix this please?
The onclick event needs to return false in order to halt the default behaviour – the actual click – from taking place.
If you were using jQuery, the .preventDefault() method is a nicer way of preventing the default event behaviour – but with your hand rolled javascript you'll have to be a bit messier:
= button_to 'test ajax', :remote => true, :onclick => "ajax_test1(this); return false;"
I changed this :
= button_to 'test ajax', :remote => true, :onclick => "ajax_test1(this);"
into this:
= button_to_function 'test ajax', 'ajax_test1(this)'
and it worked!
I'm trying to add an effect to my rails project using ajax. There's 4 divs on my page:
<div header></div>
<div menu></div>
<div content></div>
<div footer></div>
When a link in the menu div is clicked, the footer should slide up, and the new page text should appear in the content div. The code works fine when I have all the files in my public folder, in the rails project, but when I put files in the View folders, it doesn't work. Any idea why?
I have a scripts.js file in my assets/javascripts folder. The code is:
var ajax_loaded = (function(response) {
$("#content").slideUp(
"fast",
function() {
$("#content")
.html(response)
.slideDown("fast");
$("#content .ajax").on("click",ajax_load);
$("#content form").on("submit",form_submit);
});
});
var form_submit = (function(e) {
e.preventDefault();
var url = $(this).attr("action");
var method = $(this).attr("method");
var data = {}
$(this).find("input, textarea").each(function(i){
var name = $(this).attr("name");
var value = $(this).val();
data[name] =value;
});
$.ajax({
"url": url,
"type": method,
"data": data,
"success": function () {
history.pop();
$(history.pop()).trigger("click");
},
"error": function () {alert("bad");}
});
});
var history = [];
var current_url_method;
var ajax_load = (function(e) {
e.preventDefault();
history.push(this);
var url =$(this).attr("href");
var method = $(this).attr("data-method");
if (current_url_method != url + method) {
current_url_method = url + method;
$.ajax({
"url": url,
"type": method,
"success": ajax_loaded,
});
}
});
$("#menu a").on("click",ajax_load);
$("#menu a.main").trigger("click");
I have a style.css.scss in my assets/stylesheets folder. The code is:
#header {
background: #333333;
height:50px;
}
#menu {
background: #ffffff;
}
#content { background: #eeeeee;height:100px;}
#footer {
background: #333333;
color: #ffffff;
height: 100px;
}
In my routes folder I have:
Books::Application.routes.draw do
get "about_us", :to => "static_pages#about_us"
get "contact_us", :to => "static_pages#contact_us"
get "invite_us", :to => "static_pages#invite"
get "faq", :to => "static_pages#faq"
root :to => 'books#index'
Index method in the Books controller is:
def index
render :layout => "application"
end
In my Views/layouts, in application.html.erb, I have (this change a bit from my public folder, where links were of the form About Us):
<!DOCTYPE html>
<html>
<head>
<title>Books</title>
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
<body>
<div id = "header">My header</div>
<div id = "menu">
<%= link_to "About Us",about_us_path,:class => "main" %>
<%= link_to "FAQ", faq_path, :class => "main" %>
<%= link_to "Invite Others", invite_us_path, :class => "main" %>
<%= link_to "Contact Us", contact_us_path, :class => "main" %>
</div>
<div id = "content">
<%= yield %>
</div>
<div id = "footer">My footer</div>
<script src = "http://code.jquery.com/jquery-1.9.1.min.js">
</script>
</body>
</html>
Anyway, if you have any ideas on what's wrong, I'd be grateful, thanks. Like I said, it wokrs in the public folder, so I think my scripts.js is ok. Could it be something to do with the ':class => "main" ' part?
You may want to make sure scripts.js is being served when you load the page. I see that you include application.js in your layout file. This file is used as a manifest file in the Rails asset pipeline, so make sure it's configured to include scripts.js when it loads. It will need to have a line like this:
//= require scripts
In a browser, assuming you are running a rails server on port 3000, you can make sure your scripts file is accessible by visiting http://localhost:3000/assets/scripts.js. You can make sure it's included in application.js by visiting http://localhost:3000/assets/application.js.
More information on the asset pipeline: http://guides.rubyonrails.org/asset_pipeline.html
Hope this helps.
got it sorted. I wrapped all the code in my scripts.js in a
$(document).on("ready", function(){......});
That was the main thing.
Also, I was calling the jquery library twice, which probably didn't help - //= require jquery, in my application.js.erb file, and also from my application.html.erb.
Anyway, the case is resolved! Thanks for everything.
I'm trying to update the content of "mydiv" without refreshing the entire index page.
#mydata is given by mycontroller. I need to recalculate it every n seconds and pass it to "mydiv"
With "link_to" it works!
index.html.erb
<%=
link_to('refresh', '/mycontroller/index', :remote => true)
%>
<div id="mydiv">
<%=
#mydata
%>
</div>
index.js.erb
$('#mydiv').html('<%= escape_javascript(#mydata) %>')
Now I need to refresh the content of "mydiv" automatically every n seconds (so without click on the link). I have tried solutions from:
First Link
Second Link
but no luck.
In my application.js I have writed this:
function executeQuery() {
$.ajax({
//url: '/index',
success: function(data) {
$('#mydiv').html(data)
}
});
setTimeout(executeQuery, 500);
}
$(document).ready(function() {
setTimeout(executeQuery, 500);
});
For who is facing my same problem, I solved it by replacing
$('#mydiv').html(data)
with
$('#mydiv').load('/mycontroller/index #mydiv')
Use setInterval() instead of using setTimeout().
Ref: https://www.w3schools.com/jsref/met_win_setinterval.asp
function executeQuery() {
$.ajax({
type: 'GET',
url: 'example.com/url/', // Provide your response URL here.
success: function(data) {
$('#mydiv').html(data);
}
});
}
setInterval(executeQuery(), (n * 1000)); // Replace 'n' with how many seconds you want.
This code will run the executeQuery() method in every 'n' seconds interval. So that your requirement is accomplished.
Set layout to false in the action and just pass on the relevent content, not the entire page
def action1
<your code here>
end
def action2
<your code here>
render :layout => false
end
Your view for action2 should have content pertaining only to #mydiv.
A better solution would be to use a single action and change render options based on type of request. (Ajax or non ajax)
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}];