I want to make a custom component for the Quasar Dialog. And inside that component I want to use slots, but I'm not sure how to do that.
This is my CustomDialogComponent.vue where I have defined a cancelBtn slot and a confirmBtn slot:
<template>
<!-- notice dialogRef here -->
<q-dialog ref="dialogRef" #hide="onDialogHide">
<q-card class="q-dialog-plugin">
<q-card-section>
<strong>{{ title }}</strong>
</q-card-section>
<q-card-section>
<slot name="cancelBtn" #click="handleCancelClick"></slot>
<slot name="confirmBtn" #click="handleConfirmClick"></slot>
</q-card-section>
</q-card>
</q-dialog>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
import { useDialogPluginComponent } from 'quasar';
defineProps({
title: {
type: String,
required: false,
default: 'Alert',
},
});
defineEmits([
...useDialogPluginComponent.emits,
]);
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
useDialogPluginComponent();
const handleConfirmClick = () => {
console.log('Confirm Button Clicked');
onDialogOK();
};
const handleCancelClick = () => {
console.log('Cancel Button Clicked');
onDialogCancel();
};
</script>
And the Quasar docs show that I can invoke it via a $q.dialog({ ... }) Object. With props etc all set inside that object. So that would look something like this:
<template>
<div #click="showDialog">Show The Dialog</div>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar';
import CustomDialogComponent from 'src/components/CustomDialogComponent.vue'
const $q = useQuasar();
const showDialog = () => {
$q.dialog({
component: CustomDialogComponent,
// props forwarded to your custom component
componentProps: {
title: 'Alert title goes here',
},
})
};
</script>
But there are no properties inside the Dialog Object for me to pass in my slots. So where can I pass in the cancelBtn and confirmBtn slots I created in CustomDialogComponent.vue?
I asked directly and apparently there is no way to use slots at this time. They might add this functionality later.
Related
I'm working to create a geocoding component that allows a user to search for their address, using Quasar's <q-select /> component. I'm running in to one issue with the popup however.
After a user enter's the search query, I fetch the results from an API and the results are set to a reactive local state (which populates the select's options). Instead of the popup displaying though, it closes, and I have to click on the chevron icon twice for the popup to display the results.
This first image is what it looks like when I first click in to the input.
The second image shows what happens after entering a query. The data is fetched, options are set, and the popup closes.
The third image shows the select after clicking on the chevron icon twice.
How do I programmatically show the popup, so that once the results are fetched, the popup is displayed correctly?
Edit: Created a working repro here.
<template>
<q-select
ref="geolocateRef"
v-model="state.location"
:options="state.locations"
:loading="state.loadingResults"
clear-icon="clear"
dropdown-icon="expand_more"
clearable
outlined
:use-input="!state.location"
dense
label="Location (optional)"
#clear="state.locations = undefined"
#input-value="fetchOptions">
<template #prepend>
<q-icon name="place " />
</template>
<template #no-option>
<q-item>
<q-item-section class="text-grey">
No results
</q-item-section>
</q-item>
</template>
</q-select>
</template>
<script lang='ts' setup>
import { reactive } from 'vue';
import { debounce, QSelect } from 'quasar';
import { fetchGeocodeResults } from '#/services';
const state = reactive({
location: undefined as string | undefined,
locations: undefined,
loadingResults: false,
geolocateRef: null as QSelect | null,
});
const fetchOptions = debounce(async (value: string) => {
if (value) {
state.loadingResults = true;
const results = await fetchGeocodeResults(value);
state.locations = results.items.map(item => ({
label: item.title,
value: JSON.stringify(item.position),
}));
state.loadingResults = false;
state.geolocateRef?.showPopup(); // doesn't work?
}
}, 500);
</script>
I'd also posted this question over in the Quasar Github discussions, and someone posted a brilliant solution.
<template>
<q-select
v-model="state.location"
:use-input="!state.location"
input-debounce="500"
label="Location (optional)"
:options="options"
dense
clear-icon="bi-x"
dropdown-icon="bi-chevron-down"
clearable
outlined
#filter="fetchOptions">
<template #prepend>
<q-icon name="bi-geo-alt" />
</template>
<template #no-option>
<q-item>
<q-item-section class="text-grey">
No results
</q-item-section>
</q-item>
</template>
</q-select>
</template>
<script lang='ts' setup>
import { reactive, ref } from 'vue';
import { QSelect } from 'quasar';
import { fetchGeocodeResults } from '#/services';
interface Result {
position: {
lat: number;
lng: number;
}
title: string;
}
const state = reactive({
...other unrelated state,
location: undefined as string | undefined,
});
const options = ref([]);
const fetchOptions = async (val: string, update) => {
if (val === '') {
update();
return;
}
const needle = val.toLowerCase();
const results = await fetchGeocodeResults(needle);
options.value = results.items.map((item: Result) => ({
label: item.title,
value: JSON.stringify(item.position),
}));
update();
};
</script>
New to Quasar & Vue.
I am using q-file which allow pick file & drag to drop file.
However, how do i display the image for preview?
Q-uploader seems work but how do i change the ui of it?
Link to component from Quasar:
https://quasar.dev/vue-components/file-picker
In you template define a q-file and q-img element. Add a #change handler and updateFile function. The q-img will contain the picture you selected.
import { ref } from 'vue';
import { defineComponent } from 'vue';
<script lang="ts">
export default defineComponent({
name: 'Component Name',
components: {},
setup () {
const image = ref(null);
const image1Url = ref('')
return {
image,
image1Url,
updateFile() {
imageUrl.value = URL.createObjectURL(image.value);
}
}
}
})
</script>
<div>
<q-file
v-model="image"
label="Pick one file"
filled
style="max-width: 300px"
#change="updateFile()"
/>
</div>
<div>
<q-img
:src="imageUrl"
spinner-color="white"
style="height: 140px; max-width: 150px"
/>
</div>
Create an #change hook on q-file:
In the script set the url from the file passed in from q-file:
how can I avoid “You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors.” if I am using google-map-react to display the map and react-places-autocomplete in another component to get the address and coordinates ?
//LocationMapPage component that displays the map box and pass the props to it
class LocationMapPage extends Component {
render() {
let {latLng,name,address} = this.props.location;
return (
<MapBox lat={latLng.lat} lng={latLng.lng} name={name} address={address}/>
)
}
}
//MapBox component
import React from "react";
import GoogleMapReact from 'google-map-react';
import apiKey from "../../configureMap";
const Marker = () => <i className="fa fa-map-marker fa-2x text-danger" />
const MapBox = ({lat,lng, name, address}) => {
const center = [lat,lng];
const zoom = 14;
return (
<div style={{ height: '300px', width: '100%' }}>
<GoogleMapReact
bootstrapURLKeys={{ key: apiKey }}
defaultCenter={center}
defaultZoom={zoom}
>
<Marker
lat={lat}
lng={lng}
text={`${name}, ${address}`}
/>
</GoogleMapReact>
</div>
);
}
export default MapBox;
Map is blank:
The Error in the console:You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors.
How to solve?
I am using google-map-react, react-places-autocomplete in the project.
AS temporary solution to my specific use case where I use the google map API's in two different components I have just added the script in the index.html:
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places"></script>
I did it in order to avoid that particular error as per of the documentation on the react-places-autocomplete GitHub page.
Unfortunately the link in the head of the index.html caused the same error. I found another workaround. Not the best solution, but works for now:
import React, { useEffect, useState } from 'react';
import GoogleMapReact from 'google-map-react';
export default () => {
const [mapActive, setMapActive] = useState(false);
useEffect(() => {
const t = setTimeout(() => {
setMapActive(true)
}, 100);
return () => {
window.clearTimeout(t);
};
}, [])
return (
<>
{ mapActive && <GoogleMapReact
bootstrapURLKeys={ {
key: ...,
language: ...
} }
defaultCenter={ ... }
defaultZoom={ ... }
>
</GoogleMapReact> }
</>
);
};
You could set a global variable and load the Google JavaScript only if the global variable is not set:
<script type="text/javascript">
if(document.isLoadingGoogleMapsApi===undefined) {
document.isLoadingGoogleMapsApi=true;
var script = document.createElement('script');
script.src='https://maps.googleapis.com/maps/api/js?key=[your-key]&callback=[yourInitMethodName]&v=weekly';
script.type='text/javascript';
script.defer=true;
document.getElementsByTagName('head')[0].appendChild(script);
}else{
[yourInitMethodName]();
}
</script>
In my case there is an arbitrary number of maps in a web application (starting at 0) and the user can add additional maps at runtime.
Most of the users do not use any map so loading it by default would cost unnecessarily loading time.
I have an Electron app + Vue for rooting. I am having problems loading the content into a newly opened window. The window is launched from a Vue component. When it opens I get a blank window and:
Not allowed to load local resource: file:///app/src/Products.vue
I have tried different methods mentioned on stackoverflow but the error still persists.
<style scoped>
</style>
<template>
<div class="container-fluid">
Parent window...
<button type="submit" class="btn btn-primary" v-on:click="add">+ Add Product</button>
</div>
</template>
<script>
export default {
methods: {
add: function () {
const remote = require('electron').remote
const BrowserWindow = remote.BrowserWindow
let win
win = new BrowserWindow({
height: 600,
width: 800
})
win.loadURL(`file://${__dirname}/app/src/Products.vue`)
win.openDevTools()
}
}
}
</script>
In your case, child window must be created from the main process to launch a child window with local resources in Electron. You can use ipc (ipcMain, ipcRenderer) for this.
For example,
In main process :
function createChildWindow(payload) {
let child = new BrowserWindow({ parent :mainWindow
});
child.loadURL(url.format({
pathname: path.join(__dirname, 'child.html'),
protocol: 'file:',
slashes: true,
}));
child.once('ready-to-show', () => {
child.show()
});
}
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
createChildWindow(arg);
});
In renderer process(web page) :
const {ipcRenderer} = window.require('electron')
async launchChildWindow(){
ipcRenderer.send('asynchronous-message', '<payload>');
}
You can also write custom events like this,
// Renderer process
ipcRenderer.invoke('some-name', someArgument).then((result) => {
// ...
})
// Main process
ipcMain.handle('some-name', async (event, someArgument) => {
const result = await doSomeWork(someArgument)
return result
})
Using jquery-ui to create a dialog is pretty easy:
<script>
$(function() {
$( "#dialog" ).dialog();
});
</script>
<div id="dialog" title="Basic dialog">
<p>This is the default dialog which is useful for displaying information. The dialog window can be moved, resized and closed with the 'x' icon.</p>
</div>
...but one still needs a div in the HTML for this to work. In Dojo:
var dlg = new dijit.Dialog({
title:"dialog",
style: "width:30%;height:300px;"
});
dlg.show();
would just do the trick without anything specified in the html section, can jquery-ui do this? (I have to use jquery-ui here)
Thanks,
David
While I'm not sure why you would want to open a dialog with no content, you could easily create a new one on the fly and invoke the jquery dialog against it:
$("<div>hello!</div>").dialog();
basic code
var d = $("#someId");
if (d.length < 1)
d = $("<div/>").attr("id", "someId")
.appendTo("body");
else
d.dialog('destroy');
d.html('some message')
.dialog({ some_options })
.dialog("open");
and you can probably put rap this in an extension method.
Update (my full code listing)
(function($) {
$.extend({
showPageDialog: function (title, content, buttons, options) {
/// <summary>Utility to show a dialog on the page. buttons and options are optional.</summary>
/// <param name="buttons" type="Object">Dialog buttons. Optional, defaults to single OK button.</param>
/// <param name="options" type="Object">Additional jQuery dialog options. Optional.</param>
if (!buttons)
buttons = { "Ok": function () { $(this).dialog("close"); } };
var defOptions = {
autoOpen: false,
modal: true,
//show: "blind",
//hide: "explode",
title: title,
buttons: buttons
};
if (options)
defOptions = $.extend(defOptions, options);
var pd = $("#pageDialog");
if (pd.length < 1)
pd = $("<div/>").attr("id", "pageDialog")
.appendTo("body");
else
pd.dialog('destroy');
pd.html(content)
.dialog(defOptions)
.dialog("open");
}
}//end of function show...
)//end of extend Argument
})(jQuery)
Sample Usage
$.showPageDialog(title, message, {
"Yes": function () {
$(this).dialog("close");
// do something for 'yes'
},
"No": function () {
// do something for no
}
}
var div = document.createElement('div');
div.innerHTML = "Hello World";
$(div).dialog();
Juan Ayalas solution should work for modal Dialogs.
For a non modal dialog it would be better to track the id inside the function.
I use the following code which is not perfect but should work to ensure that the
id is unique. The code is nearly equal to Juan Ayalas example but uses a counter to avoid a duplicate id. (Furthermore I deleted the OK-Button as default).
(function($)
{
var dCounter=0; //local but "static" var
$.extend({
showPageDialog: function (title, content, buttons, options) {
/// <summary>Utility to show a dialog on the page. buttons and options are optional.</summary>
/// <param name="buttons" type="Object">Dialog buttons. Optional, defaults to nothing (single OK button).</param>
/// <param name="options" type="Object">Additional jQuery dialog options. Optional.</param>
if (!buttons)
buttons = {}; //{ "Ok": function () { $(this).dialog("close"); } };
var defOptions = {
autoOpen: false,
width: "auto",
modal: false,
//show: "blind",
//hide: "explode",
title: title,
buttons: buttons
};
if (options)
defOptions = $.extend(defOptions, options);
dCounter++;
//console.log("dCounter is " + dCounter);
var pdId = "#pageDialog"+dCounter;
var pd = $(pdId);
if (pd.length < 1)
pd = $("<div/>").attr("id", pdId)
.appendTo("body");
else
pd.dialog('destroy');
pd.html(content)
.dialog(defOptions)
.dialog("open");
}//end of function showPageDialog
}//end of extend options
)//end of extend argument
}//end of function definition