I'm trying to set up a .NET core project using Razor Pages and include vueJs inside the razor page for all my logic.
Something like this:
#{
ViewData["Title"] = "VueJs With Razor";
}
<h2>#ViewData["Title"].</h2>
<div id="app">
<span>{{ message }}</span>
</div>
<script>
new Vue({
el: '#app',
data: {
message : 'Hello vue.js'
}
})
</script>
I have read that mixing Vue and Razor pages is a bad practice, and one should use Razor OR Vue.
Why is this?
Mixing VueJs and Razor Pages is not necessarily a bad thing, it can be great!
I use Vue with razor for non SPA pages and the two work well together. I choose to use Vue by loading it via a script tag from a CDN and and I do not leverage the use of WebPack for transpiling, I simply write my code in (gasp) ES5. I chose this approach for the following reasons.
Using Razor pages rather than a SPA aids in SEO and search engine ranking of public facing pages.
Loading Vue directly from a CDN eliminates a whole stack of Webpack centric technology from the learning curve which makes it much easier for new devs to get up to speed on the system.
The approach still provides the reactive goodness to UI development that Vue inherently brings to the table.
By keeping with the “page model” the code that delivers site functionality is logically grouped around the backend page that delivers that functionality.
Since Vue and Razor can do many of the same things, my goal for public facing pages is to use Razor to generate as close to the final html as possible, and to use Vue to add the reactiveness to the page. This delivers great SEO benefits for crawlers that index the page by parsing the HTML returned.
I realize the my usage of Vue is quite different than going the route of a SPA and WebPack and the approach often means I can't use 3rd party Vue Components without reworking the code a bit. But the approach simplifies the software architecture and delivers a lightweight reactive UI.
By using this approach Razor can be heavily leveraged to generate the initial rendering of the HTML with some tags containing vue attributes. Then after the page loads in the browser, Vue takes over and can reconfigure that page any way desired.
Obviously, this approach will not fit the needs of all developers or projects but for some use cases it's quite a nice setup.
A few more details for those interested
Since I use vue sitewide, my global _layout.aspx file is responsible for instantiating vue. Any sitewide functionality implemented in vue is implemented at this level. Many pages have page specific vue functionality, this is implemented as a mixin on that page or a mixin in a js file loaded by that page. When the _layout.aspx page instantiates Vue it does so with all the mixins that I have registered to a global mixin array. (The page pushed it's mixin on that global mixin array)
I don’t use .vue files. Any needed components are implemented either directly on the page or if they need to be used by multiple pages then they are implemented in a partial view like the one below.:
dlogViewComponent.cshtml :
#* dlog vue component template*#
<script type="text/x-template" id="dlogTemplate">
<div class="dlog" v-show="dlog.visible" v-on:click="dlog.closeBoxVisible ? close() : ''">
<div class="dlogCell">
<div class="dlogFrame" ##click.stop="" style="max-width:400px">
<i class="icon icon-close-thin-custom dlogCloseIcon" v-if="dlog.closeBoxVisible" ##click="close()"></i>
<div class="dlogCloseIconSpace" v-if="dlog.closeBoxVisible"></div>
<div class="dlogInner">
<div class="dlogTitle" style="float:left" v-text="title"></div>
<div class="clear"></div>
<div class="dlogContent">
<slot></slot>
</div>
</div>
</div>
</div>
</div>
</script>
#* Vue dlog component *#
<script type="text/javascript">
Vue.component('dlog', {
template: '#dlogTemplate',
props: { //don't mutate these!
closeBoxVisible: true,
title: 'One'
},
data: function () {
return {
dlog: { //nest the data props below dlog so I can use same names as cooresponding prop
closeBoxVisible: (typeof this.closeBoxVisible === 'undefined') ? true : (this.closeBoxVisible == 'true'),
title: (typeof this.title === 'undefined') ? '' : this.title,
visible: false
}
}
},
methods: {
//opens the dialog
open: function () {
app.hideBusy(); //just in case, no harm if not busy
this.dlog.visible = true;
var identifyingClass = this.getIdentifyingClass();
Vue.nextTick(function () {
$("." + identifyingClass).addClass("animateIn");
fx.manageDlogOnly();
});
},
//closes the dialog
close: function () {
fx.prepDlogClose();
var identifyingClass = this.getIdentifyingClass();
this.dlog.visible = false;
$("." + identifyingClass).removeClass("animateIn");
},
getIdentifyingClass: function () {
if (this.$el.classList.length > 1) {
//the last class is always our identifying css class.
return this.$el.classList[this.$el.classList.length - 1];
} else {
throw "A dialog must have an identifying class assigned to it.";
}
}
}
});
</script>
In the above, it's the Vue.component('dlog', ... part of the js that installs the component and makes it available to the page.
The vue code on the _layout.cshtml page looks something like the code below. By instantiating Vue on the _layout.cshtml which is used by the whole site, Vue is only instantiated in a single place sitewide:
_layout.cshtml :
<script type="text/javascript">
var app = new Vue({
el: '#appTemplate',
mixins: mixinArray, //The page adds it's mixin to mixinArray before this part of the layout executes.
data: {
errorMsg: '' //used sitewide for error messages
//other data used sitewide
},
methods: {
//methods that need to be available in vue sitewide, examples below:
showBusy: function (html) {
//functionality to show the user that the site is busy with an ajax request.
},
hideBusy: function () {
//functionality to hide the busy spinner and messaging
}
},
created: function () {
//this method is particularly useful for initializing data.
}
});
</script>
What I have provided here paints a pretty clear picture of this non-traditional approach and it's benefits. However, since several people asked, I also wrote a related blog post: Using VueJs with ASP.NET Razor Can Be Great!
You can do this. Sometimes you're obliged to do it, if, like us, you're migrating an existing code base and you can't convert everything at once. And as Ron C says, it works well.
If you're starting a new project, you have the luxury of choosing. Reasons for favouring an SPA and no Razor would be...
Reactivity. SPA apps generally feel (much) more reactive. Initial renders are often served from cache, before the data arrives. On first load, all resources arrive in a bundle, in one request-response. There's no, or much less, request chaining.
Workflow. Webpack, bundling and hot reloads are great. You get production builds, with minification, compilation of Vue render functions, elimination of 404 style errors, js syntax errors are trapped. The cycle from introducing an error to discovering it is greatly reduced for many errors.
SPA universe. Routing, Vuex, this really is the way of the future.
Purity. Razor and Vue do similar things at the end of the day. If you mix them, you may have a hard time keeping your head straight.
You can now also lint the VueJS templates within the Razor views:
https://www.npmjs.com/package/razor-vue-lint
Great answers and content on this question! Just to add re the OP, the official Vue documentation expressly states that you can mix and match and do what you like with Vue and that it is designed to be used incrementally so I'd say if it fits what you're trying to do then it is NOT an automatic bad practice.
Related
In a Next.js app (full-featured, not next export) that uses React Context for state management and the file-system based router, how can you implement advanced routing?
I want to have preconditions for certain pages, so for instance if you try to load /foo but the Context doesn't have a given property set correctly, it'll route you to /bar.
The actual logic is complex and varies by page, so I'm looking for an approach that's easy to maintain.
Note that these preconditions are not authorization-related, so they do not need to be enforced server-side. It's more like "you need to fill out this form before you can go here."
The use of Context imposes some constraints:
Context must be accessed in a React component or in a custom Hook
Using a custom server for routing is not an option, as that would lose the Context - it has to use client-side routing
The current Context has to be checked (I tried decorating useRouter, but if the Context was changed right before router.push, the custom Hook saw the old values)
Update: It's also good to avoid a flash when the page loads before rerouting happens, so a side goal is to return a loading indicator component in that case.
I believe you can create a HOC and wrapped every pages with you HOC that takes arguments e.g. { redirects: '/foo' }
// pages/bar.tsx
const Page = () => {...}
export default RouteHOC({ redirects: '/foo' })(Page)
the HOC file will be something like this
// hoc/RouteHOC.tsx
const RouteHOC = ({ redirects }) => (WrappedComponent) => {
// you can do your logic here with the context.. even filling up a form here
// too also can.. (like returning a modal first before the real Component).
// useEffect work here too..
const { replace } = useRouter()
// then after you want to replace the url with other page
replace(redirects)
return WrappedComponent
}
This is pretty okay to be maintainable I think. You just create all the logic in HOC and when you want to update the logic - you just have to edit it in 1 file.
Well this is one option I can think of when reading your question - sorry if I misunderstood it in any way. There will always be a better way out there as we all know we can improve and adapt to new situation every seconds :D. Cheers 🥂!!
You can do this.
const Component = () => {
const example = useExample()
return <div id='routes'>
<a href='/example1'>Example 1</a>
{example.whatever && <a href='/example2'>Example 1</a>}
</div>
}
What is best practice to change content on a page without creating a route?
BlazeLayout.render('mainLayout', { top: 'header', menu: 'menu', main: 'dashboard', bottom: 'footer' });
How can i hide/show template components inside the dashboard without creating a new route? Should this be done in helpers using some sort of if/else logic in the html and using helper for on button click? Let's say i want to show different content inside dashboard template based on button clicks (href).
Please provide a best practice and good solution that is easy with lots of components.
How can i hide/show template components inside the dashboard without
creating a new route? Should this be done in helpers using some sort
of if/else logic in the html and using helper for on button click?
You can do that but you should be aware of some points to keep your code clean and modular:
Try to wrap parts of your dashboard into own templates to keep the code clean
Use ReactiveDict in favor of many ReactiveVar instances
Wrap recurring parts in templates, too to reduce duplicate code
Register recurring helpers globally or in the most upper template of your Dashboard
Subscribe on the parent template to data that is shared across all parts of the dashboard and subscribe to local data in the respective components
Use autorun and subscription.ready() and display a loading indicator until the subscription is ready. Don't wait to have everything loaded before rendering as this may reduce the UX dramatically.
Let's say i want to show different content inside dashboard template
based on button clicks (href).
You can attach a data attribute to the button, that has a specific id of the target to be toggled:
<template name="dashboardComponent">
<a href class="toggleButton" data-target="targetId">My Link</a>
</template>
You can then read this id and toggle it's state in your ReactiveDict:
Template.dashboardComponent.events({
'click .toggleButton'(event, templateInstance) {
event.preventDefault();
// get the value of 'data-target'
const targetId = $(event.currentTarget).attr('data-target');
// get the current toggle state of target by targetId
const toggleState = templateInstance.state.get( targetId );
// toggle the state of target by targetId
templateInstance.state.set( targetId, !toggleState );
}
});
In your template you can then ask to render by simple if / else:
<template name="dashboardComponent">
<a href class="toggleButton" data-target="targetId">My Link</a>
{{#if visible 'targetId'}}
<div>target is visible</div>
{{/if}}
</template>
And your helper is returning the state:
Template.dashboardComponent.helpers({
visible(targetName) {
return Template.instance().state.get(targetName);
}
});
There could be the problem of sharing the state between parent and child templates and I suggest you to avoid Session where possible. However as beginner it is a lot easier to first use Session and then work towards a more decoupled (parameterized templates) solution step by step.
Please provide a best practice and good solution that is easy with
lots of components.
This is a high demand and it is your competency to work towards both! However here is a short peek into this:
Best practice is what works for you plus can work for others in other use cases. Try to share your work with others to see where it will fail for their use case.
Using routes has the advantage, that you can use query parameters to save the current view state in the url. That adds the advantage, that on reloading the page or sharing via link, the page state can be fully restored.
easy with lots of components is a contradiction and I don't know if you expect some magical puff that solves this complexity for you. As a software engineer it is your competency to abstract the complexity into smaller pieces until you can solve the problem within certain boundaries.
I'm currently getting used to using FlowRouter after a while using Iron Router and trying to set up some best practices. I'm subscribing to my collection at a template level.
Previously I've waited for a template to render using onRendered and then targeted my input field and applied focus(), however I am now trying to only show my template in Blaze when the subscriptions are ready using the following (please excuse the Jade but I think it's pretty clear in this case)
template(name="subjectNew")
unless Template.subscriptionsReady
+spinner
else
form
input(type="text" name="name")
So the basic idea is that until the subscriptions are ready the spinner shows. The issue I'm having is that now even when the template renders, the focus won't apply. I've tried various methods of wrapping it in an autorun call but not sure the best way of trying to target the first field when combined with this approach?
Template.subjectNew.onRendered(function() {
console.log('rendered');
$('input').first().focus();
});
Is it possible?
Many thanks for any ideas.
Your subjectNew is considered rendered even when it is only showing the spinner. Just stick your:
form
input(type="text" name="name")
Into a separate template and then attach your focus code to the onRendered handler of that other template.
template(name="subjectNew")
unless Template.subscriptionsReady
+spinner
else
+myForm
template(name="myForm")
form
input(type="text" name="name")
js:
Template.myForm.onRendered(function(){
$('input').focus()
});
I think using an autorun would be a good approach but then you would have to employ Tracker.afterFlush() to wait to set the focus after the form is rendered.
Something like:
Template.subjectNew.onRendered(function() {
this.autorun(() => {
if (this.subscriptionsReady()) {
Tracker.afterFlush(() => $('input').first().focus());
}
});
});
I'm new to Meteor and trying to get holder.js to work in the framework. It works on refresh, but when moving from one route to another, it breaks.
The documentation just says "Because Meteor includes scripts at the top of the document by default, the DOM may not be fully available when Holder is called. For this reason, place Holder-related code in a "DOM ready" event listener."
I assume I need a Template.foo.onRendered callback, but unsure how to format it. Here's the HTML:
<img class="holder" src="holder.js/120x120">
And here's the callback I've added in a .js file:
Template.contactSingle.onRendered(function() {
this.$('.holder').Holder.run();
});
Again, the holder.js images appear on refresh, but I can't get them to render when going from one page to another. I'm using FlowRouter for routing.
I'm sure it's something simple. Any help is greatly appreciated!
Change your code from:
Template.contactSingle.onRendered(function() {
this.$('.holder').Holder.run();
});
to:
Template.contactSingle.onRendered(function() {
Holder.run({images: document.querySelectorAll('.holder')});
});
Obviously you do not want to do the costly document.querySelectorAll('.holder'). If you can reduce that to you template using a class from its wrapper.
For example:
Template:
<template name="singlePost">
<div class="single-post">
<h2>This is the singlePost area.</h2>
<img class='holder' src="holder.js/300x200">
</div>
</template>
and onRendered
Template.singlePost.onRendered(function() {
Holder.run({
images: document.querySelectorAll('.single-post .holder')
});
});
Here is the situation :
On a MVC application I have a partial that is rendering a script bundle.
this partial is rendered several times.
Is there a built-in way to determine an specific Bundle is rendered before in some place on page ?
Thanks professionals
Update : here is the code for clearing the condition
main layout :
<html>
<body>
#if(someCondition){
#Html.RenderPartial("SamePartial")
#Html.RenderPartial("SamePartial")
#Html.RenderPartial("SamePartial")
}
</body>
</html>
and this is inside partial :
<div>Blah Blah Blah</div>
#Scripts.Render("~/bundles/JusNeededInsidePartial")
#Styles.Render("~/Themes/styles/JusNeededInsidePartial")
partial is rendered several times. I need a way to render bundles 1 time only
Define a Razor section:
#section MySpecialScripts {
#Scripts.Render("~/bundles/MySpecialScriptsBundle")
}
in views where you need such scripts and add the following to the main layout:
#if (IsSectionDefined("MySpecialScripts")) {
RenderSection("MySpecialScripts")
}
I'd be a bit leery about putting bundles inside your partial views like you are doing.
For best performance, I'd suggest putting scripts at the bottom of your page (ref: http://developer.yahoo.com/blogs/ydn/high-performance-sites-rule-6-move-scripts-bottom-7200.html)
If your bundles contain stylesheets, you're going to be putting them in the body, which is still valid, but I like to organize them so that they're all together.
Typically what I would do is add this to my _Layout.cshtml right before the closing </head> tag:
#RenderSection("Head", required: false)
Then, in my View, I could have:
#Html.RenderPartial("SamePartial")
#Html.RenderPartial("SamePartial")
#Html.RenderPartial("SamePartial")
#section head{
#Scripts.Render("~/bundles/myBundle")
}
And if I wanted to do something similar on another View:
#Html.RenderPartial("OtherPartial")
#Html.RenderPartial("OtherPartial")
#section head{
#Scripts.Render("~/bundles/myOtherBundle")
}
I know this is a bit different than what you were asking for, but I think there's value in the flexibility of this solution.
And if you're loading things dynamically, you could always put a simple condition in your section:
#section head{
#if(SomeCondition){
#Scripts.Render("~/bundles/modernizr")
}
}
You can do the check in the JavaScript scripts themselves:
In JavaScript you can check if something has been previously defined, and only define it if it hasn't.
For example:
if (typeof flag === 'undefined') {
// variable is undefined
flag = {};
// Here type the code that must happen only once
}
NOTE: the flag name is a global var. To avoid collisions, you'd "namespace", and use specific names for each script file like this:
if (typeof myScripts === 'undefined') {
myScripts = {};
}
if (typeof myScripts.MySpecialScripts=== 'undefined')
{
myScripts.MySpecialScripts = '';
// Add the "MySpecialScripts.js" code here
}
However, I'd render the script bundle outside the partials to ensure it's available to them. In this way you can ensure that it's rendered only once and available to all your partials.
In fact, unless it's a very large .js file, why not include it in your general bundle?
Yep, I know you feel tha your project is "better organized" if you include the scripts on the view which needs them, but that's not a good idea: you have to control if they have yet been included or not, you cand' move them to the bottom of the page if you want to and so on.
If you still want to keep your original idea you can use a the client dependency framework. It's not exactly what you want to do, but it's the best available solution. When you use this framework, you need to declare the dependencies of you components (Views) using different methods, like this:
#{Html.RequiresJs("~/myscript.js");}
Anyway, as I wrote in my comment, the problem with this kind of solution is that if you start using AJAX there is no single way to get sure that you always get the desired scripts, and that they are available on client side when you need them. Unless you use some variation of the first client-side solution that I wrote in this answer (there are better solutions based on this basic concept). Let me insist: the best solution is always to have everything ready on the client side (unless it's a really huge project that have hundreds of big js files). Remember that the js file is loaded once, and cached for later use in the browser, so downloading a big .js file it's not such a big problem.
And please, even if you think you need a server side solution, don't discard a client side solution. Remember that, even if you server the js from the server, that's 'client side' code, so, why not use a 'client side solution' for your 'client side code' problem?
Still anothe solution: ypu can use PageData to share information between views, layouts, and partial views. When you need to add a script in any of this places, use a flag in PageData. If the flag is not present, render the script, and set the flag. If the flag is present, don't render the script. In this way, the script will be rendered only the first time.
if (PageData["JusNeededInsidePartial"] == null) // Check the flag
{
#Scripts.Render("~/bundles/JusNeededInsidePartial");
PageData["JusNeededInsidePartial"] = ""; // Set the flag
}
The problem with AJAX still exists, because if you render the partial by AJAX, it will load the script in the client side everything the partial view is loaded by AJAX.