Vue doesn't render data - vue.js

I had a complex data structure and Vue handled it fine. Then I refactored it and suddenly nothing seems to work. I verified that the data structure was correctly assembled but nothing displays. Vue didn't report any errors and nothing is displayed.
After several hours I found that the Vue render system does not work when the data structure contains a circular reference. To verify I defined two classes A and B. Each can have a reference to the other.
classA.js
export default class classA {
constructor(name) {
this.name = name
this.refB = undefined
}
get name() {return this._name}
set name(n) { this._name= n}
get type() {return 'classA'}
get refB() {return this._refB}
set refB(n) { this._refB= n}
}
classB.js
export default class classB {
constructor(name) {
this.name = name
this.refA = undefined
}
get name() {return this._name}
set name(n) { this._name= n}
get type() {return 'classB'}
get refA() {return this._refA}
set refA(n) { this._refA= n}
}
Next define a Vue component that displays a list of class A. Construct the list in the mount lifecycle hook and be sure to NOT link the instance of classB back to classA. Verify the following renders a raw display of each element of the classAList
<template lang="pug">
div Circular References
div ClassAList length: {{classAlist.length}}
div(v-for="cA in classAlist")
div {{cA}}
</template>
<script>
import ClassA from '../classA'
import ClassB from '../classB'
export default {
data () {
return {
classAlist: []
}
},
mounted: function () {
let a = new ClassA('a1')
let b = new ClassB('b1')
a.refB = b
// Uncomment the next line to create a circular reference in the
// data structure. Immediately, once this next line establishes the
// circular reference the Vue render system fails, silently
// b.refA = a
this.classAlist.push(a)
console.log('Console listing the classA list ', this.classAlist)
}
}
</script>
Check the console to see the array is constructed as expected. Now uncomment the line of code that links the instance of B back to the instance of A. The console will continue to show the array is constructed as expected but the Vue render system doesn't like the circular references.
Is there a workaround?
Is there a way to get Vue to show some error message. (e.g. like JSON.stringfy does on objects like this)?

Related

How to write Testcafe selector('Withoutvalue').withText('Valid Text')?

I am facing a scenario where the element tag name and attribute is changing from env to env, but the text content alone is unique.
Therefore I am not able to define any value for Selector('could not define anything here').
How could I write a path to locate the element ?
I don't see a direct solution for this, but a workaround that could solve your issue. You could have an object that holds environments specific data for you and which helps you for specific cases as the one that you seem to be confronted with. In this object, you could also store environment specific Selectors. This could, written in TypeScript, look as follows:
import { Selector } from "testcafe";
interface EnvironmentData {
envName: string;
myVariableSelector: Selector;
}
// Set up a list that contains environment specific data objects
const CONFIGS: EnvironmentData[] = [
{
envName: "MyEnv1",
myVariableSelector: Selector("my css selector 1").withText("my text 1")
},
{
envName: "MyEnv2",
myVariableSelector: Selector("my css selector 2").withText("my text 2")
},
{
envName: "MyEnv3",
myVariableSelector: Selector("my css selector 3").withText("my text 3")
}
]
// Assuming that you're CI for instance sets a environment variable ENVIRONMENT_NAME to
// any of the specific environments MyEnv1, MyEnv2 or MyEnv3
function getConfigForEnvironment(envDataSets: EnvironmentData[]): EnvironmentData {
const envData = envDataSets.find((c) => c.envName === process.env.ENVIRONMENT_NAME);
if (envData === undefined) {
console.error(`No suitable data for environment '${process.env.ENVIRONMENT_NAME}' found!`);
process.exit(1);
}
return envData;
}
// Determine the right object before the tests start
const envData = getConfigForEnvironment(CONFIGS);
fixture`My awesome tests`.page("myTestUrl");
test("My test", async (t) => {
// Make use of the object that holds the data for the desired environment
await t.expect(envData.myVariableSelector.exists).ok("Should be fine for any environment!");
});
I did it in a simpler way using the or operator (,)
i,e I wrote the selector with Selector('div,span').withText('Value')
The attribute div and span are changing with environment so I used , to pass both and it worked.

how can a Buefy toast be made available to all components in a Vue app?

In a component in a Vue app the following method runs after a user clicks a Submit button on a form:
execute() {
let message = '';
let type = '';
const response = this.actionMode == 'create' ? this.createResource() : this.updateResource(this.data.accountId);
response.then(() => {
message = 'Account ' + this.actionMode + 'd for ' + this.data.name;
type = 'is-success';
})
.catch(e => {
message = 'Account <i>NOT</i> ' + this.actionMode + 'd<br>' + e.message;
type = 'is-danger';
})
.then(() => {
this.displayOutcome(message, type);
this.closeModal();
});
}
The displayOutcome() method in that same component looks like this:
displayOutcome(message, type) {
this.$buefy.toast.open({
duration: type == 'is-danger' ? 10000 : 3500,
position: 'is-bottom',
message: message,
type: type
});
}
The code is working fine within the component. Now I'm trying to move the displayOutcome() method into a helpers.js file and export that function so any component in the app can import it. This would centralize maintenance of the toast and prevent writing individual toasts within each component that needs one. Anyhow, when displayOutcome() gets moved over to helpers.js, then imported into the component an error appears in the console when the function is triggered:
I suspect it has to do with referring to the Vue instance so I experimented with the main.js file and changed this
new Vue({
router,
render: h => h(App),
}).$mount('#app');
to this
var vm = new Vue({
router,
render: h => h(App),
}).$mount('#app');
then in helpers.js
export function displayOutcome(message, type) {
// this.$buefy.toast.open({
vm.$buefy.toast.open({
duration: type == 'is-danger' ? 10000 : 3500,
position: 'is-bottom',
message: message,
type: type
});
}
but that resulted in a "Failed to compile." error message.
Is it possible to make displayOutcome() in helpers.js work somehow?
displayOutcome() requres a reference to this to work, which is fine if you define it as a method on your component object (the standard way). When you define it externally however, you just supply any function instead of a method, which is a function "targeted" on an object. This "targeting" is done through this. So when you're passing a simple function from an external file, there's no association to a specific object, and thus no this available.
To overcome this, you can use displayOutcome.apply(thisArg, methodArgs), where thisArg will be whatever is the this reference in your function, and methodArgs are the remaining arguments that are being passed to the function.
So displayOutcome.apply(4, ['some', 'thing']) would imply that the this reference in displayOutcome() becomes 4 in this case.
Further reading:
Understanding "This" in JavaScript
this on MDN
import { displayOutcome } from './component-utils'
// when calling displayOutcome() from within your component
displayOutcome.apply(this, ['Hello World', 'is-info'])
// component-utils.js
export function displayOutcome(message, type) {
// this.$buefy.toast.open({
this.$buefy.toast.open({
duration: type == 'is-danger' ? 10000 : 3500,
position: 'is-bottom',
message: message,
type: type
});
}
You can create an action in Vuex store and dispatch it from any component.

What is the mechanism of calling a function from a template in Vue js

I am trying to learn Vue.js. I am following a tutorial on this site https://scrimba.com/p/pZ45Hz/c7anmTk. From here I am not getting something clear.
Here is the code below and my confusion as well :
<div id="app">
<wizard :name="harry" :cast="oculus_reparo" ></wizard>
<wizard :name="ron" :cast="wingardium_leviosa"></wizard>
<wizard :name="hermione" :cast="alohomora" ></wizard>
</div>
// emojify returns the corresponding emoji image
function emojify(name) {
var out = `<img src="emojis/` + name + `.png">`
return out
}
// cast returns a spell (function) that decorates the wizard
function cast(emoji) {
var magic = emojify("magic")
return function (wizard) {
return wizard + " " + magic + " " + emoji + " " + magic
}
}
Vue.component("wizard", {
props: ["name", "cast"],
template: `<p v-html="cast(name)"></p>`
})
var app = new Vue({
el: "#app",
data: {
harry : emojify("harry" ),
ron : emojify("ron" ),
hermione : emojify("hermione")
},
methods: {
// oculus_reparo returns a spell (function) that repairs glasses
oculus_reparo: cast(emojify("oculus-reparo")),
// wingardium_leviosa returns a spell (function) that levitates an object
wingardium_leviosa: cast(emojify("wingardium-leviosa")),
// alohomora returns a spell (function) that unlocks a door
alohomora: cast(emojify("alohomora"))
}
})
So far what I have got is that, I have created a component named wizard which takes two properties - name and cast. name is getting the value from data, and so far I understand that cast is calling the method with a parameter.
So both of them should return their specific image. My first confusion: Where does wizard come from and how is it showing the data.name image? If it is because of the method call in the template then why does emoji return another image?
I think the example is unnecessarily complex for the ideas you're looking to learn.
wizard is being globally registered with Vue by Vue.component("wizard", ...). When Vue interprets each wizard call in the template it will replace it with <p v-html="cast(name)"></p> which is set in the wizard component definition. Here name gets mapped to the property that is set via :name=. v-html is just saying to render as html the return value of cast(name), here cast is the function property that is passed to the component and not the cast function locally defined. Everything after that happens as you would expect where emojify returns a template literal that is passed to cast, that then returns a function, which combines the emoji and other properties.

Geb Modules showing an error

I'm working on a geb page object with a repeating set of UI elements a div container with a text input and button within.
I am attempting the following:
class MyModule extends Module{
static content = {
textInput {$("input.editTextField")}
removeInputButton {$("button.removeButton")}
}
}
class MyPage extends Page{
static content = {
myInputs { index ->
$("div.container", index).module(MyModule)
}
}
In IntelliJ the code is highlighted in MyPage on ("div.container, index) when I hover over this I see "'$' in 'geb.Page' cannot be applied to '(java.lang.String.?)'
My goal is to be able pick an iteration of the UI and to perform something like:
myInputs(0).textInput = 'foo'
myInputs(1).textInput = 'bar'
myInputs(5).removeInputButton.click()
I've referred to the documentation for Geb but by all accounts this should work. Any help would be appreciated.
This will work, it's just that your code is not type safe enough for IntelliJ to know which method you're calling. If you change your content definition to:
myInputs { int index ->
$("div.container", index).module(MyModule)
}
then the warning will disappear.

Create a new binding context for a custom attribute

What I want
<div amazingattr.bind="foo">
${$someValueFromAmazingattr}
</div>
Just like how this works:
<div repeat.for="bar of bars">
${$index}
</div>
Where I got stuck
import {customAttribute} from "aurelia-framework";
#customAttribute("amazingattr")
export class AmazingattrCustomAttribute {
bind(binding, overrideContext) {
this.binding = binding;
}
valueChanged(newValue) {
this.binding.$someValueFromAmazingattr = newValue;
}
}
While this works, the $someValueFromAmazingattr is shared outside the custom attribute's element, so this doesn't work:
<div amazingattr.bind="foo">
Foo: ${$someValueFromAmazingattr}
</div>
<div amazingattr.bind="bar">
Bar: ${$someValueFromAmazingattr}
</div>
Both of the "Foo:" and the "Bar:" show the same last modified value, so either foo or bar changes, both binding change to that value.
Why I need this?
I'm working on a value animator, so while I cannot write this (because value converters cannot work this way):
${foo | animate:500 | numberFormat: "0.0"}
I could write something like this:
<template value-animator="value:foo;duration:500">
${$animatedValue | numberFormat: "0.0"}
</template>
I imagine I need to instruct aurelia to create a new binding context for the custom attribute, but I cannot find a way to do this. I looked into the repeat.for's implementation but that is so complicated, that I could figure it out. (also differs in that is creates multiple views, which I don't need)
After many many hours of searching, I came accross aurelia's with custom element and sort of reverse engineered the solution.
Disclaimer: This works, but I don't know if this is the correct way to do it. I did test this solution within embedded views (if.bind), did include parent properties, wrote parent properties, all seem to work, however some other binding solution also seem to work.
import {
BoundViewFactory,
ViewSlot,
customAttribute,
templateController,
createOverrideContext,
inject
} from "aurelia-framework";
#customAttribute("amazingattr")
#templateController //This instructs aurelia to give us control over the template inside this element
#inject(BoundViewFactory, ViewSlot) //Get the viewFactory for the underlying view and our viewSlot
export class AmazingattrCustomAttribute {
constructor(boundViewFactory, viewSlot) {
this.boundViewFactory = boundViewFactory;
this.viewSlot = viewSlot;
}
bind(binding, overrideContext) {
const myBindingContext = {
$someValueFromAmazingattr: this.value //Initial value
};
this.overrideContext = createOverrideContext(myBindingContext, overrideContext);
//Create our view, bind it to our new binding context and add it back to the DOM by using the viewSlot.
this.view = this.boundViewFactory.create();
this.view.bind(this.overrideContext.bindingContext, overrideContext);
this.viewSlot.add(this.view);
}
unbind() {
this.view.unbind(); //Cleanup
}
valueChanged(newValue) {
//`this.overrideContext.bindingContext` is the `myBindingContext` created at bind().
this.overrideContext.bindingContext.$someValueFromAmazingattr = newValue;
}
}