I'm setting up my first ever ASP.NET MVC server and teaching myself how to use it for a particular project I'm working on.
I have a couple of controller "endpoints" set up and working wonderfully for HTTP POST requests but, as a "tangential" part of this project, I would like to have the controller serve up a page with a file upload form.
Specifically, I'd really like to use the Vue File Agent, preferably implemented using the CDN distribution method because I'd rather not install additional components on the server for this one little thing if I can get away with it.
I'm not new to ASP.NET, HTML, or CSS (and even limited JavaScript), but I am unfamiliar with using ASP.NET MVC controllers and views to serve page(s). As stated, I've figured out how to use the controllers to handle the POST requests I'm generating from other systems.
Also, I have a "basic" understanding of how the view works and I've got the controller serving up an HTML file. However, when I try to include the Vue File Agent component via the CDN, I'm not getting anything.
I've tried a variety of different Vue File Agent samples just to get something on the page, including the Gmail Inspired Demo on CodePen, but I must be missing something because I can't get the actual "sample" to show up and it seems to be totally ignoring all of the CSS styling.
In my controller, I have this:
Function Index() As ActionResult
Return View()
End Function
And my Index.vbhtml file looks like this:
#Code
Layout = Nothing
End Code
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Insurance Submission Form</title>
<link rel="stylesheet" href="https://unpkg.com/vue-file-agent#latest/dist/vue-file-agent.css" />
<script src="https://unpkg.com/vue-file-agent#latest/dist/vue-file-agent.umd.js"></script>
<style>
.vfa-demo {
position: relative;
}
.vfa-demo .file-preview-wrapper::before {
background: transparent;
}
.vfa-demo .file-row {
position: relative;
z-index: 15;
line-height: 24px;
text-align: left;
background: #EEE;
margin-bottom: 5px;
padding: 2px 5px;
}
.vfa-demo .remove {
float: right;
margin-top: -3px;
}
.vfa-demo .progress {
float: right;
width: 85px;
height: 10px;
margin-top: 7px;
margin-right: 10px;
background: #FFF;
border: 1px solid #AAA;
}
.vfa-demo .progress.completed {
display: none;
}
.vfa-demo .drop-help-text {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: 2px;
background: rgba(255, 255, 255, 0.75);
z-index: 1200;
font-size: 32px;
font-weight: bold;
color: #888;
align-items: center;
justify-content: center;
display: none;
}
.vfa-demo .is-drag-over .drop-help-text {
display: flex;
}
.vfa-demo .upload-block {
border: 2px dashed transparent;
padding: 20px;
padding-top: 0;
}
.vfa-demo .is-drag-over.upload-block {
border-color: #AAA;
}
.vfa-demo .vue-file-agent {
border: 0 !important;
box-shadow: none !important;
}
</style>
</head>
<body>
<script type="text/x-template" id="vue-file-agent-demo">
<div class="vfa-demo bg-light pt-3">
<VueFileAgent class="upload-block"
ref="vfaDemoRef"
:uploadUrl="'https://www.mocky.io/v2/5d4fb20b3000005c111099e3'"
:uploadHeaders="{}"
:multiple="true"
:deletable="true"
:theme="'list'"
:maxSize="'25MB'"
:errorText="{
size: 'This file is too large to be attached',
}"
v-model="fileRecords">
<template v-slot:before-outer>
<p>Email Attachment example with drag & drop support and <span class="badge">attachment</span> keyword basic detection.</p>
<div class="form-group">
<input type="text" class="form-control" placeholder="Your Name" value="John Doe">
</div>
<div class="form-group">
<input type="email" class="form-control" placeholder="Email address" value="johndoe#example.com">
</div>
<div class="form-group">
<textarea v-model="message" class="form-control" placeholder="Your Message"></textarea>
</div>
</template>
<template v-slot:file-preview="slotProps">
<div :key="slotProps.index" class="grid-box-item file-row">
<button type="button" class="close remove" aria-label="Remove" v-on:click="removeFileRecord(slotProps.fileRecord)">
<span aria-hidden="true">×</span>
</button>
<div class="progress" :class="{'completed': slotProps.fileRecord.progress() == 100}">
<div class="progress-bar" role="progressbar" :style="{width: slotProps.fileRecord.progress() + '%'}"></div>
</div>
<strong>{{ slotProps.fileRecord.name() }}</strong> <span class="text-muted">({{ slotProps.fileRecord.size() }})</span>
</div>
</template>
<template v-slot:file-preview-new>
<div class="text-left my-3" key="new">
Select files or drag & drop here
</div>
</template>
<!-- <template v-slot:after-inner>
<div class="text-left pt-1">
Select files or drag & drop here
</div>
</template > -->
<template v-slot:after-outer>
<div title="after-outer">
<div class="drop-help-text">
<p>Drop here</p>
</div>
<button type="button" class="btn btn-primary" v-on:click="send()">Send</button>
</div>
</template>
</VueFileAgent>
</div>
</script>
<!-- ----------------------------- -->
<div class="container py-3">
<div id="app">
<h5><a target="_blank" href="https://safrazik.github.io/vue-file-agent">Vue File Agent</a> Playground</h5>
<hr>
<ul class="nav nav-pills mb-2">
<li class="nav-item">
<a target="_blank" class="nav-link" href="https://codepen.io/safrazik/pen/BaBVNEE">1. Preloading Exising Demo</a>
</li>
<li class="nav-item">
<a target="_blank" class="nav-link" href="https://codepen.io/safrazik/pen/BaBpYme">2. Profile Picture Demo</a>
</li>
<li class="nav-item">
<a target="_blank" class="nav-link active" href="https://codepen.io/safrazik/pen/OJLgvya">3. Gmail Inspired Demo</a>
</li>
</ul>
<hr>
<vue-file-agent-demo></vue-file-agent-demo>
</div>
</div>
<script type="text/javascript">
var component = {
data: function () {
return {
fileRecords: [],
message: 'I am sending you the attachments',
}
},
methods: {
removeFileRecord: function (fileRecord) {
return this.$refs.vfaDemoRef.removeFileRecord(fileRecord);
},
send: function () {
if (this.message.indexOf('attachment') !== -1 && this.fileRecords.length < 1) {
if (!confirm('You have mentioned about attachments in your message. Are you sure to send without attachments?')) {
return;
}
}
alert('Message sent!');
}
}
}
component.template = '#vue-file-agent-demo';
Vue.component('vue-file-agent-demo', component);
new Vue({
el: '#app'
});
</script>
</body>
</html>
I tried using various other different upload "templates" I've found with varying degrees of success, but I feel I'm still failing to understand some key concepts of working with ASP.NET MVC controllers and views.
For one thing, in the <template v-slot:file-preview="slotProps"> and <template v-slot:after-outer> blocks of the code above, the CodePen has the <button> elements defined with #click="somefunctionname()".
However, when I publish these, the server returns an error indicating that 'click' is not declared (IntelliSense also reports the same error in the Visual Studio IDE). I found some other examples using Vue that showed what appears to be the same basic functionality written as v-on:click="somefunctionname()", so I tried that (as I have listed above), but I'm still getting nothing.
I'm obviously not understanding how the syntax should be working here, and I'm sure it's "simply" a matter of finding the right documentation, but I'd really like to understand the ASP.NET MVC view a little better and figure this out, not only because I want to get this "pretty" upload working, but because I really want to get a better handle on how to use this technology.
What is it that I'm doing wrong and how can I get this functionality working on my ASP.NET MVC controller? How do I define these # directives in an ASP.NET MVC view without causing the compiler to freak out (I had the same issue with some of these # directives in a CSS <style> block, so it's clearly something I'm going to need to deal with)?
EDIT
So, as I sorta mentioned above, I've been testing by publishing the site to my web server and viewing the site via the public DNS name. Just to see if I would get anything different, I tried running the site from the Visual Studio debugger.
Interestingly, when I did this, the IDE broke with an exception in my _Layout.vbhtml:
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/bootstrap")
#RenderSection("scripts", required:=False)
Looking at my site's directory structure, there is no ~\bundles\ directory. Digging around a little, I found all of the jQuery and Bootstrap files located in the ~\Scripts\ directory, so I simply removed the two calls to the #Scripts.Render() method (leaving the call to #RenderSection()) and tried again. This time, I didn't get the exception, so I went ahead and published again to test. Unfortunately, I got the same results as what I posted in my screenshot, so that doesn't appear to be the source of my "issue", but I thought I'd mention it here, just in case it helps to point me in the right direction.
As I said at the beginning of my question, this is my first time setting up an ASP.NET MVC server, so I've not done much in the way of customization or modification to the base template from Visual Studio apart from creating a new controller and associated view and adding a couple of things to the web.config / global.asax to handle some of the other functionality I'm implementing. Perhaps there's something I should have done, but I don't really know where to start.
Well, I finally figured out at least part of my problem, and it's because I'm a moron. I stumbled across it when I opened up my browser's "Developer Tools" and drilled down through a few screens. Apparently, I was missing a pretty important piece of the Vue puzzle in my HTML. Once I added this line to my <head> (the "core" Vue library):
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12/dist/vue.js"></script>
I finally got a visible upload box. It's still missing all the nice-looking styling, but at least the control is there now and I can start working with it a little more.
So, I'm adding a popup menu off the header and it works... works quite well actually except for one small thing. For the life of me I can't get it to display the icons the way I want them!
In the first <li> I brute force the data-icon into the <li> and it shows but its not positioned to the left. In the others, I left the data-icon where I'm accustomed to leaving them (where they work correctly everywhere else) and they don't display at all.
Any suggestions?
<div id="header" data-role="header" data-theme="b">
<h1>MyApp</h1>
Menu
<div data-role="popup" id="popupMenu" data-theme="b">
<ul data-role="listview" data-inset="true">
<li data-icon="gear" data-iconpos="left">Settings</li>
<li>GPS</li>
<li>About</li>
</ul>
</div>
</div>
<!-- /header -->
JSFiddle
The data-iconpos attribute not defined for regular list items, works only when the < li> item is inside a navbar widget: http://www.w3schools.com/jquerymobile/jquerymobile_ref_data.asp
Please, take a look to the fiddle I made: https://jsfiddle.net/xbo8npng/1/
I have placed the icons to the left using css:
<div id="header" data-role="header" data-theme="b">
<h1>MyApp</h1>
Menu
<div data-role="popup" id="popupMenu" data-theme="b">
<ul data-role="listview" data-inset="true">
<li class="left" data-icon="gear">Settings</li>
<li class="left">GPS</li>
<li class="left">About</li>
</ul>
</div>
</div>
and the styles:
.left a{
padding-left: 2.5em !important;
padding-right: 1em !important;
}
.left a:after{
left: 2px;
right: auto;
}
I hope this helps you!
SOLUTION
I was being an idiot and dynamically adding data-role="content" to the #wrapper element on page load, making it unrecognizable by JQM.
I'm trying to implement a basic menu panel, but I'm having no luck getting it to animate or close.
Below is the basic markup as generated by JQM.
<div data-role="page" data-url="/" tabindex="0" class="ui-page ui-body-c ui-page-panel ui-page-active">
<div id="menu-panel" data-role="panel" data-position="left" data-display="reveal" data-dismissible="true" class="ui-panel ui-panel-position-left ui-panel-display-reveal ui-panel-closed ui-body-c ui-panel-animate">
<div class="ui-panel-inner">
<ul>
<li>Lorem ipsum</li>
<li>Lorem ipsum</li>
<li>Lorem ipsum</li>
<li>Lorem ipsum</li>
<li>Lorem ipsum</li>
</ul>
</div>
</div>
<div id="wrapper" data-role="content">
<span class="ui-btn-inner"><span class="ui-btn-text"></span></span>
<!-- Page content -->
</div>
</div>
CSS for #wrapper:
#wrapper {
margin: 0 auto;
min-height: 100%;
padding: 0;
position: relative;
width: 100%;
}
It seems wrong to put redundant data roles in the panel and the button, but all the examples I've looked at, including the official docs, have it that way.
The panel appears, but doesn't make use of the CSS transforms. It also doesn't push the page content over or close on any event.
I have the jQuery UI "smoothness" theme and I would like to use the "look" to style divs on my page. E.g. apply the gray, shaded, rounded-corner effect to a regular div that ISN'T an accordion, button, etc.
I thought it would be easy! Perhaps it is?!
Thanks!
Have a look at jQuery UI themeroller. You can select an existing theme or customize one, then using Chrome or Firefox/Firebug, for example, Right-click → Inspect element and copy the class names.
ui-widget, ui-widget-content and ui-corner-all are the most likely class names you will be looking for.
Here is what I came up with need some CSS tweaking, but should work.
<div id="login" align="center" class="ui-tabs ui-widget ui-widget-content ui-corner-all">
<ul id="login_header" class="ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all">
Header
</ul>
<div id="login_form" class="ui-tabs-panel ui-widget-content ui-corner-bottom">
content
</div>
It seems like it should be very simple to specify that the h:messages generated by JSF should be styled using jQueryUI's nice ui-states. But sadly I can't make it fit. It seems that the jQueryUI states require several elements (div,div,p,span) in order to make it work.
So taking inspiration directly from the jQueryUI theme demo page:
<!-- Highlight / Error -->
<h2 class="demoHeaders">Highlight / Error</h2>
<div class="ui-widget">
<div class="ui-state-highlight ui-corner-all" style="margin-top: 20px; padding: 0 .7em;">
<p><span class="ui-icon ui-icon-info" style="float: left; margin-right: .3em;"></span>
<strong>Hey!</strong> Sample ui-state-highlight style.</p>
</div>
</div>
<br/>
<div class="ui-widget">
<div class="ui-state-error ui-corner-all" style="padding: 0 .7em;">
<p><span class="ui-icon ui-icon-alert" style="float: left; margin-right: .3em;"></span>
<strong>Alert:</strong> Sample ui-state-error style.</p>
</div>
</div>
and trying to jam the css class details into my h:message as best I can:
<div class="ui-widget">
<h:messages globalOnly="true" errorClass="ui-state-error ui-corner-all ui-icon-alert" infoClass="ui-state-highlight ui-corner-all ui-icon-info"/>
</div>
I don't get the icon or sufficient padding etc but the colours make it through. So, the styles are being applied but they aren't working as intended.
Any idea how I can make this work?
On that element, you need a class that gives display: block; to get the position characteristics you want, like this:
<div class="ui-widget">
<h:messages globalOnly="true" errorClass="ui-state-error ui-corner-all ui-icon-alert block" infoClass="ui-state-highlight ui-corner-all ui-icon-info block"/>
</div>
CSS:
.block { display: block; }
Also if you're interested, here's a listing of all the classes jQuery UI uses for CSS and what they mean.