How to make HTML helper method fire eveytime partial view is rendered - javascript

I have a view ( for creating a recipe ) to which I dynamically add a partial view ( representing products ). There can be several products added to recipe. Partial view is added on button click, using jQuery, and this works fine:
$('.loadPartial').on('click', function (evt) {
evt.preventDefault();
evt.stopPropagation();
var $productsDiv = $('#productsDiv'),
url = $(this).data('url');
$.get(url, function (data) {
$productsDiv.append(data);
});
});
Partial view has a combo. Name of this combo is generated dynamically using Html helper extension method 'GetIndexedName' which adds unique index to specified name. If I click button twice it should render two partial views, after first click combo name should be "ProductsCombo0", after the second "ProductsCombo1"
#Html.DropDownList(
#Html.GetIndexedName("ProductsCombo"),
null,
htmlAttributes: new { #class = "form-control col-md-3" }
)
The problem is that the method #Html.GetIndexedName fires only after the first button click ( checked in debugger ). Next clicks only render partial view, but do use the method to generate name. All combos have name "ProductsCombo0", "ProductsCombo0"
Do you know how to make it fire everytime partial view is rendered?
If it can`t be done this way could you recommend me some other solution for generating unique ids?

Because every time you make the ajax call for a new row, It is a totally separate http call and this call does not have any information whether you are making the call for the first or eighth row. You need to store this value(which row you are making this call for) and use that when you build the dropdown name.
So first update your action method to accept a row index value.
public ActionResult AddRow(int id)
{
var vm = new AddItemVm {RowIndex = id};
vm.Items = new List<SelectListItem>()
{
new SelectListItem {Value = "1", Text = "IPhone"}
};
return PartialView("_NewItem", vm);
}
I am having small view model to pass the data between my action method and the partial view, which looks like,
public class AddItemVm
{
public int RowIndex { set; get; }
public List<SelectListItem> Items { set; get; }
}
And in the partial view, which is strongly typed to our view model, we will read the Model.RowIndex property and use that to build the dropdown name/id.
#model YourNamespaceHere.AddItemVm
<div>
#Html.DropDownList("ProductsCombo"+Model.RowIndex, Model.Items)
</div>
Now we need to make sure that we are sending a unique row index value to our AddRow method every time we want to add a new row. You can keep a javascript variable and increment it's value everytime user tries to add a new row and use that value.
$(function () {
var rowIndex = 0;
$('.loadPartial').on('click', function (evt) {
evt.preventDefault();
evt.stopPropagation();
rowIndex++;
var $productsDiv = $('#productsDiv'),
url = $(this).data('url') + '/' + rowIndex;
$.get(url, function (data) {
$productsDiv.append(data);
});
});
});

I finally found a solution to the problem. Data from AJAX call was cached and that is why function was called only once. All I had to do was to disable it. So I changed jQuery 'get' mehtod to 'post' which ( what i know ony now ) is not cached.

Related

How to pass data for partial view using Jquery

I have next code:
#foreach (var offer in Model.Packages)
{
#Html.Partial("Search/PackageOffer", new Primera.Site.WebUI.Models.ViewModels.Search.PackageOfferViewModel
{
Package = offer,
DisplayPricePerPerson = Model.DisplayPricePerPerson,
RoomsCount = Model.RoomsCount
})
}
I need to implement infinite scroll using js. How can I call render partial view on js and pass parameters on it?
As a very basic demo, you want to have a Partial View that is returning some sort of html to you. In my case this is just the next 10 numbers based on the the number submitted:
public IActionResult _InfiniteNumbers(int lastId)
{
var Ids = Enumerable.Range(lastId, 10);
return PartialView(Ids);
}
In a real world scenario this would be the next n entities of whatever - blogs, orderitems, comments.
The view for this is fairly straight forward as well:
#model IEnumerable<int>
#foreach (var number in Model)
{
<li data-number="#number">#number</li>
}
it just renders every number as a list item.
My main view then looks like this:
<h1>InfiniteNumbers</h1>
<ul id="partial-view-container">
</ul>
<input id="btnLoadMore" type="button" name="loadMore" value="Load More numbers" />
#section scripts {
<script>
$(document).ready(() => {
$("#btnLoadMore").click(() => { //the trigger
var ul = $("#partial-view-container"); //our container for all partial views
let lastId = 0; //the lastId displayed
let lastLi = $("#partial-view-container li:last-child"); //find the last number
if (lastLi.length !== 0) {
lastId = lastLi.attr("data-number"); //if we found something set the value
//lastId = +lastLi.data("number");
}
$.get(`/Home/_InfiniteNumbers?lastId=${lastId}`) //call our action method
.then((res) => {
ul.append(res); //append the html to the container
});
});
});
</script>
}
We have a <ul></ul> element that serves as our container for infinite loading. Since I am lazy, I am using a button as the trigger. In a more complex scenario, this would be an event waiting for the scrollbar to reach the bottom of the page or something similar. I'll leave this up to you.
We then query the list for its last <li> item and get its data-number attribute.
After this, we query our action method and get the next 10 items based on that number and finally just inject them into our view.

SAPUI5 TreeTable's getRows method returns empty array on the first call

I am trying to build an SAPUI5 application using TreeTable and I'm facing some problems to use its methods.
In my app, I have a button which triggers this method.
onChangeViewContext: function(oEvent) {
.........
.........
var aViewContext = oContext.oModel.getProperty(sPath + "/ViewContext");
var aDataModel = oContext.oModel.getProperty("/ApplicationCollection/" + sAppId + "/DataModel");
var oStructure = this._createParentChildStructure(aDataModel);
var oTreeModel = this.getView().getModel("treeModel");
oTreeModel.setData(oStructure);
this._oViewDetailLine = oSource.getParent().getParent().getParent();
this._oViewDetailLine.setVisible(false);
this.byId("idSelectElementsPanel").setVisible(true);
this._setSelectedItems(aViewContext, oTree);
}
What I'm trying to do here is only bind the rows with my treeModel, get tree table object and send it to my _setSelectedItems method which below.
_setSelectedItems: function(aViewContext, oTree) {
oTree.clearSelection();
var sElementName;
var aSelectedIndices = [];
var aElements = [];
var aRows = oTree.getRows();
aRows.forEach(function(row) {
if (row._oNodeState !== undefined) {
aElements.push(row.getCells()[0].getText());
}
});
I need to get rows array here because I will use it for setting selected items of tree table. The problem is when "onChangeViewContext" triggered, oTable.getRows() returns an empty array. But when I click cancel button (which just hides my tree table, nothing more) and then trigger "onChangeViewContext" function again, I can get the rows array completely.
Even on the first call when I try to get table's model, I can get the treeModel and its data correctly.
I've tried to refresh bindings, aggregations etc. But no luck.
By the way, I'm using row binding in my xml view like this :
<t:TreeTable id="idSelectElementsTree" rows="{path: 'treeModel>/'}" selectionMode="MultiToggle" enableSelectAll="false"
rowSelectionChange="onSelectElement">
I'm really drowning here so any any help would be appreciated.
Edit : rest of the setSelectedIndexes function :
aViewContext.forEach(function(name) {
sElementName = name;
if (aElements.indexOf(sElementName) !== -1) {
aSelectedIndices.push(aElements.indexOf(sElementName));
}
});
aSelectedIndices.forEach(function(idx) {
if (oTree.getRows()[idx]._bHasChildren) {
oTree.expand(idx);
}
oTree.addSelectionInterval(idx, idx);
});
What could help here is to add an event rowsUpdated="onRowsUpdated" to the table in the XML view. This event is triggered after the table has been loaded and will hence provide you with the data via;
this.getView().byId("sTableId").getRows();
The difference to your approach is that the event would not be triggered by the press of a button but automatically, as the table is rendered. You can then also use this function to trigger another one as per your use case.

javascript click event working only for first element in WebGrid MVC

I have a webgrid that gets populated on page load. In this grid I have an element that has a javascript event handled when it is clicked. Here I simply intend to sent the user to an external site. I also have this tied to a controller. Both are working for the first element. However, when it comes to anything after the first element in the list the javascript does not get called.
WebGrid:
#grid.GetHtml(tableStyle: "table table-striped table-bordered",
headerStyle: "thead-default",
columns: grid.Columns(
grid.Column("post_tran_a", Model.year_a, canSort: false, format: (item) =>
new HtmlString(Html.CheckBox("post_tran_a", (bool)item.post_tran_a.is_reviewed, new { disabled = "disabled" })
+ " " +
Html.ActionLink((string)item.post_tran_a.month, "PostTransmission", Model.controller, new { report_id = item.post_tran_a.report_id, link = item.post_tran_a.link }, new { #id = "external-link", data_url=Url.Action() })
))))
Javascript:
$("#external-link").click(function () {
var url = $("#external-link").attr("data-url");
alert(url);
});
If this approach won't work I'm open to alternative solutions.
Simplest way in your particular case might work like
$("table a").click(function () {
// you need here 'this' it is available by default
// and it points to the object on which click is called
var url = $(this).attr("data-url");
alert(url);
});
But above is too general. It will fail if you have tables having other links a where you do not want to fire the even so better approach is following
Id only works for one element. For a set of elements (e.g. multiple links). You need to use the class and access them by class as well.
I replaced your id with class and accessed it with that name as well.
grid.GetHtml(tableStyle: "table table-striped table-bordered",
headerStyle: "thead-default",
columns: grid.Columns(
grid.Column("post_tran_a", Model.year_a, canSort: false, format: (item) =>
new HtmlString(Html.CheckBox("post_tran_a",
(bool)item.post_tran_a.is_reviewed, new { disabled = "disabled" })
+ " " +
Html.ActionLink((string)item.post_tran_a.month, "PostTransmission",
Model.controller, new { report_id = item.post_tran_a.report_id, link = item.post_tran_a.link },
// See following carefully
new { #class="someuniquecalssname" data_url=Url.Action() })))))
Now the javascript will work fine
$(".someuniquecalssname").click(function () {
var url = $(this).attr("data-url");
alert(url);
});
If you are not willing to add class attribute then, Creating Unique Ids like ex-link1, ex-link2 could be possible in many cases. But they are useless for an event like above
You are using id to select the element, an id must be unique on the page. use either a class or unique ids when setting them in your code #id = "external-link--xxx"
You could also use a different selector in your jquery selector
$("#yourtableid a").click(function () {
var url = $("#external-link").attr("data-url");
alert(url);
});

MVC pass model between Parent and Child Window

Thanks in advance. Please excuse me for my grammer. I tried my best to explain my issue In my quest of solving below question I started to develop a POC first.
C# MVC No submit pass object between views
I am having an issue using TempData object and pass my model between my parent popup and child popup. My problem is I am doing TempData["StudentViewModel"] 2 times. First time insert and First time read are good but Second time read even though I make sure that I insert second time before read is not working.
I will try my best to explain it clearly.
I have a ASP.NET page called Class.cshtml. It will have a grid of all class. The user will select a ClassName column and it opens up Students.cshtml as a new popup window which has a grid with StudentName and Address columns. The user will select StudentName and it opens another popup window called StudentDetails.cshtml.
We have a ClassController.cs which is used by all popups and have C# methods. ClassController.js has all javscript code.
public ActionResult GetStudentsDetails()
{
// this will create students for each class.
//Inside student for each class it will also create Student Details.
// First Insert
TempData["StudentViewModel"] = studentViewModel;
return View("Students", studentViewModel);
}
Students.cshtml is an existing popup window like below
<div>
//this is in for loop
string anchorElementId = string.Format("AnchorElementId_{0}", i);
string selectedIndex = i.ToString();
string name = Model.Students[i].Name;
<input class="ButtonLikeHyperLink"
id="myBtnId"
onclick="ShowStudentDetails(#selectedIndex, '#name', '#anchorElementId')"
value="#Model.Students[i].Name"
type="button"/>
//for loop ends here
//First read
<span id="lblHDNStudentViewModel">
#Newtonsoft.Json.JsonConvert.SerializeObject(TempData["StudentViewModel"] as StudentViewModel)
</span>
</div>
Once user selects any StudentName in Students.cshtml popup the below js method is called which opens a Child window popup having particular student details.
ClassController.js
function ShowStudentDetails(selectedIndex, name, anchorElementId)
{
var inputParam = {};
var hiddenField = document.getElementById("lblHDNStudentViewModel");
if (hiddenField != null)
{
inputParam.StudentVM = JSON.parse(hiddenField.innerText);
inputParam.selectedIndex = selectedIndex;
inputParam.name = name;
inputParam.anchorElementId = anchorElementId;
// __callback is our custom method to call controller action method
var retVal = __callback("OnNameSelected", inputParam);
var postedData = JSON.parse(retVal.return_value);
if (postedData.Success == true)
{
// i need to do like below since Model to my popup is dynamic
multipleMatchPopup = window.open('', '', properties);
multipleMatchPopup.document.write(postedData.PartialViewHtml);
}
}
}
ClassController.cs
public JsonResult OnNameSelected(StudentViewModel StudentVM, int selectedIndex, string name, string anchorElementId)
{
// this will create student name details viewmodel for selected name and modify StudentViewModel object.
// for example
StudentDetailsViewModel vm = StudentVM[selectedIndex].DetailsVM;
//since user made selection update few properties in vm
StudentVM[selectedIndex].DetailsVM = vm;
//Second insert
// make sure to set tempdata before RenderPartialViewToString
TempData["StudentViewModel"] = StudentVM;
string sHtml = this.RenderPartialViewToString("~/Views/_PartialStudentDetailsPopup.cshtml", vm);
return Json(new
{
Success = true,
data = StudentVM,
PartialViewHtml = sHtml,
JsonRequestBehavior.AllowGet
});
}
In StudentDetails.cshtml popup I have like this
<div>
.....
<input class="ButtonLikeHyperLink"
id="#buttonId"
onclick="OnUserSelectStudentDetails()"
value="[Select]"
type="button" />
//Second read
//in fiddler innertext is show as null
<span id="lblHDNStudentDetailsViewModel">
#Newtonsoft.Json.JsonConvert.SerializeObject(TempData["StudentViewModel"] as StudentViewModel)
</span>
</div>
ClassController.js
function OnUserSelectStudentDetails()
{
var inputParam = {};
var hiddenField = document.getElementById("lblHDNStudentDetailsViewModel");
if (hiddenField != null)
{
//hiddenField.innerText is null
inputParam.StudentVM = JSON.parse(hiddenField.innerText);
var retVal = __FAFdoCallback("OnUserSelectLenderMatchingFee", inputParam);
...
}
}
ClassController.cs
public JsonResult OnUserSelectLenderMatchingFee(StudentViewModel StudentVM)
{
//StudentVM is null here
}
UPDATE
SOLUTION
I feel real stupid on this issue. As the great detective, Hercule Poirot said, "The great gray cells are not working", mine also did not work
in this case. Sometimes we think so far away from the box that we oversee the basics. I was thinking that this thing cannot be done in so simple so I was thinking about TempData and so on and forgot the fundamental point that my parent popup already have a hidden field and I can read from it and write to it it in my javascript methods of parent and child popups windows and pass it to controller action methods and get back updated and consistent model back.
Taking this basic solution this is what I did
Students.cshtml
<span id="lblHDNStudentViewModel">
#Newtonsoft.Json.JsonConvert.SerializeObject(Model)
</span>
Read this in parent window javascript method like below in ClassController.js
function ShowStudentDetails(selectedIndex, name, anchorElementId)
{
var inputParam = {};
//read
var hiddenField = document.getElementById("lblHDNStudentViewModel");
}
Read this from child window javascript method like below in ClassController.js
function OnUserSelectStudentDetails()
{
var inputParam = {};
// read in child window and access parent window element
var hiddenField = window.opener.document.getElementById("lblHDNStudentViewModel");
}
Write back to parent window element from parent window javascript method like below
document.getElementById("lblHdnCDLenderViewModel").innerText = JSON.stringify(postedData.data);
Write back to parent window element from child window javascript method like below
window.opener.document.getElementById("lblHdnCDLenderViewModel").innerText = JSON.stringify(postedData.data);

Dynamic default value for Kendo Grid

I want an auto increment column in my Kendo Grid. This field isn't server side auto increment, because I want the user to see the value and be able to change it.
My current solution is to add a click attribute to Create button and loop over rows to find the highest value and increment it.
But how can I insert this value inside the newly created row? Click event happens before the new row is created.
So there is two possible solution:
Have a variable as default value and update it in my JS code.
Access the newly created row somehow, and update the value.
This is my JS code:
function createClick(id) {
var grid = $("#" + id).data('kendoGrid');
var highestRadif = 0;
grid.tbody.find('>tr').each(function () {
var dataItem = grid.dataItem(this);
var radif = dataItem.SRadifReqR;
highestRadif = highestRadif < radif ? radif : highestRadif;
})
alert(++highestRadif);
}
You can use Grid's edit event to add your new generatedId value to new Grid's model.
This is some explanation from their documentation:
Edit
fired when the user edits or creates a data item.
e.container jQuery, jQuery object of the edit container element, which wraps the editing UI.
e.model kendo.data.Model, The data item which is going to be edited. Use its isNew method to check if the data item is new
(created) or not (edited).
e.sender kendo.ui.Grid, The widget instance which fired the event.
I suppose your click have something like this
//generate id code
vm.newId = ++highestRadif; // we need to store generated Id
grid.addRow();
then on edit event
edit: function(e) {
var model = e.model; // access edited/newly added model
// model is observable object, use set method to trigger change event
model.set("id", vm.newId);
}
Note: Your schema model's field must set property editable: true, due to enable us to change model field value using set method. Also if your field schema have validation required, you need to remove it.
model: {
id: "ProductID",
fields: {
ProductID: { editable: true, nullable: true },
}
}
Sample
I was able to put a function in the datasource schema for this.
schema: {
model: {
id: "id",
fields: {
currencyType: { defaultValue: getDefaultCurrency },
invoiceDate: { type: "date" }
}
}
}
function getDefaultCurrency() {
return _.find(vm.currencyTypes, { id: vm.currencyId });
};

Categories