Control Vue component from nested layout using Inertia JS - vue.js

I'm using Inertia JS using Vue and nested Layouts.
My main layout looks something like this:
<template>
<div>
<app-bar title="App title" type="back|dismiss|sidebar">
<!-- Slot for icons in the top right corner -->
</app-bar>
<slot />
</div>
</template>
So, an AppBar component accepting a title, a link with a back icon, dismiss icon or sidebar icon, and a slot (optionally) to provide icon links relevant to the current page.
<script>
import Layout from '#/Pages/Messenger/Layout';
export default {
metaInfo: { title: 'Report new problem' },
layout: [Layout],
...
</script>
This is a Page that is nested in the Layout.
So my question is: what is the best/preferred way to control the props and slot of the AppBar from nested Pages?
A bit like as you would do using Blade templates in Laraval or as Vue Meta does for the document page title as seen in the example above.
Maybe this is not even the best approach, in that case also let me know :)

If you are trying to pass information from your child component to your parent component such as a title, you can use $emit.
Here is a article describing how: https://hvekriya.medium.com/pass-data-from-child-to-parent-in-vue-js-b1ff917f70cc
And another SO question: https://stackoverflow.com/a/52479745/4517964

The fast way I found to pass data to persistent layouts is by:
in the child
use this:
layout: (h, page) => { return h(Layout, () => page) }
instead of:
layout: Layout,
and in the parent layout you can access your child with this.$slots.default()[0]

Related

Vue 3 pass tamplate part to another component

I have a Vue 3 application with router. (Using Bootstrap)
I have deep component in header, and main container (page in RouterView).
Question: I want to show few icons in header, but use it from page component.
What I must to use to do that?
I try to send template, dynamic import and etc, but as i understand it`s wrong ways.
I have same struct:
App
Header
Title
Icons
LeftMenu
MainContant
PageTitle
PageContant <- RouterView
Footer
So I have special template to Header Icons for each page. And I want to work with icons from page component. For example make submit icon.
I'm not completely sure if I understand the question correctly. But from what I get you want to have a slot in your Header component - docs.
So you can use it in MainContainer as follows:
<div>
<Header>
<Icon />
</Header>
</div>
In this case you'll have access to the Icon components in your MainContainer template.

Is it possible to globally define links to use a specific component?

I'm currently trying to use Nav with react-router. The default behavior reloads the page, so I'm trying to use the Link component from react-router-dom.
It's quite difficult to preserve the default styling when overriding linkAs.
Is there any global way to override link navigation behavior?
Like defining a global link render function, which I can then set to render the Link component from react-router-dom?
Yes, it's possible!
2 things are required:
Make a wrapper component that translates the Nav API to react-router-dom links.
Specify the linkAs prop to the Nav component.
Wrapper component
This is a simple component that creates a react-router-dom link while using styles from Fabric:
import { Link } from "react-router-dom";
const LinkTo = props => {
return (
<Link to={props.href} className={props.className} style={props.style}>
{props.children}
</Link>
);
};
Specify component for use in Nav
<Nav groups={links} linkAs={LinkTo} />
Have also created a full working example at https://codesandbox.io/s/xenodochial-wozniak-y10tr?file=/src/index.tsx:605-644

How to submit a form from another component when the modal OK button is clicked (bootstrap vue)

In my Vue app, I have a component that handles a simple form named TodoForm.
Using bootstrap-vue, i would like to submit this form when the OK button of a bootstrap modal is pressed.
The code looks like this:
<b-modal id="todo-form-modal">
<todo-form />
</b-modal>
I don't want to put the modal component inside the TodoForm component since the TodoForm component only handles the form behavior, not the container where it is displayed.
I could also disable the OK button and put a button inside the form myself, but i'm sure there is a proper, a better way to submit this form (it is more like an exercise than a real project with an actual deadline).
I found the #ok event in the doc (triggered when the OK button is pressed), which is nice but i'm struggling to understand how i could use it to call a onSubmit() method inside the TodoForm.
For instance, it looks like this:
<b-modal id="todo-form-modal" #ok="something">
<todo-form />
</b-modal>
Ideally, the #ok="something" should call a method inside the TodoForm component.
How can I achieve this the right way ?
Expanding on #mapawa's answer:
<template>
<b-modal ... #ok="handleOk">
<todo-form ref="todoform" ...></todo-form>
</b-modal>
</template>
<script>
import TodoForm from 'somewhere/todoform'
export default {
components: { TodoForm },
methods: {
handleOk(bvEvt) {
// This assumes the root element of the todo form is the <form>
this.$refs.todoform.$el.submit()
// Alternatively, if your Todo Form exposes a submit method
this.$refs.todoform.submit()
}
}
}
</script>
What you want to do is to reference the parent component in the child component. You can use the ref attribute for this. I can't possibly explain this better than the official docs, so take a look at this.

How can I get CKEditor 5 "Link" dialog box to pin to custom DOM element instead of 'document.body'

I'm building a Vue.js web application. I'm using CKEditor in a form that is placed inside a modal window. By design, the user's focus is "trapped" in the modal. In CKEditor, when user clicks the "Link" icon in toolbar, the editor opens a dialog box and attaches the new DOM element to 'document.body'. With respect to the DOM, the "Link" dialog is now outside of trapped focus. The user cannot click or tab his way to the "Link" dialog input.
I dug into the ckeditor5-ui source and found relevant code in balloonpanelview.js. I've unsuccessfully tried to configure CKEditor based on https://ckeditor.com/docs/ckeditor5/latest/api/module_utils_dom_position-Options.html
In my Vue.js component, I have:
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
...
data: () => ({
editor: ClassicEditor,
editorConfig: {
toolbar: ['bold', 'italic', 'bulletedList', 'numberedList', 'link'],
},
...
})
...
I want the CKEditor "Link" dialog DOM element to be attached to a DOM element id that I specify.
In Vuetify dialog component is required to disable retain-focus
<v-dialog :retain-focus="false" />
There may be much time since you opened the issue. However... This issue was happening to me too. This is happening because Bootstrap modal trap the focus in the active modal. If you're using bootstrap-vue, do this.
In your <b-modal> add the prop no-enforce-focus.
no-enforce-focus is reactive. To properly apply this workaround you can use this prop with a variable, that detects when your CKeditor have focus. If have focus, disable the enforce focus. If doesn't have, restore it. You can apply it by the following way:
<template>
<b-modal
...
:no-enforce-focus="editorFocus">
<ckeditor
...
#focus="toggleEditorFocus(true)"
#blur="toggleEditorFocus(false)"
/>
</b-modal>
</template>
<script>
export default {
...
data () {
return {
editorFocus: false
}
},
methods: {
toggleEditorFocus (val = !this.editorFocus) {
setTimeout(() => {
this.editorFocus = val
}, 10)
}
}
}
</script>
I know the setTimeout is a tricky method, but at least is working now for me.

Global header - use back button [duplicate]

In Ionic 1, we have the ability to define an <ion-nav-bar> above an <ion-nav-view>, which serves as a generic nav bar for the entire app and we could turn it off on a per-view basis (using ionNavView's hideNavBar=true|false.
It appears in Ionic 2 we have to insert an <ion-nav-bar> per page - and cannot have a global nav bar for the entire app. Is that correct, or am I missing a trick?
If so - it seems like a lot of duplicated code?
Also, it appears you do not have the ability for the NavBar to build its own back button, and you have to write the own mark-up for the back button yourself (per page) which, again, seems like a lot of code duplicate.
UPDATE:
Just like #mhartington says:
There is no way to create a global ion-navbar, as this is done on
purpose. The point of having a navbar defined for each component is so
that we can properly animate the titles, navbar background color (if
you change them) and animate other properties needed.
And about creating a custom directive to avoid duplicating ion-navbar html code:
That will still creat errors with how angular2 content projection
works. We have several issues that have been open when people try this
and the best answer is to not do it.
NOT recommended solution:
In order to avoid duplicating so much code, you can create your own custom component for the navbar.
Create a navbar.html with this code:
<ion-navbar *navbar>
<ion-title>MyApp</ion-title>
<button menuToggle="right" end>
<ion-icon name="menu"></ion-icon>
</button>
<ion-buttons *ngIf="!hideCreateButton" end>
<button (click)="createNew()"><ion-icon name="add"></ion-icon></button>
</ion-buttons>
</ion-navbar>
And then in the navbar.ts:
import {Component, Input} from '#angular/core';
import {NavController} from 'ionic-angular';
import {CreateNewPage} from '../../pages/create-new/create-new';
#Component({
selector: 'navbar',
templateUrl: 'build/components/navbar/navbar.html',
inputs: ['hideCreateButton']
})
export class CustomNavbar {
public hideCreateButton: string;
constructor(private nav: NavController) {
}
createNew(): void {
this.nav.setRoot(CreateNewPage, {}, { animate: true, direction: 'forward' });
}
}
By declaring the hideCreateButton as an input of the Component, you can decide in which pages show that button and in which ones should not be visible. So in this way, you can send information to tell the component how it should be, and customize it for each page.
So if you want to add the navbar in a page (without modifying the default template, so showing the create button) you just have to add the navbar element (binded to our custom component by us in the selector property):
<navbar></navbar>
<ion-content>
...
</ion-content>
And if you want to hide the create button (or modify you navbar like you want to) your page will look like this one:
<navbar [hideCreateButton]="hidebutton()"></navbar>
<ion-content>
...
</ion-content>
And remember that the hideButton() should be defined in your customPage.ts like this:
import {Component} from '#angular/core';
import {NavController} from 'ionic-angular';
import {FORM_DIRECTIVES, FormBuilder, ControlGroup, Validators, AbstractControl } from '#angular/common';
#Component({
templateUrl: 'build/pages/create-new/create-new.html',
directives: [FORM_DIRECTIVES]
})
export class CreateNewPage{
private hideCreateButton: boolean = true;
public hidebutton(): boolean {
return this.hideCreateButton;
}
}
For ionic 3+
What I did to solve this was simply use a custom component.
ionic generate component navbar
Add the relevant navbar html to your component template
Add any other functionality to your component .ts file
Modify your selector to something relevant, (if used command above it
should just default to 'navbar'.
Also add the component to your app.module.ts declarations
Then in any of your page templates, simply use it as a custom element
e.g
<navbar></navbar>
<ion-content padding>
...
</ion-content/>
I had a similar issue creating an Ionic 4+ app (#ionic/angular 4.6.2), I wanted to add a login button and some other global stuffs in the header.
You can achieve that in a quite simple way.
Just add a ion-header containing a ion-toolbar in your app.component.html as a global header, like this:
<ion-header class="page-header">
<ion-toolbar id="main-toolbar">
<ion-title>
<ion-label>{{ pageTitle }}</ion-label>
</ion-title>
<!-- add here all the things you need in your header -->
</ion-toolbar>
</ion-header>
<ion-router-outlet id="content" main></ion-router-outlet>
The problem here is that the "global header" will overlay the content of any page if you do only that. So has a workaround just add an empty ion-header containing an empty ion-toolbar on top of all your pages before the content tag, as follow:
<ion-header>
<ion-toolbar></ion-toolbar>
</ion-header>
<ion-content>
<!-- your content here -->
</ion-content>
Doing that the "global header" will overlay the page header and the content will begin just after it.
Then you can manage all the code for your global header controls in your app.component.ts file.
I guess there could be some strange behaviour if the main header has a height greater than the "standard" toolbar height but with some nice CSS you should be able to fix it.
Furthermore, this workaround works fine with a side menu.
Hope it helps!
I have since found out this is not possible. The only way to achieve this is by providing an <ion-navbar> and that will handle the back button automatically.
E.g.:
<ion-navbar *navbar>
<button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Settings</ion-title>
</ion-navbar>
<ion-content padding class="hub-settings">
<!-- content here -->
</ion-content>