I have a pretty basic Dropzone in an asp.net core app:
<div class="panel-body " id="dropzone">
<form asp-action="Upload" class="dropzone
needsclick dz-clickable" id="uploader">
<div class="dz-message needsclick">
Drop files here or <a class="btn btn-
primary">click</a> to upload.<br>
</div>
</form>
</div>
I'm trying to set up a params value in the config, because in some situations there is a selectlist where the user chooses a value that must be passed to the MVC controller. The param is always null and I have a feeling it's because of the way I am setting the configuration:
$(document).ready(function () {
Dropzone.options.uploader = {
acceptedFiles: ".csv, .xls, .xlsx",
init: function () {
this.on("error", function (file) {
if (!file.accepted) alert("csv, xls, and xlsx files only. File not uploaded");
this.removeFile(file);
});
},
params: { orgId: $('#orglist').val()}
};
I'm guessing my params value is calculated at doc ready and never again. How I can I pass the value currently selected when the actual file is dragged?
The answer I found was to add a listener to the send function and remove params option, and append a new parameter orgID which is a parameter in my controller action :
Dropzone.options.uploader = {
acceptedFiles: ".csv, .xls, .xlsx",
init: function () {
this.on("error", function (file) {
if (!file.accepted) alert("csv, xls, and xlsx files only. File not uploaded");
this.removeFile(file);
});
this.on("sending", function (file, xhr, formData) {
formData.append("orgId", $('#orglist').val())
})
}
Related
I would like to create a form that allows the user to upload a file (a JSON object) and then post the contents of that file to an api.
I would like to see (for now just in a console log) the contents of the file I am about to post before I post it. So the flow should go like this
User sees a blank form with an upload file field
User selects a file from their computer
Once selected, I would like to log the contents of the file to the console
Once form is submitted, I would like to post the file to an endpoint with axios
I am using the useFormik hook because I have used it elsewhere and found it more intuitive than some of Formiks other options, and because I think it will be useful for when I build a few more features, so there may be some redundant lines of code here (e.g. initialValues) but for now I'm just focused on step one - seeing the values in a file before I post them.
It seems like I should be able to upload the file, read the contents of the file, store the contents of the file in a result, then pass that result to the onSubmit callback in the eventual axios.post() request I will make.
But I don't think I'm understanding the fundamentals here.
Should I use new FormData instead of a FileReader?
Should I bother reading the contents of the file before posting?
Here's my current code:
import { useFormik } from 'formik';
export const MyUploadPerfectAssetForm = () => {
const onChange = (event: any) => {
const file = event.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
// The file's text will be printed here
console.log(e.target?.result)
const result = e.target?.result;
console.log("logging result from reader.onload " + result)
return result;
};
//shows the files values properly
reader.readAsText(file);
}
//redundant for now?
const formik = useFormik({
initialValues: {
name: null,
tags: null,
type: null,
s3URL: null,
thumbnailImageURL: null,
},
onSubmit: (values, result) => {
console.log("logging values" + JSON.stringify(values))
alert(JSON.stringify(values, null, 2));
console.log("logging values from onSubmit " + JSON.stringify(values))
const uploadPerfectAsset = async (perfectAssetValues: any) => {
//there will be an axios post request here,
console.log("upload perfect asset ran")
console.log("testing uploadPerfectAsset with result from onChange file reader result" + JSON.stringify(perfectAssetValues))
}
uploadPerfectAsset(result)
},
});
return (
<div>
<form onSubmit={formik.handleSubmit}>
<div>
<input id="file"
name="test"
type="file"
onChange={onChange}></input>
</div>
<div>
<button type="submit">Submit</button></div>
</form>
</div >
);
}
Thanks
You're almost there mate 😃 A couple of small changes are needed:
add file to initialState
move onChange function to after the hook useFormik() and add formik.setFieldValue("file", file); to set the value
remove argument perfectAssetValues from function uploadPerfectAsset() - it's unnecessary
export const MyUploadPerfectAssetForm = () => {
const formik = useFormik({
initialValues: {
name: null,
tags: null,
type: null,
s3URL: null,
thumbnailImageURL: null,
file: null
},
onSubmit: (values) => {
console.log("logging values from onSubmit ", values);
const uploadPerfectAsset = async () => {
//there will be an axios post request here,
console.log("upload perfect asset ran");
console.log(
"File info: ",
JSON.stringify(
{
fileName: values.file.name,
type: values.file.type,
size: `${values.file.size} bytes`
},
null,
2
)
);
};
uploadPerfectAsset();
}
});
const onChange = (event) => {
const file = event.target.files[0];
formik.setFieldValue("file", file);
const reader = new FileReader();
// temporarily show file contentx
reader.onload = (e) => {
// The file's text will be printed here
const result = e.target.result;
console.log("logging result from reader.onload " + result);
return result;
};
//shows the files values properly
reader.readAsText(file);
};
return (
<div>
<form onSubmit={formik.handleSubmit}>
<div>
<input id="file" name="test" type="file" onChange={onChange}></input>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
</div>
);
};
Live demo
I have somewhat of a complex requirement here (a real head-scratcher)... and I'm not sure on the best way to proceed:
Requirement:
Build a page for managing widgets (CMS content blocks) in MVC5 using AngularJS for the frontend (as per the rest of the admin UI). The problem is that each widget has its own specific set of properties. They all share some properties like Title, IsEnabled, etc.. but an HTML Widget for example will have a BodyContent field and a Slider Widget would have a collection of images, etc..
My first thought was using [UIHint] and Html.EditorFor so that each widget type will have its own markup.. I think that's pretty straightforward, but how could we get the properties from any such arbitrary widget into the AngularJS model?
Example Controller
widgetsApp.controller('widgetController', function ($scope, $http) {
$scope.emptyGuid = '00000000-0000-0000-0000-000000000000';
$scope.id = $scope.emptyGuid;
$scope.title = '';
$scope.order = 0;
$scope.enabled = false;
$scope.widgetType = '';
$scope.zoneId = $scope.emptyGuid;
// etc
// how to get properties of ANY widget type?
Is this even possible? Is there a better solution? Note, I might consider changing the code to use Knockout or some other such framework if it can support my requirements.
Edit
Note that the issue is further complicated because of the fact of needing to then pass such a model back to the server and dealing with it there. In regular MVC controllers, I can use Request.Form to inspect what other values are there, but I'm using Web API and not sure if that's possible there.
Edit 2
Okay, so I think I'm on the right track, but still having issues. Firstly, here's my progress:
I found out about .factory and made a test page like this:
<div ng-app="myApp">
<div ng-controller="controller1">
<button class="btn btn-primary" ng-click="showAllInfo()">Show Info</button>
</div>
<div ng-controller="controller2">
</div>
</div>
<script type="text/javascript">
var myApp = angular.module('myApp', []);
myApp.factory('widgetModel', function () {
return {
id: '00000000-0000-0000-0000-000000000000',
title: '',
order: 0,
enabled: false,
widgetName: '',
widgetType: '',
zoneId: '00000000-0000-0000-0000-000000000000',
displayCondition: '',
widgetValues: '',
pageId: null,
cultureCode: '',
refId: null,
};
});
// This is representative of the main controller
myApp.controller('controller1', function ($scope, widgetModel) {
$scope.emptyGuid = '00000000-0000-0000-0000-000000000000';
$scope.model = widgetModel;
$scope.model.id = $scope.emptyGuid;
$scope.showAllInfo = function () {
alert("id: " + $scope.model.id + ", New Property: " + $scope.model.myNewProperty);
};
});
// This is representative of the details controller (to add properties specific to that particular widget type)
myApp.controller('controller2', function ($scope, widgetModel) {
$scope.model = widgetModel;
$scope.model.myNewProperty = "My Awesome Widget";
});
</script>
The above test works beautifully.. however, when I use this sort of code in my real application it fails to work and the reason I believe is because the second controller is injected into the DOM later on.. here's what's happening:
I have a div as follows
<div ng-bind-html="widgetDetails"></div>
and after loading the other details, I load the html for this as such:
$http.get("/admin/widgets/get-editor-ui/" + $scope.model.id).success(function (json) {
$scope.widgetDetails = $sce.trustAsHtml(json.Content);
});
That works.. I can see my the html controls for my new properties there.. the following snippet is the HTML which is injected into the above div:
<div ng-controller="widgetDetailsController">
<div class="col-sm-12 col-md-12">
<div class="form-group">
#Html.Label("BodyContent", "Body Content", new { #class = "control-label" })
#Html.TextArea("BodyContent", null, new { #class = "form-control", ng_model = "model.bodyContent", ui_tinymce = "tinyMCEOptions_BodyContent" })
</div>
</div>
<button class="btn" ng-click="test()">Test</button>
</div>
<script type="text/javascript">
widgetsApp.controller('widgetDetailsController', function ($scope, $http, widgetModel) {
$scope.model = widgetModel;
$scope.json = angular.fromJson($scope.model.widgetValues);
$scope.model.bodyContent = $scope.json.bodyContent || "";
$scope.test = function () {
alert($scope.model.bodyContent);
};
});
</script>
When I click, the "Test" button, nothing happens...
I tried to load a controller dynamically via the method outlined at this link: http://www.bennadel.com/blog/2553-loading-angularjs-components-after-your-application-has-been-bootstrapped.htm
It doesn't work. To be honest though, I am new to AngularJS and don't really know all the ins out outs of it.. any help would be great.
IF you are just looking to get the properties and their values, then on AngularJS or Javascript side you can just iterate over the object properties to get all the properties defined over the object.
for(var key in obj){
$scope[key]=obj[key];
}
Once on scope you can bind it to the view using ng-model.
This approach would get you the data but metadata about the data such as control to render for property need would not work.
For advance scenarios you should try to send metadata about each properties that can help render it on the view.
If ng-model is setup correctly all data would be send to server.
On the server you can use the dynamic keyword as input parameter to webapi method and there should be a similar method to iterate over the payload using key value pair.
I ended up changing to KnockoutJS, partly because AngularJS ended up being a bit overkill for my needs, but also because it couldn't handle this situation very nicely (or at least there was no obvious and clean way to do it). My KnockoutJS solution is below:
In the main page, I add an html element:
<fieldset id="widget-details"></fieldset>
An example of arbitrary HTML to be injected:
<div id="widget-content" class="col-sm-12 col-md-12">
<div class="form-group">
#Html.Label("BodyContent", "Body Content", new { #class = "control-label" })
#Html.TextArea("BodyContent", null, new { #class = "form-control", data_bind = "wysiwyg: bodyContent, wysiwygConfig: tinyMCEConfig" })
</div>
</div>
<script type="text/javascript">
function updateModel() {
var data = ko.mapping.fromJSON(viewModel.widgetValues());
viewModel.bodyContent = ko.observable("");
if (data && data.BodyContent) {
viewModel.bodyContent(data.BodyContent());
}
viewModel.tinyMCEConfig = {
theme: "modern",
plugins: [
"advlist autolink lists link image charmap print preview hr anchor pagebreak",
"searchreplace wordcount visualblocks visualchars code fullscreen",
"insertdatetime media nonbreaking save table contextmenu directionality",
"emoticons template paste textcolor"
],
toolbar1: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image",
toolbar2: "print preview media | forecolor backcolor emoticons",
image_advtab: true,
templates: [
{ title: 'Test template 1', content: 'Test 1' },
{ title: 'Test template 2', content: 'Test 2' }
],
content_css: tinyMCEContentCss
};
};
function onBeforeSave() {
var data = {
BodyContent: viewModel.bodyContent()
};
viewModel.widgetValues(ko.mapping.toJSON(data));
};
</script>
Then in my script for the main page, I use the following:
$.ajax({
url: "/admin/widgets/get-editor-ui/" + self.id(),
type: "GET",
dataType: "json",
async: false
})
.done(function (json) {
var result = $(json.Content);
var content = $(result.filter('#widget-content')[0]);
var details = $('<div>').append(content.clone()).html();
$("#widget-details").html(details);
var scripts = result.filter('script');
scripts.appendTo('body');
// ensure the function exists before calling it...
if (typeof updateModel == 'function') {
updateModel();
var elementToBind = $("#widget-details")[0];
ko.cleanNode(elementToBind);
ko.applyBindings(viewModel, elementToBind);
}
})
.fail(function () {
$.notify("There was an error when retrieving the record.", "error");
});
and when I save, I call this code:
// ensure the function exists before calling it...
if (typeof onBeforeSave == 'function') {
onBeforeSave();
}
Works really well.
I have the following code on cshtml page.
<div class="buttons">
<button type="button" id="export" class="export-inventory-button" onclick="location.href='#Url.Action("ExportInventory", "Inventory")'">EXPORT INVENTORY</button>
</div>
How do I make this work in my view model?
I think I almost got it, but need some help
<div class="buttons">
<button type="button" id="export" class="export-inventory-button" data-bind="click: exportInventory">EXPORT INVENTORY</button>
</div>
My viewmodel has this code:
function exportInventory() {
filtererGridData = vm.details;
var json = ko.mapping.toJSON(vm.details);
$.ajax({ url: '/Inventory/ExportInventory', type: 'POST' }).done(function (data) {
$('#export').html(data);
}).fail(function (data) {
toastr.warn('Could not export data, please contact LGL.');
});
}
I tried this, but I get errors:
function exportInventory() {
filtererGridData = vm.details;
var json = ko.mapping.toJSON(vm.details);
$.ajax({ url: 'location.href="#Url.Action("ExportInventory", "Inventory")"', type: 'POST' }).done(function (data) {
window.location.href = responseText.url;
$('#export').html(data);
}).fail(function (data) {
toastr.warn('Could not export data, please contact LGL.');
});
}
Can someone help me figure this out?
The way you're trying to pass in the url to the ajax call is probably not working the way you expect. Also, you wouldn't need the location.href= to be part of the url parameter in the $.ajax() call.
If your view model is coded in a script tag right in your cshtml page, you can try this:
<!-- cshtml razor view code for generating the html is above this line -->
<script>
var viewModel = {
function exportInventory() {
filtererGridData = vm.details;
var json = ko.mapping.toJSON(vm.details);
//allow razor to build a javascript string for you when it renders the html
//when the browser parses this script, it will just see a simple string
var myURL = '#Url.Action("ExportINventory", "Inventory")';
//pass your variable to the jQuery ajax call
$.ajax({ url: myURL, type: 'POST' }).done(function (data) {
window.location.href = responseText.url;
//this line of code would never be called because the browser has navigated away from this page...
$('#export').html(data);
}).fail(function (data) {
toastr.warn('Could not export data, please contact LGL.');
});
}
};
</script>
Load the page and view source. If the var myUrl = line is the correct URL to your controller as a string, then you know that razor kicked in and prepared that for you on render.
I have an asp.net mvc app, using JQuery UI dialog. I'm trying to submit form data to the controler action method. I do hit the action method but my object has no data. Do you know why?
From Filter.cshtml
$("#selectUnits").dialog({
autoOpen: false,
title: 'Select Units',
width: 400,
modal: true,
minHeight: 200,
buttons: {
"Cancel": function () {
$(this).dialog("close");
},
"Submit": function () {
$.post('/Data/SetUnitNumbers', $("#frmSelectedUnits").submit(), function (data) {
alert('This worked');
});
}
} // END OF BUTTONS
}); //END OF DIALOG
From Controler...Action Method:
public void SetUnitNumbers(object data)
{
int a = 5;
}
From SelectUnits.cshtml where form 'frmSelectedUnits' lives. Essentially it is a bunch of checkboxes values that I'm trying to send back to the server:
<body>
<form id="frmSelectedUnits" action="/" >
<div id="unitNumberCheckboxes" style=" ">
<div>
#Html.CheckBox("SelectAllUnitNumbers", false)<label for="SelectAllUnitNumbers"><b>Select/Unselect All Units</b></label>
</div>
<div id="unitCheckboxes">
#foreach (var item in Model)
{
<div style="float:left">
#Html.CheckBox("UnitNumbers", false)<label for="UnitNumbers">#item.unit_number.ToString().PadLeft(3, '0') </label>
</div>
}
</div>
</div>
</form>
</body>
jQuery .post is:
$.post(url, data, success-callback-function);
where data is A map or string that is sent to the server with the request.
Calling $("#frmSelectedUnits").submit() probably won't work.
Try:
$.post('/Data/SetUnitNumbers', $("#frmSelectedUnits").serialize(), function (data) {
alert('This worked');
});
jQuery docs on .serialize()
Please help.
I am obviously no expert but using suggestions from this site, I think I am really close to doing the following
Be able to open a dynamically generated PDF in
a) a new Tab
b) an iframe
Hopefully, I just need a couple of lines of the correct syntax and I will be there.
I am dynamically generating the PDF in a controller using itextSharp
CONTROLLER
public FileStreamResult GetPdf()
{
...
return new FileStreamResult(Response.OutputStream, "application/pdf"){FileDownloadName = "download.pdf"};
}
VIEW
<input id="btnNewTab" type="button" value="New Tab" />
<input id="btnIframe" type="button" value="Iframe" />
<div id="pdfDiv"></div>
<script type="text/javascript">
$(function () {
$("#btnIframe").click(function () {
$.get('/PDFTest/GetPdf', function (pdf) {
alert(pdf.length); // Works as expected
// What do I need to put here to get the pdf to appear in a iframe
});
});
$("#btnNewTab").click(function () {
// asks me if I want to Open or Save the PDF.
// If I choose Open, the PDF opens in a completely new Window.
// Instead of the dialog, I just want the PDF to open in a new Tab
// I am probably going about this in completely the wrong way.
var HTML = "<iframe src='/PDFTest/GetPdf' style='width: 100%; height: 600px' ></iframe>";
$('#pdfDiv').html(HTML);
});
});
</script>
In response to your suggestion Darin, I changed the Controller to:
public FileStreamResult GetPdf(someInfo from View)
{
...
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition", "inline;test.pdf");
Response.Buffer = true;
Response.Clear();
Response.OutputStream.Write(ms.GetBuffer(), 0, ms.GetBuffer().Length);
Response.OutputStream.Flush();
Response.End();
return new FileStreamResult(Response.OutputStream, "application/pdf");
}
Having done that, your suggestions worked fine but I realise that I did not explain my intentions clearly enough. I have therefore changed the VIEW to reflect what i am trying to do.
input id="btnNewTab" type="button" value="New Tab" />
<input id="btnIframe" type="button" value="Iframe" />
<iframe id="iframe"></iframe>
<div id="pdfDiv">
</div>
<script type="text/javascript">
$(function () {
$("#btnIframe").click(function () {
$.ajax({
url: '/PDFTest/GetPdf',
type: "GET",
data: json, // This line will not be a problem
dataType: "json",
contentType: "application/pdf", // This line might be a problem
success: function (pdf) {
// What to I need to need to do to display the PDF in the above iframe
$("#iframe").attr("src", pdf); // HTTP Error 400 - Bad Request.
}
});
});
$("#btnNewTab").click(function () {
$.ajax({
url: '/PDFTest/GetPdf',
type: "GET",
data: json, // This line will not be a problem
dataType: "json",
contentType: "application/pdf", // This line might be a problem
success: function (pdf) {
// What to I need to need to do to disply the PDF in a new window
}
});
});
});
</script>
Action:
public ActionResult GetPdf()
{
byte[] pdf = ...
Response.AppendHeader("Content-Disposition", "inline;test.pdf");
return File(pdf, "application/pdf");
}
To open in new Tab/Window:
#Html.ActionLink("view pdf", "getpdf", "somecontroller", null, new { target = "_blank" })
To open in an iframe your code looks fine. Just make sure to set the Content-Disposition header to inline.