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
Related
I would like to export my Next.js project for the use with a Template Engine. (In my case "twig") I replaced all the texts in my HTML with the {{ ... }} twig markdown.
When I run next build && next export the HTML is generated as intended. The problem now is, that when I render e.g. the "index.html" with the twig renderer the texts are replaced again bei the markdown {{ ... }} from the .../index.js code. (Since this calls createElement and replaces all creates the tags for faster loading)
Now my question is: is it possible to disable the generating of the .js file for every page sothat I can change the .html file without it being overwritten?
P.S. The build is running as a SSG (Static Site Generator) eventhough I am not using getInitalProps, getStaticProps, getStaticPaths or getServerSideProps which I find strange. And I have no configuration in the next.config.js file.
Thank you so much in advance!
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 trying to add a new 'project' category to a morea based site, where there will be a page similar to experiences page where I'll collect all course project related experiences and assignments, and maybe even modules.
Is it ok to look for pages with 'project' in their id or maybe better to tag with a new morea tag?
Do I need to also create a new entity type called 'project' or just reuse existing entities
In MoreaGeneraor.rb I couldn't find where module level page collections are prepared, like for example module_page.data['morea_experiences'].
I thought of preparing a similar collection of 'morea_project' pages and then traversing it in the new project page.
Is that done outside of this .rb file? by Jekyll? so how do I inject my collection.
Any simpler idea?...
Thank
p.s. I've also added the following (from line 3) to 'processMoreaFile()', but it does not seem to be visible at later stages:
elsif new_page.data['morea_type'] == "assessment"
site.config['morea_assessment_pages'] << new_page
if new_page.data['morea_id'].include?('project')
site.config['morea_project_pages'] << new_page
puts "--- project page #{new_page.data['morea_id']}\n "
end
but still do not know where to let each module page find out about it's 'project' pages
This is a cool idea. The simplest approach is to avoid making changes to MoreaGenerator.rb, and instead do it using normal Jekyll mechanisms. Here's a sketch:
Add a morea_label called "Project" to each associated module, reading, experience, and assessment.
In master/src, create a new directory called project, containing a file called index.md. This adds a new top-level page to your site (i.e. http://example.com/ics101/project/index.html)
Implement the index.md file with liquid tags to create the desired page layout for your project page. For inspiration, see the index.md files in the other top-level pages (readings, experiences, etc.). You can access YAML content in Liquid.
Once you've got your rocking project page done, you'll want a link to it in the navbar. See add a menu item for instructions.
Good luck! If you get it working to your satisfaction, please post its link as a followup to this question so we can see how it turned out!
I need to scrape some URLs from some retailer product pages, but the specific URLs I need to get aren't in the html part of the page. The html looks like this for each of the items on which one would click to get to the page with the URL I need to grab:
<div id="name" class="hand bold" onclick="AVON.productcontrol.Go(45714);">ADVANCE TECHNIQUES Color Protection Conditioner Bonus Size</div>
I wrote the following to get URLs from the page, but since the actual URLs I need don’t seem to be stored in the page, it doesn’t get what I need:
def getUrls(URL):
"""input: product page url
output: list of urls to products
"""
connection = urllib.urlopen(URL)
dom = lxml.html.fromstring(connection.read())
selAnchor = CSSSelector('a')
foundElements = selAnchor(dom)
urlList = [e.get('href') for e in foundElements]
return urlList
Is there a way to get the link that the function after ‘onclick’ (I guess AVON.productcontrol.Go(#);) takes you to? I don’t fully understand html, and while I’ve read a bit about onclick, I can’t figure out how the function after 'onclick' works.
In order to find the URL that you are taken to on click, you need to find the JavaScript source code of the 'Go' function and read and understand it. It's buried somewhere within a tag or some JavaScript .js file that is referenced directly or indirectly by the HTML page. Happy digging!
Or: you automate the interaction with the web page with a tool like Selenium (http://docs.seleniumhq.org/) and just check where it takes you if you click.