I'm creating an Umbraco website, and I am creating a plugin for the backend of Umbraco so a user can export an Excel worksheet from an HTML table.
I'm using AngularJS and a C# controller to do this. Here are my files.
//This is my C# Controller at /App_Code/ExportBlankDictionaryController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Umbraco.Web.Editors;
using Umbraco.Core.Persistence;
using System.Data.SqlClient;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.IO;
namespace AngularUmbracoPackage.App_Code
{
[Umbraco.Web.Mvc.PluginController("AngularUmbracoPackage")]
public class ExportBlankDictionaryController : UmbracoAuthorizedJsonController
{
//[System.Web.Http.AcceptVerbs("GET", "POST")]
//[System.Web.Http.HttpGet]
public void ExportExcell()
{
var keys = new System.Data.DataTable("BlankDictionaryItems");
keys.Columns.Add("Keys", typeof(string));
keys.Columns.Add("Description", typeof(string));
keys.Rows.Add("Enter First Dictionary Name Here", " ");
var grid = new GridView();
grid.DataSource = keys;
grid.DataBind();
HttpContext.Current.Response.ClearContent();
HttpContext.Current.Response.BufferOutput = true;
HttpContext.Current.Response.AddHeader("Content-disposition", "attachment; filename=BlankDictionaryItems.xls");
HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";
HttpContext.Current.Response.Charset = "";
StringWriter sw = new StringWriter();
HtmlTextWriter htw = new HtmlTextWriter(sw);
grid.RenderControl(htw);
HttpContext.Current.Response.Output.Write(sw.ToString());
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.End();
}
}
}
// This is my AngularJS controller at /App_Plugins/datatable/datatable.controller.js:
angular.module("umbraco")
.controller("AngularUmbracoPackage.ExportBlankDictionaryController", function ($scope, keyResource) {
keyResource.exportToExcell().then(function (response) {
alert("Table Generated!");
})
});
This is my datatable.resource.js file within the same directory:
// Adds the resource to umbraco.resources module:
angular.module('umbraco.resources').factory('keyResource',
function ($q, $http) {
// The factory object returned
return {
// This calls the API controller we setup earlier
exportToExcell: function () {
console.log("button clicked");
return $http.post("backoffice/AngularUmbracoPackage/ExportBlankDictionary/ExportExcell");
}
};
}
);
If necessary, here is the package.manifest.json file:
{
propertyEditors:[
{
name: "DataTable editor",
alias: "my.datatable.editor",
editor:{
view: "~/app_plugins/datatable/table.html",
valueType: "JSON"
}
}
],
javascript: [
"~/app_plugins/datatable/datatable.controller.js",
"~/app_plugins/datatable/datatable.resource.js"
]
}
Here is the table.html file which is the view:
<div class="ExportDiv" ng-controller="AngularUmbracoPackage.ExportBlankDictionaryController">
<table id="table1" class="table table-bordered" runat="server">
<thead>
<tr>
<th>Key</th>
<th>Populate Dictionary Item Names in Key Column</th>
</tr>
</thead>
<tbody>
<tr>
<td>Enter First Dictionary Name Here</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<button class="btn btn-success" ng-click="exportToExcel()">Export Table</button>
Okay, so the Umbraco page is loading, the alert box comes up when the developer section of Backoffice is opened, but when I click the Export Table button, nothing happens. I am trying to get an Excel sheet to download when this button is clicked. How can I do this? Am I missing something?
Add
angular.module("umbraco")
.controller("AngularUmbracoPackage.ButtonController", function ($scope, keyResource) {
$scope.ButtonClickHandler = function(){
console.log("clicked me!");
keyResource.exportToExcell().then(function (response) {
//do something with the response from the server
}
});
then change the button element to:
<button ng-controller="AngularUmbracoPackage.ButtonController" class="btn btn-success" ng-click="ButtonClickHandler()">Export Table</button>
Related
I've been trying to isolate the mail feature from 2sxc mobius app to implement it on my own 2sxc projects, but so far I've only been successful on passing string, string dictionary. If I try to use the default string,object it gives several compiling not very specific errors.
Here's what I have working right now:
View:
<div>
<div>
<label for="testfield">Test field</label>
</div>
<div>
<input type="text" id="testfield" value="">
</div>
</div>
<div>
<button id="saveData" type="button" onclick="saveMailData()">Guardar dados</button>
</div>
<script type="text/javascript" src="/desktopmodules/tosic_sexycontent/js/2sxc.api.min.js" data-enableoptimizations="100"></script>
<script>
function saveMailData() {
var newItem = {
"user": "#Dnn.User.Username",
"testfield": $("#testfield").val()
};
$2sxc(#Dnn.Module.ModuleID).webApi.post("Form/ProcessForm", {}, newItem, true)
.success(function() {
alert("Success");
})
.error(function() {
alert("Error");
});
}
</script>
Controller:
using DotNetNuke.Security;
using DotNetNuke.Web.Api;
using System.Web.Http;
using ToSic.SexyContent.WebApi;
using System.Collections.Generic;
using System;
using System.Linq;
using System.Web.Compilation;
using System.Runtime.CompilerServices;
using DotNetNuke.Services.Mail;
using Newtonsoft.Json;
public class FormController : SxcApiController
{
[HttpPost]
[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.Anonymous)]
[ValidateAntiForgeryToken]
public void ProcessForm([FromBody]Dictionary<string,string> contactFormRequest)
{
string mailFrom = "x#x.pt";
string mailTo = "y#y.com";
string mailCc = "z#z.com";
string mailReply = "w#w.pt";
string mailSubject = "THIS IS THE SUBJECT " + contactFormRequest["user"].ToString();
string mailbody = "<table><tr><td>THIS IS THE MESSAGE BODY</td></tr></table>";
var ownerMailEngine = TemplateInstance("testmailtemplate.cshtml");
var ownerBody = ownerMailEngine.Message(contactFormRequest, this).ToString();
var ownerSubj = ownerMailEngine.Subject(contactFormRequest, this);
Mail.SendMail(mailFrom, mailTo, mailCc, "", mailReply, MailPriority.Normal, ownerSubj, MailFormat.Html, System.Text.Encoding.UTF8, ownerBody, new string[0], "", "", "", "", false);
}
private dynamic TemplateInstance(string fileName)
{
var compiledType = BuildManager.GetCompiledType(System.IO.Path.Combine("~", App.Path, fileName));
object objectValue = null;
if (compiledType != null)
{
objectValue = RuntimeHelpers.GetObjectValue(Activator.CreateInstance(compiledType));
return ((dynamic)objectValue);
}
throw new Exception("Error while creating mail template instance.");
}
}
And template:
#helper Message(Dictionary<string,string> request, ToSic.SexyContent.IAppAndDataHelpers context)
{
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">
body { font-family: Helvetica, sans-serif; }
</style>
</head>
<body>
<h1>Website contact form request</h1>
<p>Key/Value:</p>
<table width="100%">
#foreach (var item in request)
{
<tr>
<td width="10%"><b>#item.Key.ToString()</b></td>
<td>#item.Value.ToString()</td>
</tr>
}
</table>
</body>
</html>
}
#functions {
public string Subject(dynamic request, dynamic helpers) {
return "this is a subject from template";
}
}
I would really like to avoid using dynamic to receive data (it's a nightmare for beginners), so can you help me to properly pass data as objects (string,object) from JS to controller and from controller to razor template?
If you try to use you'll somehow assume that the system will correctly convert these to number, date and so forth. This will usually not be reliable and cause a lot of side-effects. For example, a number from an input-field would be a string in the browser, so it would also arrive as a string in the server.
Dates would be worse: they would be treated as strings - and no automatic detection would turn them into dates, because the JSON format used by AJAX-calls has no standard for dates.
So basically both numbers and dates would give you no benefit if you tried an object approach (as it wouldn't automatically have the other type). So I'm not sure if there is any additional benefit. Is there another reason to do this?
Hi I'm trying to delete the record from XML file which is showing data in the form of table using AngularJS my code is:
XML file
<UserDetail>
<Detail>
<EmployeeID>124578</EmployeeID>
<EmployeeName>suresh</EmployeeName>
<EmailID>suresh#xyz.com</EmailID>
</Detail>
<Detail>
<EmployeeID>587458</EmployeeID>
<EmployeeName>Namit</EmployeeName>
<EmailID>Namit#xyz.com</EmailID>
</Detail>
</UserDetail>
Here is my AngularJS code with that I m get changing XML to JSON and present in the form of table
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope, $http) {
$http.get('myDB.xml')
.then(function (response) {
var x2js = new X2JS();
$scope.details = [];
var data = x2js.xml_str2json(response.data);
$scope.details = data.UserDetail.Detail;
$scope.getID = function (id) {
var index = 0
$scope.details.splice(index, 1);
}
});
});
</script>
HTML code with that my data coming in table format
<div ng-app="myApp" ng-controller="myCtrl">
<table border="1" width="100%">
<tr>
<th>Employee ID</th>
<th>Employee Name</th>
<th>Email ID</th>
<th>Status</th>
</tr>
<tr ng-repeat="detail in details" align="center">
<td>{{detail.EmployeeID}}</td>
<td>{{detail.EmployeeName}}</td>
<td>{{detail.EmailID}}</td>
<td>
<button ng-click="getID(detail.EmployeeID)" class="btnDelete">Delete Request</button>
</td>
</tr>
</table>
</div>
Please help me to delete record form XML file on the click of button using ajax in angularJS
You may need to convert the xml to json again and then delete the object.
{
"UserDetail": {
"Detail": [
{
"EmployeeID": "124578",
"EmployeeName": "suresh",
"EmailID": "suresh#xyz.com"
},
{
"EmployeeID": "587458",
"EmployeeName": "Namit",
"EmailID": "Namit#xyz.com"
}
]
}
}
$scope.getID = function (emp) {
var jsondata = x2js.xml_str2json(xml); //Its an array of object hopefully
var index = jsondata.map(function(item) { return item.id; })
.indexOf(emp.EmployeeID);
jsondata.splice(removeIndex, 1); // your json here...
which you can convert it back to xml and update.
}
I want to export data in my html table to an excel sheet using angularjs on abutton click. I tried a code, but in vain.i m getting the button click event triggered though but nothing else seems to happen
<table class="table table-bordered table-condensed table-hover table-striped" id="tableId">
<tr ng-repeat="mas in vm1 | orderBy:orderByField:reverseSort">
<td>{{::mas.contractNumber}} </td>
<td>{{::mas.planNumber}} </td>
<td>{{::mas.businessErrorMsg }} </td>
<td>{{::mas.systemErrorMsg}} </td>
</tr>
<button class="btn btn-link" ng-click="exportToExcel('#tableId')">
<span class="glyphicon glyphicon-share"></span>Export to Excel
</button>
//controller code
app.controller("ErrorDetailController", [
"$scope", "$location", "$routeParams", "messageService", "errorService", "repositoryService", , "sharedPageService",
function ($scope, $location, $routeParams, messageService, errorService, repositoryService,sharedPageService, **Excel, $timeout**) {
$scope.exportToExcel = function (tableId) { // ex: '#my-table'
debugger;
var exportHref = Excel.tableToExcel(tableId, 'sheet name');
$timeout(function () { location.href = exportHref; }, 100); // trigger download
}
}
]);
app.factory('Excel', function ($window) {
var uri = 'data:application/vnd.ms-excel;base64,',
template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>{table}</table></body></html>',
base64 = function (s) { return $window.btoa(unescape(encodeURIComponent(s))); },
format = function (s, c) { return s.replace(/{(\w+)}/g, function (m, p) { return c[p]; }) };
return {
tableToExcel: function (tableId, worksheetName) {
var table = $(tableId),
ctx = { worksheet: worksheetName, table: table.html() },
href = uri + base64(format(template, ctx));
return href;
}
};
})
Use :
<body>{table}</body>
instead of :
<body><table>{table}</table></body> in template variable.
You can use the ng-table-to-csv module to export HTML tables into CSV files (that can be opened in Excel).
As given on the README of that repo, here is the usage:
Getting Started / Usage
Install module via bower (or download the files from the dist folder
in the repo):
shell bower install ng-table-to-csv --save
Add a reference to dist/ng-table-to-csv.js into your HTML pages.
Add ngTableToCsv as a dependency to your module:
js angular.module('your_app', ['ngTableToCsv']);
Add export-csv attribute directive on the table to define a new
csv object on the scope with generate() and link() functions on
them.
Options:
- Use the separator attribute to change the default comma separator into something else (like semicolon).
- Use the export-csv-ignore attribute to set the selector that will be used for prevent tr/th/td to be stringified.
To create an Export button from an anchro tag, use the generate()
and link() functions mentioned above from ng-click and ng-href
attributes of an anchor tag.
See below:
html
<a class="btn" title="Export Table" ng-click='csv.generate()' ng-href="{{ csv.link() }}"
download="myTable.csv">
<i class="glyphicon glyphicon-new-window"></i> Export
</a>
<table class="table table-bordered" export-csv="csv" separator=";">
<!-- table contents -->
</table>
I don't know how to use razor syntax in Javascript.
I want to make Html.ListBoxFor with items from my model. I used to use:
#Html.ListBoxFor(x => x.TagIdList, (MultiSelectList)ViewBag.Tags, new { #class = "chzn-select", data_placeholder = "Tags..." })
As you see I want also use chzn-select class, to have better layout.
For now, I just have this code above in HTML as plain text, but I want have there things from my model.
Any ideas?
There is my code in ASP.NET MVC:
#model Generator.Models.ExamModel
#{
ViewBag.Title = "Generate";
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script src="#Url.Content("~/Multiple_chosen/chosen.jquery.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/ListOfTags.js")" type="text/javascript"></script>
<script >
$(".chzn-select").chosen();
</script>
}
<link href="#Url.Content("~/Multiple_chosen/chosen.css")" rel="stylesheet" type="text/css" />
<h1>#ViewBag.Title</h1>
<h2>#ViewBag.Message</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Generate</legend>
<div class="editor-label">Numbers</div>
<div class="editor-field" id="NumberOfModels">
#Html.EditorFor(model => model.NumberOfQuestions)
</div>
<div class="editor-label">Tags</div>
<div id="itemsmodel"></div>
<br>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
And there is javascript file:
var models = document.getElementById("NumberOfQuestions");
var modelsTable = document.getElementById("itemsmodel");
models.addEventListener("change", drawModels, false);
function drawModels() {
var modelsNum = parseInt(models.value);
var curModels = modelsTable.childElementCount;
if (modelsNum > curModels) {
var delta = modelsNum - curModels;
for (var i = 0; i < delta; i++) {
var input = document.createElement("div");
input.className = "editor-field";
input.innerHTML = "#Html.ListBoxFor(x => x.TagIdList, (MultiSelectList)ViewBag.Tags, new { #class = \"chzn-select\", data_placeholder = \"Tags...\" })";
modelsTable.appendChild(input);
}
} else {
while (modelsTable.childElementCount > modelsNum) {
modelsTable.removeChild(modelsTable.lastChild);
}
}
}
drawModels();
My ViewModel: ExamModel.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ExamGenerator.Models
{
public class ExaminationModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<int> TagIdList { get; set; }
public int NumberOfQuestions { get; set; }
public string Content { get; set; }
}
}
My ActionResult Generate() in controller:
public ActionResult Generate()
{
ViewBag.Tags = new MultiSelectList(genKolEnt.TAGS, "Id", "Name", null);
return View();
}
While you can generate HTML in Javascript using Razor, if the Javascript is in an MVC view, I find that injecting into JS leads to maintenance problems. You ideally want all your JS in separate files to allow for bundling/caching and the ability to break-point the JS code (which is harder in the view).
Either inject only simple things into JS on the page, or inject elements instead.
You can inject your template Razor list into a dummy script block, so you can extract the html from it later. The type="text/template" means the browser will ignore it e.g.:
<script id="ListTemplate" type="text/template">
#Html.ListBoxFor(x => x.TagIdList, (MultiSelectList)ViewBag.Tags, new { #class = "chzn-select", data_placeholder = "Tags..." })
</script>
The view page now looks like this (left out the irrelevant parts):
#section styles{
<link href="#Url.Content("~/Multiple_chosen/chosen.css")" rel="stylesheet" type="text/css" />
}
<h1>#ViewBag.Title</h1>
<h2>#ViewBag.Message</h2>
<script id="ListTemplate" type="text/template">
#Html.ListBoxFor(x => x.TagIdList, (MultiSelectList)ViewBag.Tags, new { #class = "chzn-select", data_placeholder = "Tags..." })
</script>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Generate</legend>
<div class="editor-label">Numbers</div>
<div class="editor-field" id="NumberOfModels">
#Html.EditorFor(model => model.NumberOfQuestions)
</div>
<div class="editor-label">Tags</div>
<div id="itemsmodel"></div>
<br>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Script now looks like this (jQuery version with JS as comments):
// ListOfTags.js file
// This is a shortcut DOM ready handler for $(document).ready(function(){ YOUR CODE HERE })
$(function () {
// Attach an event handler for the "change" event
$('#NumberOfQuestions').change(function () {
var $numberOfQuestions = $(this); // Convert current DOM element (the counter) to a jQuery element
var $modelsTable = $('#itemsmodel'); // document.getElementById("itemsmodel");
var modelsNum = ~~$numberOfQuestions.val(); // parseInt(models.value);
var curModels = $modelsTable.children().length; // modelsTable.childElementCount
var delta = modelsNum - curModels;
// While too few, add more
while (delta > 0) {
var $input = $('<div>').addClass('editor-field'); // document.createElement("div"); .className = "editor-field";
var template = $('#ListTemplate').html(); // Fetch the template from a script block (id="ListTemplate")
$input.html(template); // input.innerHTML =
$modelsTable.append($input); // modelsTable.appendChild(input);
delta--;
}
// While too many, remove the last
while (delta++ < 0) {
$modelsTable.children().last().remove(); // modelsTable.removeChild(modelsTable.lastChild);
}
}).change(); // Trigger an initial change event so it runs immediately
});
Notes/tips:
Place any JS in the page, at the bottom of the view, as it is easier to find. It does not matter where the #section Scripts is as the master page determines where it is injected on the final page.
Always use single quotes (') in Javascript constants by default, so that nested strings can be " which are more often required than 's. Just a good habit to get into. In fact if you had used them your code may have worked as you have added \ escaping to the quotes which will mess up the Razor processing
e.g.:
= '#Html.ListBoxFor(x => x.TagIdList, (MultiSelectList)ViewBag.Tags, new { #class = "chzn-select", data_placeholder = "Tags..." })';
If you add a #RenderSection("styles", required: false) to your master page(s) you can do the same thing for CSS as you do for scripts (ensuring all CSS is loaded in the header (for consistency). Just place them in a #section styles block.
e.g.
<head>
...
#Styles.Render("~/Content/css")
#RenderSection("styles", required: false)
...
</head>
~~ is a handy (and fast) alternative to parseInt to convert values to integers.
Use $ as a prefix for jQuery object variables. This makes it easier to remember when to use jQuery methods vs DOM properties.
Test controller code:
private MultiSelectList TagList()
{
var items = new List<KeyValuePair<int, string>>() {
new KeyValuePair<int, string>(1, "MVC"),
new KeyValuePair<int, string>(2, "jQuery"),
new KeyValuePair<int, string>(3, "JS"),
new KeyValuePair<int, string>(4, "C#"),
new KeyValuePair<int, string>(5, "PHP")
};
MultiSelectList list = new MultiSelectList(items, "key", "value", null);
return list;
}
// Get request starts with one list
public ActionResult Test()
{
ExamModel vm = new ExamModel()
{
NumberOfQuestions = 1,
TagIdList = new List<int>()
};
ViewBag.Tags = TagList();
return View(vm);
}
[HttpPost]
public ActionResult Test(ExamModel model)
{
ViewBag.Tags = TagList();
return View(model);
}
If it's a static JavaScript file and you are not generating it dynamically with razor view engine It won't work because in this case there is no processing performed on a server side. It is the same as accessing static html page/css file/image and etc...
On the other hand if this JavaScript is part of some Razor view, which means that it gets rendered by razor view engine, when you have return View() (or anything like that) in your controller action, than this code should work.
The problem is, java script files are not processed by server, so you won't be able to insert anything in those using ASP.NET MVC. Razor files on the other hand are processed on server so you can insert data into those (either through view bag or model).
One way is:
.cshtml:
<script>
var someVariable = '#model.data';
</script>
then use this variable in your javascript file:
function someFunction(){
var myData = window.someVariable;
}
The other way is to have all javascript in .cshtml file and render it as a partial view.
#Html.Partial("Path/to/javascript/in/razor/view")
edit: seeing your code, this will not help you very much.
If you want to dynamically add/remove dom elements, you will have to do it with javascript: either generate them with "document.createElement()" or load them via ajax if you want some server side processing.
#Html.ListBoxFor
is a server side helper that generates tag and fills it up depending on the parameters. You can do that with javascript as well.
I want to have an autocompletition which would look like the one in the image, with blue background and x-s for deletion of the chosen items.
In the example, I wrote 'j' and two users with j in their names are listed in the dropdown. What is the best way to accomplish this?
Here is the jquery plugin that you are looking for
click here to learn more..
I recommend you to use jQuery UI. You have an autocomplete widget and you can customize the CSS as you wish. For selecting multiple items you can use tagsinput plugin.
Here is a sample code for tagsinput:
$('#emails').tagsInput({
width: 'auto', defaultText: 'Add email', isEmail: true
});
You need to use ajax and may be jquery ui autocomplete widget for this:
Since you are using asp.net, you can first create something like a handler in your application.
Sample Handler
<%# WebHandler Language="C#" Class="SimpleHandler" %>
using System;
using System.Web;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Net;
using System.Text;
using System.IO;
public class SimpleHandler : IHttpHandler {
UCA.Common.DataControl.DBUtility dbu = new UCA.Common.DataControl.MsSqlDbUtility();
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/plain";
if (context.Request.QueryString["query"] != null)
{
context.Response.Write("You asked for "+ context.Request.QueryString["query"]);
return;
}
public bool IsReusable {
get {
return false;
}
}
}
The in your html page, use this as a base,
<html>
<body>
<form>
<input type="text" id="txtSearch"/>
<input type="button" id="btnSubmit" onclick="getDetails(document.getElementById("txtSearch").value)" value="Submit"/>
</form>
<br>
<div id="txtResult"><b>Person info will be listed here.</b></div>
<script type="text/javascript">
function getDetails(keyword)
{
var xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
document.getElementById("txtResult").innerHTML=xmlhttp.responseText;
}
xmlhttp.open("GET","simplehandler.ashx?query="+keyword,true);
xmlhttp.send();
}
</script>
</body>
</html>