Blazor and DataTables: pass params like columns - datatables

I have integrated in my Blazor WebAssembly a DataTables. In the index.html I added the following javascript code
<script>
function DataTablesAdd(table) {
$(document).ready(function () {
$(table).DataTable();
});
}
function DataTablesRemove(table) {
$(document).ready(function () {
$(table).DataTable().destroy();
// Removes the datatable wrapper from the dom.
var elem = document.querySelector(table + '_wrapper');
elem.parentNode.removeChild(elem);
});
}
</script>
In a Razor page I added
#inject IJSRuntime JSRuntime
#implements IDisposable
#code {
public void Dispose()
{
JSRuntime.InvokeAsync<bool>("DataTablesRemove", "#tableData");
}
protected async override Task OnAfterRenderAsync(bool firstRender)
{
await JSRuntime.InvokeAsync<string>("DataTablesAdd", new string[] { "#tableData" });
await base.OnAfterRenderAsync(firstRender);
}
}
The DataTables is working. Now, I want to not order some columns, for example in the following image I don't want to order by Gap analysis and Publication Plan.
So, in the index.html I added another function
function DataTablesAdd(table, columns) {
$(document).ready(function () {
$(table).DataTable({
"columns": columns
});
});
}
In the page I added this C# code
protected async override Task OnAfterRenderAsync(bool firstRender)
{
string cols = "[{ name = \"Username\", orderable = \"true\" }, " +
"{ name = \"Gap analysis\", orderable = \"false\" }," +
"{ name = \"Publication plan\", orderable = \"false\" }," +
"{ name = \"Last update\", orderable = \"true\" }]";
await JSRuntime.InvokeAsync<string>("DataTablesAdd", new string[] { "#tableData" }, cols);
await base.OnAfterRenderAsync(firstRender);
}
Unfortunately, it doesn't work and also the DataTables is not applied to the table for a jQuery error.
How can I pass same parameters to the javascript function and use them in the DataTables definition?

You could pass the arguments to the JavaScript function as object array.
protected async override Task OnAfterRenderAsync(bool firstRender)
{
string cols = "[{ name = \"Username\", orderable = \"true\" }, " +
"{ name = \"Gap analysis\", orderable = \"false\" }," +
"{ name = \"Publication plan\", orderable = \"false\" }," +
"{ name = \"Last update\", orderable = \"true\" }]";
await JSRuntime.InvokeAsync<string>("DataTablesAdd", new object[] { "#tableData", cols });
await base.OnAfterRenderAsync(firstRender);
}
At the JavaScript capture the columns string and parse it back to JavaScript object as follows.
function DataTablesAdd(table, columns) {
$(document).ready(function () {
$(table).DataTable({
"columns": JSON.parse(columns)
});
});
}
Disclaimer: I have not tested the code.

For now, I found a very simple way to resolve this issue. I changed the function for adding a DataTables like the following code
function DataTablesAdd(table) {
$(document).ready(function () {
$(table).DataTable({
"order": [],
"columnDefs": [{
"targets": 'no-sort',
"orderable": false,
}]
});
});
}
Then, where I define the table, I add the class with value no-sort.
<table class="table table-hover" id="tableData">
<thead>
<tr>
<th>Last update</th>
<th>Username</th>
<th class="no-sort">Gap analysis</th>
<th class="no-sort">Publication plan</th>
</tr>
</thead>
</table>
So, the DataTables render doesn't allow to sort for the headers with class="no-sort".
I still have to sort out how to pass data from the function parameters to DataTables.

Related

Using dropzone.js in vue, calling function with image file name

I'm having a hard time getting anything to work with this the way I need it, but I have a working dropzone instance in my Vue project.
I can upload the image and call functions within the dropzone code, however, I need to call a function directly from the form in the html in order to send the 'card' object.
All I need to do is call a function when a file is added through the dropzone form, with the filename.
My code:
<div class="uk-width-3-10">
<form v-on:change="imageChange(card)" method="post" action="{{url('product/parts/upload/store')}}" enctype="multipart/form-data"
class="dropzone" v-bind:id="'dropzone-'+i">
</form>
</div>
...
imageChange(Card){
console.log('working');
},
addCard(){
Vue.nextTick(function () {
new Dropzone("#dropzone-"+cardIndex, {
maxFilesize: 12,
renameFile: function (file) {
var dt = new Date();
var time = dt.getTime();
return time + file.name;
},
acceptedFiles: ".jpeg,.jpg,.png,.gif",
addRemoveLinks: true,
timeout: 50000,
removedfile: function (file) {
console.log(file.upload.filename);
var name = file.upload.filename;
var fileRef;
return (fileRef = file.previewElement) != null ?
fileRef.parentNode.removeChild(file.previewElement) : void 0;
},
init: function() {
this.on("addedfile",
function(file) {
instance.imageZoneNames.push({name: file.upload.filename});
console.log(file);
console.log(instance.imageZoneNames);
});
}
});
});
}
Dropzone has many events, You used removedfile() event! there is another event called addedfile() and executes when a file is added to the dropzone list
imageChange(card) {
console.log(card)
},
addCard() {
Vue.nextTick(() => {
new Dropzone('#dropzone-` + cardIndex, {
addedfile(file) {
this.imageChange(file);
}
}
}
}

How to filter search result by index type in kademi?

Here is my code snippet to search profile, blog, and content from kademi site using SearchManager API from search application.
keyword = params['q'];
var json = {
"query": {
"match": {"_all":keyword}
},
"highlight": {
"fields" : {
"*" : {},
"content" : {
"type" : "plain"
}
}
}
};
var indexes = ["profile", "bran-103166797", "blogs-103166797"]; // profile, content, blog
var sm = applications.search.searchManager;
var result = sm.search(JSON.stringify(json), indexes);
If you see my screenshot below, there are several index type for index names = profile. I just want to get data from index type = profile with index name = profile.
There's a couple of changes you should make
Firstly, instead of naming the indexes directly (eg bran-103166797), you should use AppIndexers so that the correct name is generated. Otherwise when you publish a new version of the website your search will still be indexing the old version:
var sm = applications.search.searchManager;
var indexers = sm.appIndexers;
var profileIndexer = indexers.profile;
var contentIndexer = indexers.content;
Then you can use the prepareSearch method on SearchManager which lets you directly manipulate the search builder:
log.info("using indexers {} {}", profileIndexer, contentIndexer);
var builder = sm.prepareSearch(profileIndexer, contentIndexer);
builder.setSource(JSON.stringify(json));
builder.setTypes("profile", "html");
Then you can execute the search query using the elasticsearch API methods. Note that in this example I'm using inline js scripts rather then a js controller, so i need to set the results in a request attribute so the template can access it.
var result = builder.execute().actionGet();
log.info("result {}", result);
http.request.attributes.result = result;
Here's a full worked example:
http://docs.kademi.co/howtos/devs/advanced-search-pages-with-the-searchmanager-api.html
And the source of the template in that example is here:
<html>
<head>
<title>search page</title>
</head>
<body>
#script()
<script>
var keyword = http.request.params.q;
var json = {
"query": {
"match": {"_all":keyword}
},
"fields" : ["nickName", "title"],
"highlight": {
"fields" : {
"*" : {},
"content" : {
"type" : "plain"
}
}
}
};
var sm = applications.search.searchManager;
var indexers = sm.appIndexers;
var profileIndexer = indexers.profile;
var contentIndexer = indexers.content;
log.info("using indexers {} {}", profileIndexer, contentIndexer);
var builder = sm.prepareSearch(profileIndexer, contentIndexer);
builder.setSource(JSON.stringify(json));
builder.setTypes("profile", "html");
var result = builder.execute().actionGet();
log.info("result {}", result);
http.request.attributes.result = result; // make available to templating
</script>
#end
<div class="container">
<h1>Search</h1>
<p class="pull-right lead">Showing $request.attributes.result.hits.hits.size() of $request.attributes.result.hits.totalHits hits</p>
<table class="table table-striped">
#foreach( $hit in $request.attributes.result.hits)
<tr>
<td>
$!hit.fields.nickName.value $!hit.fields.title.value
</td>
<td>$hit.type</td>
</tr>
#end
</table>
</div>
<!-- for debugging, just display the search result as json -->
<pre>$request.attributes.result</pre>
</body>

Pass selected item from drop down list to viewmodel

I have four controls on the page, a simple form with first and last names, date of birth and this drop down that contains some names of countries. When I make changes to the these controls I am able to see those changes in my viewModel that is passed in as a parameter in the SavePersonDetails POST below, but I never see the LocationId updated in that view model and I am not sure why.
This is what I have in my markup, Index.cshtml:
#model Mvc4withKnockoutJsWalkThrough.ViewModel.PersonViewModel
#using System.Globalization
#using Mvc4withKnockoutJsWalkThrough.Helper
#section styles{
#Styles.Render("~/Content/themes/base/css")
<link href="~/Content/Person.css" rel="stylesheet" />
}
#section scripts{
#Scripts.Render("~/bundles/jqueryui")
<script src="~/Scripts/knockout-2.1.0.js"></script>
<script src="~/Scripts/knockout.mapping-latest.js"></script>
<script src="~/Scripts/Application/Person.js"></script>
<script type="text/javascript">
Person.SaveUrl = '#Url.Action("SavePersonDetails", "Person")';
Person.ViewModel = ko.mapping.fromJS(#Html.Raw(Json.Encode(Model)));
var userObject = '#Html.Raw(Json.Encode(Model))';
var locationsArray = '#Html.Raw(Json.Encode(Model.Locations))';
var vm = {
user : ko.observable(userObject),
availableLocations: ko.observableArray(locationsArray)
};
ko.applyBindings(vm);
</script>
}
<form>
<p data-bind="with: user">
Your country:
<select data-bind="options: $root.availableLocations, optionsText: 'Text', optionsValue: 'Value', value: LocationID, optionsCaption: 'Choose...'">
</select>
</p>
</form>
This is my View Model:
public class PersonViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public string LocationId { get; set; }
public IEnumerable<SelectListItem> Locations { get; set; }
}
I have a simple controller that loads my Person and a drop down list containing three countries.
private PersonViewModel _viewModel;
public ActionResult Index()
{
var locations = new[]
{
new SelectListItem { Value = "US", Text = "United States" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "MX", Text = "Mexico" },
};
_viewModel = new PersonViewModel
{
Id = 1,
FirstName = "Test",
LastName = "Person",
DateOfBirth = new DateTime(2000, 11, 12),
LocationId = "", // I want this value to get SET when the user changes their selection in the page
Locations = locations
};
_viewModel.Locations = locations;
return View(_viewModel);
}
[HttpPost]
public JsonResult SavePersonDetails(PersonViewModel viewModel)
{
int id = -1;
SqlConnection myConnection = new SqlConnection("server=myMachine;Trusted_Connection=yes;database=test;connection timeout=30");
try
{
// omitted
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
finally
{
myConnection.Close();
}
return Json(id, "json");
}
Lastly, here is my Person.js file, I am using knockout
var Person = {
PrepareKo: function () {
ko.bindingHandlers.date = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
element.onchange = function () {
var observable = valueAccessor();
observable(new Date(element.value));
}
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var observable = valueAccessor();
var valueUnwrapped = ko.utils.unwrapObservable(observable);
if ((typeof valueUnwrapped == 'string' || valueUnwrapped instanceof String) && valueUnwrapped.indexOf('/Date') === 0) {
var parsedDate = Person.ParseJsonDate(valueUnwrapped);
element.value = parsedDate.getMonth() + 1 + "/" + parsedDate.getDate() + "/" + parsedDate.getFullYear();
observable(parsedDate);
}
}
};
},
ParseJsonDate: function (jsonDate) {
return new Date(parseInt(jsonDate.substr(6)));
},
BindUIwithViewModel: function (viewModel) {
ko.applyBindings(viewModel);
},
EvaluateJqueryUI: function () {
$('.dateInput').datepicker();
},
RegisterUIEventHandlers: function () {
$('#Save').click(function (e) {
// Check whether the form is valid. Note: Remove this check, if you are not using HTML5
if (document.forms[0].checkValidity()) {
e.preventDefault();
$.ajax({
type: "POST",
url: Person.SaveUrl,
data: ko.toJSON(Person.ViewModel),
contentType: 'application/json',
async: true,
beforeSend: function () {
// Display loading image
},
success: function (result) {
// Handle the response here.
if (result > 0) {
alert("Saved");
} else {
alert("There was an issue");
}
},
complete: function () {
// Hide loading image.
},
error: function (jqXHR, textStatus, errorThrown) {
// Handle error.
}
});
}
});
},
};
$(document).ready(function () {
Person.PrepareKo();
Person.BindUIwithViewModel(Person.ViewModel);
Person.EvaluateJqueryUI();
Person.RegisterUIEventHandlers();
});
As you can see, I have the data in the page but none of them show as selected
Your solution is overly complex and is leading to certain weirdness with your data. Instead of trying to patch the Titanic, your best bet is to start over and simplify:
Your page's model contains all the information you need. There's no need to try to create two totally separate view models to work with the user data versus locations. With the mapping plugin, you can specify different "view models" for various objects in your main view model, and there's a simpler pattern that can be followed to set all that up. Here's what I would do:
// The following goes in external JS file
var PersonEditor = function () {
var _init = function (person) {
var viewModel = PersonEditor.PersonViewModel(person);
ko.applyBindings(viewModel);
_wireEvents(viewModel);
}
var _wireEvents = function (viewModel) {
// event handlers here
}
return {
Init: _init
}
}();
PersonEditor.PersonViewModel = function (person) {
var mapping = {
'Locations': {
create: function (options) {
return new PersonEditor.LocationViewModel(options.data)
}
}
}
var model = ko.mapping.fromJS(person, mapping);
// additional person logic and observables
return model;
}
PersonEditor.LocationViewModel = function (location) {
var model = ko.mapping.fromJS(location);
// additional location logic and observables
return model;
}
// the following is all you put on the page
<script src="/path/to/person-editor.js"></script>
<script>
$(document).ready(function () {
var person = #Html.Raw(#Json.Encode(Model))
PersonEditor.Init(person);
});
</script>
Then all you need to bind the select list to the locations array is:
<p>
Your country:
<select data-bind="options: Locations, optionsText: 'Text', optionsValue: 'Value', value: LocationId, optionsCaption: 'Choose...'">
</select>
</p>
Based on your updated question, do this.
1.We do not need locationsArray actually. Its already in user.Locations (silly me)
2.On Index.cshtml, page change the JavaScript like this.
var userObject = #Html.Raw(Json.Encode(Model)); // no need for the quotes here
jQuery(document).ready(function ($) {
Person.PrepareKo();
Person.EvaluateJqueryUI();
Person.RegisterUIEventHandlers();
Person.SaveUrl = "#Url.Action("SavePersonDetails", "Knockout")";
Person.ViewModel = {
user : ko.observable(userObject)
};
Person.BindUIwithViewModel(Person.ViewModel);
});
3.On your Person.js page, inside RegisterUIEventHandlers #Save button click event, do this.
$('#Save').click(function (e) {
var updatedUser = Person.ViewModel.user();
updatedUser.Locations.length = 0; // not mandatory, but we don't need to send extra payload back to server.
// Check whether the form is valid. Note: Remove this check, if you are not using HTML5
if (document.forms[0].checkValidity()) {
e.preventDefault();
$.ajax({
type: "POST",
url: Person.SaveUrl,
data: ko.toJSON(updatedUser), // Data Transfer Object
contentType: 'application/json',
beforeSend: function () {
// Display loading image
}
}).done(function(result) {
// Handle the response here.
if (result > 0) {
alert("Saved");
} else {
alert("There was an issue");
}
}).fail(function(jqXHR, textStatus, errorThrown) {
// Handle error.
}).always(function() {
// Hide loading image.
});
}
});
5.Finally, our markup
<p data-bind="with: user">
Your country:
<select data-bind="options: Locations,
optionsText: 'Text',
optionsValue: 'Value',
value: LocationId,
optionsCaption: 'Choose...'">
</select>
</p>
On an unrelated side-note,
The jqXHR.success(), jqXHR.error(), and jqXHR.complete() callbacks are
deprecated as of jQuery 1.8. To prepare your code for their eventual
removal, use jqXHR.done(), jqXHR.fail(), and jqXHR.always() instead.

MVC4 .Net , controls on hidden fields

I wonder if it's possible to have controls (dataanotation) on hidden fields (HiddenFor or hidden EditorFor) ?
I don't think so, but we never know.
There are a lot of posts on how to hide EditorFor such as :
TextBoxFor vs EditorFor, and htmlAttributes vs additionalViewData
In my case,in a view I have a jquery call to a WCF REST service, that in success case fill my EditorFor. I would like that the Required DataAnotation to be applied on that EditorFor, would it be possible ?
I think that as long as the EditorFor is invisible the DataAnotation cannot be applied. Would it have a way to apply the DataAnotation on the hidden EditorFor ?
Here is the code :
To hide the EditorFor :
#Html.EditorFor(model => model.VilleDepart, "CustomEditor", new {style = "display:none;" })
The CustomEditor :
#{
string s = "";
if (ViewData["style"] != null) {
// The ViewData["name"] is the name of the property in the addtionalViewData...
s = ViewData["style"].ToString();
}
}
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { style = s })
the model :
string _VilleDepart;
[Required]
[Display(Name = "Ville Départ")]
public string VilleDepart
{
get
{
if (Commune != null) {
return Commune.Commune1;
}
return _VilleDepart;
}
set {
_VilleDepart = value;
}
}
The JQuery call to WCF REST Service :
$(document).ready(function () {
$([document.getElementById("IVilleDepart"), document.getElementById("IVilleArrivee")]).autocomplete({
source: function (request, response) {
$.ajax({
cache: false,
type: "GET",
async: false,
dataType: "json",
url: GetSearchCommunetURl + "(" + request.term + ")",
success: function (data) {
//alert(data);
response($.map(data, function (item) {
return {
label: item['Commune'] + ' (' + item['CodePostal'] + ')',
val: item
}
}))
},
error: function (response) {
alert("error ==>" + response.statusText);
},
failure: function (response) {
alert("failure ==>" + response.responseText);
}
});
},
select: function (e, i) {
if (e.target.id == "IVilleDepart") {
VilleDepart = i.item.val;
EVilleDepart.value = VilleDepart.Commune;
ECodePostalDepart.value = VilleDepart.CodePostal;
ECodeINSEEDepart.value = VilleDepart.CodeINSEE;
}
if (e.target.id == "IVilleArrivee") {
VilleArrivee = i.item.val;
EVilleArrivee.value = VilleArrivee.Commune;
ECodePostalArrivee.value = VilleArrivee.CodePostal;
ECodeINSEEArrivee.value = VilleArrivee.CodeINSEE;
}
},
minLength: 2
});
});
If I don't hide the EditorFor I can see it is correctly filled after the WCF REST service call and the Required DataAnotation is applied.
There are other way to hide the EditorFor, for instance to apply the style='width:0px;height:0px'
It hides but disable the Required DataAnotation,
if I apply the style='width:0px;height:1px', we don't see a lot of the EditorFor but the Required DataAnotation is active.
I've seen an answer at http://www.campusmvp.net/blog/validation-of-hidden-fields-at-the-client-in-asp-net-mvc
(but it seems i had badly searched precedently, the validation of hidden field is treated in some blogs and sites).
To active the validation of hidden fields, you just have to add this little javascript line :
$.validator.setDefaults({ ignore: null });
and it works !
Apparently it doesn't work with mvc2, but works since mvc3.

dojox.grid.DataGrid populated from Servlet

I'd like to hava a Dojo dojox.grid.DataGrid with its data from a servlet.
Problem: The data returned from the servlet does not get displayed, just the message "Sorry, an error has occured".
If I just place the JSON string into the HTML, it works. ARRRRGGH.
Can anyone please help me!
Thanks
Jeff Porter
Servlet code...
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
res.setContentType("json");
PrintWriter pw = new PrintWriter(res.getOutputStream());
if (response != null) pw.println("[{'batchId':'2001','batchRef':'146'}]");
pw.close();
}
HtmL code...
<div id="gridDD" dojoType="dojox.grid.DataGrid"
jsId="gridDD" style="height: 600x; width: 100%;"
store="ddInfo" structure="layoutHtmlTableDDDeltaSets">
</div>
var rawdataDDInfo = ""; // empty at start
ddInfo = new dojo.data.ItemFileWriteStore({
data: {
identifier: 'batchId',
label: 'batchId',
items: rawdataDDInfo
}
});
<script>
function doSelectBatchsAfterDate() {
var xhrArgs = {
url: "../secure/jsonServlet",
handleAs: "json",
preventCache: true,
load: function(data) {
var xx =dojo.toJson(data);
var ddInfoX = new dojo.data.ItemFileWriteStore({data: xx});
dijit.byId('gridDD').setStore(ddInfoX);
},
error: function(error) {
alert("error:" + error);
}
}
//Call the asynchronous xhrGet
var deferred = dojo.xhrGet(xhrArgs);
}
</script>
<img src="go.gif" onclick="doSelectBatchsAfterDate();"/>
When you create the dojo.data.ItemFileWriteStore using the JSON data returned from server. You just provide the items, you still needs to specify the metadata. The correct code should be as below.
var ddInfoX = new dojo.data.ItemFileWriteStore({
data: {
identifier: 'batchId',
label: 'batchId',
items: xx
}
});
And you don't need the dojo.toJson function which converts the JSON object to a JSON string. The dojo.data.ItemFileWriteStore requires a JSON object as the parameter, not a JSON string.