Turbo_stream work from controller, but not from view - hotwire-rails

I'm working on a rails 7 app with hotwire.
I have a form modal, when subimited, turbo should remove html from modal and show flash message.
When i put my turbo code in my controller i works:
# emails_controller.rb
def forward
service = Email::Forward.new(
recipients: params[:recipients].split(','),
sender: current_user.email,
html: params[:html_to_forward]
).call
if service.errors.empty?
flash.now[:success] = t('.success')
else
flash.now[:error] = t('.error')
end
respond_to do |format|
format.turbo_stream do
render turbo_stream: [
turbo_stream.update('forward_modal', ''),
turbo_stream.update('flash', partial: 'shared/flash', locals: { flash: flash })
]
end
end
But when i try to put the same code in a forward.turbo_stream.slim view, it do nothing, and i have no error message.
# views/emails/forward.turbo_stream.rb
= turbo_stream.update('forward_modal', '')
= turbo_stream.update('flash', partial: 'shared/flash', locals: { flash: flash })
Any idea about what is happening ?

Maybe a typo... but your code references the file as "views/emails/forward.turbo_stream.rb", but should probably be "views/emails/forward.turbo_stream.slim"? Or perhaps you have both files and have the good code only in the bad file? I've made similar mistakes with "action.html.slim" vs. "action.turbo_stream.slim". Taking a while to get used to the new naming convention.

Related

R Shiny - Trigger a shinyalert popup directly from the UI using javascript

With the following piece of code I'm able to trigger a pure javascript alert by clicking on the question-mark of the fileInput:
fileInput('peptides',
span("Peptides file ",
id="peptidesSpan",
tags$a(
tags$i(class='fa fa-question-circle'),
href = "#",
onclick = "alert('Oops!'); return false;")
),
multiple=FALSE,
accept = c('text/csv','text/comma-separated-values',
)
)
I was wondering if I could trigger a shinyalert popup (https://github.com/daattali/shinyalert/) instead of a simple javascript alert directly form the UI without any observer in the server side.
Something like:
shinyalert("Oops!", "Something went wrong.", type = "error")
If there is not a workaround to do that, any other suggestion with an observer would be welcome as well.
I think using an observer is not at all inconvenient.
Instead of alert(), invoke Shiny.setInputValue(id, value);, and then on your server side you can observeEvent(input[id], { shinyalert() }).
Read this article for details: https://shiny.rstudio.com/articles/communicating-with-js.html
You only need to use one observe code block to achieve this.
An example
Define a customized function in your UI Javascript code and call it in your onclick.
You can put this function say in helper.js in the 'www' folder in your project folder, that will be www/helper.js. Include this file in your Shiny UI code by tags$head(tags$script(src = "helper.js"))
function showAlert(message, type = "info") {
Shiny.setInputValue(
'alertMessage',
{
message: message,
type: type
},
{ priority: 'event' }
);
}
Then on the Shiny server side define the observer once
observeEvent(input$alertMessage, {
alertData <- input$alertMessage
req(alertData)
shinyalert("title", alertData$message, type = alertData$type)
})
It's one of the few times that I answer my own post, but after searching a bit more on stack-overflow I found a workaround inspired by this post.
I downloaded the sweetAlert.js file of the Sweet Alert library directly from here
I create a www folder in the root of my Shiny application
I added the sweetAlert.js file in the www directory and in the dashboardBody() of the ui.R I added the following line:
tags$head(tags$script(src="sweetAlert.js"))
Now I'm able to call directly the Swal.fire function with any argument as I would normally do in any other framework which runs javascript.

This block contains invalid or unexpected content on Custom HTML

I am editing a document in draft mode Wordpress 5.2.2 in the Gutenberg editor, and add this Custom HTML block:
<pre><code class="language-typescript">const simple = &ltT&gt(cl: T) => cl;
class Hero {
constructor(public position: [number, number]) {}
}
interface { hello: number }
const errorOne = &ltT&gt(cl: T) => new cl(); // Cannot use 'new' with an expression whose type lacks a call or construct signature.</code></pre>
and it happily works as expected in preview. I save as draft.
When I return the HTML is ghosted and I get the error in the title. I can convert to HTML and it works again, but then it errors again when I return to it later.
It seems this error is talked about everywhere but the explanations are nonsense and resolve nothing.
If my Custom HTML is valid (which it seems to be), why does it work and then give an error. How do I fix this?
I think the main issue is not converting < & > properly in your code. They are missing the semicolon at the end of the string.
This code is working fine:
<pre><code class="language-typescript">const simple = <T>(cl: T) => cl;
class Hero {
constructor(public position: [number, number]) {}
}
interface { hello: number }
const errorOne = <T>(cl: T) => new cl(); // Cannot use 'new' with an expression whose type lacks a call or construct signature.</code></pre>
When you insert the code with missing semicolon, WordPress saved as is. However, when trying the load the page again WordPress compares the saved content (with missing characters) to the one generated from the block (which is probably attempting to display correct HTML). This process led to an error as both texts were not identical.
If you want to check the error yourself you can check the console through developer tool in your browser (F12 in Chrome).

Manually use precompiled handlebars templates

How to manually use the precompiled handlebars.js templates?
Let's say, we have
source = "<p>Hello, my name is {{name}}</p>"
data = { name: "Joe" }
Currently, I have
template = Handlebars.compile(source)
render: -> template(data)
The source is coming from the database, and in order to cut down on the compilation time, I want to use a compilation step, precompiling the template server side with Handlebars.precompile(source) and then using something like:
template = precompiled_template
render: -> precompiled_template(data)
The precompiled_template is a string with function definition, so that doesn't work.
Also, I've found that Hanlebars.compile(source)() == Handlebars.precompile(source), but after browsing the source codes of handlebars, it's compilers and runtime, I'm still not sure how to achieve this.
if you did not find the right question till now, the answer is pretty simple.
Handlebars comes with a C pre-compiler on the command line, if you can access your shell you can simple just compile your templates each separated or merge them together into one file.
you can install Handlebars via npm / or build it on your system.
on the shell you can access the help file
$> Handlebars [ENTER]
You will see a help file like >
- f --output Output File etc etc ..
- m --min Minimize Output
$> Handlebars mysupertemplate.handlebars -f compiled.js -m ("-m" if
you want to minify the js file)
To run Handlebars.compile in the browser is a huge loss in performance, so it's worth a try to precompile on the server before sending the file to the browser.
To register Handlebars templates in your browser you have to load them like this:
var obj = {"foo":"bar"}
var template = require("./mytemplate-file.js") // require.js example
template = template(Handlebars) // Pass Handlebars Only if the template goes mad asking for his Father
var html = Handlebars.templates[template-name](obj)
For example if you have more then one template registered in the "templates-file" you will be able to access after the require call all templates by name using
var html = Handlebars.templates["templateName"]({"foo":"bar"});
You can go even further by register all the know helper within the file and / or making custom helpers for partials like so..
*// This will be the helper in you app.js file*
Handlebars.registerHelper("mypartials", function(partialName, data) {
var partial = Handlebars.registerPartial(partialName) || data.fn
Handlebars.partials[partialName] = partial
})
And in your template file you can put this...
{{#mypartial "divPartial"}}
<div id="block"><h2>{{foo}}</h2><p>{{bar}}</p></div>
{{/mypartial}}
{{#mypartial "formPartial"}}
<form id="foo"><input type="text" value="{{foo}}" name="{{bar}}"></form>
{{/mypartial}}
Now you can access this files by calling
var html = Handlebars.partials["divPartial"]({"foo":"bar","bar":"foo"})
var formHtml = Handlebars.partials["formPartial"]({"bar":"bar","foo":"foo"})
Hope this helped a bit ..
This speed test http://jsperf.com/handlebars-compile-vs-precompile/3 gave the answer.
Apparently, one solution is to eval() that resulting string and it will work.
The code is
var data = { name: "Greg" };
var source = "<p>Howdy, {{ name }}</p>";
eval("var templateFunction = " + Handlebars.precompile(source));
var template = Handlebars.template(templateFunction);
template(data);
=> "<p>Howdy, Greg</p>"
Of course one needs to be careful with eval and probably a better solution exists.

How to add variable to server-side binding

i have javascript code that looks like this:
$('#lnkPopup').click(function()
{
var id = $(this).attr('rel');
var msgCount = '<%= Model.ElementAt('+id+').MailCount %>';
});
<%= Model.ElementAt('+id+').MailCount %> doesn't work.
so how do i add a javascript variable to a serverside query this?
There are two possible ways to answer this question.
1) The short answer is you can't do exactly what you're attempting.
The first thing to understand is that the code between the server tags <% %> is only server side code.
The tag...
<%=
...means the server will generate the output and send the result to the client.
Javascript & jQuery code is client-side code only.
So your javascript / jQuery can not interact directly with the server side code. However, you can generate the client-side code from the server-side.
2) How do we get a value from the client-side code to the server-side?
There are a couple of approaches to this, and it will depend on the style you've chosen for your web application, and the problem at hand.
Post or Get to a URL - this could be
using performed using AJAX.
Generate the javascript / jQuery code
you need on the server so you don't
need to "post back".
var mailCountTable = {};
<% foreach (var id in Model.Ids) { %>
elementTable['<%= id $>'] = '<%= Model.ElementAt(id).MailCount %>';
<% } %>
$('#lnkPopup').click(function()
{
var id = $(this).attr('rel');
var msgCount = mailCountTable[id];
});
Alternatively you can get your mailCountTable using $.getJSON. Or, with lazy loading:
function getMailCount(id) {
if (mailCountTable.length == 0)
$.ajax({async: false, url: '/mailcounttable', format: 'json',
success: function(data) { mailCountTable = data; } });
return mailCountTable[id];
}
Basically, you don't. The server code will execute at the server, long before the javascript executes on teh client.
You would have to make an ajax call back to the server in order for server side code to be aware of javascript values.
Alternatively, you could write a javascript hash table with elements and mail counts using server side code. That function could then look up it's value in that hash table upon execution.
There are many ways to solve this, the key concept to understand is that when javascript is executing, your server code is done, so you better have all your data on the page, or call back to the server with ajax.

Read in a html page within asp.net mvc

i am porting over a website from asp.
i have one page that i can't figure out how to migrate it.
The page is dynamic in the sense that it reads in other html pages and sticks the content into the main "container" page. In the middle of the asp page it has sections like below
<%
Dim fso1, f11, ts1, s1
Const ForReading1 = 1
Set fso1 = CreateObject("Scripting.FileSystemObject")
Set ts1 = fso1.OpenTextFile("" & Server.MapPath("newsletters/welcome.html") & "", ForReading)
s1 = ts1.ReadAll
Response.Write s1
ts1.Close
set fso1 = nothing
set f11 = nothing
set ts1 = nothing
set s1 = nothing
%>
Any suggestions in ASP.net MVC for best way to read in other html pages and stick them into a page view.
I assume that these are HTML fragments, not full pages. You could convert them to partial views -- pretty trivial, you just add the correct page directive and rename to .ascx. Then you would use Html.RenderPartial to include the partial in your main view. Another way would be to create your own HtmlHelper extension that works like RenderPartial but simply reads the named file and writes it to the response just like you are currently doing.
Ex1:
<% Html.RenderPartial( "welcome.ascx" ); %>
Ex2:
<% Html.RenderHtml( Server.MapPath( "newletters/welcome.html" ) ); %>
Note that in the first case the view file needs to live in the Views directory. In the second case, you can reference the file from anywhere that the worker process has read access. You'll need to create the second method yourself. Perhaps something similar to:
public static class MyHtmlHelperExtensions
{
public static void RenderHtml( this HtmlHelper helper, string path )
{
var reader = new StreamReader( path );
var contents = reader.ReadToEnd();
helper.ViewContext.HttpContext.Response.Write( contents );
}
}
Please note that you'll have to add error handling.
To read the contents of a text file in .Net, use the File.ReadAllText method.
The exact equivalent of your code snippet would be
<%= File.ReadAllText(Server.MapPath("newsletters/welcome.html")) %>
You should write like this:
string html = System.IO.File.ReadAllText(HttpContext.Current.Server.MapPath("~/htm/external/header.htm"));

Resources