I'm trying to append an element to one of my pages in a Volt project, via opal-browser, like so:
if RUBY_PLATFORM == 'opal'
require 'browser'
$document.body << my_dom_element.to_n
end
# controller code below
Unfortunately, I'm getting an error:
[Error] TypeError: null is not an object (evaluating 'value.nodeType')
(anonymous function) (main.js, line 7772)
The DOM element that I'm appending is perfectly sound. It's just the following code:
document.createElement( 'myElement' )
I'm assuming that Volt doesn't like me to append elements to a page in a controller, rather than manually creating it in a view HTML file. Is there a way I can work around this? I kind of need to do this element-appending thing for compatibility with another library that I'm using in conjunction with Volt.
Ok, so the problem is that the code you have is being loaded as soon as the compiled .js file loads. You need to run the code once the DOM is ready. The easy way to do this in volt is to run it on a {action}_ready method:
module Main
class MainController < Volt::ModelController
def index_ready
# run view code here
end
end
end
The index_ready method will be called after the index view has rendered and the dom is ready.
Related
I'm trying to integrate Vue js in existing rails app with lots of slim templates. It would be nice if I could use vue directives in slim templates
(yes, it's possible), get an HTML and pass it to Vue instance for further development. I know that there are slim-lang loaders that compile slim to html, or that you can use slim syntax inside <template lang="slim">.
But I don't want to separately send the result of every single ruby/rails method via AJAX. I want Rails to do its job and give the resulting HTML to Vue instance. Any thoughts and suggestions about this?
The solution I've found is quite simple. All you need to do is wrap your slim markup inside of vue component tag in your .slim views, and add inline-template attribute to it.
Example:
# header.html.slim
<v-header inline-template>
header
nav
- categories.each do |category|
= link_to category.name, category, '#click.prevent': 'doSomething'
</v-header>
or
v-header inline-template=""
header
nav
- categories.each do |category|
= link_to category.name, category, '#click': 'doSomething'
Ruby code will be executed first, template engine will convert everything to html with vue directives, then Vue will recognize its directives and take control of the markup. After I implemented this, I got rid of jquery and asset pipeline. But the views are the same. I did not migrate any of html files to vue components. With this feature, you can partially apply Vue js to your existing rails project and start writing modern javascript code.
There is no silver bullet to combine server-side and client-side templating. Even if you can render vue templates on the server the key context is missing (the interactive state of the page in the client).
There are some rather simple but flawed techniques that you could use to combine server-side and client rendering:
Create a controller that serves up your views
Rails.application.routes.draw do
scope 'templates' do
get '*path', to: 'templates#show'
end
end
class TemplatesController < ApplicationController
def show
if lookup_context.exists?(params[:path])
render template: params[:path]
else
head :not_found
end
end
end
require 'rails_helper'
RSpec.describe "Templates", type: :request do
describe "GET /template/*path" do
it "fetches template if it exists" do
get '/templates/foo/bar.html.erb'
expect(response).to have_http_status(200)
expect(response.body).to match "TEST"
end
it "returns 404 if it does not exist" do
get '/templates/foo/non_existant.html.erb'
expect(response).to have_http_status(:not_found)
end
end
end
However the devil is in details - this would only really work if your views are completely static and do not need any input data.
Render views without a layout
If you instead want to render normally from your controllers but not include the entire layout you can register a custom format:
# config/initializers/mime_types.rb
Mime::Type.register "text/x-html-template", :template
Sometimes you may need to stop spring in order for configuration changes to be picked up.
You can then disable the layout for this format:
class ApplicationController < ActionController::Base
before_action :add_html_to_template_lookup!, if: -> { request.format.template? }
layout :is_template_request?
private
def is_template_request?
request.format.template? ? false : "application"
end
# this will make rails fall back to rendering the "normal" html views
def add_html_to_template_lookup!
request.formats << Mime::Type.lookup("text/html")
end
end
I don't really understand how the wrapper works. I understood the example of the catalyst tutorial but I don't know how to apply specific CSS file for a specific template.
Should I use [% IF %] statement in my wrapper.tt in order to select a specific template ? Do I call the CSS file with stash, like I do for a template in the controller ?
Some examples or hints would be great, thanks
You can certainly assign the CSS file to a stash variable in your controller.
sub frobnicate :Local {
my ($self, $c) = #_;
# ...
# this would probably be implied, i.e. in a properly configured Catalyst
# you don't have to actually set this, it will just load the right thing
$c->stash->{template} = 'frobnicate';
# this one is for loading the right CSS
$c->stash->{css_file} = 'frobnication.css';
}
And then in your TT wrapper, possibly wrapped in [% IF css_file %]...[% END %]:
<head>
<link rel="stylesheet" href="[% css_file %]">
</head>
That would work, but it is not good practice, because you are breaking the separation of concerns. The way the page looks should have nothing to do with your application controller.
You could also just load each CSS file whenever it's needed, but that is bad practice too, because it will impact page load times and the order in which things are loaded. Typically one puts CSS at the top in the <head>, and most javascript files at the end of the page, just before the </body> so that there is already content rendered before the browser goes off and fetches and runs javascript.
A more flexible, but also more complex solution is to write a method in your View that can be exposed as a TT vmethod to the Template Toolkit, and use it to add CSS files to the stash when needed. You can do that with expose_methods.
package MyApp::View::TT; # or whatever you have called this
# ...
expose_methods => [qw/add_css_file/],
# ...
sub add_css_file {
my ( $c, $self, $css_file ) = #_;
push #{ $c->stash->{_css_files} }, $css_file;
return;
}
Now you can use that in your template files. You can have a block at the very top or very bottom of each file to add CSS files to the list of files that should be loaded right where they belong to logically.
<h1>Order Confirmation</h1>
[% add_css_file('confirmation.css') %]
In your wrapper, you can iterate that list of files and load each of them. As you can see this approach comes with the benefit of allowing you to have more than one file.
<head>
[% FOREACH file IN _css_files %]
<link rel="stylesheet" href="[% file %]">
[% END %]
</head>
They'll be available in the stash because the wrapper gets rendered after the inner part of the page, but you can't do it from the template directly, because you cannot change the stash within Template Toolkit. If there are no files, this will not do anything because the loop has nothing to iterate over.
Note how I called the stash key _css_file. The underscore _ indicates that it's meant to be a private variable. Perl has no concept of private or public, but that's a convention to tell other developers not to mess with this.
It would be advisable to now add a second method to the View to read the list and return it. That would decouple the implementation detail of how the list of files is stored completely from the template. You could change it entirely without having to touch the template files at all.
If you have that exposed method, it would be smart to for example make sure that each file is only included once, e.g. with List::Util::uniq, or by using a hash and accessing the keys instead of going for an array.
I originally used this approach for javascript files rather than CSS. For CSS in your application I actually believe it would be smarter to condense all the styles into one file, and minify it. Your users will download most of them anyway, so why have the overhead of loading multiple files and making each initial page load a little bit slower, and blowing up their cache, if you can just have the very first page load be a tiny bit longer and then read everything from cache?
I'm currently working on a Jekyll plugin that transforms the output HTML in the post_render hook of :documents. I would like to provide access to some information derived from the result of these transformations via a Liquid tag. This is obviously problematic, since I can only be sure that all transformations have been executed when the post_render hook of :site is called, but at that point Liquid tags have already been evaluated.
This leads to my question: Can a Jekyll plugin trigger a "re-rendering" of a page after post_render has fired, so that Liquid tags that were not available when the page was first rendered are then populated?
After reading parts of the Jekyll source code (site.rb and renderer.rb), I have learned how to implement such a "two-stage" plugin.
Since the initial file contents are overwritten during the rendering process, they have to be stored explicitly:
Jekyll::Hooks.register [:documents, :pages], :pre_render do |doc|
Jekyll::MyPlugin.unrendered_docs[doc.relative_path] = doc.content
end
After the site has been rendered, the document doc can then be re-rendered as follows:
Jekyll::Hooks.register :site, :post_render do |site, payload|
...
doc.content = Jekyll::MyPlugin.unrendered_docs[doc.relative_path]
doc.output = Jekyll::Renderer.new(site, doc, payload).run()
doc.trigger_hooks(:post_render)
...
end
I'm working on a Rails app and was wondering if a controller function is required for every static page even if it just contains HTML and CSS? I know I have to create a view and change routes.rb, but why do you have to define a controller function for each page?
class StaticPagesController < ApplicationController
def home
end
def about
end
end
So here do I need to define "about" even if it is just static content?
No, it's not required. If the method that routes maps to is not defined in the controller specified and there is no view file with that name, then you'll get an error that the method is not defined in the controller. But if the view is present and the method is not, it will go ahead and accept it as if the method was defined.
Have a look at high_voltage gem if you don't want to define those empty actions
I am trying to open HTML file from the local URI which I use as XML Editor, to edit xml data that come from Silverlight application, then close browser window and return back edited xml data to the Silverlight application.
I tried to use HtmlPage.Window.Navigate but I don't quit like it.
I have tried using a method from: http://weblogs.asp.net/dwahlin/archive/2010/05/10/integrating-html-into-silverlight-applications.aspx
but instanly got an exception "failed to invoke ShowJobPlanIFrame"
Is there any way to handle this task?
"Out of browser" mode doesn't fit.
Thanks.
===========================================================================
Update:
It worked out using IFrame overlay.
Button click invokes the following code in C#:
var scriptObject = (ScriptObject)HtmlPage.Window.GetProperty("ShowJobPlanIFrame");
scriptObject.InvokeSelf(url);
Where "ShowJobPlanIFrame" is as defined at:
http://weblogs.asp.net/dwahlin/archive/2010/05/10/integrating-html-into-silverlight-applications.aspx
This allowed me to pass data into XML editor and then get it back.
An error with JavaScript function invocation I told above, was my fault in JavaScript code itself.
A very similar scenario: https://stackoverflow.com/a/7919065/384316
Try using an iframe overlay, then you can load any HTML-like content.
There is an excellent explanation of how to do this here:
http://www.wintellect.com/cs/blogs/jlikness/archive/2010/09/19/hosting-html-in-silverlight-not-out-of-browser.aspx
It worked out using IFrame overlay.
Button click invokes the following code in C#:
var scriptObject = (ScriptObject)HtmlPage.Window.GetProperty("ShowJobPlanIFrame");
scriptObject.InvokeSelf(url);
Where "ShowJobPlanIFrame" is as defined at:
http://weblogs.asp.net/dwahlin/archive/2010/05/10/integrating-html-into-silverlight-applications.aspx
This allowed me to pass data into XML editor and then get it back.
An error with JavaScript function invocation I told above, was my fault in JavaScript code itself.
Did you try NavigationFramework of Silverlight? It's capability may support your needs in a more simple way than using multiple browser pages.