Polymer number property assignment changes value - properties

I have a strange problem.
I implemented a Polymer element for my heating control and have a property for the actual temperature.
The value of the property is something like 23.5 (float value).
When I assign the transfered 'correct' value to the property, the value is changed.
To show the problem I added a console output:
temperature: 23 - states[id].val: 23.2
temperature type: number states[id].val type: number
As you can see in the output the value is rounded during assignment. I don't use any observers or something which could modify the value.
Any ideas why the value is changed?
Here's the relevant part of my Polymer element:
<dom-module id="hm-thermostat">
<link rel="import" type="css" href="./styles/materialdesignicons.css">
<template>
<style is="thermostatStyle">
..
</style>
..
<div class="data">
<div class="flex-display act-vert-align">
<div class="mdi mdi-thermometer mdi-24px margin-right"></div>
<div class="text-width" hidden$="{{small}}">Aktuelle Temperatur:</div>
<div class="actualTemp">{{temperature}}°C</div>
</div>
</div>
</template>
<script>
Polymer({
is: 'hm-thermostat',
properties: {
..
temperature: {
type: Number
},
..
},
..
dataCallback: function (states) {
for (var id in states) {
if (id.indexOf(this.deviceid) >= 0) {
switch (id) {
..
case "hm-rpc.0." + this.deviceid + ".1.TEMPERATURE":
this.temperature = states[id].val;
console.log("temperature: " + this.temperature + " - states[id].val: " + states[id].val);
console.log("temperature type: " + typeof this.temperature + " states[id].val type: " + typeof states[id].val);
break;
..
}
} else {
..
}
}
}
});
</script>
</dom-module>

Related

How to validate multiple user inputs within just one popup using Vue-SweetAlert2

As a coding training, right now I'm making a web page where you can click a "Create" button, which triggers a popup, where you are supposed to fill in 6 data inputs, whose input style varies like text, select etc. (See the code and the attached image below)
<template>
<v-btn class="create-button" color="yellow" #click="alertDisplay">Create</v-btn>
<br/>
<p>Test result of createCustomer: {{ createdCustomer }}</p>
</div>
</template>
<script>
export default {
data() {
return {
createdCustomer: null
}
},
methods: {
alertDisplay() {
const {value: formValues} = await this.$swal.fire({
title: 'Create private customer',
html: '<input id="swal-input1" class="swal2-input" placeholder="Customer Number">' +
'<select id="swal-input2" class="swal2-input"> <option value="fi_FI">fi_FI</option> <option value="sv_SE">sv_SE</option> </select>'
+
'<input id="swal-input3" class="swal2-input" placeholder="regNo">' +
'<input id="swal-input4" class="swal2-input" placeholder="Address">' +
'<input id="swal-input5" class="swal2-input" placeholder="First Name">' +
'<input id="swal-input6" class="swal2-input" placeholder="Last Name">'
,
focusConfirm: false,
preConfirm: () => {
return [
document.getElementById('swal-input1').value,
document.getElementById('swal-input2').value,
document.getElementById('swal-input3').value,
document.getElementById('swal-input4').value,
document.getElementById('swal-input5').value,
document.getElementById('swal-input6').value
]
}
})
if (formValues) {
this.createdCustomer = this.$swal.fire(JSON.stringify(formValues));
console.log(this.createdCustomer);
}
}
}
}
</script>
Technically, it's working. The popup shows up when the "create" button is clicked, and you can fill in all the 6 blanks and click the "OK" button as well. But I want to add some functionalities that check if the user inputs are valid, I mean things like
address should be within 50 characters
firstName should be within 20 characters
customerNumber should include both alphabets and numbers
and so on.
If it were C or Java, I could probably do something like
if(length <= 50){
// proceed
} else {
// warn the user that the string is too long
}
, but when it comes to validating multiple inputs within a single popup using Vue-SweetAlert2, I'm not sure how to do it, and I haven't been able to find any page that explains detailed enough.
If it were just a single input, maybe you could use inputValidor like this
const {value: ipAddress} = await Swal.fire({
title: 'Enter an IP address',
input: 'text',
inputValue: inputValue,
showCancelButton: true,
inputValidator: (value) => {
if (!value) {
return 'You need to write something!'
}
}
})
if (ipAddress) {
Swal.fire(`Your IP address is ${ipAddress}`)
}
, but this example only involves "one input". Plus, what this checks is just "whether an IP address has been given or not" (, which means whether there is a value or not, and it doesn't really check if the length of the IP address is correct and / or whether the input string consists of numbers / alphabets).
On the other hand, what I'm trying to do is to "restrict multiple input values (such as the length of the string etc)" "within a single popup". Any idea how I am supposed to do this?
Unfortunately the HTML tags to restrict inputs (e.g. required, pattern, etc.) do not work (see this issues),
so I find two work around.
Using preConfirm as in the linked issues
You could use preConfirm and if/else statement with Regex to check your requirement, if they are not satisfied you could use Swal.showValidationMessage(error).
const{value:formValue} = await Swal.fire({
title: "Some multiple input",
html:
<input id="swal-input1" class="swal-input1" placeholder="name"> +
<input id="swal-input2" class="swal-input2" placeholder="phone">,
preConfirm: () => {
if($("#swal-input1").val() !== "Joe"){
Swal.showValidationMessage("your name must be Joe");
} else if (!('[0-9]+'.test($("#swal-input2").val())){
Swal.showValidationMessage("your phone must contain some numbers");
}
}
});
Using Bootstrap
In this way Bootstrap does the check at the validation, you need to include class="form-control" in your input class and change a little your html code.
If some conditions fails, the browser shows a validation message for each fields, in the order they are in the html code.
const {value: formValue} = await Swal.fire({
title: 'Some multiple inputs',
html:
'<form id="multiple-inputs">' +
'<div class="form-group">' +
'<input type="text" class="form-control swal-input1" id="swal-input1" min=2 max=4 required>' +
'</div>' +
'<div class="form-group">' +
'<input type="text" class="form-control swal-input2" id="swal-input2" placeholder="Name" pattern="[A-Za-z]" required>' +
'</div>' +
'</form>',
});
I have tried both the solution, actually only with Bootstrap3 but it should work also with the latest release.

v-html with conditional rendered

I'm learning Vue.js and I really love it, but I'm currently facing a problem.
I have this code in my template:
<button type="button" class="btn btn-warning ml-auto" v-if="steps.length - 1 == currentStep" #click="submitProject" v-html="paymentAmount">{{model.projectSelectedOptions.length < 1 ? 'Publish without option (free)' : paymentAmount }}</button>
And the paymentAmount computed:
paymentAmount() {
var amount = 0;
this.model.projectSelectedOptions.forEach(function(option) {
if (option == 1) {
option = 19.99
} else if (option == 2) {
option = 9.99
} else {
option = 7.99
}
amount += option;
});
return 'Next : payment (' + amount.toFixed(2) + ' € <span class="price-ht">HT</span>)';
}
My problem is that if I put v-html="paymentAmount" in my button, I never see "Publish without option (free)", just "Next : payment (0 € HT)".
If I remove v-html attribute, I can see "Publish without option (free)" but when I have selected some options, Vue.js render "Next : payment (0 € <span class="price-ht">HT</span>)" (so with raw span).
How should I do it?
EDIT:
For the moment, I added a button with a different v-if condition, but it would be cool if I can do it in one line of code, I don't like the duplicate 😅.
How about putting all your logic inside of your computed property?
HTML part:
<button type="button" class="btn btn-warning ml-auto" v-if="steps.length - 1 == currentStep" #click="submitProject" v-html="paymentAmount"></button>
Vue part:
paymentAmount() {
if(this.model.projectSelectedOptions.length < 1)
{
return 'Publish without option (free)';
}
else
{
var amount = 0;
this.model.projectSelectedOptions.forEach(function(option) {
if (option == 1) {
option = 19.99
} else if (option == 2) {
option = 9.99
} else {
option = 7.99
}
amount += option;
});
return 'Next : payment (' + amount.toFixed(2) + ' € <span class="price-ht">HT</span>)';
}
}
You might use a conditional span as button content:
<button
type="button"
class="btn btn-warning ml-auto"
v-if="steps.length - 1 == currentStep"
#click="submitProject">
<span v-if="model.projectSelectedOptions.length < 1">Publish without option (free)</span>
<span v-else v-html="paymentAmount"></span>
</button>

Dynamically Tally Child Elements by Classname in Vue

I have a page that allows a user to drag/drop images into pre-defined DIVs, then I tally up the total value of the images based on their class name. What I am trying to do is get vue to read the values from each outer div.answer and get the class names of the child images.
My source code is:
<div
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint"
></div>
<script>
Vue.component('box-answers', {
props: ['level','hint'],
template: '<div class="droppable answer :id="level" :title="hint"></div>'
});
new Vue({
el: '#mainapp',
data: {
boxes: [
{ id: 1, level: 'baselevel-1', hint: 'x 1' },
{ id: 2, level: 'baselevel-2', hint: 'x 20' },
{ id: 3, level: 'baselevel-3', hint: 'x 400' },
{ id: 4, level: 'baselevel-4', hint: 'x 8,000' },
{ id: 5, level: 'baselevel-5', hint: 'x 160,000' }
]
}
</script>
This converts to the follow HTML (the nested DIVs and SPANs are user-possible entries by dragging):
<div id="baselevel-5" class="droppable answer" title="x 160,000">
<div><img src="images/line.gif" alt="Five" class="imgfive"></div>
<span><img src="images/dot.gif" alt="One" class="imgone"></span>
</div>
...
<div id="baselevel-1" class="droppable answer" title="x 1">
<span><img src="images/line.gif" alt="One" class="imgone"></span>
</div>
Currently, I have jQuery/JavaScript calculating the point values using the following:
$(function(j) {
var arAnswers = Array(1);
count = 0; //
j("div.answer").each(function( idx ) {
currentId = j(this).attr('id');
ones = 0;
fives = 0;
if ( j("#" + currentId).children().length > 0 ) {
ones = j("#" + currentId).children().find("img.imgone").length * 1;
fives = j("#" + currentId).children().find("img.imgfive").length * 5;
arAnswers[count] = ones + fives; //Tally box value
count++;
}
});
});
I would like Vue to perform similar iteration and addition to return total value of ones and fives found based on the image classname.
Currently, you are approaching this problem as a pure-play DOM operation. If that is what you need then you can simply use $refs:
<!-- NOTICE ref -->
<div ref="boxAnswers"
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint">
</div>
Inside your high-level component, you will have a function like:
function calculate() {
// NOTICE $refs
const arAnswers = this.$refs.boxAnswers.map((x) => {
// $el is the DOM element
const once = x.$el.querySelectorAll('img.imgone').length * 1;
const fives = x.$el.querySelectorAll('img.imgfive').length * 5;
return once + fives
});
return arAnswers;
}
But this is not the correct Vue way of doing things. You have to think in terms of events and data model (MVVM - don't touch DOM. DOM is just a representation of your data model). Since, you have a drag-n-drop based application, you have to listen for drag, dragstart, dragend and other drag events. For example:
<!-- NOTICE drop event -->
<div #drop="onDropEnd(box, $event)"
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint">
</div>
Your onDropEnd event handler will look like:
function onDrop(box, $event) {
// box - on which box drop is happening
// $event.data - which image is being dropped
// Verify $event.data is actually the image you are intending
if ($event.data === 'some-type-image') {
// Do the counting manipulations here
// ... remaining code
}
}
This is not a complete code as I don't know other components. But it should help you with the required direction.

VueJS: How to bind a datetime?

I receive from a WebAPI a JSON object that has this property:
"BirthDate": "2018-02-14T15:24:17.8177428-03:00",
the HTML:
<input type="date" v-model="BirthDate" />
I bind that object using VueJS, but
VueJS give this message in the console:
The specified value "2018-02-14T15:24:17.8177428-03:00" does not conform to the required format, "yyyy-MM-dd".
On this case the only relevant part is 2018-02-14, I can discard the other information.
I tried to create a Two Way filter to convert that Date Time to the required format but did not have success. See VueJS two way filter
How can I convert and bind that Date/Time format to the required Date Format of the HTML date input ?
Considering myDate is your property, you can use:
<input type="date" :value="myDate && myDate.toISOString().split('T')[0]"
#input="myDate = $event.target.valueAsDate">
Since v-model is only syntactic sugar to :value and #input, you can use them instead. In this case, we used and changed them a little (to format the String that is the output of the date input to a Date object and vice-versa).
Check demo and caveats below.
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
myDate: new Date('2011-04-11T10:20:30Z')
},
methods: {
setMyDateToToday() {
this.myDate = new Date();
},
addADayToMyDate() {
if (this.myDate) // as myDate can be null
// you have to set the this.myDate again, so vue can detect it changed
// this is not a caveat of this specific solution, but of any binding of dates
this.myDate = new Date(this.myDate.setDate(this.myDate.getDate() + 1));
},
}
});
// Notes:
// We use `myDate && myDate.toISOString().split('T')[0]` instead
// of just `myDate.toISOString().split('T')[0]` because `myDate` can be null.
// the date to string conversion myDate.toISOString().split('T')[0] may
// have timezone caveats. See: https://stackoverflow.com/a/29774197/1850609
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ message }}</p>
<input type="date" :value="myDate && myDate.toISOString().split('T')[0]"
#input="myDate = $event.target.valueAsDate">
<p>
<code>
myDate: {{ myDate }}</code>
</p>
<button #click="setMyDateToToday">Set date one to today</button>
<button #click="addADayToMyDate">Add a day to my date</button>
</div>
i think this not related to vueJs , the input type="date" expected a date in YYYY-MM-DD format, or empty
see here : https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date,
it would be better if you split date object as date and time field
Correction to #acdcjunior in that this shouldn't be off by one day
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
myDate: new Date('2011-04-11T10:20:30Z')
},
methods: {
setMyDateToToday() {
this.myDate = new Date();
},
addADayToMyDate() {
if (this.myDate) // as myDate can be null
// you have to set the this.myDate again, so vue can detect it changed
// this is not a caveat of this specific solution, but of any binding of dates
this.myDate = new Date(this.myDate.setDate(this.myDate.getDate() + 1));
},
getDateClean(currDate) {
// need to convert to UTC to get working input filter
console.log(currDate);
let month = currDate.getUTCMonth() + 1;
if (month < 10) month = "0" + month;
let day = currDate.getUTCDate();
if (day < 10) day = "0" + day;
const dateStr =
currDate.getUTCFullYear() + "-" + month + "-" + day + "T00:00:00";
console.log(dateStr);
const d = new Date(dateStr);
console.log(d);
return d;
}
}
});
// Notes:
// We use `myDate && myDate.toISOString().split('T')[0]` instead
// of just `myDate.toISOString().split('T')[0]` because `myDate` can be null.
// the date to string conversion myDate.toISOString().split('T')[0] may
// have timezone caveats. See: https://stackoverflow.com/a/29774197/1850609
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ message }}</p>
<input type="date" :value="myDate && myDate.toISOString().split('T')[0]"
#input="myDate = getDateClean($event.target.valueAsDate)">
<p>
<code>
myDate: {{ myDate }}</code>
</p>
<button #click="setMyDateToToday">Set date one to today</button>
<button #click="addADayToMyDate">Add a day to my date</button>
</div>

Vue.js method returning undefined when accessing data

I'm using Vue.js to populate a list of locations, and using a method that should return a formatted map link using the address.
The problem is that in the method, trying to return this.Address ends up in undefined. Here's what I've got:
// the Vue model
pbrplApp = new Vue({
el: '#pbrpl-locations',
data: {
'success' : 0,
'errorResponse' : '',
'wsdlLastResponse' : '',
't' : 0,
'familyId' : 0,
'brandId' : 0,
'srchRadius' : 0,
'lat' : 0,
'lon' : 0,
'response' : []
},
methods: {
getDirections: function(address,city,st,zip){
// THIS Doesn't work:
//var addr = this.Address + ',' + this.City + ',' + this.State + ' ' + this.Zip;
var addr = address + ',' + city + ',' + st + ' ' + zip;
addr = addr.replace(/ /g, '+');
return 'https://maps.google.com/maps?daddr=' + addr;
}
}
});
// the template
<ul class="pbrpl-locations">
<template v-for="(location,idx) in response">
<li class="pbrpl-location" :data-lat="location.Latitude" :data-lon="location.Longitude" :data-premise="location.PremiseType" :data-acct="location.RetailAccountNum">
<div class="pbrpl-loc-idx">{{idx+1}}</div>
<div class="pbrpl-loc-loc">
<div class="pbrpl-loc-name">{{location.Name}}</div>
<div class="pbrpl-loc-address">
<a :href="getDirections(location.Address,location.City,location.State,location.Zip)" target="_blank">
{{location.Address}}<br>
{{location.City}}, {{location.State}} {{location.Zip}}
</a>
</div>
</div>
</li>
</template>
</ul>
Right now, I'm having to pass the address, city, state and zip code back to the model's method, which I know is wrong - but I can't find anything in the vue.js docs on the proper way to get the values.
What's the proper way to get the method to reference "this" properly? Thanks for your help.
this.Address doesn't work because Address is not part of your data. It looks like what you are doing is iterating through response, which somehow gets populated with locations.
You could just pass location to getDirections instead of each of the parameters.
getDirections(location){
let addr = location.Address+ ',' + location.City + ',' + location.State + ' ' + location.Zip
....
}
And change your template to
<a :href="getDirections(location)" target="_blank">
In general, in Vue methods, this will refer to the Vue itself, which means that any property that is defined on the Vue (of which properties defined in data are included) will be accessible through this (barring there being a callback inside the method that incorrectly captures this). In your case, you could refer to any of success, errorResponse, wsdlLastResponse, t, familyId, brandId, srchRadius, lat, lon, or response through this in getDirections.