How to check if vue3 component prop is explicitly passed? - components

I am trying to pass a function as property inside a component in Vue3. Here is the code:
// ExampleComponent.vue
<template>
Here goes component content...
</template>
<script>
export default {
name: ''
}
</script>
<script setup>
import { onMounted } from "vue"
const props = defineProps({
onLoad: {
type: Function,
default() {
return {}
}
}
})
onMounted(() => {
if (props.onLoad) { // This doesn't work and passes every time
props.onLoad()
}
})
</script>
Here is the parent component calling child ExampleComponent.vue. It may or may not pass the onLoad function.
<ExampleComponent \>
What I want is that call this function only if the property on-load is passed? Is there a way in Vue3 to check if the property is passed explicitly?

you can use watcheffect :
javascript
watchEffect(() => {
if(props.onLoad) props.onLoad()
});
once '''onLoad''' value changed, it will tigger.

Related

Can't get html element using js file in SPFX

I am trying to build dynamic content from a SharePoint list using SPFX. I'd like to use jQuery to build an accordion view of the data. The issue is that I can't even seem to get the element once the page is rendered.
In my code I am requiring a file called ota.js with the following code:
console.log('Start');
function otaExpand(){
console.log('otaExpand Function Called');
let spListContainer = document.getElementById('spListContainer');
console.log(spListContainer);
}
window.addEventListener("load", otaExpand());
In my ts file this is my render method:
public render(): void {
this.domElement.innerHTML = `
<div>
<div id="spListContainer">TEST</div>
</div>
`;
//this._renderListAsync();
//($('.accordion', this.domElement) as any).accordion();
}
When I review the console, I get my messages, but the element itself comes back as null.
console.log
I am using SharePoint 2019 on premise with the following configuration.
+-- #microsoft/generator-sharepoint#1.10.0
+-- gulp-cli#2.3.0
`-- yo#2.0.6
node --version
v8.17.0
I should also mention I am using TypeScript with no JavaScript framework.
Does anyone know why I can't access this element from my js file?
Thanks!
My overall goal is to call list data and apply an accordion style to it (https://jqueryui.com/accordion), but I can't even get passed capturing the element to change it.
I've tried calling my code from a js file as well as trying to put the code directly in the html. Neither worked.
OK, I finally figured out what I was doing wrong. I was calling my jQuery in the render() method rather than in _renderList where this.domElement actually makes sense.
Here's my code in case anyone wants to avoid the pain I put myself through. This allows you to specify a list in the site and you just need to add the fields you want to display.
import { Version } from '#microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneChoiceGroupOption,
IPropertyPaneConfiguration,
PropertyPaneChoiceGroup,
PropertyPaneCustomField,
PropertyPaneTextField
} from '#microsoft/sp-webpart-base';
import { escape } from '#microsoft/sp-lodash-subset';
import styles from './GetSpListItemsWebPart.module.scss';
import * as strings from 'GetSpListItemsWebPartStrings';
import {
SPHttpClient,
SPHttpClientResponse
} from '#microsoft/sp-http';
import * as jQuery from 'jquery';
import 'jqueryui';
import { SPComponentLoader } from '#microsoft/sp-loader';
import PropertyPane from '#microsoft/sp-webpart-base/lib/propertyPane/propertyPane/PropertyPane';
export interface IGetSpListItemsWebPartProps {
title: string;
description: string;
listField: string;
}
export interface ISPLists {
value: ISPList[];
}
export interface ISPList {
ID: string;
Title: string;
Website: {
Description : string,
Url : string
};
Description : string;
}
export default class GetSpListItemsWebPart extends BaseClientSideWebPart<IGetSpListItemsWebPartProps> {
private _getListData(): Promise<ISPLists> {
return this.context.spHttpClient.get(this.context.pageContext.web.absoluteUrl + "/_api/web/lists/GetByTitle('" + this.properties.listField + "')/Items",SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}
private _renderListAsync(): void {
this._getListData()
.then((response) => {
this._renderList(response.value);
})
.catch(() => {});
}
private _renderList(items: ISPList[]): void {
let listData = `
<h1>${this.properties.title}</h1>
<h2>${this.properties.description}</h2>
<div class="accordion">
`;
items.forEach((item: ISPList) => {
let Description : string;
item.Description ? Description = item.Description : Description = "";
listData += `
<h3> ${item.Title}</h3>
<div>
<table>
<tr>
<td>OTA URL</td>
<td>${item.Website.Description}</td>
</tr>
<tr>
<td>Description</td>
<td>${Description}</td>
</tr>
</table>
</div>
`;
});
listData += '</div>';
this.domElement.innerHTML = listData;
const accordionOptions: JQueryUI.AccordionOptions = {
animate: true,
collapsible: true,
icons: {
header: 'ui-icon-circle-arrow-e',
activeHeader: 'ui-icon-circle-arrow-s'
}
};
jQuery('.accordion', this.domElement).accordion(accordionOptions);
}
public render(): void {
this._renderListAsync();
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('title',{
label: strings.TitleFieldLabel
}),
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
}),
PropertyPaneTextField('listField', {
label: strings.ListFieldLabel
})
]
}
]
}
]
};
}
public constructor() {
super();
SPComponentLoader.loadCss('//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css');
}
}
Your code from the "ota.js" file is probably called before your HTML is initialized (i.e. before the "render()" function is executed). To make sure this is the case, you could add log to the "render()" function to see when it's called.
In other words, "window.load" event happens long before "render()" function is called. This is how web parts are loaded - dynamically after full load of the page. Or "window.load" does not happen at all - web parts may be loaded by the user when using the page designer, i.e. without page reload.
To fix the issue, you should get the element after it's created, i.e. after the "render()" function creates the element you are trying to get.

wrapper emitted() not working in vue test utils composition api

I am trying to make a simple test to check that the button is emitting an event called "click" every time it is detected, the problem is that when I use wrapper.emitted('click') to validate that it is receiving it, it always arrives as an empty object... I don't know what I may be doing wrong.
Current versions:
Vue: 3.2.31
Vitest: 0.7.12
Vite: 2.8.6
Vue/test-utils: 2.0.0-rc.17
<template>
<button
class="eci-button"
:disabled="props.disabled"
#click="handleClick"
>
{{ props.label }}
</button>
</template>
<script setup lang="ts">
/* Interfaces and types */
interface Props {
label: string
disabled?: boolean
}
/* Props */
const props = withDefaults(defineProps<Props>(), {
disabled: false
})
/* Events */
const emit =
defineEmits<{ (e: 'click'): void }>()
/* Methods */
const handleClick = () => {
emit('click')
}
</script>
<style lang="scss" src="./Button.scss"></style>
Test
test('should render and emit event at click', async () => {
const label = 'Siguiente'
const wrapper = mount(Button, {
props: {
label
}
})
wrapper.trigger('click')
expect(wrapper.emitted()).toHaveProperty('click')
})
Result
OK, I found the problem... I am using happy-dom as test environment together with Vitest... the problem is that when you pass a property as "optional" for some reason it interprets it as if it was "true"... to explain myself...
I have a default prop "disabled" set to "false" which means that my button should print as:
in the DOM.. but, for some reason it interprets it as being true and that causes the "click" events not being executed.. i will keep looking for some fix for this, at the moment i have migrated for jsdom and solved.

How to send data from parent to child component in Vue.js

I am new to vue.js and currently I am building an app for learning purposes.
What I want to do:
I have a parent component which has a bunch of buttons with different id's.
The child component will wait for those id's to be sent by the parent and it will decide what to display based on the id. Thats all.
I wont post the full code because it's too large but I have tried a bunch of stuff like props and state but honestly it is so confusing.
I come from React background and I am still confused.
Parent component
<template>
<button id="btn1">Show banana</button>
<button id="btn2">Show orange</button>
</template>
<script>
export default {
name: 'Parent',
data: function {
//something
},
props: {
// ?????
}
};
</script>
**Child component**
<template>
<p v-html="something.text">
</template>
<script>
export default {
name: 'Child',
data: function() {
something: ''
if(id from parent === id I want) {
something = object.withpropIneed
}
},
};
</script>
You need to map the data from parent and pass it to child, thats it!
In example i make passing a html string and binding that html received through 'fromParentHtml' prop mapped on child, so inside child component 'this.fromParentHtml' pass to exists because it is defined in props and every time you click in parent button executes the 'show' function and change the value from passed prop to child through parent 'html' data .. =)
<template>
<div>
Current html sent to child '{{html}}'
<br>
<button #click="show('banana')">Banana</button>
<button #click="show('orange')">Orange</button>
<button #click="show('apple')">Apple</button>
<!-- Create child component -->
<child-component :fromParentHtml="html"></child-component>
</div>
</template>
<script>
export default {
name: "test3",
components: {
'child-component': {
template: "<div>Child component... <br> <span v-html='fromParentHtml'></span> </div>",
//Child component map a prop to receive the sent html from parent through the attribute :fromParentHtml ...
props: {
fromParentHtml: {
required: true,
type: String
}
}
}
},
data(){
return {
html: ''
}
},
methods: {
show(fruit){
this.html = '<span>The fruit is ' + fruit + ' !</span>';
}
}
}
</script>
<style scoped>
</style>
If helped you please mark as correct answer! Hope it helps.
Edit 1:
Assuming you have webpack to work with single file components, to import another component just do:
<template>
<div>
<my-child-component></my-child-component>
</div>
</template>
<script>
//Import some component from a .vue file
import ChildComponent from "./ChildComponent.vue";
export default {
components: {
//And pass it to your component components data, identified by 'my-child-component' in the template tag, just it.
'my-child-component': ChildComponent
},
data(){
},
methods: {
}
}
</script>
Just for the sake of it, I think you were looking for this:
<template>
<button id="btn1" #click = "id = 1">Show banana</button>
<button id="btn2" #click = "id = 2">Show orange</button>
<child-component :childid = "id"></child-component>
</template>
<script>
import childComponent from 'childComponent'
export default {
name: 'Parent',
data () {
return {
id: 0
}
},
components: {
childComponent
}
};
</script>
**Child component**
<template>
<p v-html="something.text">
</template>
<script>
export default {
name: 'Child',
props: {
childid: String
},
data: function() {
something: ''
if(this.childid === whatever) {
something = object.withpropIneed
}
},
};
</script>
Solved my problem by taking a different approach.
I have implemented state and my component behaves exactly as I wanted to.
I found this link to be helpful for me and solved my problem.
Thank you.

Setting iframe height to scrollHeight in ReactJS using IframeResizer

The typical solution to the problem doesn't work in in React due to its dynamically generated component structure and event model, as opposed to traditional static HTML. I tried with react-iframe-resizer-super but not found perfect solution.
My code:
import React, {PropTypes} from 'react';
import ReactIframeResizer from 'react-iframe-resizer-super';
class Frame extends React.Component {
constructor() {
super();
}
componentDidUpdate() {
const iframeResizerOptions = {
// log: true,
// autoResize: true,
checkOrigin: false,
// resizeFrom: 'parent',
// heightCalculationMethod: 'max',
// initCallback: () => { console.log('ready!'); },
// resizedCallback: () => { console.log('resized!'); },
};
}
render() {
return (
<div style={{position: 'relative'}}>
<IframeResizer iframeResizerOptions={iframeResizerOptions}>
<iframe scrolling="no" src="https://en.wikipedia.org/wiki/Main_Page" allowfullscreen
style={{width:'100%', height:'100%'}}
}}></iframe>
</IframeResizer>
</div>
);
}
}
Then I got following error:
Uncaught ReferenceError: IframeResizer is not defined
Is there a way in React to set the height of an iframe to the height of its scrollable contents or is there any alternative way to archive this requirement?
I refer following link:
https://www.npmjs.com/package/react-iframe-resizer-super
This question is long decease, but I thought I would add just in case anyone else looking for clarification on using react-iframe-resizer-super + iframe-resizer (JS)
The problem in the code above is a misspelling of the imported component.
import ReactIframeResizer from 'react-iframe-resizer-super';
Should be:
import IframeResizer from 'react-iframe-resizer-super';
As you've used it inside your Frame component.
For those looking for clarification on using the library, here is my dead simple working solution:
Install dependencies on React project containing iFrame yarn add react-iframe-resizer-super iframe-resizer
Include iframeResizer.contentWindow.min.js on the page that you are using as the source of your iFrame.
Usage in React:
DynamicIFrame.jsx
import React from 'react';
import IframeResizer from 'react-iframe-resizer-super';
export const DynamicIFrame = props => {
const { src } = props;
const iframeResizerOptions = {
log: true,
// autoResize: true,
checkOrigin: false,
// resizeFrom: 'parent',
// heightCalculationMethod: 'max',
// initCallback: () => { console.log('ready!'); },
// resizedCallback: () => { console.log('resized!'); },
};
return (
<IframeResizer src={src} iframeResizerOptions={iframeResizerOptions} />
);
};

Call a handlebars helper from a rendered template

I'm using a helper method to render a template, like so:
module.exports = (Handlebars) => {
Handlebars.registerHelper('foo', () => {
var template = require('../path/to/template.hbs')
return template;
})
}
In the rendered template, I'd like to call another helper, bar.
// template.hbs
<div>
{{foo}}
</div>
// bar.js
module.exports = (Handlebars) => {
Handlebars.registerHelper('bar', ()=> {
// do something here
}
}
But the rendered template seems to have no visibility of the helper, i.e. bar isn't called. What's a good way to get around this?

Resources