How can I specify the optional parameters in the dynamic routing? - next.js

Suppose I have a page that will show the details of each chapter in the book. User can navigate to other chapters from that page, as the screen has buttons of chapters in the sidebar, and the main content shows the details of the chapter.
Now the user will navigate to that page from a list of books. At the page information, there's no chapter information of that book.
Now if the URL is \bookSlug, so the page will show the details of the first chapter, even it is not mentioned in the URL, it will fetch the list of chapters then fetch the details of the first chapter from that list. This behaviour remains the same for URL \bookSlug\firstChapterSlug.
So, in react-router I used to do it like this.
<Route path={'/:bookId/:chapterId?'} component={ChapterDetails} >
And on the page, I handle the params, that id the chapterId is undefined to just fetch the first chapter or just fetch the chapter mentioned in the chapterId param.
Now, I'm stuck here,
<Link href={'/[bookSlug]/[chapterSlug]' as={'/someBookName'} >
It is just not working, even I have marked the params in the getStaticPaths optional GetStaticPaths<Props, {bookSlug: string, chapterSlug?: string}> s that I can write logic.
How can I achieve that react-router behaviour here in nextJS dynamic routing?

Since Next 9.5 you can do it by using optional catch-all routes.
You would have a route like this /[bookId]/[[...chapterId]].
And to navigate, since Next 9.5.3 you don't need "as" property in Link so Next sould handle this correctly :
<Link href='/some-book'/>

Navigation like that is not possible and incorrect with nextjs, if you provide href='/[bookSlug]/[chapterSlug]' you have to provide as='/some-book/some-chapter'
<Link href={'/[bookSlug]/[chapterSlug]' as={'/someBookName'} />
// this will produce an error
Error: The provided as value (/some-book) is incompatible with the href value (/[bookSlug]/[chapterSlug])
Possible soutions
There are two possible solutions for what you are trying to achieve
If you want the route /some-book to match with [bookSlug] and display the page for the book details and only display some chapter you have to create an index.tsx file which will be used to fetch only the book details and data for the first chapter and return a new page component from it. For example, this is what the pages folder structure might look like,
- pages
- [bookSlug]
- index.tsx
- [chapterSlug].tsx
// index.tsx can be used to fetch and display the book details and first chapter
And you have to explicitly navigate to that page like below
// for navigating to /some-book (which will display only the first chapter)
<Link href='/[bookSlug]' as={'/some-book'} />
// for navigating to /some-book/some-chapter
<Link href='/[bookSlug]/[chapterSlug]' as='/some-book/some-chapter'/>
The second solution is to only keep the [bookSlug] part of the dynamic route and fetch the book details and data for all the chapters and pass it as props. To display each chapter details page make use of shallow-routing.
When it comes to navigating to each chapter you have to make use of router.push instead of the Linkcomponent.
If you were thinking of conditionally returning paths from the function getStaticPaths where you only provide the value for param bookSlug and omit chapterSlug something like { paths: [{ params: { bookSlug: 'some-book'} }]}, this will not work.
For /[bookSlug]/[chapterSlug].js if you try to do something like that it will generate a error at build time
Error: A required parameter (chapterSlug) was not provided as a string in getStaticPaths for /[bookSlug]/[chapterSlug]

Related

How do I set Ghost Blog Custom Routes.yaml Collection Title / Meta Description in my custom template?

Using the Ghost blog routes.yaml file it is possible to use the collections block to create a custom collection made from some tag(s) and / or other data. You can also tell this collection to use a custom theme template see:
https://docs.ghost.org/tutorials/creating-content-collections/
https://docs.ghost.org/concepts/routing/#content-structure
For instance:
collections:
/example/:
permalink: /example/{slug}/
controller: channel
filter: tag:example-tag
template:
- example
All of the above works and my collection properly uses my new example theme file.
The issue is that unlike the tag page (for example-tag) my new custom collection does not have a readily documented way to work with the title etc.
It does not pull the title / meta description from the tag used to build the collection (which would be great for collections built from single tags). In an attempt to work around that I tried some {{#has}} statements but I can't figure out what context the custom route would fit into.
With the above example routes.yaml the title for the custom collection ends up as 'My Site Name (Page 1)' — and there is no Meta Description.
This issue also extends into the Open Graph data which lists an identical title as well as no description for the custom collection.
My guess is that it may be possible to use a data property attached to the routes.yaml file to achieve this (see: https://docs.ghost.org/concepts/routing/#data) but I haven't found a solution as of now.
While my initial attempts at googling for a solution came up empty, this is the best reference I have seen to the issue:
https://forum.ghost.org/t/dynamic-routing-page-post-data-context-in-default-hbs-nested-navigation-on-custom-collections/4204
https://github.com/TryGhost/Ghost/issues/10082
I found a way to work around.
You create a page called example in the Ghost Admin tool.
Customize routes (instead of collections) in the routes.yaml as following:
routes:
/example/:
controller: channel
filter: tag:example-tag
template: example
data: page.example
The page.example will use the metadata of this page in the Ghost.
This is possible only with workaround described in issue: https://github.com/TryGhost/Ghost/issues/10082
Generally do following:
create page Example (with slug example) and fill metadata title & description you want
in routes.yaml alter your collection definition /example/ add following:data: page.example to link your collection root with specified page
now in your template definition example.hbs you could use e.g. {{#page}} {{content}} {{/page}} tag to insert content from your page. You can do it also in default.hbs template which is included from your example.hbs. So replace: <title>{{meta_title}}</title> in default.hbs with following:
{{#unless page}}
<title>{{meta_title}}</title>
{{else}}
{{#page}}
<title>{{meta_title}}</title>
<meta name="description" content="{{meta_description}}"/>
{{/page}}
{{/unless}}
This will set specific title/description for your collection root pages in general way. It is possible to generate schema.org metadata in the similar way. Unfortunately Facebook and Twitter metadata is not so simple to do because, {{ghost_head}} tag in default.hbs already inserts site metadata to this page. Last note: besides {{meta_title}} and {{meta_description}} I suppose you could use all metadata fields defined here.
In default.hbs I added the following block:
{{{ block "metaTitle"}}}
and eg. in post.hbs I filled that block as follows:
{{!< default}}
<div class="content-area">
<main class="site-main">
{{#post}}
{{#contentFor "metaTitle"}}
<title>{{title}}</title>
{{/contentFor}}
...
For other pages like page.hbs, blog.hbs, author.hbs I did the same. I think that solution is more flexible because we have more control over title value.

Override item-list.html.twig & time.html.twig inside a view

I've created a view that outputs a <ul> with a <time> element in each <li>. I need to convert the HTML list to a select.
So I did 2 things:
Copied item-list.html.twig and changed <ul> to a <select>
Copied time.html.twig and changed <time> to <option>
Although this works I need those 2 templates to be scoped to my view named: 'vcon-selection-table'. I tried many things but nothing works:
views-view-table--vcon-selection-table--item-list.html
views-view-table--vcon-selection-table-item-list.html
views-view-table--vcon-selection-table.html--dataset-item-list
So the question is: What template name should I use to override the templates item-list.html.twig and time.html.twig inside my view?
Is the purpose of that view to only provide that <select> element and <option> elements? Then this really isn't the way to go. Don't build a form or form elements with Views.
You'd better provide a custom form or a custom item_list in a custom block. The block then can be placed where ever you need it. There are plenty of tutorials out there. Maybe take the following as a starting point: https://valuebound.com/resources/blog/creating-a-custom-form-in-a-block-in-two-steps-in-Drupal-8
Otherwise, if you really want to continue your road I think you have to simply preprocess the list and time fields to switch to a different template according your desired conditions. I strongly guess there is no option that a custom template for a field from a node in a view will automatically be taken into account by naming patterns. You have to add that template suggestion manually first.
Snippet to be placed in MYMODULE.module file or MYTHEME.theme file.
/**
* Implements hook_theme_suggestions_HOOK_alter().
*/
function MYMODULE/MYTHEME_theme_suggestions_item_list_alter(array &$suggestions, array $variables) {
// Use \Drupal::routeMatch() to get the current view ID or some other indicator.
// Sample: Get the current view ID.
$view_id = \Drupal::routeMatch()->getRouteObject()->getDefault('view_id');
// Install Devel, Kint module, then uncomment the following line and flush caches to inspect the outcome.
// ksm($view_id);
// Add the template suggestion.
// Add your template now under /templates/item-list--VIEWID.html.twig
if (!empty($view_id)) {
$suggestions[] = 'item_list__' . $view_id;
}
}
Downside of this approach is that this would trigger all item_lists that live on that exact page/view to take this template. You could fine-grain your templating much better with a custom module and a custom item_list and/or form.
Alternatively you can also move the logic to the date field and a custom view mode and an extra template for that. Then at least it stays where it belongs to.
To enable Twig debugging and getting the built-in template suggestions printed as HTML comments follow this article: https://www.drupal.org/docs/8/theming/twig/debugging-twig-templates

How can I make Meteor templates available to targeted audiences via an URL?

I want to build a Blog, of sorts, with Meteor but, rather than just have a Blog such as platypus.meteor.com, I want to create a separate Meteor template for each Blog "post" and then send a link to select people such as "platypus.meteor.com/thispost"
In this way, the person would only see the post I intend them to see; to see others, they would have to guess at other values, such as "/thatpost", "/theotherpost" etc.
And in my case, if they stumbled across them, no big deal.
This is my plan:
Create one template at a time:
<template name="thispost">
. . .
</template>
...and then allow access to that to whomever I apprise of its availability (that is, they simply enter the link I send them into their browser).
I don't know what sort of routing I need to set up; I'm open to either IronRouter or FlowRouter. At any rate, I want an URL like "platypus.meteor.com/thispost" (after a "meteor deploy platypus" of this project) to show the user the contents of that Template and nothing else.
So my question is: what do I have to do, routing-wise, to accomplish this?
How about simply:
Router.route("/:templateName/:postId",{
template: this.params.templateName,
data: function(){ return Posts.findOne({ _id: this.params.postId })
});
Then you can generically share any post with any template and have the template name appear right in the route.

Conditional link to node within views?

I have two content types, book and chapter. Each chapter node contains a node reference of the book to which it belongs. I have created a view which displays the title of each chapter for a given book. Within the view, the title field has been configured to link to its node.
All works well. I am now interested in updating the view to not display a link to a chapter's node when the chapter's body is empty. Thus this view would display a list of chapter titles for a book and link only to those chapters that have a body.
Can this be accomplished using out of the box Views functionality? If not, how would something like this be accomplished?
Thanks.
I'd use the Views Custom Field module to implement your custom logic. It allows you to grab the data fetched by Views and manipulate it at will with PHP. Very handy.
I'm answering my own question because my response to ceejayoz is poorly formatted.
What I did to accomplish this was to first download and enable views_customfield. Second, I placed Title and Body fields within the view, both excluded from display. Third, within a Customfield: PHP code field I placed the following code:
<?php
if (strlen(trim($data->node_revisions_body)) == 0) {
return $data->node_title;
} else {
return l($data->node_title, drupal_get_path_alias('node/' . $data->nid));
}
?>
There's also this clever workaround which allows you to achieve this very easily:
Add 2 title fields one with link and one with no link, and make both
them exclude from display.
Add body field,
In No result behavior put title with no link replacement token to
it.
In Rewrite results behavior put title with link replacement token to
it.
Tick hide if empty.
Source

Change size of user/password login box

I don't know how to change the size of the login username/password boxes on the drupal site that I'm trying to build. I'm stumbling through the theming, and don't know where to find the file that needs to be changed in order to have boxes that fits the aesthetic (so a file path would be very helpful).
I'm hoping it's a css solution. You can see the site first hand at innovatefortomorrow[dot]org and my firebug screenshot http://www.jonrwilson.com/user-login-form.png (I don't have enough reputation points to attach an image or two hyperlinks).
Thanks!
read this as well! This is an alternative answer!
Ok... you are about to enter one of the most exciting and complex features of Drupal: the form API or - for brevity - FAPI. Some theory first, and then the solution! :)
All forms in Drupal are built by the drupal_get_form() function, that accepts an array as parameter. Each field in the array is basically a field of your form, and each field has a number of proprieties, each of them define additional characteristics of the the field, like for example its default value, if it is required or optional and - yes - in the case of textfields... how large they have to be! You can find a detailed explanation of the structure of form arrays here on the drupal site.
The beauty of the form API is that the function that renders the form invokes a number of hooks at various moments during its building process, so you can implement these hooks in order to "alter" a form before it is finalised and sent to the browser.
The most commonly hooks for form alteration are hook_form_alter() and hook_form_FORM_ID_alter(). The first is executed for any form processed by the drupal engine, the latter only for the specific form named "FORM_ID". I will not get into any more details on the internal working of the form API, but here you can read more.
As for your specific case, I assume you are using the standard "user block" shipping with Drupal. In this case I suggest you implement hook_form_FORM_ID_alter() in a form similar to this one:
mymodule_form_user_login_block_alter(&$form, $form_state) {
$form['pass']['#size'] = 43;
}
Hope this helps! :)
I think in this case you have to go to the your html ( or tpl), not your css file to edit it.
One quick way is to search the relevant string (i.e., size="43" name="name" etc) in order to find the correct part.
Here is the way to theme a user login form in drupal 6 with the preprocess function in a template file and not in a module.
in template.php put this code:
function yourThemename_preprocess_user_login(&$variables) {
$variables['form']['name']['#size'] = 15;
$variables['form']['pass']['#size'] = 15;
$variables['rendered'] = drupal_render($variables['form']);
}
create a new file user-login.tpl.php (if it's not already there) and just paste this:
<?php print $rendered; // this variable is defined in the preprocess function user_login and print the login form ?>
Don't forget to clear theme cache or system cache in the performance settings.

Resources