I am using laravel livewire for my website and on that web, I created a multi-step form where I have to fetch google maps API in the first step. as I know I have to use wire: ignore when using third-party JavaScript library in Livewire component. but the problem comes when I switch from form step 2 to step 1, where google map is not rendered
This is my livewire view.
<div wire:ignore>
<div id="address-map-container" class="mt-2" style="width:100%;height:320px; ">
<div style="width: 100%; height: 100%" id="address-map"></div>
</div>
</div>
<!-- End Google Maps -->
<!-- Seach in google maps -->
<input type="text" wire:model.lazy="address_address" id="address-input"
class="mt-4 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md map-input"
placeholder="Search in Google Maps">
<input type="hidden" wire:model.lazy="address_latitude" id="address-latitude" value="0" />
<input type="hidden" wire:model.lazy="address_longitude" id="address-longitude" value="0" />
<!-- End in Seach google maps -->
This is my javascript code.
<script>
function initialize() {
$('form').on('keyup keypress', function(e) {
var keyCode = e.keyCode || e.which;
if (keyCode === 13) {
e.preventDefault();
return false;
}
});
const locationInputs = document.getElementsByClassName("map-input");
const autocompletes = [];
const geocoder = new google.maps.Geocoder;
for (let i = 0; i < locationInputs.length; i++) {
const input = locationInputs[i];
const fieldKey = input.id.replace("-input", "");
const isEdit = document.getElementById(fieldKey + "-latitude").value != '' && document.getElementById(fieldKey + "-longitude").value != '';
const latitude = parseFloat(document.getElementById(fieldKey + "-latitude").value) || -6.251855;
const longitude = parseFloat(document.getElementById(fieldKey + "-longitude").value) || 106.978942;
const map = new google.maps.Map(document.getElementById(fieldKey + '-map'), {
center: {lat: latitude, lng: longitude},
zoom: 15
});
const marker = new google.maps.Marker({
map: map,
position: {lat: latitude, lng: longitude},
});
marker.setVisible(isEdit);
const autocomplete = new google.maps.places.Autocomplete(input);
autocomplete.key = fieldKey;
autocompletes.push({input: input, map: map, marker: marker, autocomplete: autocomplete});
}
for (let i = 0; i < autocompletes.length; i++) {
const input = autocompletes[i].input;
const autocomplete = autocompletes[i].autocomplete;
const map = autocompletes[i].map;
const marker = autocompletes[i].marker;
google.maps.event.addListener(autocomplete, 'place_changed', function () {
marker.setVisible(false);
const place = autocomplete.getPlace();
geocoder.geocode({'placeId': place.place_id}, function (results, status) {
if (status === google.maps.GeocoderStatus.OK) {
const lat = results[0].geometry.location.lat();
const lng = results[0].geometry.location.lng();
setLocationCoordinates(autocomplete.key, lat, lng);
}
});
if (!place.geometry) {
window.alert("No details available for input: '" + place.name + "'");
input.value = "";
return;
}
if (place.geometry.viewport) {
map.fitBounds(place.geometry.viewport);
} else {
map.setCenter(place.geometry.location);
map.setZoom(17);
}
marker.setPosition(place.geometry.location);
marker.setVisible(true);
});
}
}
function setLocationCoordinates(key, lat, lng) {
const latitudeField = document.getElementById(key + "-" + "latitude");
const longitudeField = document.getElementById(key + "-" + "longitude");
latitudeField.value = lat;
longitudeField.value = lng;
Livewire.emit('lat')
}
And this is my livewire controller
// Form properties
public $currentPage = 1;
public $property_name;
public $address_address;
public $postal_code;
public $property_phone_number;
public $number_of_rooms;
public $pages = [
1 => [
'heading' => 'General Information',
'subheading' => 'Enter',
],
2 => [
'heading' => 'Property Detail',
'subheading' => 'Enter',
],
];
private $validationRules = [
1 => [
'property_name' => ['required', 'min:3'],
'address_address' => ['required', 'min:3'],
'postal_code' => ['required', 'min:3'],
'property_phone_number' => ['required', 'min:3'],
'number_of_rooms' => ['required'],
],
2 => [
'password' => ['required', 'string', 'min:8'],
'confirmPassword' => ['required', 'string', 'same:password', 'min:8'],
],
];
public function updated($propertyName)
{
$this->validateOnly($propertyName, $this->validationRules[$this->currentPage]);
}
public function goToNextPage()
{
$this->currentPage++;
}
public function goToPreviousPage()
{
$this->currentPage--;
}
Can someone help me how to emit the google maps API and listen to it in JavaScript, therefore, google maps will always be rendered? Thanks
Related
I have some google maps api that i've already craeted in javascript, but, for some time my collagues decided to use vue framework on the apps.
I tried to put my initMap of my googlemaps javascript inside vue methods and trye to call it on windows.initmap outside vue app, but it doesn't work,
here are my javascript code bellow
let base_url = '{{ envx('APP_BASE_URL') }}';
let cdn = '{{ envx('CDN_ENDPOINT') }}';
let endpoint = '{{ envx('API_ENDPOINT') }}';
addEventListener('load', () => {
const {
createApp
} = Vue;
const initialState = () => {
return {
containerClass: 'container-grid',
loading: false,
properties: <?= json_encode($list_properti) ?>,
filter: {
page: 1,
limit: 12,
keyword: '',
harga: '',
status_unit: '',
jenis: '',
id_propinsi: '',
}
}
}
const app = createApp({
data() {
return initialState()
},
methods: {
checkData() {
console.log(this.properties.DATA);
},
getPropertiImage(imageUrl) {
const urlOnly = `{{ file_exists('${imageUrl}') }}`
if (urlOnly) return base_url + 'assets/img/no-data.png'
if (imageUrl === null) return base_url + 'assets/img/no-data.png'
const checkImage = imageUrl.split("")
const getFlag = [checkImage[0], checkImage[1]].join("")
if (getFlag !== '1|') return base_url + 'assets/img/no-data.png'
return imageUrl.replace('1|', cdn + '?key=')
},
setToBillions(item) {
let price = item;
let idrPrices = `{{ rupiah((float) '${price}') }}`;
let prices = idrPrices.split('.');
if (prices.length > 3) {
return [prices[0], prices[1]].join('.') + " Juta";
}
return idrPrices;
},
cicilan(val) {
let cicilan = (val / 1000000).toFixed(1);
if (cicilan < 1) {
return 'Rp.' + (cicilan * 1000).toLocaleString('id-ID') + 'rb/bln';
}
return 'Rp.' + cicilan.toLocaleString('id-ID') + 'jt/bln';
},
getAvatar(url) {
if (url) {
let explodeAva = url.split('|member');
if (explodeAva.length == 2) {
return cdn + '?key=member' + explodeAva[1];
}
return url;
}
return base_url + 'assets/img/avatar/1552FBA78C75D6FBA33F.png';
},
changePage(id) {
if (this.loading) {
return false;
}
document.getElementById('container-loading').scrollIntoView({
behavior: 'auto',
block: 'start',
inline: 'nearest'
});
this.filter.page = id;
this.updateContent()
this.initMap()
},
async initMap() {
const centerPosition = {
lat: -1.6160679698214473,
lng: 117.38277669882174
}
let map = new google.maps.Map(document.getElementById("container-map"), {
center: centerPosition,
zoom: 5,
});
const areaProperties = []
for (const properti of this.properties.DATA) {
const exampleUrl = `{{ envx('CDN_ENDPOINT') }}?key=`;
let imageUrl = properti.GBR1;
imageUrl = imageUrl.replace('1|', exampleUrl);
const urlAvatar = ``
let imageMember = properti.AVATAR_MEMBER;
const imageAvatar = imageMember.replace('1|', imageMember);
const propertiAreas = {
position: new google.maps.LatLng(properti.LATITUDE, properti.LONGITUDE),
content: ` <template v-if="!loading" v-for="(properti, index) in properties.DATA">
<div class="card card-container card-shadow card--radius mb-2">
<div class="img-container">
<div class="img-top">
<div class="badge-report bg-colors-blue" v-html="properti.JENIS_PROPERTI"></div>
</div>
<img class="card-img-top img-card" v-bind:src="'' + getPropertiImage(properti.GBR1)"
alt="''+ properti.NAMA">
</div>
<div class="card--body m-4">
<small class="d-flex gap-2 align-items-center text-dim">
<img src="assets/img/shape/location.png" class="icon-location" height="12">
<div style="width: 100%; text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap; "
v-html="properti.ALAMAT"></div>
</small>
<h4 v-html="properti.NAMA"></h4>
<small class="text-dim">Mulai dari</small>
<h3 v-html="'Rp. '+ setToBillions(properti.HARGA)"></h3>
<small class="text-dim d-block">Cicilan dari <b class="text-black"
style="font-family: 'Futura'" v-html="cicilan(properti.CICILAN)"></b></small>
<small class="text-dim d-block">Suku Bunga <b class="text-black"
style="font-family: 'Futura'" v-html="'Dari '+ properti.BUNGA + '%'"></b></small>
<small class="d-flex gap-2 align-items-center mt-2"
style="font-size: 12px; color: var(--c-blue); font-family: 'FuturaMD';">
<img src="'' + getAvatar(properti.AVATAR_MEMBER)" height="25" class="rounded-circle">
<div v-html="properti.NAMA_AGEN"></div>
</small>
</div>
<div class="card-label--foot bandingkan-button" role="button" #onclick="handleCompare()">
Bandingkan
</div>
</div>
</template>
`
}
areaProperties.push(propertiAreas)
}
var infowindow = new google.maps.InfoWindow();
const icon = {
url: 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.2.1 by #fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M36.8 192H603.2c20.3 0 36.8-16.5 36.8-36.8c0-7.3-2.2-14.4-6.2-20.4L558.2 21.4C549.3 8 534.4 0 518.3 0H121.7c-16 0-31 8-39.9 21.4L6.2 134.7c-4 6.1-6.2 13.2-6.2 20.4C0 175.5 16.5 192 36.8 192zM64 224V384v80c0 26.5 21.5 48 48 48H336c26.5 0 48-21.5 48-48V384 224H320V384H128V224H64zm448 0V480c0 17.7 14.3 32 32 32s32-14.3 32-32V224H512z"/></svg>'
),
scaledSize: new google.maps.Size(20, 20)
}
let marker, i;
for (i = 0; i < areaProperties.length; i++) {
marker = new google.maps.Marker({
position: areaProperties[i].position,
icon: icon,
map: map,
});
google.maps.event.addListener(marker, 'click', (function(marker, i) {
return function() {
infowindow.setContent(areaProperties[i].content);
infowindow.open(map, marker);
}
})(marker, i));
}
},
// async updateContent() {
async updateContent() {
this.loading = true;
this.containerClass = 'w-full'
const filterData = this.filter;
const filterStrings = []
const lastKey = Object.keys(filterData).pop();
for (const key in filterData) {
if (filterData[key] !== "" || filterData[key] !== null) {
filterStrings.push(key)
filterStrings.push('=')
filterStrings.push(filterData[key])
if (key !== lastKey) {
filterStrings.push('&')
}
}
}
//FIXME string zero values
const params = filterStrings.join('')
await $.ajax({
type: 'get',
dataType: 'json',
url: base_url + 'api/properti/list-properti?' + params,
success: res => {
this.properties = res;
}
})
this.loading = false;
this.containerClass = 'container-grid'
}
}
}).mount('#app')
window.initMap = app.initMap();
})
And here is the error that i got
How could i solve this?
You shouldn't call window.initMap = app.initMap(); after creating the Vue instance but rather in the mounted lifecycle hook:
mounted() {
window.initMap = app.initMap();
}
I am using a 'Custom Datatable' solution in order to modify picklist values within a datatable. Project code may be referenced here:
https://live.playg.app/play/picklist-in-lightning-datatable
I have made changes so that I can retrieve data from the custom object: Payment__c, and I am attempting modify the picklist values for the Payment_Status__c field. My 'debugging' method has been to create numerous console.log statements to verify data during the updating process. Picklist values are currently hardcoded (have not figured out how to dynamically pull from SF yet). Inline edit of individual cells works fine, and I am able to save those values as well (though changes are not reflected until I perform a manual page refresh). Picklist selection is working, but I am unable to save the currently selected picklist value in the datatable.
I believe that the intended trigger event for picklist selection changes--'valueselect', is not being fired, and the handleSelection method is not receiving this event when a new picklist selection is made.
The lightning component used on Salesforce is c-customDatatableDemo:
customDatatableDemo.js
import { LightningElement, track, wire } from 'lwc';
import getPayments from '#salesforce/apex/PaymentController.getPayments';
import saveRecords from '#salesforce/apex/PaymentController.saveRecords';
import { updateRecord } from 'lightning/uiRecordApi';
import { refreshApex } from '#salesforce/apex';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class CustomDatatableDemo extends LightningElement {
#track data = [];
//have this attribute to track data changed
//with custom picklist or custom lookup
#track draftValues = [];
#track lastSavedData = [];
connectedCallback() {
this.columns = [
{
label: 'Name',
fieldName: 'Name',
editable: false
}, {
label: 'Invoice Number',
fieldName: 'Invoice_Number__c',
editable: true
}, {
label: 'Invoice Amount',
fieldName: 'Invoice_Amount__c',
type: 'currency',
editable: true
}, {
label: 'Invoice Date',
fieldName: 'Invoice_Date__c',
type: 'date',
editable: true
}, {
label: 'Payment Status',
fieldName: 'Payment_Status__c',
type: 'picklist',
typeAttributes:
{
placeholder: 'Choose Status',
options: [
{ label: 'Needs to Be Paid', value: 'Needs to Be Paid' },
{ label: 'Issued', value: 'Issued' },
{ label: 'Voided', value: 'Voided' },
] // List of Payment Status picklist options
, value: {fieldName: 'Payment_Status__c' } // default value for picklist
, context: {fieldName: 'Id' } // binding Payment Id with context variable to be returned back
}
},
{
label: 'Description', fieldName: 'Work_Description__c', type: 'text', editable: true
}];
// Get Payments data
getPayments()
.then(result => {
this.data = result;
this.error = undefined;
})
.catch(error => {
this.error = error;
this.data = undefined;
})
// Save last saved copy
this.lastSavedData = JSON.parse(JSON.stringify(this.data));
}
updateDataValues(updateItem) {
console.log('START--updateDataValues()');
let copyData = [... this.data];
copyData.forEach(item => {
if (item.Id === updateItem.Id) {
for (let field in updateItem) {
console.log('updateDataValues() item.Id = ' + JSON.stringify(updateItem.Id));
console.log('updateItem[field] = ' + JSON.stringify(updateItem[field]));
item[field] = updateItem[field];
console.log('UPDATED--item[field] = ' + updateItem[field]);
}
}
});
//write changes back to original data
this.data = [...copyData];
console.log('this.data = ' + JSON.stringify(this.data));
let tempData = [...this.data];
//console.log('tempData = ' + JSON.stringify(tempData));
console.log('END--updateDataValues()');
}
updateDraftValues(updateItem) {
console.log('START--updateDraftValues()');
console.log('stringify draft updateItem = ' + JSON.stringify(updateItem));
let draftValueChanged = false;
let copyDraftValues = [...this.draftValues];
//store changed value to do operations
//on save. This will enable inline editing &
//show standard cancel & save button
let i = 0;
copyDraftValues.forEach(item => {
if (item.Id === updateItem.Id) {
i++;
console.log('i = ' + i);
for (let field in updateItem) {
console.log(i + '. UpdateDraftValues--item id if selected...item Id = ' + item.Id + ' & item value = ' + item.value);
item[field] = updateItem[field];
console.log('item[field] = ' + updateItem[field]);
}
draftValueChanged = true;
console.log('draftValueChanged = TRUE');
}
});
//draftValueChanged = true;
if (draftValueChanged) {
console.log('YESdraftValueChanged');
console.log('copyDraftValues = ' + JSON.stringify(copyDraftValues));
this.draftValues = [...copyDraftValues];
//console.log('draftValues = ' + JSON.stringify(draftValues));
} else {
console.log('NOdraftValue!Changed');
this.draftValues = [...copyDraftValues, updateItem];
let testDraftValues = {... this.draftValues};
console.log('JSON.stringify(testDraftValues) = ' + JSON.stringify(testDraftValues));
}
console.log('STOP--updateDraftValues()')
}
//listener handler to get the context and data
//updates datatable
picklistChanged(event) {
console.log('START--picklistChanged()');
console.log('EVENT type - ' + event.type);
event.stopPropagation();
let dataReceived = event.detail.data;
let updatedItem = { ...dataReceived };
console.log('picklistChanged()...updatedItem = ' + JSON.stringify(updatedItem));
this.updateDraftValues(updatedItem);
this.updateDataValues(updatedItem);
/* console.log('event.value = ' + event.value);
this.value = event.target.value;
event.stopPropagation();
let dataReceived = event.detail.data;
let updatedItem = { ...dataReceived };
console.log('updatedItem.context ' + updatedItem.context);
console.log('updatedItem.value ' + updatedItem.value);
console.log('updatedItem = ' + JSON.stringify(updatedItem));
this.updateDraftValues(updatedItem);
this.updateDataValues(updatedItem);
console.log('picklistChanged() = ' + JSON.stringify(updatedItem)); */
console.log('STOP--picklistChanged()');
}
handleSelection(event) {
console.log('START--handleSelection()');
this.updateDraftValues(event.detail.draftValues[0]);
console.log('this.updateDraftValues(event.detail.draftValues[0]);')
/* event.stopPropogation();
let dataReceived = event.detail.data;
let updatedItem = { ...dataReceived };
this.updateDraftValues(updatedItem);
this.updateDraftValues(updatedItem); */
console.log('STOP--handleSelection() = ' + JSON.stringify(updatedItem));
}
//handler to handle cell changes & update values in draft values
handleCellChange(event) {
console.log('START--handleCellChange()');
console.log('handleCellChange');
this.updateDraftValues(event.detail.draftValues[0]);
console.log('handleCellChange value = ' + JSON.stringify(this.updateDraftValues));
console.log('END--handleCellChange()');
}
handleSave(event) {
if (event.type === 'picklistchanged'){
}
console.log('START--handleSave');
console.log('Updated items = ', this.draftValues);
// save last saved copy
this.lastSavedData = JSON.parse(JSON.stringify(this.data));
console.log('this.lastSavedData = ' + JSON.stringify(this.lastSavedData));
this.fldsItemValues = event.detail.draftValues;
console.log('this.fldsItemValues = ' + JSON.stringify(this.fldsItemValues));
const inputsItems = this.fldsItemValues.slice().map(draft => {
const fields = Object.assign({}, draft);
console.log('JSON.stringify() fields ' + JSON.stringify(fields));
return { fields };
});
// Show toast after successful update
const promises = inputsItems.map(recordInput => updateRecord(recordInput));
Promise.all(promises).then(res => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Success',
message: 'Records Updated Successfully!!',
variant: 'success'
})
);
this.fldsItemValues = [];
return this.refresh();
}).catch(error => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Error',
message: 'An Error Occured!!',
variant: 'error'
})
);
}).finally(() => {
// Clear draft values
this.draftValues = [];
});
// Refresh the window after successful save
//window.open('url','_self');
//document.location.reload(true);
//cmp.find("table-component-id").set("v.draftValues", null);
console.log('END--handleSave');
}
handleCancel(event) {
//remove draftValues & revert data changes
this.data = JSON.parse(JSON.stringify(this.lastSavedData));
this.draftValues = [];
}
async refresh() {
console.log('async refresh');
await refreshApex(this.data);
//this.connectedCallback();
}
}
customDatatableDemo.html
<template>
<lightning-card title="Invoicing" icon-name="custom:custom17">
<div class="slds-var-m-around_medium">
<template if:true={data}>
<c-custom-data-table
object-api-name="Payment__c"
key-field="Id"
data={data}
value=""
show-row-number-column
columns={columns}
onpicklistchanged={picklistChanged}
onvalueselect={handleSelection}
draft-values={draftValues}
oncellchange={handleCellChange}
onsave={handleSave}
oncancel={handleCancel}>
</c-custom-data-table>
<template if:true={data.error}></template>
</template>
</div>
</lightning-card>
<p>Selected value is: {value}</p>
</template>
customDataTable.js
import LightningDatatable from 'lightning/datatable';
//import the template so that it can be reused
import DatatablePicklistTemplate from './picklist-template.html';
import { loadStyle } from 'lightning/platformResourceLoader';
import CustomDataTableResource from '#salesforce/resourceUrl/CustomDataTable';
export default class CustomDataTable extends LightningDatatable {
static customTypes = {
picklist: {
template: DatatablePicklistTemplate,
typeAttributes: ['label', 'placeholder', 'options', 'value', 'context'],
},
};
constructor() {
super();
Promise.all([
loadStyle(this, CustomDataTableResource),
]).then(() => {})
}
}
picklist-template.html (Same folder as customDataTable)
<template>
<c-datatable-picklist label={typeAttributes.label} value={typeAttributes.value}
placeholder={typeAttributes.placeholder} options={typeAttributes.options} context={typeAttributes.context}>
</c-datatable-picklist>
</template>
datatablePicklist.js
import { LightningElement, api, track } from 'lwc';
export default class DatatablePicklist extends LightningElement {
#api label;
#api placeholder;
#api options;
#api value;
#api context;
handleChange(event) {
//show the selected value on UI
this.value = event.detail.value;
//fire event to send context and selected value to the data table
this.dispatchEvent(new CustomEvent('picklistchanged', {
composed: true,
bubbles: true,
cancelable: true,
detail: {
data: { context: this.context, value: this.value }
}
}));
}
}
datatablePicklist.html
<template>
<div class="picklist-container">
<lightning-combobox name="picklist" label={label} value={value} placeholder={placeholder} options={options}
onchange={handleChange}></lightning-combobox>}
</div>
</template>
lwcEditSaveRow.js
import { LightningElement, wire, track } from 'lwc';
import getAccounts from '#salesforce/apex/lwcEditSaveRowCtrl.getAccounts';
import { updateRecord } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { refreshApex } from '#salesforce/apex';
const columns = [
{
label: 'Name',
fieldName: 'Name',
type: 'text',
}, {
label: 'Phone',
fieldName: 'Phone',
type: 'phone',
editable: true,
}, {
label: 'Industry',
fieldName: 'Industry',
type: 'text',
editable: true,
}, {
label: 'Type',
fieldName: 'Type',
type: 'text',
editable: true
}, {
label: 'Description',
fieldName: 'Type',
type: 'text',
editable: true
}
];
export default class LwcEditSaveRow extends LightningElement {
columns = columns;
#track accObj;
fldsItemValues = [];
#wire(getAccounts)
cons(result) {
this.accObj = result;
if (result.error) {
this.accObj = undefined;
}
};
saveHandleAction(event) {
this.fldsItemValues = event.detail.draftValues;
const inputsItems = this.fldsItemValues.slice().map(draft => {
const fields = Object.assign({}, draft);
return { fields };
});
const promises = inputsItems.map(recordInput => updateRecord(recordInput));
Promise.all(promises).then(res => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Success',
message: 'Records Updated Successfully!!',
variant: 'success'
})
);
this.fldsItemValues = [];
return this.refresh();
}).catch(error => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Error',
message: 'An Error Occured!!',
variant: 'error'
})
);
}).finally(() => {
this.fldsItemValues = [];
});
}
async refresh() {
await refreshApex(this.accObj);
}
}
lwcEditSaveRow.html
<template>
<lightning-card>
<div class="slds-m-around_medium">
<h3 class="slds-text-heading_medium"><lightning-icon icon-name="custom:custom84" size="small"></lightning-icon> <strong style="color:#270086; font-size:13px; margin-right:5px;"> How to inline Edit/Save Rows With Lightning Datatable in Lightning Web Component (LWC) </strong></h3>
<br/><br/>
<template if:true={accObj.data}>
<lightning-datatable key-field="Id"
data={accObj.data}
columns={columns}
onsave={saveHandleAction}
draft-values={fldsItemValues}
hide-checkbox-column
show-row-number-column>
</lightning-datatable>
</template>
<br/>
<br/>
<!--Start RelatedTopics Section-->
<div style="border:1px #ddd solid; padding:10px; background:#eee; margin:40px 0;">
<p data-aura-rendered-by="435:0"><img src="https://www.w3web.net/wp-content/uploads/2021/05/thumbsUpLike.png" width="25" height="25" style="vertical-align:top; margin-right:10px;" data-aura-rendered-by="436:0"><strong data-aura-rendered-by="437:0"><span style="font-size:16px; font-style:italic; display:inline-block; margin-right:5px;">Don't forget to check out:-</span>An easy way to learn step-by-step online free Salesforce tutorial, To know more Click <span style="color:#ff8000; font-size:18px;" data-aura-rendered-by="442:0">Here..</span></strong></p>
<br/><br/>
<p data-aura-rendered-by="435:0"><img src="https://www.w3web.net/wp-content/uploads/2021/07/tickMarkIcon.png" width="25" height="25" style="vertical-align:top; margin-right:10px;" data-aura-rendered-by="436:0"><strong data-aura-rendered-by="437:0"><span style="font-size:17px; font-style:italic; display:inline-block; margin-right:5px; color:rgb(255 128 0);">You May Also Like →</span> </strong></p>
<div style="display:block; overflow:hidden;">
<div style="width: 50%; float:left; display:inline-block">
<ul style="list-style-type: square; font-size: 16px; margin: 0 0 0 54px; padding: 0;">
<li>How to get selected checkbox value in lwc</li>
<li>how to display account related contacts based on AccountId in lwc</li>
<li>how to create lightning datatable row actions in lwc</li>
<li>how to use if and else condition in lwc</li>
<li>how to display selected radio button value in lwc</li>
</ul>
</div>
<div style="width: 50%; float:left; display:inline-block">
<ul style="list-style-type: square; font-size: 16px; margin: 0 0 0 54px; padding: 0;">
<li>display account related contacts based on account name in lwc</li>
<li>how to insert a record of account Using apex class in LWC</li>
<li>how to get picklist values dynamically in lwc</li>
<li>how to edit/save row dynamically in lightning component</li>
<li>update parent field from child using apex trigger</li>
</ul>
</div>
<div style="clear:both;"></div>
<br/>
<div class="youtubeIcon">
<img src="https://www.w3web.net/wp-content/uploads/2021/11/youtubeIcon.png" width="25" height="25" style="vertical-align:top; margin-right:10px;"/> <strong>TechW3web:-</strong> To know more, Use this <span style="color: #ff8000; font-weight: bold;">Link</span>
</div>
</div>
</div>
<!--End RelatedTopics Section-->
</div>
</lightning-card>
</template>
Example of changing and saving non-picklist value in datatable:
Changing and saving non-picklist value
Example of changing and saving a picklist value:
Changing and saving picklist value (1)
Last bit of console output:
Changing and saving picklsit value (2)
As this is my first time working with Lightning Web components, I would greatly appreciate any assistance you may provide. Thanks in advance.
I was able to get this custom data table component working for something I am developing to effectively bind records as rows. Here 2 differences I notice:
I did not include onvalueselect and oncellchange methods for the component html declaration, only the onpicklistchanged.
You may want to use your custom server side controller method instead of the built-in uiRecordApi->updateRecord one. Something tells me this may not play nice with row data from the data table. In my implementation, I was simply able to pass the draftValues over to the server side method which has a single parameter of list of sObject:
async saveRecords(event){
const updatedFields = event.detail.draftValues;
this.draftValues = [];
this.showSpinner = true
try{
await saveRelatedRecords({sObjs: updatedFields}) // server side save
.then((result) => {
this.updateMessage = result;
})
this.showSpinner = false;
if(this.updateMessage == 'success'){
this.showToast('Success', 'Record(s) Updated', 'success');
} else{
this.showToast('Error', this.updateMessage, 'error');
}
await refreshApex(this._wiredRecordData);
this.unsavedData = this.records;
} catch (error) {
this.showSpinner = false;
this.showToast('Error while updating or refreshing records', error.body.message, 'error');
}
}
In regards to your data not refreshing, I suggest you wire your getPayments method so that you can always get a fresh set from the server side, rather than trying to keep track of and maintain data changes on the client side, something like:
_wiredRecordData;
#wire(getRelatedRecords)
relatedRecords(getRecsResult){
const { data, error } = getRecsResult;
this._wiredRecordData = getRecsResult;
if(data){
this.records = data;
}
}
Then after your success toast in your save method, you can use
await refreshApex(_wiredRecords); and lwc knows to rerender based on the wiring (or at least I think that's how it works.)
Lastly, here is the server side controller method I have to dynamically generate column header information so it does not need to be hard coded into the component. However I did need to modify the custom component a bit to include a new "fieldapi" attribute so that the client side method knows what field to set for the changed value during the onpicklistchanged action.
#AuraEnabled(cacheable=true)
public static String getColumnHeaders(String sObjAPI, String fieldAPIs)
{
List<ColumnHeaderInfo> colHeaders = new List<ColumnHeaderInfo>();
Schema.DescribeSObjectResult sObjDesc = Schema.getGlobalDescribe().get(sObjAPI).getDescribe();
Boolean objIsUpdateable = sObjDesc.isUpdateable();
Map<String, Schema.SObjectField> objFields = sObjDesc.fields.getMap();
for(String field: fieldAPIs.split(','))
{
if(!objFields.keySet().contains(field.toLowerCase())){
continue;
}
Schema.DescribeFieldResult fieldDesc = objFields.get(field).getDescribe();
if(!fieldDesc.isAccessible()){
continue;
}
ColumnHeaderInfo colHeader = new ColumnHeaderInfo();
colHeader.label = fieldDesc.getLabel();
colHeader.fieldName = fieldDesc.getName();
colHeader.editable = fieldDesc.isUpdateable() && sObjDesc.isUpdateable();
colHeader.type_x = fieldDesc.getType().name().toLowerCase();
if(colHeader.type_x == 'picklist')
{
colHeader.type_x = 'picklist';
List<Schema.PicklistEntry> picklistValues = fieldDesc.getPicklistValues();
colHeader.typeAttributes = new TypeAttributes();
colHeader.typeAttributes.options = new List<Option>();
for(Schema.PicklistEntry ple: picklistValues)
{
Option opt = new Option();
opt.value = ple.getValue();
opt.label = ple.getLabel();
colHeader.typeAttributes.options.add(opt);
}
colHeader.typeAttributes.context = new FieldName();
colHeader.typeAttributes.context.fieldName = 'Id';
colHeader.typeAttributes.value = new FieldName();
colHeader.typeAttributes.value.fieldName = fieldDesc.getName();
colHeader.typeAttributes.fieldapi = fieldDesc.getName();
}
// multi-select picklist fields not supported so make them read-only
if(colHeader.type_x == 'multipicklist'){
colHeader.editable = false;
}
colHeaders.add(colHeader);
}
return JSON.serialize(colHeaders).replaceAll('type_x', 'type');
}
private class ColumnHeaderInfo
{
public String label;
public String fieldName;
public String type_x;
public Boolean editable;
public TypeAttributes typeAttributes;
}
private class TypeAttributes
{
public String placeholder;
public List<Option> options;
public FieldName value;
public FieldName context;
public FieldName label;
public FieldName tooltip;
public String fieldapi;
public String target;
}
private class Option
{
public String label;
public String value;
}
private class FieldName
{
public String fieldName;
}
I generate an input throught jquery like this:
marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location,
title: address,
draggable: true,
zoom: 19,
icon: pinImages,
shadow: pinShadows
});
lat = results[0].geometry.location.lat();
lng = results[0].geometry.location.lng();
$("#lng").html("<input type=hidden name=lng value=" + lng + ">");
$("#lat").html("<input type=hidden name=lat value=" + lat + ">");
google.maps.event.addListener(marker, 'dragend', function (marker) {
latLng = marker.latLng;
lat = latLng.lat();
lng = latLng.lng();
$("#lng").html("<input type=hidden name=lng value=" + lng + ">");
$("#lat").html("<input type=hidden name=lat value=" + lat + ">");
});
In html it looks like:
<div id="lat" value="53.486755"><input type="hidden" name="lat" value="53.4867546"></div>
<div id="lng" value="-2.054067"><input type="hidden" name="lng" value="-2.054066599999942"></div>
Which to me looks absolutely fine. Now when form is submitted controller is being called:
public function updateEntity(EntityRequestUpdate $request)
{
dd($request->input('lat'));
However data dump is giving me null. Why would it be happening?
EntityRequestUpdate:
public function rules()
{
return [
'name' => 'required|min:3|max:100',
'type' => 'required|exists:entity_types,type',
'email' => 'required|email|max:100',
'building_name' => 'nullable',
'address' => ['required','max:191', new Address],
'town' => ['required','max:191', new Hyphens],
'postcode' => ['required','max:191', new Postcode],
'telephone' => ['nullable','max:191', new Telephone],
'ical' => 'nullable|active_url|max:191',
'eventbrite' => 'nullable|numeric',
'tags' => 'nullable|max:255',
'url' => 'nullable|max:191',
'video' => 'nullable|max:191',
'bio' => 'nullable|max:500',
'main' => 'nullable|max:2000',
'lat' => 'nullable',
'lng' => 'nullable'
];
I have used google map using AngularJs which is working fine. However, on clicking any marker infowindow opens but I need to open it by clicking a radio button outside the map, here is the code sample
HTML
<ng-map default-style="true" center="[24.8615, 67.0099]" zoom="11" scroll-wheel="false" zoom-to-inculude-markers="auto">
<info-window id="rider">
<div ng-non-bindable="">
{{vm.store.name}} <br/>
{{vm.store.title}}<br/>
</div>
</info-window>
<marker ng-repeat="(id, store) in vm.stores" id="{{id}}" icon="../assets/images/rider.png"
position="{{store.position}}"
on-click="vm.showStore(event, id)"></marker>
JavaScript/AngularJs
vm.stores = [
{position: [24.8820869, 67.06881520000002], title: 'Bahadrubad'},
{position: [24.8753973, 67.04096709999999], title: 'Mazar e Quaid'},
{position: [24.8758, 67.0230], title: 'Karachi Zoo'},
{position: [24.8532941, 67.01622309999993], title: 'Saddar Town'}
];
NgMap.getMap().then(function (map) {
vm.map = map;
});
vm.showStore = function (e, storeId) {
vm.store = vm.stores[storeId];
vm.map.showInfoWindow('rider', this);
};
Once the radio button list is initialized:
<label data-ng-repeat="store in vm.stores">
<input type="radio" name="storeList" ng-model="store" ng-value="{{store}}" ng-change='vm.showStoreExt({{$index}})' />
{{store.title}}
</label>
you could specify event handler to display info window as shown below:
vm.showStoreExt = function (index) {
vm.store = vm.stores[index];
var marker = vm.map.markers[index];
vm.map.showInfoWindow('rider', marker);
};
Working example
angular.module('mapApp', ['ngMap'])
.controller('mapController', function (NgMap) {
var vm = this;
vm.stores = [
{ position: [24.8820869, 67.06881520000002], title: 'Bahadrubad' },
{ position: [24.8753973, 67.04096709999999], title: 'Mazar e Quaid' },
{ position: [24.8758, 67.0230], title: 'Karachi Zoo' },
{ position: [24.8532941, 67.01622309999993], title: 'Saddar Town' }
];
NgMap.getMap().then(function (map) {
vm.map = map;
});
vm.showStoreExt = function (index) {
vm.store = vm.stores[index];
var marker = vm.map.markers[index];
vm.map.showInfoWindow('rider', marker);
};
vm.showStore = function (e, storeId) {
vm.store = vm.stores[storeId];
vm.map.showInfoWindow('rider', this);
};
});
<script src="https://code.angularjs.org/1.4.8/angular.js"></script>
<script src="https://maps.googleapis.com/maps/api/js"></script>
<script src="https://rawgit.com/allenhwkim/angularjs-google-maps/master/build/scripts/ng-map.js"></script>
<div ng-app="mapApp" ng-controller="mapController as vm">
<label data-ng-repeat="store in vm.stores">
<input type="radio" name="storeList" ng-model="store" ng-value="{{store}}" ng-change='vm.showStoreExt({{$index}})' />
{{store.title}}
</label>
<ng-map default-style="true" center="[24.8615, 67.0099]" zoom="11" scroll-wheel="false" zoom-to-inculude-markers="auto">
<info-window id="rider">
<div ng-non-bindable="">
{{vm.store.name}} <br/> {{vm.store.title}}
<br/>
</div>
</info-window>
<marker ng-repeat="(id, store) in vm.stores" id="{{id}}" position="{{store.position}}" on-click="vm.showStore(event, id)"></marker>
</ng-map>
</div>
I want to create a field object in ASP.NET MVC and store it to a database. The field should contain its coordinates which the user can select with the google maps api.
And now, how can I pass the coordinates of the markers, which are stored in a javascript array, to the Model´s List?
This is my Controller for this:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using BL;
using MvcAgrarTest.Models;
namespace MvcAgrarTest.Controllers
{
public class FieldsController : Controller
{
private MvcAgrarContext db = new MvcAgrarContext();
// GET: Fields
public ActionResult Index()
{
var field = db.CreateFieldViewModel.Include(f => f.FieldType);
return View(field.ToList());
}
// GET: Fields/Create
public ActionResult Create()
{
var model = new CreateFieldViewModel();
ViewBag.FieldTypeId = new SelectList(db.FieldType, "FieldTypeId", "Name");
return View(model);
}
// POST: Fields/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateFieldViewModel model, CreateFieldViewModel field, string[] markers)
{
if (ModelState.IsValid)
{
PostCoordinates(markers);
db.CreateFieldViewModel.Add(field);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.FieldTypeId = new SelectList(db.FieldType, "FieldTypeId", "Name", field.FieldTypeId);
return Json(true);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
Here my Create View with the Google Maps script:
#model MvcAgrarTest.Models.CreateFieldViewModel
#{
ViewBag.Title = "Create";
}
<h2>Feld anlegen</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #placeholder = "Feldname", #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.FieldTypeId, "Feldtyp", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("FieldTypeId", null, "--Feldart auswählen--", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.FieldTypeId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Size, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Size, new { htmlAttributes = new { #placeholder = "In Hektar", #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Size, "", new { #class = "text-danger" })
</div>
</div>
<div id="google">
<script src="https://maps.googleapis.com/maps/api/js?sensor=false" type="text/javascript"></script>
<script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.3.2.min.js" type="text/javascript"></script>
<script src="http://ajax.microsoft.com/ajax/jquery.validate/1.5.5/jquery.validate.min.js" type="text/javascript"></script>
<script type="text/javascript">
var map;
function initialize() {
var myLatLng = new google.maps.LatLng(50.617109, 8.065738);
var myOptions = {
zoom: 5,
center: myLatLng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
// array to store markers that has been drawn
var markers = [];
// event listener draw a marker
google.maps.event.addListener(map, 'click', function (e) {
var marker = new google.maps.Marker();
marker.setPosition(e.latLng);
marker.setMap(map);
marker.setVisible(true);
markers.push(marker);
// draw polygon on marker click
// once user clicks on on of the markers
google.maps.event.addListener(marker, 'click', function (e) {
document.getElementById("label").innerHTML = "";
drawPoints(markers);
label.style.borderStyle = "dotted"
for (i = 0; i < markers.length; ++i) {
document.getElementById("label").innerHTML += markers[i].position;
}
// empty the markers array
markers = [];
});
});
}
function drawPoints(markers) {
var poly = new google.maps.Polygon;
var points = [];
for (var i = 0; i < markers.length; i++) {
points.push(markers[i].getPosition());
}
poly.setMap(map);
poly.setPath(points);
poly.setVisible(true);
}
</script>
<body onload="initialize()">
<div id="map_canvas" style="width: 500px; height: 300px"></div>
</body>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Erstellen" class="btn btn-default" />
</div>
</div>
</div>
}
and finally my ViewModel to create the field:
public class CreateFieldViewModel
{
[Key]
[Display(Name="ID")]
public int FieldId { get; set; }
[Required]
[Display(Name="Fieldname")]
public string Name { get; set; }
[Required]
[Display(Name="Fieldtype")]
public int FieldTypeId { get; set; }
[Required]
[Range(0.01, int.MaxValue, ErrorMessage = "The Size can´t be 0 or less")]
[Display(Name="Fieldsize")]
public float Size { get; set; }
[Display(Name="Coordinates")]
public List<string> Coordinates { get; set; }
public virtual FieldType FieldType { get; set; }
}
/edit: Id did it like this now, but it still doesn´t work
changed part from the google maps API:
var passdata = [];
google.maps.event.addListener(marker, "click", function(e){
for (y=0; y<markers.length; ++y){
passdata[y] = markers[y].position
}
drawpoints(markers);
$.ajax({
url: "#Url.Action("Create","Fields")",
typ: "POST",
datatype: "json",
data: JSON.stringify({coordinates: passdata}),
contentType: "application/json; charset=utf-8",
traditional: true,
success: function (data) {
alert(data);
},
});
And the Controller Function:
// GET: Fields/Create
public ActionResult Create()
{
var model = new CreateFieldViewModel();
ViewBag.FieldTypeId = new SelectList(db.FieldType, "FieldTypeId", "Name");
return View(model);
}
// POST: Fields/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateFieldViewModel field, string[] coordinates)
{
if (ModelState.IsValid)
{
field.Coordinates = coordinates;
db.CreateFieldViewModel.Add(field);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.FieldTypeId = new SelectList(db.FieldType, "FieldTypeId", "Name", field.FieldTypeId);
return View(field);
}
I understood, (looking by you code) that you want to go with submitting form, and not ajax?
If so, you have two choices (known to me):
Create html hidden input and paste there as value serialized JSON of your markers array - but you will have to deserialize this value later. In this example you need to change Coordinates type in your model to string.
JavaScript
// some sample coordinates, you need to extract them from markers
var coordinates = ["12,21","213,231"];
var dataToSend = JSON.stringify(coordinates);
$("#coordinatesInput").val(dataToSend);
HTML
<input hidden id="coordinatesInput" name="model.Coordinates" value=""/>
Create dynamically, using JavaScript (for example append function) many hidden html inputs with name model.Coordinates[i] and value of single coordinate (use some loop).
JavaScript
var coordinates = ["231,2132","312,231","231,321"];
var container = $("#container");
//container, where you will be adding inputs, in you example it could be form-horizontal class
coordinates.forEach(function(val, i){
container.append('<input hidden name="model.Coordinates['+i+']" value="'+val+'">');
});
Of course using AJAX is much better way, and AlexB answer is way to go, but mechanism of it is a little different. It is good to use only AJAX (not form submitting), because in your example you need to use two Actions, one for AJAX and one for form submitting.
You can pass your Javascript array to your controller using an Ajax call :
$.ajax({
url : "#Url.Action("MethodName", "ControllerName")",
contentType : "application/json; charset=utf-8",
dataType : "json",
type : "POST",
data : JSON.stringify({coordinates: markers})
})
(where markers is your JS array to pass to the controller, I guess)
And your controller will look like
public ActionResult MethodName(Single[] coordinates)
{
// ...
}