I have an ASP.NET MVC application which I've inherited. The most recent new bits of functionality use custom Vue2 components. Each MVC view is effectively a Single Page Application as I understand it. Each view has its own index.js file e.g.
import Vue from "vue"
window.Vue = Vue
import Test from '#/components/test'
const vm = new Vue({
el: "#test",
components: {
Test
},
data() {
return {}
}
})
The top level component is a single file component e.g. Test.vue:
<template>
<div>
<h1>{{ message }}</h1>
</div>
</template>
<script>
export default {
name: 'Test',
props: {
message: {
type: String,
default: 'This has not worked'
}
}
}
</script>
The MVC view itself looks like:
#{
ViewBag.Title = "Test";
Layout = "~/Views/Shared/_MentorNet.cshtml";
}
<div id="test" v-cloak>
<test message="This has worked"></test>
</div>
#section scripts
{
<script src="#Url.Content("~/dist/js/test.js")" defer></script>
}
The /dist/js/test.js file is created as a result of running Webpack with vue-loader.
With Vue2 this works fine. In this example the text "This has worked" appears when the controller returns the view.
However I'm trying to upgrade to Vue3 and this isn't working. The index.js file now looks like:
import { createApp } from "vue"
import Test from '#/views/admin/test'
const app = createApp(Test);
app.mount("#test");
The component and the view are the same. The component is displayed but the props are not passed through - the view returns the text "This has not worked".
I've looked at the Vue2 migration guide and can't see any breaking changes that would affect this. Can anyone help please?
I've got the answer thanks to a response on the Vue Land Discord channel.
The problem was with the code in the index.js file:
const app = createApp(Test);
app.mount("#test");
When passing a component with a template/render function to createApp it completely overwrites the content of the mount target, in this case
<div id="test">
<test message="This has worked"></test>
</div>
so the
<test message="This has worked"></test>
isn't used. The correct way to do it is either to pass the props as the second argument of create App:
const app = createApp(Test, {
message: "This has worked"
});
or, and the way I'm going to be doing it:
const app = createApp({
components: { Test }
});
Related
New to Quasar & Vue.
I am using q-file which allow pick file & drag to drop file.
However, how do i display the image for preview?
Q-uploader seems work but how do i change the ui of it?
Link to component from Quasar:
https://quasar.dev/vue-components/file-picker
In you template define a q-file and q-img element. Add a #change handler and updateFile function. The q-img will contain the picture you selected.
import { ref } from 'vue';
import { defineComponent } from 'vue';
<script lang="ts">
export default defineComponent({
name: 'Component Name',
components: {},
setup () {
const image = ref(null);
const image1Url = ref('')
return {
image,
image1Url,
updateFile() {
imageUrl.value = URL.createObjectURL(image.value);
}
}
}
})
</script>
<div>
<q-file
v-model="image"
label="Pick one file"
filled
style="max-width: 300px"
#change="updateFile()"
/>
</div>
<div>
<q-img
:src="imageUrl"
spinner-color="white"
style="height: 140px; max-width: 150px"
/>
</div>
Create an #change hook on q-file:
In the script set the url from the file passed in from q-file:
I use Quasar framework in Version 1.6.2. and want to use a component (Drawer.vue) for my drawer. The component is used in my layout file (MainLayout.vue).
I get the following error message in my console:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "rightDrawerOpen"
The drawer works, but not on the first click – only on subsequent clicks.
What is the correct way to pass the parents model to my drawer?
Component: Drawer.vue
<template>
<q-drawer show-if-above v-model="rightDrawerOpen" side="right" bordered>
<q-list>
<q-item-label header>Menü</q-item-label>
</q-list>
</q-drawer>
</template>
<script>
export default {
name: "Drawer",
props: {
rightDrawerOpen: Boolean
}
};
</script>
Parent: MainLayout.vue
<Drawer :right-drawer-open="rightDrawerOpen" />
I would move the q-drawer component back into the MainLayout.vue component, then put the q-list into the child component. Then toggling "rightDrawerOpen" will occur in the "data" of MainLayout.vue instead of on the props of the child component which is where the error is coming from. Example:
MainLayout.vue:
<template>
<q-layout>
<q-drawer show-if-above v-model="rightDrawerOpen" side="right" bordered>
<DrawerContents />
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
<script>
import DrawerContents from './DrawerContents.vue';
export default {
components: {
DrawerContents,
},
data() {
return {
rightDrawerOpen: true,
};
},
};
</script>
DrawerContents.vue:
<template>
<q-list>
<q-item-label header>Menü</q-item-label>
</q-list>
</template>
I have an issue with knockout components in our SPA, on a certain page I'm trying to implement a component; all observables passed through the params attribute are working as they should. But when I create ko.observable(), ko.computed, .. in the component's viewmodel they just don't work as they should. (Btw we're working with asp.net mvc and we need to serve cshtml files as the templates, the razor in the views helps us with several parsing options)
So getting the files are not directly the issue, what is weird is that in the cshtml file, i can't use the unwrapped text: test, I have to write text: test(). computeds aren't updated either, nothing seems to do what it should.
What I think I'm missing is that the component is never added through ko.applyBindings() but that's purely a guess. can anybody shed some light on this please?
What to do with adding a knockout component after page has already loaded. And yes I've searched around, been fiddling all day without avail.. thanks in advance.
This is in our mainpage.
<track-item-list params="value: data, selected: selectedItems"></track-item-list>
Below within script tags:
if (!ko.components.isRegistered('track-item-list')) {
ko.components.register('track-item-list', { require: '#Url.Content("~/Scripts/viewmodels/items/_cItemList.js")' });
}
There's a viewmodel js file:
define(['knockout', 'cshtml!/items/_cItemList'], function (ko, cshtmlString) {
function ItemViewModel(params) {
var self = this;
self.selected = params.selected;
self.test = ko.observable('Hello World!');
}
return { viewModel: ItemViewModel, template: cshtmlString };
});
cshtml template:
<span data-bind="text: selected().length"></span>
<ul data-bind="foreach: selected">
<li data-bind="text: Total"></li>
</ul>
<span data-bind="text: test"></span>
The cshtml file is loaded via a custom script:
define(['jquery'], function($) {
var buildText = {};
return {
load: function (name, req, onLoad, config) {
var self = this,
file = name,
segments = file.split('/');
// If the module name does not have an extension, append the default one
if (segments[segments.length - 1].lastIndexOf('.') == -1) {
file += '.cshtml';
}
$.get(name, function (html, status, xhr) {
console.log('html loaded: ' + name);
onLoad(html);
});
}
};
});
React newbie here. I'm stumped. I have a main class, that has a Header area and a Body area. The Header area has navigation links to download different categories of JSON. On startup, a random post is inserted into the body:
var React = require('react');
var Header = require('./header');
var PostRandom = require('./post-random');
module.exports = React.createClass({
render: function(){
console.log("MAIN.render");
return <div>
<Header />
{this.content()}
</div>
},
content: function(){
console.log("MAIN.content");
if(this.props.children){
return this.props.children
}else{
return <PostRandom />
}
}
});
Here's Header:
var React = require('react');
var Router = require ('react-router');
var Link = Router.Link;
var categories = {
cats: [{
name: 'Writing',
id: 10
},
{
name: 'Essays',
id: 15
},
{
name: 'Poetry',
id: 23
}
]
};
module.exports = React.createClass({
getInitialState: function(){
console.log("HEADER.getInitialState")
return {
topics: categories.cats,
}
},
render: function(){
console.log("HEADER.RENDER");
return <nav className="navbar navbar-static-top">
<div className="container-fluid">
<IndexLink to="/" className = "navbar-brand">
Home
</IndexLink>
<ul className = "nav navbar-nav navbar-right">
{this.renderTopics()}
</ul>
</div>
</nav>
},
renderTopics: function(){
console.log("HEADER: renderTopics")
return this.state.topics.slice(0,8).map(function(topic){
return <li >
<Link activeClassName="active" to={"topics/" + topic.id}>
{topic.name}
</Link>
</li>
});
}
});
When I click on any of the links in Header, via a Route, it loads Topic and passes it the correct topic ID. componentWillReceiveProps in Topic then makes a call to a store which downloads a list of posts for that topic. This all works fine. The Router is pretty simple:
<Route path="/" component = {Main}>
<Route path="topics/:id" component = {Topic} />
</Route>
THE PROBLEM: For some reason beyond my conception, Main is re-rendered every time I click on a Link in Header. This causes Header to re-render, which makes Topic re-render, which results in a 2nd call to the Store. I don't get it. The console output looks like this:
MAIN.render
MAIN.content
HEADER.RENDER
HEADER: renderTopics
TOPIC: componentWillReceiveProps
TOPIC: render
TOPIC:renderPosts
MAIN.render
MAIN.content
HEADER.RENDER
HEADER: renderTopics
TOPIC: componentWillReceiveProps
TOPIC: render
TOPIC:renderPosts
STORE: triggerChange
TOPIC:onChange
TOPIC: render
TOPIC:renderPosts
STORE: triggerChange
TOPIC:onChange
TOPIC: render
TOPIC:renderPosts
Immediately after the initial render of Topic, Main calls render again! This results in the Topic getting loaded twice, and two calls to componentWillReceiveProps, thus two trips to the store. I end up downloading the JSON twice, and get two different random arrays of posts. I see an initial list of random posts, then it is replaced by the 2nd random list of posts. Ugh.
If I reload the page, staying in the same topic, Main is NOT called a second time - so it's something about clicking on the Link that causes the re-render. Here's the console output from a reload:
MAIN.render
MAIN.content
HEADER.getInitialState
HEADER.RENDER
HEADER: renderTopics
TOPIC: getInitialState
TOPIC: componentWillMount
TOPIC: render
TOPIC:renderPosts
58 STORE: triggerChange
TOPIC:onChange
TOPIC: render
TOPIC:renderPosts
Also interesting: if I change the Link tag to just go back to the main page - that is, not calling/rendering Topic at all - like so:
<Link activeClassName="active" to={"/"}>
Main still gets re-rendered, which re-renders Header! Console output:
MAIN.render
MAIN.content
HEADER.RENDER
HEADER: renderTopics
So it has nothing to do with what goes on in Topic, or calls to the Store - the Link tag, when clicked, makes its parent component re-render! I'm sure I must be doing something wrong - but what? Thanks for any insight/help you can offer!
Render shouldn't trigger side effects. If you find yourself trying to control when render is called, you're going down the wrong path.
Routes render when their path is matched, the only time it won't rerender is if you write a shouldComponentUpdate that returns false in conditions when it shouldn't rerender--but don't use that to solve your current problem.
Instead of fetching data in componentWillReceiveProps, it should happen in componentWillMount.
In an ASP Page I have a AngularJS modules directly applied. The ASP-page get from the server a parameter, that is to be used in the module. Like this (foo is the parameter):
<script>
var myApp = angular.module('myApp', []);
myApp.controller('myController', function($scope ) {
$scope.foo = #Model.foo;
...
Now I would like to outsource the module in a separate JS file.
How can I inject the parameter into the module then?
Just define parameters in global scope, and use it in angular app.
Index.cshtml:
<!-- this shoud be initialized before angular appplication -->
<script type='text/javascript'>
window.backendParams = {
foo: #Model.foo ,
bar: #Model.bar
}
</script>
app.js:
myApp.controller('myController', function($scope ) {
$scope.foo = window.backendParams.foo;
...