Vue2: passing function as prop triggers warning that prop is already set - javascript

I am new to Vue and I am so far enjoying the Single File Components.
Prior to making what I really want to make, I figured I would try some small things to see if I grasp the concept.
So I set off to make a component for opening an XMLHttpRequest, with a progress bar.
<template >
<div v-if="showQ">
<div class="text-muted">
<span>{{humanReadableLead}}</span>
<span :class="'text-'+color">{{humanReadableMessage}}</span>
<span>{{humanReadableEnd}}</span>
</div>
<div class="progress">
<div
class="progress-bar progress-bar-striped progress-bar-animated"
:class="'bg-'+color"
role="progressbar"
:style="{width: String(percent)+'%'}"
:aria-valuenow="percent"
aria-valuemin="0"
aria-valuemax="100"
></div>
</div>
<div class="text-right text-muted form-text text-small">
<span class="float-left">{{xhrMessage}}</span>
<span
class="badge"
:class="'badge-'+color"
data-toggle="tooltip"
data-placement="right"
:title="readyStateTooltip"
>
{{xhr.readyState}}
</span>
<span
class="badge"
:class="'badge-'+color"
data-toggle="tooltip"
data-placement="right"
:title="statusTooltip"
>
{{xhr.status}}
</span>
<span
v-if="transferComplete"
#click="goodbye"
class="badge badge-secondary"
data-toggle="tooltip"
data-placement="right"
title="Dismiss progress bar"
>
×
</span>
</div>
</div>
</template>
<script>
import {httpStatusCodes, httpReadyStateCodes} from './http-responses';
export default {
props: {
method: {
type: String,
default: "GET",
validator: function(value) {
return ["GET", "POST", "PUT", "DELETE"].includes(value)
}
},
url: {
type: String,
required: true
},
async: {
type: Boolean,
default: true
},
success: {
type: Function,
default: function() {
console.log(this.xhr.response)
}
},
readystatechange: {
type: Function,
default: function(event) {
}
},
automaticCloseQ: {
type: Boolean,
default: false
}
},
data: function() {
return {
xhr: new XMLHttpRequest(),
httpStatusCodes:httpStatusCodes,
httpReadyStateCodes:httpReadyStateCodes,
color: "primary",
percent: 0,
humanReadableLead: "",
humanReadableMessage: "",
humanReadableEnd: "",
xhrMessage: "",
showQ: true,
completeQ: false
}
},
computed: {
readyStateTooltip: function() {
var rs = this.xhr.readyState,
rsc = httpReadyStateCodes[rs]
return `Ready state ${rs}: ${rsc}`
},
statusTooltip: function() {
var s = this.xhr.status
// s = s == 0 ? 218 : s
var sc = httpStatusCodes[s]
return `Status ${s}: ${sc}`
},
transferComplete: function() {
return this.completeQ
}
},
methods: {
open: function() {
this.xhr.open(this.method, this.url, this.async)
},
send: function() {
this.xhr.send()
},
goodbye: function() {
this.showQ = false
}
},
created: function() {
var that = this
that.open()
that.xhr.addEventListener("error", function(event) {
that.color = "danger"
that.xhrMessage = "An error has occured."
})
this.xhr.addEventListener("progress", function(event) {
if (event.lengthComputable) {
var percentComplete = event.loaded / event.total * 100;
that.percent = percentComplete
} else {
that.percent = 100
that.xhrMessage = "Unable to compute progress information since the total size is unknown."
}
})
that.xhr.addEventListener("abort", function(event) {
that.color = "danger"
that.xhrMessage = "The transfer has been canceled by the user."
});
that.xhr.addEventListener("load", function(event) {
that.color = "success"
that.xhrMessage = "Transfer complete."
that.completeQ = true
if (that.automaticCloseQ) { that.showQ = false }
that.success()
})
that.xhr.addEventListener("readystatechange", function(event) {
that.readystatechange(event)
})
that.send()
}
}
</script>
<style scoped>
</style>
and in index.html
<div id="request" style="width:50%;" >
<http-request :url="'./<some-file>'"/>
</div>
with JS
var progress = new Vue({
el: '#request',
components: { httpRequest }
})
and this works fairly nicely...
However, there are a few small bugs that for the life of me I can not figure out:
I would like to define a function onSuccess that I pass to the prop success, but this throws an error from Vue
the computed properties for statusTooltip does not get updated
trying to set automaticCloseQ results in the default value no matter how I try to bind
e.g.
var onSuccess = function() {console.log('here')}
<http-request :url="'./<some-file>'" :success="onSuccess" :automaticCloseQ="true"/>
what am I missing?

Hope this helps.
I would like to define a function onSuccess that I pass to the prop success, but this throws an error from Vue
You are defining onSuccess outside of Vue. It should be defined in Vue's methods
the computed properties for statusTooltip does not get updated
In Javascript, an object is passed by reference. xhr always reference the same object. That's why the computed value won't update. See https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats . 1 way to solve it is to have another reactive data called xhrStatus and update this status manually in the xhr's event listeners.
trying to set automaticCloseQ results in the default value no matter how I try to bind
(I dunno what this means...)

Related

conditionally wrap <td> data in a link with vue

I'm trying to look at a certain table cell td element and set a condition, so that if the condition is met I show the data in a link, and if not I just show the data as text
I can't seem to figure out how to make this happen. I currently have a hover over that is set to that link and I've set the condition on my anchor tag but it's not working at all.
I have a snippet below, but basically I expect to see 0 printed as a link that triggers the hover/popover functionality. If test is set to 1, I would expect to just see a printed 1
Where am i wrong here?
<script>
new Vue({
el: "#app",
props: {
text: {
type: String,
required: true
}
},
data: {
timeout: null,
showCard: false,
isLoaded: false,
selected: '',
test: 0
},
methods: {
mouseover: function (event) {
console.log(event.pageX, event.pageY)
clearTimeout(this.timeout)
var self = this
this.timeout = setTimeout(function () {
self.showCard = true
setTimeout(function () {
self.isLoaded=true;
}, 500)
}, 500)
},
mouseleave: function () {
var self = this
this.timeout = setTimeout(function () {
self.showCard = false
self.isLoaded = false
}, 200)
},
cardOver: function () {
console.log('card over')
clearTimeout(this.timeout);
this.showCard = true
},
cardLeave: function () {
var self = this
this.timeout = setTimeout(function () {
self.showCard = false
self.isLoaded = false
}, 200)
}
}
});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<table id="app">
<tbody>
<td>
<a v-if="test == 0" href="javascript:void(0)"
#mouseover="mouseover"
#mouseleave="mouseleave">#{{test}}
</a>
<div id="hovercard" v-show="showCard" #mouseover="cardOver" #mouseleave="cardLeave">
<div :class="['bg', {'loaded': isLoaded}]"></div>
<div class="content">
<p>Orders ready</p>
</div>
</div>
</td>
</tbody>
</table>
Wrap your anchor inside in-built component tag conditionally. below is the sample snippet.
<td>
<component :is="test == 0 ? 'a' : 'span'" #mouseover="mouseover" #mouseleave="mouseleave" :href="'javascript:void(0)' || ''" target="_blank">{{ test }}</component>
</td>
Have you considered
<a v-if="test == 0"
href="javascript:void(0)"
#mouseover="mouseover"
#mouseleave="mouseleave">{{test}}
</a>
<span v-else>{{test}}</span>
Docs: Conditional rendering

Vue js. Data fields not binding

I have the following definition for the Vue element:
new Vue({
el: "#app",
data: {
latitude1: 'a',
name: 'aa'
},
mounted() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(position => {
this.latitude1 = position.coords.latitude;
})
} else {
this.latitude1 = "WTF??"
// this doesn't work either:
// this.$nextTick(() => { this.latitude1 = "WTF??" })
}
},
methods: {
// button works... WTF?!?
doIt() {
this.latitude1 = "WTF??"
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<div>{{ latitude1 }}: {{ name }}</div>
<button #click="doIt">Do it</button>
</div>
I can see the location data being populated. The alert displays the latitude but the 2 way binding for the data field latitude1 is not working.
I have tried storing the object state using this and that also did not work.
My html is as follows:
<div class="form-group" id="app">
<p>
{{latitude1}}
</p>
</div>
One of the things to do inside the Vue.js is to use the defined methods for reactive properties changes.
Here is a code I've provided for it:
function error(err) {
console.warn(`ERROR(${err.code}): ${err.message}`);
}
var options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};
new Vue({
el: "#app",
data: {
latitude1: 'a',
name: 'aa'
},
mounted: function() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(position => {
console.log(position.coords.latitude);
Vue.set(this, 'latitude1', position.coords.latitude);
}, error, options)
}
}
});
I also set error handler and options for the navigator query. For following the results please check the console.

Vue.js component with Vuex.js (instead of vue-i18n.js)

I've been trying to reproduce the button behavior that I've here, but with a different implementation. Basically, I'm trying to use Vuex instead of vue-i18n.js for internationalization purposes.
I now have the following code block, the purpose of which is to create language states and perform a XMLHttpRequest (for the .json files storing the various translations):
Vue.use(Vuex);
var storelang = new Vuex.Store({
state: {
lang: {}
},
mutations: {
LANG: function (state, ln) {
function loadJSON(callback) {
var xobj = new XMLHttpRequest();
xobj.overrideMimeType("application/json");
xobj.open('GET', '../resources/i18n/' + ln + '.json', true);
xobj.onreadystatechange = function () {
if (xobj.readyState == 4 && xobj.status == "200") {
callback(xobj.responseText);
}
};
xobj.send(null);
}
loadJSON(function (languageJSON) {
state.lang = JSON.parse(languageJSON);
})
},
strict: true
}
});
var mix = Vue.mixin({
computed: {
lang: function () {
return storelang.state.lang;
}
}
});
On my component constructor (created and initialized in the root Vue instance), I've the following:
components: {
lang: {
template: '<button type="button" class="btn btn-info" #click.prevent=activate(lang.code) #click="setActiveLang" v-show="!isActive">{{ lang.code }}</button>',
props: [
'active',
'lang'
],
computed: {
isActive: function() {
return this.lang.code == this.active.code
}
},
methods: {
activate: function(code) {
storelang.dispatch('LANG', code);
},
setActiveLang: function() {
this.active = this.lang;
}
},
ready: function() {
storelang.dispatch('LANG', 'en'); //default language
}
}
}
On my root Vue instance's data object, I've added the following:
langs: [{
code: "en"
}, {
code: "fr"
}, {
code: "pt"
}],
active: {
"code": "pt"
}
And finally, on my html:
<div v-for="lang in langs">
<p>
<lang :lang="lang" :active.sync="active"></lang>
</p>
</div>
I cannot figure out what I'm doing wrong here.
UPDATE
Here's a JsFiddle (I've exchanged the XMLHttpRequest request for json arrays). Also, this is a working example, but the language selector buttons do not hide when the respective language is selected, which is the opposite of what I want. Meaning that, I'm attempting to hide each individual language selector button when the user clicks it and selects the respective language (while showing the other language selector buttons).
The solution involves saving anactive state in the store, in addition to the lang state:
new Vuex.Store({
state: {
active: {},
lang: {}
Adding an ACTIVE mutation:
ACTIVE: function(state, ln) {
var langcode = 'en'
//portuguese
if (ln === 'pt') {
langcode = 'pt'
}
//french
if (ln === 'fr') {
langcode = 'fr'
}
state.active = langcode
}
On the computed properties block, one also needs to add getter functions for the active state and return the langcode that is currently active:
Vue.mixin({
computed: {
lang: function() {
return storelang.state.lang
},
enIsActive: function() {
return storelang.state.active == 'en'
},
frIsActive: function() {
return storelang.state.active == 'fr'
},
ptIsActive: function() {
return storelang.state.active == 'pt'
}
}
})
Then, it is just a question of conditionally displaying each of the buttons on the component template by adding v-show="!enIsActive", v-show="!frIsActive", etc.:
var langBtn = Vue.extend({
template: '<button type="button" class="btn btn-info" #click.prevent=activate("en") v-show="!enIsActive">en</button><button type="button" class="btn btn-info" #click.prevent=activate("pt") v-show="!ptIsActive">pt</button><button type="button" class="btn btn-info" #click.prevent=activate("fr") v-show="!frIsActive">fr</button>',
Finally, on the activate method, adding a new line to change the active state when the user clicks a button:
methods: {
activate: function(x) {
storelang.dispatch('LANG', x)
storelang.dispatch('ACTIVE', x)
}
},
The full working code here.

Unable to process binding in devextreme / knockout

I have another function that calls the `"GetEmployee" function from the server and when I check the call in the Chrome Debugger I can see my details coming through as expected so it's not a server side error.
However, if I place a breakpoint on the third line of quickBookingSource the breakpoint is never reached. I do have quickBooking:quickBookingSource in the viewModel definition and there are no typos.
Also, you'll see I have added a ternary operator with "blah" text as the safety net but to no avail
The error message I'm getting is:
Uncaught ReferenceError: Unable to process binding "text: function (){return project }"
Message: project is not defined
Code is:
HTML
<div data-bind="dxTileView: {listHeight:tileWidgetHeight,itemClickAction:sendProject,baseItemHeight: 80, baseItemWidth: 100,dataSource:quickBooking}">
<div data-options="dxTemplate : { name:'item' }" class="tile">
<h2 data-bind="text: project"></h2>
<p data-bind="text: name"></p>
<p data-bind="text: costCenter"></p>
<p>Jetzt Büchen</p>
</div>
</div>
JS
var quickBookingSource = DevExpress.data.createDataSource({
load: function (loadOptions) {
if (loadOptions.refresh) {
var deferred = new $.Deferred();
callService("GetEmployee",
{
employeeNo: aktivEmployee.id
},
function (result) {
var mapped = $.map(result, function (data) {
return {
name: data.LastNProjects? data.LastNProjects["Name"]:"blah",
project: data.LastNProjects? data.LastNProjects["Address"]:"blah",
costCenter: data.LastNCostCenters? data.LastNCostCenters["Name"]:"blah"
}
});
deferred.resolve(mapped);
});
return deferred.promise();
}
},
});
Thanks in advance
I reproduced your case in the following fiddle http://jsfiddle.net/tabalinas/7aSS7/.
Request to server is mocked with setTimeout.
You can click Refresh button to reload dataSource. The demo shows that your code works correctly. It seems that problem is in client code behind the scene if server code is ok.
<div class="dx-viewport dx-theme-ios dx-version-major-6 dx-theme-ios-typography">
<div data-bind="dxButton: { text: 'Refresh', clickAction: reloadData }"></div>
<span data-bind="visible: loading">Loading ...</span>
<div data-bind="dxTileView: { listHeight: tileWidgetHeight, itemClickAction: sendProject, baseItemHeight: 200, baseItemWidth: 100, dataSource: quickBooking }">
<div data-options="dxTemplate : { name:'item' }" class="tile">
<h2 data-bind="text: project"></h2>
<p data-bind="text: name"></p>
<p data-bind="text: costCenter"></p>
<p>Jetzt Büchen</p>
</div>
</div>
</div>
// stub service call
var callService = function(method, data, success) {
var fakeData = [
{ LastNProjects: { Name: 'test project1' }, LastNCostCenters: { Name: 'cost center1' }},
{ LastNProjects: { Name: 'test project2' }, LastNCostCenters: { Name: 'cost center2' }},
{ LastNProjects: { Name: 'test project3' }, LastNCostCenters: { Name: 'cost center3' }},
{ LastNProjects: { Name: 'test project4' }, LastNCostCenters: { Name: 'cost center4' }}
];
setTimeout(function() {
success(fakeData);
}, 1500);
};
var quickBookingSource = DevExpress.data.createDataSource({
load: function (loadOptions) {
vm.loading(true);
if (loadOptions.refresh) {
var deferred = new $.Deferred();
callService("GetEmployee",
{
employeeNo: 'id'
},
function (result) {
var mapped = $.map(result, function (data) {
return {
name: data.LastNProjects? data.LastNProjects["Name"]:"blah",
project: data.LastNProjects? data.LastNProjects["Address"]:"blah",
costCenter: data.LastNCostCenters? data.LastNCostCenters["Name"]:"blah"
}
});
deferred.resolve(mapped);
vm.loading(false);
});
return deferred.promise();
}
},
});
var vm = {
loading: ko.observable(false),
reloadData: function() {
quickBookingSource.load();
},
tileWidgetHeight: 300,
quickBooking: quickBookingSource,
sendProject: function(args) {
console.log("send " + args.itemData.name);
}
};
ko.applyBindings(vm);

knockoutjs bindings issue

I'm having issues with my knockoutjs implementation. Seems to be working fine on Chrome and Safari IE and FF have thrown a hissy fit.
The message which I encounter is as follows:
Unable to parse bindings. Message: TypeError: 'AccountName' is
undefined; Bindings value: value: AccountName
The issue is happening within a script tag which serves as a knockout template:
<div id="newAccountDialog" class="dialog" data-bind="dialog: { autoOpen: false, resizable: false, modal: true, width: 350, title: 'Exchange Account'}, template: { name: 'dialogFormTemplate', data: CurrentAccount }, openDialog: IsNew"></div>
<script id="dialogFormTemplate" type="text/html">
<form id="dialogForm">
<h1>Exchange Account Manager</h1>
<p>Add new or edit an existing exchange account settings.</p>
<label for="AccountName">
Account
</label>
<input id="AccountName" name="AccountName" type="text" data-bind="value: AccountName, valueUpdate: 'afterkeydown'" class="ui-widget-content ui-corner-all" />
<div class="buttonsContainer floatRight">
<button id="Save" data-bind="click: $root.SaveAccount, dialogcmd: { id: 'newAccountDialog', cmd: 'close'}, jqButton: { icons: { primary: 'ui-icon-disk' } }">Save & Close</button>
</div>
</form>
</script>
I assume some sort of early binding is being triggered on the template
data : CurrentAccount
where an undefined / null is being passed into CurrentAccount. I have seen this issue outside of script tags, but only if the observable is not defined or null.
My viewmodel looks as following:
var AccountModel = function () {
var self = this;
self.Accounts = ko.observableArray([]);
self.CurrentAccount = ko.observable(null);
self.IsNew = ko.observable(false);
self.LoadAccounts = function () {
$account.invoke("GetAccounts", {}, function (data) {
var mapped = $.map(data, function (item) {
var account = new Account(item);
var innerMapped = $.map(item.Mailboxes, function (mailbox) {
return new Mailbox(mailbox);
});
account.Mailboxes(innerMapped);
return account;
});
self.Accounts(mapped);
});
}
self.EditAccount = function (data) {
self.CurrentAccount(data);
self.IsNew(true);
}
self.SaveAccount = function () {
if (self.CurrentAccount().Id() <= 0) {
$account.invoke('AddAccount', ko.toJS(self.CurrentAccount()), function (data) {
self.Accounts.push(new Account(data));
self.CurrentAccount(new Account(data));
self.IsNew(true);
});
} else {
$account.invoke('UpdateAccount', ko.toJS(self.CurrentAccount()), function (data) {
//self.CurrentAccount(new Account(data));
});
}
}
self.CreateAccount = function () {
self.IsNew(true);
var account = { Id: 0, AccountName: '', IsNTLM: -1, Email: '', Password: '', Domain: 'mydomain', ExchangeVersion: 1, Mailboxes: [] };
self.CurrentAccount(new Account(account));
}
};
My dialog bindingHandler is defined as follows:
ko.bindingHandlers.dialog = {
init: function (element, valueAccessor) {
var options = ko.utils.unwrapObservable(valueAccessor()) || {};
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).dialog('destroy');
});
$(element).dialog(options);
}
};
I have ommited the Account object, as it is possibly not required in this context.
I would appreciate any help.
Thank you in advance.
There is no "early" binding in Knockout. Everything is bound when you call ko.applyBindings. But certain bindings can stop or delay binding of their descendant elements. template is one of those when you use the if or ifnot options. In your case, you can use the if option like this:
template: { name: 'dialogFormTemplate', data: CurrentAccount, 'if': CurrentAccount }
Note: The quotes around if are required in some older browsers.

Categories

Resources