ionic 4 ng-true-value equivalence - ionic4

I'm rewriting an ionic 1 app to ionic 4.5.0, and i have a calculator like this in ionic 1:
HTML:
<ion-toggle toggle-class="toggle-positive" id="myid" ng-model="data.product1" ng-false-value="0" ng-true-value="50">{{data.product1}}
<i class="icon ion-social-euro"></i>
</ion-toggle>
ControllerJs:
$scope.data = {
'product1' : 0,
'product2' : 0,
'product3' : 0
}
On ionic 1 we had directives like:
ng-false-value="0" ng-true-value="50"
so, when toggle were on, the true value were 50, and false 0.
On my new app, i'm trying to do the same:
HTML:
<ion-item>
<ion-label>Product 1 {{data.product1}}</ion-label>
<ion-toggle name="product1" [(ngModel)]="data.product1"></ion-toggle>
</ion-item>
ts
data: any = {
product1: 0,
product2: 0,
product3: 0
};
{{data.product1}} is showing 0, but when i activate the toggle it's showing true or false
I have found this solution, but it's for angular: https://juristr.com/blog/2018/02/ng-true-value-directive/
Is there any equivalent or something else for ng-true-value and ng-false-value in ionic 4?

One approach is to use the ionChange event to add/subtract 50 when the toggle changes value.
In this example we are passing the product key of the data object, and the toggled value. This way you can reuse the function.
<ion-item>
<ion-label>Product 1 {{data.product1}}</ion-label>
<ion-toggle name="product1" [(ngModel)]="toggle.product1" (ionChange)="toggleValues('product1', 50)"></ion-toggle>
</ion-item>
toggle = {
'product1': false;
// add more
}
toggle50Values(productKey: string, value: number) {
if (this.toggle[productKey]) { // toggle is true
this.data[productKey] += value;
} else { // toggle is false
this.data[productName] -= value;
}
}
Documentation: Ion-toggle

The correct approach is:
HTML:
<ion-item>
<ion-label>Product1 {{data.product1}}</ion-label>
<ion-toggle name="product1" [(ngModel)]="data.product1" (ionChange)="calculateProduct1()"></ion-toggle>
</ion-item>
.ts
data: any = {
product1: 0,
product2: 0
}
calculateProduct1() {
if (this.data.product1) { // toggle is true
this.data.product1 = 50;
} else { // toggle is false
this.data.product1 = 0;
}
}
Thanks to https://stackoverflow.com/users/1934484/tomas-vancoillie

Related

vue data-table server side search next button disabled when less than 10 pages in result

I implemented server side search and pagination with Vue data-table (v-data-table). When I start typing in the search bar, at first with only an 'n' it returns 55 pages, which is correct and I can move through the pages with next/previous button. But when the search is 'ni' and only returns 25 items (it calculates correctly this should be 3 pages) my next button is disabled... see below. Does anybody have an idea of where this goes wrong. I also attached my code starting with the data-table template...
<v-data-table
:headers="headers"
:items="all_items"
:search="search"
height="300px"
:server-items-length="totalPages"
fixed-header
:items-per-page="10"
:page="page"
:options.sync="options"
#update:options="onOptionsChanged"
></v-data-table>
</v-card>
</v-container>
</template>
<script>
import axios from "axios";
export default {
name: "datatable",
mounted() {
axios.get('http://localhost:8080/api/fields?model_id=1').then(response => this.headers = response.data.results)
axios.get('http://localhost:8080/api/freemodelitems?model_id=1').then(response => {
this.totalPages = response.data.count > 10 ? Math.ceil(response.data.count / 10) : 1
this.page = this.options.page
for (let i = 0; i < response.data.results.length; i++) {
this.all_items.push(response.data.results[i].data)
}
})
},
watch: {
search: function (val) {
this.search = val
this.onOptionsChanged(this.options, true)
return val
}
},
methods: {
onOptionsChanged(options, page0=false) {
console.log(page0)
axios.get('http://localhost:8080/api/freemodelitems?model_id=1' +
'&page=' +
(!page0 ? this.options.page : 1) +
'&search=' +
this.search).then(response => {
this.page = !page0 ? this.options.page : 1
this.totalPages = response.data.count > 10 ? Math.ceil(response.data.count / 10) : 1
console.log(this.totalPages)
console.log(this.page)
this.all_items = [];
for (let i = 0; i < response.data.results.length; i++) {
this.all_items.push(response.data.results[i].data)
}
})
},
},
data() {
return {
search: "",
options: {},
headers: [],
all_items: [],
pageSize: 10,
totalPages: 0,
page: 1,
}
},
}
</script>
The problem is :server-items-length="totalPages". You are setting the property with total amount of pages, but you need to set it with total amount of items or remove it all together because the component can calculate the number of pages itself.
Quoted from documentation of prop server-items-length:
Used only when data is provided by a server. Should be set to the total amount of items available on server so that pagination works correctly

ThreeJS component working in VueJS 2 but not 3

I'm upgrading my app to VueJS 3. I read that you could keep the same components. But I have an error in the console now, although I didn't change anything. Here is my component:
<template>
<v-container>
<div
#click="onClick"
#mousemove="onMouseMove"
id="menu3D"
style="background-color: transparent; position: fixed; left: 20px; width:15%; height:100%;">
</div>
<v-row class="text-center">
<v-col
class="mb-5"
cols="12"
>
<h2 class="headline font-weight-bold mb-3">
Accueil
</h2>
<v-row justify="center">
<p>
Client: {{ JSON.stringify(client)}}
</p>
<p>
Mouse: {{ JSON.stringify(mouse)}}
</p>
<p>
Container: {{ JSON.stringify(container)}}
</p>
</v-row>
</v-col>
</v-row>
</v-container>
</template>
<script>
import * as Three from 'three';
export default {
name: 'Accueil',
mounted() {
this.init();
},
methods: {
init() {
this.createScene();
this.createCamera();
this.userData.formes.forEach((x) => this.createShape(x));
this.addSpotlight(16777215);
this.addAmbientLight();
this.animate();
window.addEventListener('resize', this.onResize);
},
onResize() {
const container = document.getElementById('menu3D');
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.camera.aspect = container.clientWidth / container.clientHeight;
this.camera.updateProjectionMatrix();
},
createScene() {
this.renderer = new Three.WebGLRenderer({
antialias: true,
alpha: true,
});
const container = document.getElementById('menu3D');
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setClearColor(0xffffff, 0);
container.appendChild(this.renderer.domElement);
},
createCamera() {
const container = document.getElementById('menu3D');
this.camera = new Three.PerspectiveCamera(50,
container.clientWidth / container.clientHeight, 0.01, 1000);
this.camera.position.set(0, 5, 20);
this.camera.zoom = 1;
},
createShape(shape) {
const material = new Three.MeshStandardMaterial({
color: '#0000ff',
roughness: 1,
metalness: 0.5,
emissive: 0,
depthFunc: 3,
depthTest: true,
depthWrite: true,
stencilWrite: false,
stencilWriteMask: 255,
stencilFunc: 519,
stencilRef: 0,
stencilFuncMask: 255,
stencilFail: 7680,
stencilZFail: 7680,
stencilZPass: 7680,
});
switch (shape.nom) {
case 'Box': {
this.geometry = new Three.BoxBufferGeometry(1.8, 1.8, 1.8);
break;
}
case 'Sphere': {
this.geometry = new Three.SphereBufferGeometry(1, 8, 6, 0, 6.283185, 0, 3.141593);
break;
}
case 'Dodecahedron': {
this.geometry = new Three.DodecahedronBufferGeometry(1.2, 0);
break;
}
case 'Icosahedron': {
this.geometry = new Three.IcosahedronBufferGeometry(1.5, 0);
break;
}
default: {
return false;
}
}
this.mesh = new Three.Mesh(this.geometry, material);
this.mesh.name = shape.nom;
this.mesh.userData = shape.userData;
this.mesh.receiveShadow = true;
this.mesh.castShadow = true;
this.mesh.position.set(0, shape.userData.position.y, 0);
this.scene.add(this.mesh);
return true;
},
addSpotlight(color) {
const light = new Three.SpotLight(color, 2, 1000);
light.position.set(0, 0, 30);
this.scene.add(light);
},
addAmbientLight() {
const light = new Three.AmbientLight('#fff', 0.5);
this.scene.add(light);
},
verifForme(e) {
const t = this;
const elt = t.scene.getObjectByName(e);
t.intersects = t.raycaster.intersectObject(elt);
if (t.intersects.length !== 0) {
// s'il ne figure pas dans le tableau, on le met en premier
if (t.userData.souris.indexOf(e) < 0) {
t.userData.souris.unshift(e);
console.log(`${t.userData.souris[0]} survolé!`);
}
if (t.userData.souris[0] === e) {
const obj = t.intersects[0].object;
obj.material.color.set(`#${elt.userData.couleurs[1]}`);
obj.scale.set(obj.scale.x < 1.4
? obj.scale.x + t.VITESSE_ZOOM
: obj.scale.x, obj.scale.y < 1.4
? obj.scale.y + t.VITESSE_ZOOM
: obj.scale.y, obj.scale.z < 1.4
? obj.scale.z + t.VITESSE_ZOOM
: obj.scale.z);
obj.rotation.y += t.VITESSE_ROTATION / t.RALENTISSEMENT;
t.replacer(obj, obj.userData.position.y + obj.userData.decalage);
} else {
t.retrecir(e, elt);
}
} else {
if (t.userData.souris.indexOf(e) >= 0) {
t.userData.souris = t.userData.souris.filter((forme) => forme !== e);
}
t.retrecir(e, elt);
}
},
onClick(event) {
event.preventDefault();
if (this.userData.souris.length > 0) {
console.log(`${this.userData.souris[0]} cliqué!`);
} else {
console.log('clic dans le vide!');
}
},
onMouseMove(event) {
const container = document.getElementById('menu3D');
this.mouse.x = (event.offsetX / container.clientWidth) * 2 - 1;
this.mouse.y = -(event.offsetY / container.clientHeight) * 2 + 1;
this.client.clientX = event.clientX;
this.client.clientY = event.clientY;
this.container.width = container.clientWidth;
this.container.height = container.clientHeight;
// console.log(JSON.stringify(this.mouse))
},
replacer(e, py) {
// la ligne suivante est pour éviter les tremblements
if (Math.abs(e.position.y - py) < 0.05) { return true; }
let rhesus = 10 * this.VITESSE_DEPLACEMENT;
if (this.userData.souris[0] !== e.name) { rhesus *= 3; }
// console.log(e.name+': '+this.userData.souris[0]+' - '+rhesus)
if (e.position.y > py) { rhesus = -1; }
e.position.set(0, Math.trunc(10 * e.position.y + rhesus) / 10, 0);
return true;
},
retrecir(n, e) {
// on vérifie si le truc cliqué est dessus
let dec = 0;
const elt = this;
if ((elt.userData.souris.length > 0)
&& (elt.userData.formes.map((x) => x.nom).indexOf(n)
< elt.userData.formes.map((x) => x.nom).indexOf(elt.userData.souris[0]))) {
dec = Math.trunc(10
* e.parent.getObjectByName(elt.userData.souris[0]).userData.decalage
* 2.1) / 10;
}
e.material.color.set(`#${e.userData.couleurs[0]}`);
e.rotation.y += elt.VITESSE_ROTATION;
e.scale.set(e.scale.x > 1
? e.scale.x - elt.VITESSE_ZOOM : e.scale.x,
e.scale.y > 1
? e.scale.y - elt.VITESSE_ZOOM : e.scale.y,
e.scale.z > 1
? e.scale.z - elt.VITESSE_ZOOM : e.scale.z);
const newY = e.userData.position.y + dec;
if (e.position.y !== newY) {
elt.replacer(e, newY);
}
},
animate() {
const elt = this;
requestAnimationFrame(this.animate);
this.raycaster.setFromCamera(this.mouse, this.camera);
this.userData.formes.map((x) => x.nom).forEach((x) => elt.verifForme(x));
if (this.userData.souris.length > 0) {
document.body.style.cursor = 'pointer';
} else { document.body.style.cursor = 'default'; }
this.camera.updateProjectionMatrix();
this.renderer.render(this.scene, this.camera);
},
},
data: () => ({
container: { height: 0, width: 0 },
client: { clientX: 0, clientY: 0 },
scene: new Three.Scene(),
camera: null,
renderer: Three.WebGLRenderer,
mesh: new Three.Mesh(),
factor: 0,
mouse: new Three.Vector2(1, 1),
raycaster: new Three.Raycaster(),
intersects: [],
VITESSE_ROTATION: 0.05,
VITESSE_DEPLACEMENT: 0.1,
VITESSE_ZOOM: 0.05,
RALENTISSEMENT: 3,
userData: {
souris: [],
formes: [
{
nom: 'Box',
userData: {
position: {
x: 0,
y: 7.8,
z: 0,
},
couleurs: [
'aaaaaa',
'095256',
],
decalage: 0.5,
},
},
{
nom: 'Icosahedron',
userData: {
position: {
x: 0,
y: 5.5,
z: 0,
},
couleurs: [
'aaaaaa',
'087F8C',
],
decalage: 0.5,
},
},
{
nom: 'Dodecahedron',
userData: {
position: {
x: 0,
y: 3.1,
z: 0,
},
couleurs: [
'aaaaaa',
'5AAA95',
],
decalage: 0.4,
},
},
{
nom: 'Sphere',
userData: {
position: {
x: 0,
y: 1,
z: 0,
},
couleurs: [
'aaaaaa',
'86A873',
],
decalage: 0.2,
},
},
],
},
}),
};
</script>
And here is the error I have in the console with VueJS 3:
three.module.js?5a89:24471 Uncaught TypeError:
'get' on proxy: property 'modelViewMatrix' is a read-only and
non-configurable data property on the proxy target but the proxy did not
return its actual value (expected '#<Matrix4>' but got '[object Object]')
at renderObject (three.module.js?5a89:24471)
at renderObjects (three.module.js?5a89:24458)
at Proxy.WebGLRenderer.render (three.module.js?5a89:24258)
at animate (HelloWorld.vue?fdab:192)
If anyone has got a clue, thanks in advance...
It worked with Vue 2
Reason it worked fine with Vue 2 lies in the fact Vue 2 is using different reactivity system based on Object.defineProperty API.
The same API is used by THREE.js a lot to add some non-writable and non-configurable properties to it's data structures
When object with such property was passed to Vue (by declaring it inside data for example), Vue just skipped such property resulting in stored value/object being non-reactive (as Vue could not detect property access while rendering the component template)
Vue 3 proxies
Vue 3 is using new reactivity system base on ES6 proxies.
This is pretty new and even that a lot of effort has been put into developing and testing it, issues like this will arise as people start migrating (And I completely agree with #Serg - Vue 3 is still new and unless you have skill and time to "live on the edge" you should wait a bit before migrating from Vue 2)
This new reactivity system doesn't play well with non-writable non-configurable properties on objects - you can find minimal reproducible example in this sandbox
Imho it is a bug and is reported to Vue#next repo
sandbox uses composition API but that doesn't matter as using reactive() is the same as declaring your variables inside data() function (Vue just do it automatically for you)
Workarounds
As said before, problem is in reactivity system. I'm not an expert on THREE.js but from what I know it doesn't make much sense to put the THREE data structures into Vue reactivity system - all point of reactivity is to detect data changes and re-render template when needed. THREE has its own rendering system and is usually using single <canvas> HTML element so it makes no sense to trigger Vue re-render on THREE data structures change...
There are multiple ways to opt-out from Vue reactivity:
Use Object.freeze() on your objects. Not very useful in this case but good to know
Do not declare your variables in data() and assign the values in created()/mounted() hook (example bellow). You can assign them into component itself (this) if you need to access them in multiple methods or as a local variables (const/let) whenf you don't need it
When using Composition API, do not use reactive() on THREE data structures
NOTE: Even if they admit it is a bug, only way of fixing it is to leave such property and object it holds non-reactive (not putting Proxy around that object) so result will be same as opting-out of reactivity completely. But using this workaround also gives you faster and less memory hungry app as all the reactivity is not really that cheap
Example - creating non-reactive component properties
export default {
data() {
return {
};
},
mounted() {
this.init();
},
methods: {
init() {
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(...);
this.renderer = new THREE.WebGLRenderer({ ... })
this.geometry = new THREE.PlaneBufferGeometry( );
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
this.plane = new THREE.Mesh(this.geometry, material);
this.scene.add(this.plane);
this.renderer.render(this.scene, this.camera);
},
}
toRaw(vue3) - At this time, you can feel his strength !
You can use this method, to solve a series of these problems
If mesh/xxx is a ref variable
scene.add(toRaw(mesh.value))
renderer.value.render(toRaw(scene.value), camera.value);
I am using threejs + vue3 + pinia. Pinia was wrapping objects in Proxy too, but I need to pass 3d object to it sometimes (inside if other model). So I had a model like:
class SomeModel {
otherProp: 'some value',
graphicObject: new THREE.Object3D(),
}
The way I fixed this issue is by changing graphicObject prop to a function, that return 3d object, that was saved in other variable. It looks like this:
class SomeModel {
otherProp: 'some value',
constructor(graphicObject) {
this.graphicObject = () => graphicObject,
}
}
new SomeModel(new THREE.Object3D());
This way 3d object is hidden from Vue at all time, if you dont pass this object directly to any reactive variable. And the way you access it in other methods is by just calling this function like in example:
<script setup>
import { ref } from 'vue';
// You may just call constructor inside new SomeModel() if you want.
const graphicObject = new THREE.Object3D();
const someModel = ref(new SomeModel(graphicObject));
function changePosition(x, y, z) {
// should not emit errors, because 3d object is not reactive -
// it's just a value, returned from function.
someModel.value.graphicObject().position.set(x, y, z);
}
</script>

Vue #click counter and update background for 2 minutes for reusable component

I have a reusable vue component which is basically a user card that displays an array of data for multiple users.
What I would like to do is when clicking on the word alert in the corner for a particular user, the background for that users card will change colours for 2 minutes and then revert to the way it was before but it will add the number 1 next to alert and keep incrementing for each click.
So far I have:
<div class="card" v-for="(item, index) in arrays" :key="item.id" v-bind:id="item.name">
<div class="l" v-on:click="greet()" >Alert{{count}}</div>
which is where I click on alert and display counter.
data () {
return {
count: null,
arrays: [
{
name: 'John',
},
{
name: 'Alice',
}
]
...
And a function to change colour and counter:
greet: function (event) {
var name = this.arrays[1]['name'];
var orig = document.getElementById(name).style.background;
document.getElementById(name).style.background = '#454647';
setTimeout(function(){
document.getElementById(name).style.background = orig;
}, 3000);
this.count = this.count + 1;
// setInterval(function(){(document.getElementById(name).style.background = 'blue')}, 3000);
},
I have 2 problems, 1. The counter updates and displays for all cards and not only on the one I'm clicking. 2. In order to target the card I'm clicking on to change background I added arrays[1] to test it working on that user but I need a way for it to change according to the user card I clicked on.
Thanks so much!
So you need individual counters per card/person like:
data () {
return {
arrays: [
{
name: 'John',
count:0
},
{
name: 'Alice',
count:0
}
]
...
Then you can pass the index of the array to your function like:
<div class="card" v-for="(item, index) in arrays" :key="item.id" v-bind:id="item.name">
<div class="l" v-on:click="greet(index)" >Alert{{arrays[index].count}}</div>
</div>
then
greet: function (arrIndex) {
//var name = this.arrays[1]['name'];
var orig = document.getElementById(this.arrays[arrIndex].name).style.background;
setTimeout(function(){
document.getElementById(this.arrays[arrIndex].name).style.background = orig;
}, 3000);
this.arrays[arrIndex].count++
//this.count = this.count + 1;
// setInterval(function(){(document.getElementById(name).style.background = 'blue')}, 3000);
}

Which is the best way to use this script in the Vue.js?

I found a tutorial that covered the same functionality for Angular. Here is the code:
openModal() {
document.getElementById('imgModal').style.display = "block";
}
closeModal() {
document.getElementById('imgModal').style.display = "none";
}
plusSlides(n) {
this.showSlides(this.slideIndex += n);
}
currentSlide(n) {
this.showSlides(this.slideIndex = n);
}
showSlides(slideIndex);
showSlides(n) {
let i;
const slides = document.getElementsByClassName("img-slides") as HTMLCollectionOf < HTMLElement > ;
const dots = document.getElementsByClassName("images") as HTMLCollectionOf < HTMLElement > ;
if (n > slides.length) {
this.slideIndex = 1
}
if (n < 1) {
this.slideIndex = slides.length
}
for (i = 0; i < slides.length; i++) {
slides[i].style.display = "none";
}
for (i = 0; i < dots.length; i++) {
dots[i].className = dots[i].className.replace(" active", "");
}
slides[this.slideIndex - 1].style.display = "block";
if (dots && dots.length > 0) {
dots[this.slideIndex - 1].className += " active";
}
}
}
It directly changes class names and styles using document. Which is the best way to implement this functionality in vue.js?
Option 1 - $refs
For getting element from DOM use ref.
If you set in HTML for an element ref attribute, e.g. <button ref="myButton"> then you can change the style in the same way as in your code:
this.$refs.myButton.style.display="none"
Regarding loops: ref will help only if the elements with the same ref were created by v-for. In this case this.$refs.<your ref> will be an array. Example: let's say you have images displayed by v-for with the same ref:
<img v-for="image in images" ref="imgSlide"...>
Then you can manipulate this.$refs.imgSlide as an array:
this.$refs.imgSlide.forEach(el => el.style.display = 'none')
Option 2 - Class and Style Bindings
Let's say you have <img id='imgModal'...>. You want to change display in 'openModal' method.
Do the folowing steps:
Add style binding: <img id='imgModal' :style="{ display: displayValue }"...
Add binding variable in data and initialize it:
data: {
displayValue: 'none'
}
Change the value of binding variable in the method:
openModal() {
this.displayValue = 'block'
}
With this approach, you don't need loops to change the style for multiple elements. Changing the binding variable will affect all elements bound with it.
Read more about Class and Style Bindings
Specifically for display = "none": Don't hide elements by changing display explicitly. Use v-if or v-show instead.

VueJS: Get left, top position of element?

While converting legacy jquery code, I stuck at getting position of element.
My goal is display floating div above a tag.
Here is my template.
<a href="#none" v-for="busstop in busstops"
:id="busstop.id"
#mouseenter="onMouseEnterBusStop"
#mouseleave="onMouseLeaveBusStop"
onclick="detailsOpen(this);">
<div :class="['marker-info', markInfoBusStop.markerInfoActiveClass]"
:style="markInfoBusStop.markerInfoStyle">
<p><strong>{{markInfoBusStop.busStopNo}}</strong>{{markInfoBusStop.busStopName}}</p>
<dl>
...
</dl>
</div>
Vue code is below.
data: {
markInfoBusStop: {
busStopNo: '12345',
markerInfoActiveClass: '',
markerInfoStyle: {
left: '200px',
top: '200px'
}
},
...
},
methods: {
onMouseEnterBusStop: function(ev) {
let left = ev.clientX;
let top = ev.clientY;
this.markInfoBusStop.markerInfoActiveClass = 'active';
this.markInfoBusStop.markerInfoStyle.left = left + 'px';
this.markInfoBusStop.markerInfoStyle.top = top + 'px';
}
I'm just using current mouse pointer's position, but need element's absolute position.
My legacy jquery is using $(this).position() as below.
$(document).on("mouseenter", "a.marker", function() {
var left = $(this).position().left;
var top = $(this).position().top;
$(".marker-info").stop().addClass("active").css({"left":left, "top": top});
});
This is not a Vue question per se, but more a javascript question. The common way to do this now is by using the element.getBoundingClientRect() method. In your case this would be:
Create a ref on your <a> element and pass that ref in a method like this:
<a ref = "busstop"
href="#none"
v-for="busstop in busstops"
#click = getPos(busstop)>
In your methods object create the method to handle the click:
methods: {
getPos(busstop) {
const left = this.$refs.busstop.getBoundingClientRect().left
const top = this.$refs.busstop.getBoundingClientRect().top
...
}
}
Supported by all current browsers:
https://caniuse.com/#feat=getboundingclientrect
More info here:
https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
this.markInfoBusStop.markerInfoStyle.left = left + 'px';
Above isn't reactive.
1. use Vue.set
See Doc
2. use computed
for example (you still need to customize to fit your needs.)
data
{
left: 200,
top: 200,
}
method
onMouseEnterBusStop: function(ev) {
this.left = ev.clientX;
this.top = ev.clientY;
}
computed
markerInfoStyle: function(){
return {
left: this.left + 'px',
top: this.top + 'px'
}
}