JQuery MVC collection name attributes - c#

I have a table, and I've dynamically added / removed rows from it.
The table contains fields that will be posted back as a collection to MVC.
In this scenario updating the table with jquery means I up with collections that contain incomplete collections for example ...
function add() {
$.ajax({
url: "GetRow",
type: 'post',
dataType: 'html',
timeout: 30000,
data: {
RowIndex: otherRows.length,
"Item.Property1": $("option:selected", dropDown).text(),
"Item.Property2": $("option:selected", dropDown).val()
},
success: function (result) {
var newRow = $(result);
tableBody.append(newRow);
}
});
}
This function makes an ajax call to go get an mvc action result that returns the result of a row at the given index and additionally sets some default values from a drop down on my page.
Now lets assume I have a similar function called "delete" in which using jquery i remove a row and at this point the table has 3 rows (ignoring the header row of course), and the row i'm removing is the middle row.
this means the fields in my rows end up looking something like this ...
//Row 1:
<input type="text" name="SomeCollection[0].Property1" />
//Row 2:
Nothing, deleted of course
//Row 3:
<input type="text" name="SomeCollection[2].Property1" />
So if i now do a postback, because of the inconsistent id range, the MVC model binder will only bind item 1 for me and item 2 (actually item 3 prior to the client side delete) is not mapped.
The idea is that i want my server logic to do a very simple "if the id for an item in my collection is not in the post data, delete it from the database", this way all the collection manipulation can be entirely client side in save on constant postbacks on this very heavy page.
So I started putting a function together in jquery to fix the problem ...
function updateNames(table) {
var rows = $("tr", table);
var index = 0;
rows.each(function () {
var inputFields = $("input");
inputFields.each(function (){
//replace name="bla[<any number>].Anything" with
// name="bla[" + index + "].Anything"
});
index++;
});
}
So my question is ... How do i say to jquery "replace [] in the name attribute with [index]"?
I know this wont solve the problem of nested collections / other such complex scenarios but for that once i have this function solved I can always extend it later and my requirements are not that involved yet.
EDIT:
Some additional detail on my current thought pattern ... good or bad?
if i grab the name attribute value and "walk the chars" until i find the first "[" and the next "]" then replace everything in between, it should solve my problem, but this type of thing on IE probably slow as hell.
Anyone got a neat "just call this clever function" type answer? (if there is one).
EDIT 2:
Wow I really gotta look harder ... what a dumbass i feel like right now for 2 reasons (not looking and regex is an obvious solution here)
JQuery: how to replace all between certain characters?
if i can figure out the right regex i've got my solution (maybe that's the question I should have asked as i'm constantly annoyed by the crazyness of regex).
But the power of a regex cannot be under estimated :)

This should work for you:
function updateNames(table) {
var rows = $("tr", table);
var index = 0;
rows.each(function () {
var inputFields = $(this).find("input");
inputFields.each(function (){
var currentName = $(this).attr("name");
$(this).attr("name", currentName.replace(/\[(.*?)\]/, '['+index+']'));
});
index++;
});
}
If there are multiple inputs inside each row and you just want to update one then consider using the 'starts with' jQuery selector: http://api.jquery.com/attribute-starts-with-selector/.

Though you've already got a resolution, I thought a working demo of how one might implement this functionality from the ground up might be useful.
Supposing you have a button with a class of delete on each row, you can achieve this with the following:
$('.delete').click(function(e) {
// Get table body and rows
var body = $(this).closest('tbody');
// Remove current row
$(this).closest('tr').remove();
// Get new set of rows from table body
var rows = body.find('tr')
// Update all indeces in rows
var re = new RegExp(/\[[0-9]+\]/);
var index = 0;
rows.each(function () {
$(this).find('input').each(function (e) {
var input = $(this).attr('name');
if (input) {
$(this).attr('name', input.replace(re, '['+index+']'));
}
});
index ++;
});
});
This should delete the row and update all indeces for every input in the remaining rows so that they increment properly again (and thus will be bound properly by the model binder). Additionally, it should be restricted to the current table. Here it is in action.

Related

Why is my element not seen as equating to itself?

I've got an html input text element created like this in C#:
boxIndex1 = new TextBox()
{
CssClass = "dplatypus-webform-field-input indexcell",
Width = TEXTBOX_WIDTH,
ID = "boxIndex1foapalrow2"
};
...and this jQuery to respond to the blur event of "boxIndex1foapalrow2" and its cousins ("boxIndex2foapalrow3", "boxIndex3foapalrow4", etc.):
$(document).on("blur", '.indexcell', function (e) {
var $currentindexcell = $(this);
if ($currentindexcell == $('[id$=boxIndex1foapalrow2]')) {
alert('index cell 1 exited'); // testing
}
});
I stepped through it, and the element assigned to $currentindexcell when I tab out of "boxIndex1foapalrow2" seems to be what I'm expecting:
<input name="ctl00$ctl24$g_5f3fedca_19f7_4bc3_b84e_efbef0c48a33$ctl00$boxIndex1foapalrow2" type="text" id="ctl00_ctl24_g_5f3fedca_19f7_4bc3_b84e_efbef0c48a33_ctl00_boxIndex1foapalrow2" class="dplatypus-webform-field-input indexcell" style="width:88px;">
...but the alert is not showing/the if condition equates to false. Why? It seems to me that the value of $currentindexcell in this instance does equal $('[id$=boxIndex1foapalrow2]'), but why doesn't it seem that way to the Javascript execution engine?
Two jQuery objects that contain the same set of elements are not equal. To test whether your jQuery object matches a selector, use .is():
if ($currentindexcell.is('[id$=boxIndex1foapalrow2]')) {
If you really wanted to check for equality, you should compare the actual elements (not the jQuery objects that hold them):
if (this == $('[id$=boxIndex1foapalrow2]')[0]) {

Adding numbers from two data frames in Deedle using multi key index

I am new to Deedle. I searched everywhere looking for examples that can help me to complete the following task:
Index data frame using multiple columns (3 in the example - Date, ID and Title)
Add numeric columns in multiple data frames together (Sales column in the example)
Group and add together sales occurred on the same day
My current approach is given below. First of all - it does not work because of the missing values and I don't know how to handle them easily while adding data frames. Second - I wonder if there is a better more elegant way to do it.
// Remove unused columns
var df = dfRaw.Columns[new[] { "Date", "ID", "Title", "Sales" }];
// Index data frame using 3 columns
var dfIndexed = df.IndexRowsUsing(r => Tuple.Create(r.GetAs<DateTime>("Date"), r.GetAs<string>("ID"), r.GetAs<string>("Title")) );
// Remove indexed columns
dfIndexed.DropColumn("Date");
dfIndexed.DropColumn("ID");
dfIndexed.DropColumn("Title");
// Add data frames. Does not work as it will add only
// keys existing in both data frames
dfTotal += dfIndexed
Table 1
Date,ID,Title,Sales,Market
2014-03-01,ID1,Title1,1,US
2014-03-01,ID1,Title1,2,CA
2014-03-03,ID2,Title2,3,CA
Table 2
Date,ID,Title,Sales,Market
2014-03-02,ID1,Title1,2,US
2014-03-03,ID2,Title2,2,CA
Expected Results
Date,ID,Title,Sales
2014-03-01,ID1,Title1,3
2014-03-02,ID1,Title1,2
2014-03-03,ID2,Title2,5
I think that your approach with using tuples makes sense.
It is a bit unfortunate that there is no easy way to specify default values when adding!
The easiest solution I can think of is to realign both series to the same set of keys and use fill operation to provide defaults. Using simple series as an example, something like this should do the trick:
var allKeys = seris1.Keys.Union(series2.Keys);
var aligned1 = series1.Realign(allKeys).FillMissing(0.0);
var aligned2 = series2.Realign(allKeys).FillMissing(0.0);
var res = aligned1 + aligned2;

JSON.NET XML to JSon

whilst trying to work on something else, i stumbled across JSON.NET, and have a quick question regarding the results.
I have a XML Field in sql, which i return in a data reader, I then run this through the following:
XmlDocument doc = new XmlDocument();
doc.LoadXml(rdr.GetString(0));
en.Add(JsonConvert.SerializeXmlNode(doc));
en is a List as there could be many rows returns. the JSON that is created is as follows with real data modified but the structure intact:
"{\"Entity\":{\"#xmlns:xsd\":\"http://www.w3.org/2001/XMLSchema\",\"#xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\",\"AKA\":{\"string\":[\"Name 1\",\"Name 2\"]},\"Countries\":{\"string\":[\"UK\",\"US\"]},\"IdentNumbers\":{\"string\":[\"Date Set 2\",\"Data Set 1\",\"Data Set 3\",\"Data Set 4\"]},\"PercentageMatch\":\"94\"}}"
So if there were 3 entries then msg.d would contain three values as can be seen from FireBug output below
How do i loop through this information on the client side, and present it in a table?
EDIT
So for the table layout. Any single item needs to have a heading and its associated value, for any items that have one or more value, then i need the table to have a single heading with each item on a new line. Something similiar to this:
Heading 1
Single Item Value
Heading 2
First Item Value \n
Second Item Value
Heading 2
Single Item Value
EDIT
Ok, kind of getting to where I want it. i've produced this:
success: function (msg) {
var resultHtml = "";
$.each(msg.d, function (i, entity) {
//now entity will contain one row of data - you could access the following objects :
//entity.AKA is an array with which you could loop with
resultHtml += '<label><b>Countries:</b></label>';
resultHtml += '<text>' + entity.Countries + '</text>';
resultHtml += '<label><b>Ident:</b></label>';
resultHtml += '<text>' + entity.IdentNumbers + '</text>';
//etc
});
Which produces the output of heading in bold with the value underneath. What I know need to work out, is how to only show one instance at a time, and have pages to move through :-) Any Idea?
using $.each, maybe? Here's the syntax :
$.each(msg.d, function(i, entity) {
//now entity will contain one row of data - you could access the following objects :
//entity.AKA is an array with which you could loop with
//entity.Countries
//entity.IdentNumbers
//etc
});
Then you could construct that table in your each loop. If you give me more info on how you'd want to set up your table (the format), we could help you on that.
Here's a fiddle for you. Resize the output window and check the table : http://jsfiddle.net/hungerpain/9KBDg/

Saving a reordered List item from an MVC View Model

I have a view model which binds to a 'TreasureHuntDetails' object, which contains a list of clues. Here's part of the data model for it.
public TreasureHuntDetails()
{
Clues = new List<Clue>();
}
[Key]
public int TreasureHuntId { get; set; }
public List<Clue> Clues { get; set; }
On the page, I have a table. A foreach loop iterates through the list of clues to add them to the table, e.g.
#for (int i = 0; i < Model.Clues.Count; i++)
The table elements inside the for loop are quite large, but here's an example of one of the table element columns:
<td>#Html.DisplayFor(m => Model.Clues[i].Location)</td>
All well and good so far. Then I'm using JQuery UI to allow the items of the table to be reordered using drag and drop, like this:
<script type="text/javascript">
$(document).ready(function()
{
$("#clueTable tbody").sortable().disableSelection();
});
</script>
All well and good, I can drag and drop the elements.
The problem is that I don't know how to save the new order of elements and save them back to the database.
The first thing I tried was simply passing the list of clues to a controller method, but I found that once the list of clues reached the controller method, it was always null.
For example:
#Url.Action("ViewCluePage", #Model.Clues)
Even if I send the whole #Model, list of clues within is always null. Removing the new list instantiation from the constructor of the data model didn't solve this problem.
Another thing I tried was wrapping the whole table into a HTML form, but still the list of clues remains null.
So basically, this question is really two questions:
1) Why is the list of clues always null after sending the model object to a controller.
2) How to save the new order of the list of items?
UPDATE: As per suggestion by #recursive, I see where I made an error when trying to submit the clue elements to the HTML form.
I used this outside the for loop which iterated over the clue elements:
#Html.HiddenFor(m => m.Clues)
I had to add the HiddenFor lines inside of the for loop (for each clue item), and for EACH property of the clue item, e.g.
#Html.HiddenFor(m => m.Clues[i].Id)
So that would be one step forward to be able to get the list items sent to the controller, but I think I still need code that will reflect the new order of the clue items when sent to the controller. Currently, on rearranging the order of the elements on screen using the JQuery sortable() method, this doesn't change the order of the elements as they are stored in the data model binded to the view (#Model.Clues).
1) As #resursive said in his comment, you need to have hidden elements on the page that map to properties in your Clue class.
2) As for persisting the order of clues, you'll need to add a column to your database that holds the position of each clue in the list and add the position property to your class. So your class would need to include
public int Position {get;set;}
which should pull from the database when the page is created. Then just before rendering the page, you should reorder the clue list based on the Position variable.
Edit: Use jquery's sortable attribute. Check out this thread for reference. In the stop drag event (or right before your submit), loop through each of your draggable objects and set the value of each of the hidden Position properties of your objects.
var positionIndex = 0;
$('.draggableObjectClass).each(function () {
$(this).find('input[id$=_Position]').val(positionIndex++);
});
but I think I still need code that will reflect the new order of the clue items when sent to the controller.
You won't, as you are now iterating over them in a for loop, they will be indexed in the order that you sent them to the view. Your order must already be maintained.
Taking advice from the answers posted here already, I came up with the following solution.
With already having this method in place to implement the drag and drop reordering of the UI elements,
$(document).ready(function()
{
$("#clueTable tbody").sortable().disableSelection();
});
I needed a way to be able read the in the new order of items and send it to the MVC controller. To do this I used the Razor #Html.AttributeEncode method to write the Id's of each item to a column on each row of the table, like this:
<td class="Ids" id="#Html.AttributeEncode(Model.Clues[i].Id)">#{var number = i + 1; #number}</td>
(This is wrapped around a for loop which iterates through the list of items.)
Then, I created the following Javascript function, which is invoked from a 'SaveNewOrder' button I placed above my table of elements (the user presses this once they have finished reordering the items on the table):
function getNewOrder()
{
var positions = new Array();
for (var i = 0; i < $('.Ids').length; i++)
{
positions[i] = $('.Ids')[i].id;
}
$.ajax(
{
type: "POST",
url: "#Url.Action("ReorderClues", "Clues")",
data:{ treasureHuntDetails: $("form").serialize(), ids: JSON.stringify(positions) }
contentType:'application/json'
}).done(function()
{
window.location.href = '#Url.Action("Clues", Model)';
}).
}
What this is does is reads the Id elements from each of the table items, and writes them into the array - so this array contains the NEW order of Id's. The data model containing the items doesn't change after reordering the table elements, hence why this was necessary.
It then uses a JQuery Ajax method to invoke a 'ReOrderClues' method on my 'Clues' MVC controller, passing a serialised version of the data model (containing a list of the clue items in the original order) and an array containing a list of the clue Id's in the new order. When the result is returned from the controller (.done), I invoke a controller which refreshes the page elements.
So rather than having to maintain a position value associated with each clue (which would involve significant refactoring elsewhere in the code), what I'm doing is swapping the contents of the clues around to reflect the new order, but keeping the Id's in the same position.
This is how I achieved that using an MVC Controller:
public ActionResult ReorderClues(TreasureHuntDetails treasureHuntDetails, int[] ids)
{
using (var db = new TreasureHuntDB())
{
var clues = treasureHuntDetails.Clues;
var newClues = NewOrderList(clues, ids);
// Save the changes of each clue
for (var i = 0; i < newClues.Count;i++ )
{
db.Entry(clues[i]).CurrentValues.SetValues(newClues[i]);
db.SaveChanges();
}
treasureHuntDetails.Clues = newClues;
TempData["Success"] = "Clues reordered";
}
return RedirectToAction("Clues", treasureHuntDetails);
}
public List<Clue> NewOrderList(List<Clue> clues, int[] ids)
{
var newClueOrder = new List<Clue>();
// For each ID in the given order
for (var i = 0; i < ids.Length; i++)
{
// Get the original clue that matches the given ID
var clue = clues.First(clue1 => clue1.Id == ids[i]);
var newClue = Clue.Clone(clue);
// Add the clue to the new list.
newClueOrder.Add(newClue);
// Retain the ID of the clue
newClueOrder[i].Id = clues[newClueOrder.Count - 1].Id;
}
return newClueOrder;
}
In the above code snippet, TreasureHuntDB is my Entity Framework database context.

MVC 2: How can I create links from Dropdown list options available when list datasource is a database

So I have multiple lists such as this one:
<%: Html.DropDownList("CPUList", new SelectList((IEnumerable)ViewData["CPUList"], "Price", "Name"))%>
The datasource is a LinQ to SQL *.dbml model
Controller assigns data to the ViewData as such, filtering results on string value "platform":
if (platform == "i7)
{
var processor = from prod in _ctx.Products
where prod.Attributes == 1366"
select prod;
var ram = from prod in _ctx.Products
where prod.Attributes == "TripleChannel"
select prod;
ViewData["CPUList"] = processor;
ViewData["RAMList"] = ram;
}
Basically I am attempting a PC customization page and I ideally would
like people to be able to click on their selected option like a link
to open a new small window with a detailed description of the component selected.
I already have a view that takes a productID as a parameter and basically
displays a long description (prod.LongDesc) for anyone particualr product.
Except I don't know how I go about creating the dropdown list of
links foreach available option/name and create the correct url that will open in a new window.
It's my first week of programming basically, so if you believe I am going a totally wrong way about implementing this function do let me know, seems to work great so far though populating the list as required according to parameters.
In your DropDownList I would rather use the product id as the value, this way you could just do an ajax request on that product id to get more information about it.
So you could have an Action that looks somewhat like the following:
public ActionResult GetRam(Guid productId)
{
var cpu = Products.First(x => x.Id == productId);
switch (cpu.Attribute)
{
case "1366":
ViewData["Ram"] = Products.Where(x => x.Attribute == "TrippleChannel").ToArray();
break;
case "1155":
ViewData["Ram"] = Products.Where(x => x.Attribute == "DualChannel").ToArray();
break;
}
return PartialView("_RamList");
}
However you might want to consider having a relationship between what parts are compatible with each other. But for now, consider the above action to make it easy.
If you have the product id as value in your dropdown you could simply do something like this using jQuery:
<script>
$(document).ready(function () {
$("#CPUList").change(function () {
$.ajax({
url: '/Product/GetRam/' + $(this).val(),
dataType: 'html',
success: function (data) {
$('#RAMListPlaceHolder').html(data);
});
});
});
</script>
Where RAMListPlaceHolder is a div where you will put the html from the partial view _RamList

Categories