Iron router sublayouts - getting data context on yield - meteor

Consider the following templates:
layout
pageLayoutStandard
aboutUs
'layout' is my top-level template, which I specify using:
Router.configure({
layoutTemplate: 'layout',
});
Inside layout.html I have the following:
<main id="site-main" role="main">
{{>Template.dynamic template=page.pageLayoutTemplate }}
</main>
I pass some data in from the route: a page object which has a property 'pageLayoutTemplate', having the value 'pageLayoutStandard'.
Inside 'pageLayoutStandard' template, I have:
{{> yield }}
If I visit the '/about-us' route, I render the 'aboutUs' template into 'pageLayoutStandard' - no worries.
And now to my problem...
In my 'aboutUs' template, I expect the 'data' property of the instance to contain the data I passed down from iron-router. However, I find that my the data property contains a 'Template' object; specifically, it contains 'pageLayoutStandard'.
So it looks like 'yield' doesn't like living in a sub-layout - it wants to live at the top level layout for it to get the data from the route. This I validated by moving my yield to the top level layout - the 'aboutUs' template then gets the right data.
Is there any way I can get 'yield' to get the correct data context when it exists in a sublayout?

One solution is to access the data using
Router.current().data()

I my self am fairly new to Iron-Router, but I believe it may be a similar issue that I cam across in another way. There are also several ambiguities in your question, such as where and how are you specifically defining the data context.
Basically I discovered that {{> yield}} creates it's own <body> tags. This also means that things like Template.body.events(); don't propagate into this new Iron-router <body>. This is a known "bug" with Iron-Router.
There is a workaround that has been developed to solve that particular issue, but I'm not sure it's relevant to your case, or at least may not solve the problem since you are not looking to propagate the main body template.
In the end it may be that your routing logic is somewhat inverted, as you mentioned, with the intended usage of Iron-Router.
I believe a better way to perform what you want would be to have:
<main id="site-main" role="main">
{{> yield}}
</main>
With something like this in your router definition
Router.map(function (){
this.route("/about-us", {
template: "pageLayoutStandard"
}
});
You should then be able to set your data context and rendering as per usual.

Related

Nuxt3 navigateTo gives Uncaught (in promise) TypeError

I am trying to programmatically route to a detail page from within a list in Nuuxt3 app:
#/pages/items/index.vue
<script setup lang="ts">
const gotoDetail = async (itemId) => {
await navigateTo(`/items/${itemId}`)
}
</script>
<template>
<div>
<NuxtLayout name="main-standard">
<template #main-content>
<div v-for='item in items' :key=item>
<div #click='gotoDetail(item.id)'
</div>
</template>
<template #main-content>
<!-- aside content -->
</template>
<NuxtLayout>
</div>
</template>
And I am getting this error message:
ERROR: Uncaught (in promise) TypeError: Cannot read properties of null (reading 'parentNode')
I have searched for an answer and most solutions mention to wrap the <NuxtLayout> with a div. But that did'nt solve my issue.
I am using a default Layout. So the<NuxtLayout name="main-standard"> is inside this default layout. Both pages: index.vue and [itemId].vue are in the pages/items/ folder.
I am doing something wrong but just can't find it. Does anyone see whats going on?
A few things here are going to break your code.
One, the middle div with the click handler is missing a closing >, contents, and a closing </div>. (Of course you may have omitted that for brevity)
items isn't defined, so there's nothing to iterate over.
You're using the same v-slot name #main-content for multiple templates, but each slot name should be unique. The # attributes (shorthand for v-slot) should match the slot names you write in your layout, and those must also be unique within the component.
The main problem looks to be related to the way you're using layouts. To mess with layout on a page using the component, you have to add this into the setup script:
definePageMeta({
layout: false,
});
A different way of applying your custom layout to this page is to replace the false boolean with the name of the layout, and omit the tag from this page altogether. For that to work, app.vue should have a <NuxtLayout> tag wrapping the <NuxtPage>
Not a breaking change, but it may also simplify things to write
<NuxtLink :to="`/items/${itemId}`">{{ whatever you wanted inside that div }} </NuxtLink>
If you need to run code before navigating to that page, you can add it into the top-level middleware folder, and call that named middleware on the page before which you want it to run.

Calling holder.js in Meteor

I'm new to Meteor and trying to get holder.js to work in the framework. It works on refresh, but when moving from one route to another, it breaks.
The documentation just says "Because Meteor includes scripts at the top of the document by default, the DOM may not be fully available when Holder is called. For this reason, place Holder-related code in a "DOM ready" event listener."
I assume I need a Template.foo.onRendered callback, but unsure how to format it. Here's the HTML:
<img class="holder" src="holder.js/120x120">
And here's the callback I've added in a .js file:
Template.contactSingle.onRendered(function() {
this.$('.holder').Holder.run();
});
Again, the holder.js images appear on refresh, but I can't get them to render when going from one page to another. I'm using FlowRouter for routing.
I'm sure it's something simple. Any help is greatly appreciated!
Change your code from:
Template.contactSingle.onRendered(function() {
this.$('.holder').Holder.run();
});
to:
Template.contactSingle.onRendered(function() {
Holder.run({images: document.querySelectorAll('.holder')});
});
Obviously you do not want to do the costly document.querySelectorAll('.holder'). If you can reduce that to you template using a class from its wrapper.
For example:
Template:
<template name="singlePost">
<div class="single-post">
<h2>This is the singlePost area.</h2>
<img class='holder' src="holder.js/300x200">
</div>
</template>
and onRendered
Template.singlePost.onRendered(function() {
Holder.run({
images: document.querySelectorAll('.single-post .holder')
});
});

Meteor: Passing array parameter to template

I'm trying to pass an array to another template in Meteor.
Why? Because I would like to create a small template for each Bootstrap element, allowing me to reuse components much more easily.
{{> dropdown id="dropdown1" textDropdown="My dropdown!" listItems=["item1", "item2"] }}
This does not seem to work unfortunately.
Any clue? Does what I'm doing even make sense? I'm new to Meteor.
Thanks!
Spacebars is currently pretty limited in what it can accept - you'll need to add a helper to accomplish this:
Template.myTemplate.helpers({
listItems: ['item1', 'item2']
});
And them modify your template:
{{> dropdown id="dropdown1" textDropdown="My dropdown!" listItems="{{listItems}}"}}
Make sure to update myTemplate to the parent template's name.

Template.body vs Template.myTemplate

I am currently going through the Meteor tutorial (https://www.meteor.com/try), and have come across something about Templates that puzzles me.
In the tutorial, a simple "Todo List" application gets created. In this app, the following HTML is placed into the simple-todos.html file:
<!-- simple-todos.html -->
<head>
<title>Todo List</title>
</head>
<body>
<div class="container">
<header>
<h1>Todo List</h1>
</header>
<ul>
{{#each tasks}}
{{> task}}
{{/each}}
</ul>
</div>
</body>
<template name="task">
<li>{{text}}</li>
</template>
Then, the following JavaScript is placed into the simple-todos.js file:
// simple-todos.js
Tasks = new Mongo.Collection("tasks");
if (Meteor.isClient) {
// This code only runs on the client
Template.body.helpers({
tasks: function () {
return Tasks.find({});
}
});
}
At this point, the example works exactly as intended. However, as I poke around in the documentation, as well as look at other examples on the web, I have noticed slightly different syntax: using Template.myTemplate instead of Template.body.
So, out of curiosity, I altered my JavaScript file to read:
Template.task.helpers({ ...
instead of:
Template.body.helpers({ ...
However, when I run the application now, the client does not display the data from the collection. I don't get any errors about undefined types, like I do if I misspell the template name in the JavaScript, so it seems that it is resolving the template correctly. But why isn't it getting used or rendered?
And to go a little further: when is it appropriate to use Template.myTemplate and when is it appropriate to use Template.body?
The helpers code only works for the template it's attached too.
So, code that works for Template.task will only apply to templates named "task".
Template.body is like the one-off that exists because it would be weird if it didn't. It's a way for you to specifically target the <body>, even though technically, there's no template named "body".
So, what is going on:
Parent template = body
Child template = task
Your logic says:
In the parent template, for each task that we find, render an instance of the child template "task".
If you change your helper from body to task, you won't get any output at all, unless you mimic the pattern that's already happening:
<template name="task">
{{#each tasks}}
do something
{{/each}}
</template>
That's because <body> is the parent template, and you should treat it as such:
<template="body>stuff that normally goes in an HTML body here</template>
When you remove the helpers for the body, then no data gets displayed at all because helpers pass data into the template. And with no helper for the template i.e. the body, you get no data.

View property returns 'undefined' when following a linkTo route

I have a custom view to display breadcrumbs for the current model navigation on a page, here's how it looks in my template (which is the singular model template):
<div class="breadcrumbs">
{{view Mdm.BreadcrumbView}}
</div>
Here's what my view looks like to render out the breadcrumbs:
App.BreadcrumbView = Ember.View.extend
templateName: 'breadcrumb'
penultimate: (->
content = #get('controller.content.self_and_ancestors')
content[content.length - 2]
).property('controller.content')
last: (->
content = #get('controller.content.self_and_ancestors')
content[content.length - 1]
).property('controller.content')
Here's the breadcrumb template that renders the data:
<ul class="breadcrumb">
{{#if view.penultimate}}
<li>{{#linkTo group view.penultimate}}Back{{/linkTo}}</li>
{{/if}}
<li>{{view.last.name}}</li>
</ul>
My model that the view is using (controller.content) has nested data self_and_ancestors that hold the breadcrumb data that I need for the current page I'm on. I'm only interested in seeing the last object in that array and the one just before it to navigate one page back (that's what the penultimate and last are displaying). The breadcrumbs render perfectly and I can navigate fine until I click the 'Back' (penultimate) link. When I hit that I got the following error:
Uncaught TypeError: Cannot read property 'length' of undefined
For some reason the content is undefined, but I'm not exactly sure why. Any thoughts?
Edit:
Welp, here's an example http://jsbin.com/ejazin/2. It is, however, working in this example, which makes me thing something else may be the problem. Basically every time you click a breadcrumb link in my app, the router does a find based on the ID which makes an ajax call to find the current group for that route and all sub groups. Maybe the view is trying to pull out the sub_groups data before it's come back from the server... Is there any way to check / prevent this?
Edit Edit:
This might convey what I'm trying to do a bit more - http://jsbin.com/obazus/1/.
Edit Edit Edit:
Ok, the issue is definitely the fact that the view is rendering and calling penultimate before the ajax request has finished in the model. That being said - what's the best way to make sure it renders after the ajax request for the model is finished?
After looking more closely at your use case, what might help is to check out the new router facelift here. It's still not wildly documented and should soon appear on the ember website as a blog post, but it's already merged into ember-latest so you will have access to the new implementation. Especially the beforeModel & afterModel hook's seam to be relevant for you.
For example in the afterModel hook you can do stuff like:
App.PostsIndexRoute = Ember.Route.extend({
afterModel: function(posts, transition) {
if (posts.length === 1) {
this.transitionTo('post.show', posts[0]);
}
}
});
Hope it helps.

Resources