I need to be able to edit a table of data in the browser.
I have seen in MVCContrib there is a HTML helper to render out a table. Useful... but what about if I want the user to be able to edit that table? From what I can see it does not help there.
What is the best way to approach this?
Traditional FORM with a TABLE inside? If so is MVC smart enough to parse the posted data back into a collection of rows? How would that work anyway?
Or perhaps it should just switch to edit mode when a row is clicked (using javascript etc) then when user moves to a different row an AJAX action is called to submit just the one row. I can imagine the logic could get complex here - this would presumably still use a form but would I have to insert it into the DOM dynamically?
I also need to be able to add rows to this table. I do not require paging support.
Is there an off the shelf solution out there?
Should I go back to web forms? :)
Take a look at Phil Haack's blog where he describes how to model bind to a list.
Maybe this can help?
I've got the same problem, and I have found a solution for it. Don't think it's the most beautiful, but it's ideal for me, because one of my requirements was be able to edit table data using jQuery's Jeditable plugin.
So I generate a table using MVCContrib's Grid<> extension:
Html.Grid<Somenamespace.Line>( Model.InvoiceLines )
.Attributes( id => "InvoiceGrid" )
.Columns( column => {
column.For( li => li.LineItem.ItemDescription ).Attributes( name => ".LineItem.ItemDescription", #class => "click" );
column.For( li => li.LineItem.InvoiceUnitNetPrice ).Named( "Unit net price " ).Attributes( name => ".LineItem.InvoiceUnitNetPrice", #class => "click" );
column.For( li => li.LineItem.InvoiceQuantity ).Attributes( name => ".LineItem.InvoiceQuantity", #class => "click" );
})
.Render();
//rest of the code
Html.Submit("_submit", "Save");
Right now You can edit in place values, but it doesn't upgrade corresponding model.
All the magic happens after user clicks submit button:
$(document).ready(function() {
$('#_submit').click(function(e) {
e.preventDefault();
$('#InvoiceGrid tbody tr').each(function(index) {
var hidden = $('<input />').attr({ type: 'hidden', name: 'InvoiceLines.Index', value: index });
$(this).children('td:first-child').before(hidden);
$(this).children('td:not(:first-child)').each(function() {
$(this).append($('<input />').attr({ type: 'hidden', value: $(this).text(), name: 'InvoiceLines[' + index + ']' + $(this).attr('name') }));
});
});
$('form').submit();
});
//editable stuff
$('.click').editable(function(value, settings) {
return (value);
}, { submit: 'OK' });
});
In every TD I create hidden input, with value from that TD, in every row input with Index, and the most important here is 'name' attribute: Name of collection in Model[here goes index].rest.of.path, so in this particular case (example):
InvoiceLines[2].LineItem.ItemDescription
Hope it'll help, because rich grid isn't always an answer ;)
Regards
Mateusz
I would checkout one of the javascript UI libraries first:
ExtJS Grid
Yahoo DataTable
Flexigrid
WebForms are easier when it comes to quickly developing rich UI's like editable grids.
Last night I implemented a simple solution: form + table inside, using input fields in the cells with naming convention as described in Phil Haack's blog (thanks to #BengtBe for link).
It's working but its a bit fiddly (e.g. adding rows with jquery requires me to work out the next unused index).
So I am still looking for more solutions.
One I have discovered is the extjs library which provides a very rich grid. I have yet to work out whether there is an easy way to post back the grid data to one of my controller actions yet though...
Related
I am trying to set up a table in an index view of my "relations" controller including an editable dataslider which directly safes the "preference" value to my db.
Is it possible to combine bootstrap dataslider with "best_in_place" or form method or is that nonsense? (I am newbie on ruby on rails) Does anybody have any suggestions ? Thanks for your help!
I am using "bootstrap slider rails"
Doing what you want through forms may be a little challenging - you'll have to nest each one in a form, ensure that submitting the form doesn't reload the page, etc.
The easiest thing to do might be to pass an onChange function to your data slider that posts the data to the backend. Something along the lines of this (I don't know the data-slider component so I'm sort of guessing as to syntax):
html:
<input id="slider" />
js:
$("#slider").slider().change(function(data) {
$.post({ url: "my/url",
success: function(response) { ... },
...
});
});
I have used the "select_tag" for a multi-select field:
%td
=select_tag('cars',options_from_collection_for_select(#cars, 'id', 'name'), {:multiple=>true,:size => 10})
It is fine, BUT, after user selected the options, I would like to have the feature that "After selection, if user click somewhere else all the selected options are return back to unselected", that's the multi-select field is back to unselected.
How to do this if I use select_tag??
First–off, this question has nothing to do with Rails or your usage of the select tag.
You can handle this on the client side with a sprinkle of Javascript — here's an example that uses jQuery:
$('select').blur(function(){
$('option', this).attr('selected', false);
});
// This seems to work as well, haven't tested in anyhow further, though
$('select').blur(function(){
$(this).val('');
});
You can see it in action here: http://www.jsfiddle.net/jZNAn/1
Say I have a list of users in the left column, generated by <%= current_user.clients %> and the second column is empty by default.
However, when the user clicks on one of the links, the second column becomes populated with the projects associated with that user - without the entire page being reloaded (i.e. using AJAX).
I would also like to continue the process, so when they click on a project from that user, the third column is populated with other things (e.g. the name of images, etc.).
How do I accomplish this in Rails?
I assume you are using Rails 3 and jQuery (I'm not well-versed in prototype). It's easy to switch jQuery for prototype in Rails 3: https://github.com/rails/jquery-ujs
For the link:
Something
Using JavaScript and jQuery, write a function that sucks in links of class first_column_link (please rename to something more reasonable, by the way):
$(function() {
$('.first_column_link').bind('click', function() {
$.getJSON('/clients/' + $(this).attr('data-client-id'), function(data) {
// Populate the second column using the response in data
});
});
});
This doesn't work on browsers that don't support or have otherwise disabled JavaScript. Gracefully degrading would likely be a good idea, but without more context, I can't advise you how to best do it.
<%= link_to_remote current_user.clients, go_to_controller_path %>
Proceed from there.
go_to_controller_path routes to an action which renders javascript to update the 2nd column (probably with a partial).
I'm working with ASP.NET MVC 2 and building a simple business app. Here are some of the details:
The app deals with work orders and
has a work order index view. The
view has a table listing the work
orders, and several controls (text
boxes, check boxes, and drop down
lists) to select the criteria for
which work orders to display.
I'm using viewmodels. The work order
index view has a viewmodel with
properties for each and every
control.
I've implemented paging similar to
what is being done in the answer to
this question:
How do I do pagination in ASP.NET MVC?
I'm using LINQ's Skip() and Take() as
demonstrated, and ActionLinks for the
navigation.
If I load the page and don't
manipulate any of the controls, I can
click on the page number ActionLinks
and move around just fine between
pages of work orders. However, if I
change something, my changes are lost
when I navigate to another page.
For example, if I'm on page 1 and
click an unchecked check box, and
then click on the link for page 2,
the second page of results will load
but the check box will revert to its
previous state.
I understand why this happens, but I'm wondering what is the best thing to do from a design standpoint.
Potential solutions I can think of:
Set all the control values as route
values in the ActionLinks. This
seems really nasty, and could result
in very long URLs or query strings. Actually, now that I think of it this wouldn't work without a way to capture the control values.
Since ActionLinks don't post
anything, replace them with buttons.
Again, this seems like a bad idea.
Change the ActionLinks to links that
fire off a jQuery script that does a
POST. I think this is the most
promising option so far. Do many
developers do it this way?
This seems like a common enough problem, but none of these options feel quite right. I wonder if I'm missing something.
Can't you just save the changes back to the database when the user toggles the checkboxes (using jQuery):
$("input[type=checkbox]").click(function() {
$.ajax({
type: "POST",
url: "/ControllerName/SaveInfo?id=" + {id},
success: function(){
alert("Data Saved: " + msg);
}
});
});
In the end, I wound up getting rid of the ActionLinks for the paging, and replaced them with regular anchor tags. The current page index is now stored in a hidden form value:
<input id="page" name="page" type="hidden" value="" />
<p>
<% for (var i = 1; i <= (int)Math.Ceiling(Model.RowsMatchingCriteria / (double)Model.PageSize); i++) { %>
<%--
If the page number link being rendered is the current page, don't add the href attribute.
That makes the link non-clickable.
--%>
<a class="pageLink" <%= i != Model.Page ? #"href=""javascript:void(0);""" : string.Empty %>><%: i %></a>
<% } %>
</p>
Then I added the following jQuery script, which sets the hidden page value and submits the form when a link is clicked:
$(document).ready(function () {
$('.pageLink:[href]').click(function () {
$('#page').val($(this).text()); // Set hidden field value to the text of the page link, which is the page number.
$('form:first').submit();
});
});
Problem solved.
Best bet is to effectively simulate viewstate by "logging" the changes to a hidden field when a user paginates. To do so:
1) Figure out what data you need to capture and a data format to do so in {ie -- an array of json objects}
2) Setup the link that handles the prev/next to fire off a method to collect the "changed" things and stuff them into objects and into a hidden field.
3) When posting the form, parse the hidden field, extract data and profit.
in Telerik ASP.NET MVC TabStrip, I want the page to remember which tab was selected last and persist the selection through multiple page requests.
What I have is a partial view that shows in multiple pages and it contains the TabStrip. With SelectedIndex the set tab always get selected, which nullifies user's selection.
I couldn't find any official way of doing this through the Telerik APIs, nor any useful advice on their forum, so I decided to go it my own way with the use of:
Html.Telerik().TabStrip().ClientEvents() both the OnSelect() and OnLoad()
The cookie plugin for jQuery
Then I wired them up as below, in the partial view that contains the TabStrip.
.ClientEvents(events => events
.OnSelect(() =>
{
%>
function(e) {
var item = $(e.item);
$.cookie('selectedTabIndex', item.index(), { path: '/' });
}
<%
})
.OnLoad(() =>
{
%>
function(e) {
var tabStrip = $("#TabStrip").data("tTabStrip");
var index = $.cookie('selectedTabIndex');
var domElement = $("li", tabStrip.element)[index];
tabStrip.select(domElement);
}
<%
})
)
Edit: I realised that my answer was little bit lacking in explanation so I've added:
In case it's not obvious, the OnSelect
is capturing the index of the selected
tab when it is selected and writing
that to a cookie called
selectedTabIndex. The path is being
set so it will cover our whole site,
but if you leave that out it will
create a new cookie for each different
path (which may be your desired
behaviour). Someone more familiar with
the jQuery cookie plugin please
correct me if I'm wrong there, I
haven't used it much.
Then in the OnLoad it's doing the
opposite, basically. It finds the
tabStrip, gets the index from the
cookie, then gets the domElement of
the tab at the index from the cookie
and tells the tabStrip to select that
domElement.
This seems to work pretty well in Chrome and IE, but there may be some quirks in FFox 3.
I hope the Telerik team considers adding this to their API, as it strikes me as being a pretty useful feature to have baked-in. Apologies if it already is, but I couldn't find it in the docs.