I can instantiate (sub)components manually using tags, but I don't know how to do it dynamically, or, how to insert and remove different components in the same area using tags.
Today I instantiate each (sub)component this way:
Ractive.load( '/templates/global/example.html' ).then( function ( Example )
{
ractive.components.example = new Example( { el : 'aside' } );
});
But the new (sub)component can't see the data of it's parent instance in mustache, only his own data.
Here's a dynamic component:
Ractive.components.dynamic = Ractive.extend({
template: '<component/>',
components: {
component: function() {
return this.get('name');
}
},
oninit: function(){
this.observe('name', function(){
this.reset();
}, { init: false});
}
});
Just pass in the name of the component it should implement:
<dynamic name='{{name}}'/>
See it in action below
Ractive.components.a = Ractive.extend({ template: 'I am A {{foo}}' });
Ractive.components.b = Ractive.extend({ template: 'I am B {{foo}}' });
Ractive.components.c = Ractive.extend({ template: 'I am C {{foo}}' });
Ractive.components.dynamic = Ractive.extend({
template: '<component/>',
components: {
component: function() {
return this.get('name');
}
},
oninit: function(){
this.observe('name', function(){
this.reset();
}, { init: false});
}
});
var r = new Ractive({
el: document.body,
template: '#template',
data: {
foo: 'foo',
list: ['a', 'b', 'c'],
name: 'a'
}
});
<script src="http://cdn.ractivejs.org/latest/ractive.js"></script>
<script id='template' type='text/ractive'>
{{#each list}}
<input type='radio' name='{{name}}' value='{{.}}'>{{.}}
{{/each}}
<br>
<dynamic name='{{name}}'/>
</script>
Would like to upvote #martypdx's answer more than once. Inspired by his answer, I came up with something I'd like to share. It adds the following things:
Create multiple instances of a type of Component (Ractive.extend)
Component instances are alive and can receive messages from external listeners even if they have been unrendered.
Here is my fiddle: https://jsfiddle.net/vikikamath/z5otgzpL/9/
// Inspired by http://stackoverflow.com/a/31080919/405117
var mapper = {
'A': function(instanceId){
var handleAData;
return Ractive.extend({
template: 'I am A this is my data: <ul>{{#datas}}<li>{{.}}</li>{{/datas}}</ul>',
data: {
datas:[]
},
onrender: function() {
handleAData = function(txt){
this.push('datas', txt);
}.bind(this);
r.on(instanceId, handleAData);
},
onunrender: function() {
r.off(instanceId, handleAData);
}
});
}
,'B': function(instanceId){
var handleBData;
return Ractive.extend({
template: 'I am B this is my data: <ul>{{#datas}}<li>{{.}}</li>{{/datas}}</ul>',
data: {
datas:[]
},
onrender: function() {
handleBData = function(txt){
this.push('datas', txt);
}.bind(this);
r.on(instanceId, handleBData);
},
onunrender: function() {
r.off(instanceId, handleBData);
}
});
}
,'C': function(instanceId){
var handleCData;
return Ractive.extend({
template: 'I am C this is my data: <ul>{{#datas}}<li>{{.}}</li>{{/datas}}</ul>',
data: {
datas:[]
},
onrender: function() {
handleCData = function(txt){
this.push('datas', txt);
}.bind(this);
r.on(instanceId, handleCData);
},
onunrender: function() {
r.off(instanceId, handleCData);
}
});
}
,'D': function(instanceId){
var handleDData;
return Ractive.extend({
template: 'I am D this is my data: <ul>{{#datas}}<li>{{.}}</li>{{/datas}}</ul>',
data: {
datas:[]
},
onrender: function() {
handleDData = function(txt){
this.push('datas', txt);
}.bind(this);
r.on(instanceId, handleDData);
},
onunrender: function() {
r.off(instanceId, handleDData);
}
});
}
}
/* arbitrarily select a component */
function pickRandomComponent() {
return String.fromCharCode(Math.floor(Math.random() * Object.keys(mapper).length) + 65);
}
var DynamicComponent = Ractive.extend({
template: '<component/>',
components: {
component: function() {
return this.get('name');
}
},
oninit: function(){
this.observe('name', function(){
this.reset();
}, { init: false});
}
});
var r = new Ractive({
el: 'main',
template: '#template',
components: {
dummy: Ractive.extend({ template: 'Welcome message' }),
dynamic: DynamicComponent
},
data: {
foo: 'foo',
list: ['dummy'],
name: 'dummy',
textdata: ''
},
oninit: function() {
this.on("sendDataToCurrentComponent", function() {
r.fire(this.get('name'), this.get('textdata'))
}.bind(this));
this.on('addComponent', function(){
var rndComponent = pickRandomComponent();
var now = Date.now();
var componentInstanceName = rndComponent + '-' + now
this.components[componentInstanceName] = mapper[rndComponent](componentInstanceName)
this.push('list', componentInstanceName);
}.bind(this));
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/ractive/0.7.3/ractive.min.js"></script>
<script id='template' type='text/ractive'>
<button on-click="addComponent">Add</button>
<div>
<input type="text" value="{{textdata}}" placeholder="Send Text to current component" width="900" on-blur="sendDataToCurrentComponent"/>
</div>
{{#each list}}
<input type='radio' name='{{name}}' value='{{.}}'>{{.}}
{{/each}}
<br>
<dynamic name='{{name}}'/>
</script>
<main></main>
Related
I want to update my variable nbeBugs but is not accessible inside then function.
My function getApi is async function because the API take time to response
template.vue
<script>
import ChartDataLabels from 'chartjs-plugin-datalabels'
import getApi from '../../api/api'
const plugins = [ChartDataLabels]
export default {
data() {
return {
plugins: [ChartDataLabels],
nbeBugs: [10,10,10,10,10],
chartGitlab: null,
lightOptions3: {
plugins: {
legend: {
labels: {
color: '#ddd'
},
display: true,
position: 'bottom',
align: 'center'
},
datalabels: {
color: '#EEE',
labels: {
value: {
font: {
weight: 'bold',
size: 24,
}
}
}
}
}
}
}
},
mounted () {
this.getData()
},
methods: {
getData() {
let url_application = 'api/bugs_gitlab'
getApi(url_application, null).then(function(results){
console.log(results.data)
this.nbeBugs = results.data
})
this.chartGitlab = {
labels: ['Bloquant','Critique','Majeur','Moyen','Faible'],
datasets: [
{
borderColor: "#071426",
data: this.nbeBugs,
backgroundColor: ["#FF6384","#36A2EB","#FFA726","#66BB6A","#a855f7"],
hoverBackgroundColor: ["#FF6384","#36A2EB","#FFA726","#66BB6A","#a855f7"]
}
]
}
}
}
}
</script>
<template>
<div class="col-12 xl:col-6">
<div class="card p-3 h-full">
<h5>Nombre de bugs Gitlab</h5>
<div class="flex h-full justify-content-center align-content-center flex-wrap card-container yellow-container">
<Chart type="doughnut" :data="chartGitlab" :options="lightOptions3" :plugins="plugins"/>
</div>
</div>
</div>
</template>
<style scoped>
</style>
api.js
import axios from "axios";
let path = import.meta.env.VITE_API_URL;
const axiosObjet = axios.create({
baseURL: path,
timeout: 8000,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.apiToken
}
});
async function getApi(api_path, data) {
//console.log(headers)
console.log(path)
//console.log(axiosObjet)
try {
let res = await axiosObjet.get(api_path)
if(res.status == 200){
// test for status you want, etc
console.log(res.status)
}
// Don't forget to return something
return res
}
catch (err) {
console.error("getApi error : " + err);
}
}
export default getApi
If the parameter you pass to .then() function is a regular function, it has its own scope and, inside it, the outer scope's variables won't be accessible using this..
Use an arrow function instead:
getApi(url_application, null).then(({ data }) => {
console.log(data);
this.nbeBugs = data;
});
Side note: you're updating this.chartGitlab before the request returns. Either move that update inside then() or use await in front of the server request. Most likely, this will work as expected:
methods: {
async getData() {
let url_application = "api/bugs_gitlab";
await getApi(url_application, null).then(({ data }) => {
console.log(data);
this.nbeBugs = data;
});
this.chartGitlab = {
labels: ["Bloquant", "Critique", "Majeur", "Moyen", "Faible"],
datasets: [
{
borderColor: "#071426",
data: this.nbeBugs,
backgroundColor: ["#FF6384","#36A2EB","#FFA726","#66BB6A","#a855f7"],
hoverBackgroundColor: ["#FF6384","#36A2EB","#FFA726","#66BB6A","#a855f7"],
},
],
};
},
}
I am trying to submit a form with a ibanElement, locally it works perfectly but on my test environment : Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://js.stripe.com') does not match the recipient window's origin (https://my.test.com)
any suggestion?
Thanks for the help
const ELEMENT_TYPE = 'card';
const PAYMENT_METHODS_CREDIT_CARD = "CREDIT_CARD";
const PAYMENT_METHODS_SEPA = "SEPA";
Vue.use(localeService);
const localeFromCookies = Vue.prototype.$getLocale(localeCookieName, null);
Vue.use(VeeValidate, { locale: localeFromCookies });
Vue.use(VueMask.VueMaskPlugin);
var paymentMethodsApp = new Vue({
el: '#app-payment-methods',
data() {
return {
wizardStepSize : 'md',
paymentMethod : PAYMENT_METHODS_CREDIT_CARD,
billingAddressStreet: null,
billingAddressLine2: null,
billingAddressCity: null,
billingAddressPostalCode : null,
billingAddressCountry: null,
billingEmail: null,
stripe: null,
elements: null,
cardElement: null,
isCardElementEmptyOrInvalid : true,
ibanElement : null,
isIbanElementEmptyOrInvalid : true,
paidCustomerId: null,
paidSubscriptionId : null,
stripeClientSecret : null,
stripePaymentConfirmation: false,
paymentMethodsSubmitting : false,
billingDataChecking : true,
alerts: []
}
},
methods: {
onCreditCardPaymentSelected() {
Vue.nextTick(function () {
paymentMethodsApp.mountStripeCardElement();
paymentMethodsApp.unmountStripeSepaIbanElement();
});
},
onInvoiceEBankingPaymentSelected() {
Vue.nextTick(function () {
paymentMethodsApp.unmountStripeCardElement();
paymentMethodsApp.unmountStripeSepaIbanElement();
});
},
onInvoicePhysicalPaymentSlipSelected() {
Vue.nextTick(function () {
paymentMethodsApp.unmountStripeCardElement();
paymentMethodsApp.unmountStripeSepaIbanElement();
});
},
onSEPAPaymentSelected(){
Vue.nextTick(function () {
paymentMethodsApp.unmountStripeCardElement();
paymentMethodsApp.mountSepaElement();
});
},
saveCustomer() {
const customerData = {
"paymentMethod" : this.paymentMethod,
"billingAddressStreet" : this.billingAddressStreet,
"billingAddressLine2" : this.billingAddressLine2,
"billingAddressCity" : this.billingAddressCity,
"billingAddressPostalCode" : this.billingAddressPostalCode,
"billingAddressCountry" : this.billingAddressCountry,
"billingEmail" : this.billingEmail,
}
return axios.post("/billings/customers", customerData)
.then(response => {
this.paidCustomerId = response.data.paidCustomerId;
})
.catch(error => {
if (error.response.status === 401 || error.response.status === 403) {
location.reload();
}
this.createAlert(saveBillingAddressErrorMsg);
});
},
saveSubscription() {
return axios.post("/billings/subscriptions")
.then(response => {
this.paidSubscriptionId = response.data.paidSubscriptionId;
this.stripeClientSecret = response.data.clientSecret;
this.stripePaymentConfirmation = response.data.paymentConfirmation;
})
.catch(error => {
if (error.response.status === 401 || error.response.status === 403) {
location.reload();
}
this.createAlert(saveBillingAddressErrorMsg);
});
},
loadBillingDetails() {
axios.get("/billings")
.then(response => {
const responseData = response.data;
if(responseData) {
this.paidCustomerId = responseData.paidCustomerId;
this.paidSubscriptionId = responseData.paidSubscriptionId;
this.billingAddressStreet = responseData.billingAddressStreet;
this.billingAddressLine2 = responseData.billingAddressLine2;
this.billingAddressCity = responseData.billingAddressCity;
this.billingAddressPostalCode = responseData.billingAddressPostalCode;
this.billingAddressCountry = responseData.billingAddressCountry;
this.billingEmail = responseData.billingEmail;
if(responseData.paymentMethod) {
this.paymentMethod = responseData.paymentMethod;
}
}
})
.catch(error => {
if(error.response) {
if(error.response.status === 401 || error.response.status === 403) {
location.replace("/");
}
}
})
.then(()=> {
this.billingDataChecking = false;
})
},
onProceedWithPaymentClicked() {
this.$validator.validateAll()
.then(isFormValid => {
if (isFormValid) {
this.doProceedWithPayment();
}
});
},
async doProceedWithPayment() {
this.paymentMethodsSubmitting = true;
this.disableStripeCardElement(true);
//this.disableStripeIbanElement(true);
await this.saveCustomer();
if(this.paidCustomerId) {
await this.saveSubscription();
if(this.paymentMethod === PAYMENT_METHODS_CREDIT_CARD) {
if (this.stripeClientSecret) {
if (this.stripePaymentConfirmation) {
await this.confirmCardPaymentWithStripe(this.stripeClientSecret);
} else {
await this.confirmCardSetupWithStripe(this.stripeClientSecret);
}
}
} else {
location.replace("/watchers");
}
if(this.paymentMethod === PAYMENT_METHODS_SEPA) {
if (this.stripeClientSecret) {
if (this.stripePaymentConfirmation) {
await this.confirmSepaDebitPayment(this.stripeClientSecret);
} else {
await this.confirmSepaDebitSetup(this.stripeClientSecret);
}
}
} else {
location.replace("/watchers");
}
}
this.paymentMethodsSubmitting = false;
this.disableStripeCardElement(false);
this.disableStripeIbanElement(false);
},
confirmCardPaymentWithStripe(stripeCustomerClientSecret) {
return this.stripe
.confirmCardPayment(stripeCustomerClientSecret, {
payment_method: {
card: this.cardElement,
// billing_details: {
// name: nameInput.value,
// },
}
})
.then((result) => {
if(result.error) {
this.$validator.errors.add({
field: 'creditCard',
msg: result.error.message
});
} else {
// Successful subscription payment
location.replace("/watchers"); //reload page and next step of activation wizard
}
});
},
confirmCardSetupWithStripe(stripeCustomerClientSecret) {
return this.stripe
.confirmCardSetup(stripeCustomerClientSecret, {
payment_method: {
card: this.cardElement,
// billing_details: {
// name: nameInput.value,
// },
}
})
.then((result) => {
if(result.error) {
this.$validator.errors.add({
field: 'creditCard',
msg: result.error.message
});
} else {
// Successful subscription payment
location.replace("/watchers"); //reload page and next step of activation wizard
}
})
},
confirmSepaDebitSetup(stripeCustomerClientSecret) {
return this.stripe
.confirmSepaDebitSetup(stripeCustomerClientSecret, {
payment_method: {
sepa_debit: this.ibanElement,
billing_details: {
name: 'test',
email: this.billingEmail,
},
},
})
.then(function (result) {
// Handle result.error or result.setupIntent
location.replace("/watchers"); //reload page and next step of activation wizard
})
},
confirmSepaDebitPayment(stripeCustomerClientSecret) {
return this.stripe
.confirmSepaDebitPayment(stripeCustomerClientSecret, {
payment_method: {
sepa_debit: this.ibanElement,
billing_details: {
name: 'test',
email: this.billingEmail,
},
},
})
.then(function (result) {
// Handle result.error or result.paymentIntent
location.replace("/watchers"); //reload page and next step of activation wizard
})
},
initStripe() {
this.stripe = Stripe(stripePublishableToken);
},
mountStripeCardElement() {
this.paymentMethod = PAYMENT_METHODS_CREDIT_CARD
const stripeOptions = {
// stripeAccount: this.stripeAccount,
// apiVersion: this.apiVersion,
locale: localeFromCookies ? localeFromCookies : "auto"
};
const style = {
invalid: {
color: "#a94442",
iconColor: "#a94442"
}
};
const createOptions = {
// classes: this.classes,
style: style,
// value: this.value,
hidePostalCode: true
// iconStyle: this.iconStyle,
// hideIcon: this.hideIcon,
// disabled: this.disabled,
};
this.elements = this.stripe.elements(stripeOptions);
this.cardElement = this.elements.create(ELEMENT_TYPE, createOptions);
this.cardElement.mount('#stripe-element-mount-point');
this.cardElement.on('change', this.onStripeCardElementChange);
},
mountSepaElement() {
this.paymentMethod = PAYMENT_METHODS_SEPA
const stripeOptions = {
// stripeAccount: this.stripeAccount,
// apiVersion: this.apiVersion,
locale: localeFromCookies ? localeFromCookies : "auto"
};
const style = {
invalid: {
color: "#a94442",
iconColor: "#a94442"
}
};
const createOptions = {
// classes: this.classes,
style: style,
// value: this.value,
supportedCountries : ['SEPA']
//hidePostalCode: true
// iconStyle: this.iconStyle,
// hideIcon: this.hideIcon,
// disabled: this.disabled
};
this.elements = this.stripe.elements(stripeOptions);
this.ibanElement= this.elements.create('iban', createOptions);
this.ibanElement.mount('#stripe-element-mount-point2');
this.ibanElement.on('change', this.onStripeSepaElementChange);
},
unmountStripeCardElement() {
this.$validator.errors.remove("creditCard");
this.cardElement.unmount();
},
unmountStripeSepaIbanElement() {
this.$validator.errors.remove("ibanSepa");
this.ibanElement.unmount();
},
onStripeCardElementChange(event) {
this.isCardElementEmptyOrInvalid = event.empty;
if (event.error) {
this.isCardElementEmptyOrInvalid = true;
this.$validator.errors.add({
field: 'creditCard',
msg: event.error.message
});
} else {
this.$validator.errors.remove("creditCard");
}
},
onStripeSepaElementChange(event) {
this.isIbanElementEmptyOrInvalid = event.empty;
if (event.error) {
this.isIbanElementEmptyOrInvalid = true;
this.$validator.errors.add({
field: 'ibanSepa',
msg: event.error.message
});
} else {
this.$validator.errors.remove("ibanSepa");
}
},
disableStripeCardElement(disabled) {
this.cardElement.update({disabled: disabled});
},
disableStripeIbanElement(disabled) {
this.ibanElement.update({disabled: disabled});
},
setActiveTab () {
const activeTab = this.$formWizard.attr("active-tab");
if(activeTab) {
const wizard = this.$refs.wizard;
if(wizard) {
const currentActiveTabIndex = wizard.activeTabIndex;
wizard.changeTab(currentActiveTabIndex,activeTab);
wizard.tabs.forEach((tab, index)=>{
if(index < activeTab)
tab.checked = true
});
}
}
},
createAlert(message) {
const alertItem = {
key: new Date().getTime(),
message: message,
type: "danger"
};
this.alerts.push(alertItem);
},
onPageResize() {
if (window.innerWidth > 992) { //media query value
this.wizardStepSize = 'md'
} else {
this.wizardStepSize = 'xs'
}
}
},
computed: {
$formWizard() {
return $("#payment-methods-form-wizard");
},
disabledForm() {
return this.paymentMethodsSubmitting
|| this.billingDataChecking
|| (this.paidCustomerId && this.paidSubscriptionId);
},
disabledPaymentMethod() {
return this.paymentMethodsSubmitting
|| this.billingDataChecking;
},
disableSubmitButton() {
return this.paymentMethodsSubmitting
|| this.billingDataChecking
|| (this.paymentMethod === PAYMENT_METHODS_CREDIT_CARD && this.isCardElementEmptyOrInvalid)
|| (this.paymentMethod === PAYMENT_METHODS_SEPA && this.isIbanElementEmptyOrInvalid)
|| this.errors.any();
},
showInvoiceEBankingPaymentMethod() {
return wearerCountry !== 'CH';
}
},
mounted() {
this.setActiveTab();
this.initStripe();
this.mountStripeCardElement();
this.loadBillingDetails();
},
created() {
window.addEventListener('resize', this.onPageResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.onPageResize)
}
});
<tab-content>
<div>
<div class="row" v-if="paymentMethod == PAYMENT_METHODS_CREDIT_CARD">
<div class="form-group col-xs-12 col-md-6 required" :class="{'has-error': errors.has('creditCard') }">
<label class="control-label">[[#{registration.payment.creditCard}]]</label>
<div class="form-control">
<form id="stripe-element-form" >
<div id="stripe-element-mount-point"></div>
<slot name="stripe-element-errors">
<div id="stripe-element-errors" role="alert"></div>
</slot>
<button ref="submitButtonRef" type="submit" class="hide" ></button>
</form>
</div>
<span v-if="errors.has('creditCard')" class="help-block">
{{ errors.first('creditCard') }}
</span>
</div>
</div>
<div class="row" v-if="paymentMethod == PAYMENT_METHODS_SEPA">
<div class="form-group col-xs-12 col-md-6 required" :class="{'has-error': errors.has('ibanSepa') }">
<label class="control-label">[[#{registration.payment.sepa}]]</label>
<div class="form-control">
<form id="stripe-element-form" >
<div id="stripe-element-mount-point2"></div>
<slot name="stripe-element-errors">
<div id="stripe-element-errors" role="alert"></div>
</slot>
<button ref="submitButtonRef" type="submit" class="hide" ></button>
</form>
</div>
<div id="mandate-acceptance">
By providing your payment information and confirming this payment, you
authorise (A) Smartwatcher Technologies AG and Stripe, our payment service provider
and/or PPRO, its local service provider, to send instructions to your
bank to debit your account and (B) your bank to debit your account in
accordance with those instructions. As part of your rights, you are
entitled to a refund from your bank under the terms and conditions of
your agreement with your bank. A refund must be claimed within 8 weeks
starting from the date on which your account was debited. Your rights
are explained in a statement that you can obtain from your bank. You
agree to receive notifications for future debits up to 2 days before
they occur.
</div>
<span v-if="errors.has('ibanSepa')" class="help-block">
{{ errors.first('ibanSepa') }}
</span>
</div>
</div>
</div>
</tab-content>
I am trying to add markers on map, using a view and component
In view where i call an api and then i pass that data to component using v:bind but console.log in that component shows an empty array, when i make some changes in that component the page reloads and data is fetched following is my code in view and then component.
//Script for View
<script>
import Maps from '../components/Maps.vue';
import LoadingOverlay from '../components/loading.vue';
import MessageHelper from '../helpers/messageHelper';
import RepositoryFactory from '../repositories/RepositoryFactory';
const Catalogs = RepositoryFactory.get('catalogs');
export default {
components: {
Maps,
LoadingOverlay,
},
data() {
return {
zones_and_locations: [],
loadingConfig: {
isLoading: true,
cancellable: true,
onCancelMessage: this.onCancelMessage(),
isFullPage: true,
},
};
},
created() {
this.fetchPlacesData();
},
methods: {
async fetchPlacesData() {
const { data } = await Catalogs.getZoneLocations();
if (data.output === null) {
this.nullDataException();
} else {
const places = [];
data.output.forEach((value, index) => {
places.push(value);
});
this.zones_and_locations = places;
this.loadingConfig.isLoading = false;
}
},
onCancelMessage() {
return MessageHelper.getLoadingCancelMessage();
},
nullDataException() {
this.exceptionMessage = 'Data from API is not available.';
console.log(this.exceptionMessage);
},
},
};
</script>
//Script For Map.vue
<script>
import MarkerClusterer from '#google/markerclusterer';
import GoogleMapsHelper from '../helpers/GoogleMapsHelper';
export default {
name: 'GoogleMap',
props: ['zones_and_locations'],
data() {
return {
country: '',
markers: [],
map: '',
};
},
created() {
this.loadGoogleMaps();
},
methods: {
markerClusterer(map, markers) {
return new MarkerClusterer(
map,
markers,
{ imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m' },
);
},
async loadGoogleMaps() {
try {
const google = await GoogleMapsHelper();
const geoCoder = new google.maps.Geocoder();
const map = new google.maps.Map(document.getElementById('map'), {
zoom: 7,
mapTypeControl: false,
fullscreenControl: false,
});
geoCoder.geocode({ address: 'Singapore' }, (results, status) => {
if (status !== 'OK' || !results[0]) {
throw new Error(status);
}
// set Center of Map
map.setCenter(results[0].geometry.location);
map.fitBounds(results[0].geometry.viewport);
});
this.map = map;
let zones = [];
Array.prototype.forEach.call(this.zones_and_locations, child => {
var obj = {
lat: parseFloat(child.lat),
lng: parseFloat(child.lng),
}
var position = {
position: obj,
};
zones.push(position);
});
if (zones.length > 0) {
const markers = zones.map(x => new google.maps.Marker({ ...x, map }));
this.markerClusterer(map, markers);
}
} catch (error) {
console.error(error);
}
},
},
};
</script>
//Template for View
<template>
<div>
<!-- START::Loading Overlay -->
<LoadingOverlay
v-bind:loadingConfig="loadingConfig">
</LoadingOverlay><!-- END::Loading Overlay -->
<Maps v-bind:zones_and_locations="zones_and_locations"
></Maps>
</div>
</template>
//Template for Component
<template>
<div>
<div id="map" class="google-map"></div>
</div>
</template>
Data from the parent component are loaded asynchronously, so the created lifecycle hook inside the component is executed before the actual data comes, when they're still set as an empty array and are not reactive to change.
You can fix this by setting a watch inside the component, like this:
methods: {
...
},
watch: {
zones_and_locations (newVal, oldVal) {
this.loadGoogleMaps();
}
}
orelse you can set a reference to the child component and invoke its method when data comes:
<!-- main view -->
<Maps
v-bind:zones_and_locations="zones_and_locations"
ref="myMap"
></Maps>
async fetchPlacesData() {
const { data } = await Catalogs.getZoneLocations();
if (data.output === null) {
this.nullDataException();
} else {
const places = [];
data.output.forEach((value, index) => {
places.push(value);
});
this.zones_and_locations = places;
// using $refs
this.$refs.myMap.loadGoogleMaps();
this.loadingConfig.isLoading = false;
}
},
I got an error when use my element in vue-cli.
I do not why joint.shapes do not contain my element!
test.vue:
// Cannot read property 'Component' of undefined"
<script>
const joint = require('jointjs');
joint.dia.Element.define('example.Component', {
attrs: {
...
},
}, {
markup: ...,
}, {
createComponent: function() {
return new this({
attrs:{
...
}
});
}
});
export default {
name: "FlowCanvas",
mounted() {
var graph=new joint.dia.Graph;
var paper = new joint.dia.Paper({
...
});
var el1=joint.shapes.example.Component.createComponent();
el1.position(100,100);
},
}
</script>
I created a simple app to search video using youtube-api, but when I use npm start it was not give me any errors but give me the warning Warning: Unknown proponItemSearchedon <searchItem> tag. Remove this prop from the element.
in searchItem (created by listItem)
in div (created by listItem)
in listItem
Here is my code:
var React = require('react');
var Item = require('./item.jsx');
var searchItem = React.createClass({
getInitialState : function() {
return {
'queryString' : ''
};
},
handleSearchClicked : function() {
this.props.onItemSearched(this.state);
this.setState({
'queryString' : ''
});
},
handleChangedNameItem : function(e) {
e.preventDefault();
this.setState({
'queryString' : e.target.value
});
},
render : function () {
return (
<div>
<label>
<input id="query" type="text" onChange={this.handleChangedNameItem} value={this.state.queryString} placeholder="Search videos..." />
<button id="search-button" onClick={this.handleSearchClicked}>Search</button>
</label>
</div>
);
}
});
And this is listItem what i show my results
var listItem = React.createClass({
getInitialState : function() {
return {
'results' : []
};
},
handleQuerySearch : function(query) {
var req = gapi.client.youtube.search.list({
'part': 'snippet',
'type': 'video',
'q' : encodeURIComponent(query).replace(/%20/g, "+"),
'order' : 'viewCount',
});
//execute request
req.execute((res) => {
var results = res.result;
this.setState({
'results' : results.items
});
});
},
render : function() {
var listItem = this.state.results.map( item => {
return(
<Item title={item.snippet.title} videoid={item.id.videoId} />
);
});
return (
<div>
<searchItem onItemSearched={this.handleQuerySearch} />
<div className="list-item">
{listItem}
</div>
</div>
);
}
});
module.exports = listItem;
React wants all components to be written in class format. Meaning the names need to be capitalized.
searchItem needs to be SearchItem
You can also define the props that will be received on search item
var SearchItem = React.createClass({
propTypes: {
onItemSearched: React.PropTypes.func
},
....
});