Binding and accessing dynamic combobox/listbox collection in caliburn.micro - binding

I'm using Caliburn Micro MVVM. I want to make category selection usercontrol consisting of few dynamic comboboxes (or listboxes) based on generic tree collection. User must choose any leaf node from category tree, so new collections will keep appearing as long as selected node has children beneath it. Depth may vary.
I want it to look like this: http://i.imgur.com/c2uzv.png
...and so far it looks like this:
CategorySelectorModel.cs:
public BindableCollection<BindableCollection<Category>> Comboboxes { get; set; }
CategorySelector.xaml:
<ItemsControl x:Name="Comboboxes">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding}" DisplayMemberPath="Name"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
So there's my question: Would it be possible to specify an event for each created combobox and access its SelectedItem property?

It was easier than I expected. My question was pretty unfortunate from this point. I started with this:
<ComboBox ItemsSource="{Binding}" DisplayMemberPath="Name" cal:Message.Attach="CategoryChanged($this.SelectedItem)"/>
Every node of my category tree has a Depth property. Since depth of last selected element is related to number of collections, I just used this property to remove all unnecessary collections when any selected item has changed.
public void CategoryChanged(object selected)
{
int depth = 0;
var newcombobox = new BindableCollection<Category>();
foreach (var node in _tree.All.Nodes)
{
if (node.Data.Equals(selected))
{
foreach (var category in node.DirectChildren.Values)
{
newcombobox.Add(category);
}
depth = node.Depth;
}
}
if (newcombobox.Count > 0)
{
Comboboxes.Add(newcombobox);
}
RemoveFollowing(Comboboxes, depth);
}

Related

How to fix object set in grid?

In my application i have a class like:
public class Team {
private Country teamId;
private Set<Player> playerSet;
private Set<Player> substitutes;
private Set<Coach> coachSet;
}
When i instantiate a grid like:
Grid<Team> grid = new Grid<>(Team.class);
and set allTeam() from database it shows object for playerSet and coachSet.
My question is i just want to show players name and coach name concate by ,or \n.
Any idea how can i do that?As a beginner it is complicated for me
I see three options.
The first option is the one you already found yourself: concatenate their names in a single String. This can be done like this:
grid.addColumn(team -> {
Set<String> coachNames = new HashSet<>();
for (Coach coach : team.getCoaches()){
coachNames.add(coach.getName());
}
return String.join(", ", coachNames);
});
The second one would be to make use of the Grid item Detail - you could show a coaches grid in the item details. Since you want to display both coaches and players, this option is probably not the best but I wanted to mention the possibility. (Placing two grids inside the item details is possible, but quite strange. Not optimal user experience.)
grid.setItemDetailsRenderer(new ComponentRenderer<>(team -> {
Grid<Coach> coachGrid = new Grid<>(Coach.class);
coachGrid.setItems(team.getCoaches());
return coachGrid;
}));
A third option would be to have the team grid on one side of the view, and on the other you show some relevant stuff of the selected item of the team grid. You can have a separate Grid for the coaches, one for the players, one for the substitutes. You could implement this team detail layout also as a separate view if you wish. If your Team object will get more complicated with more sets, collections and other relative properties, the more will this option become appealing, as this is quite scalable/expandable.
grid.addSelectionListener(event -> {
if(event.getFirstSelectedItem().isPresent()){
buildTeamDetails(event.getFirstSelectedItem().get())
}
})
private void buildTeamDetails(Team team){
// build your team detail layouts here
}
You can configure which columns are shown in the grid by using grid.removeAllColumns() and then adding all columns you want to have in the grid with grid.addColumn(). Within addColumn() you can create a renderer that defines how the fields (coachName and playerSet) are displayed in the grid.
Let's have a class Team like
public class Team {
private String coachName;
private Set<Player> playerSet;
private Set<Object> objects;
//getters and setters
}
and a class Player like
public class Player {
private String firstName;
private String lastName;
// getters and setters
}
Now you want to only have coach and player names in the grid. So (in my example) for coachName we can just use the field's getter and we can create a comma separated String for the playerSet with java streams easily.
Configure the grid like:
grid.setItems(team);
grid.removeAllColumns();
grid.addColumn(new TextRenderer<>((ItemLabelGenerator<Team>) Team::getCoachName))
.setHeader("Coach");
grid.addColumn(new TextRenderer<>((ItemLabelGenerator<Team>) team1 -> team1.getPlayerSet().stream()
.map(player1 -> player1.getFirstName() + " " + player1.getLastName())
.collect(Collectors.joining(", "))))
.setHeader("Players")
.setFlexGrow(1);
Then the result looks like:

ASP.NET MVC - Using IF Statement in my Details View

I have this in my Details View
<tr>
<td>#Html.DisplayNameFor(model => model.ACTION_STATUS):</td>
<td>#Html.DisplayFor(model => model.ACTION_STATUS)</td>
</tr>
How do I apply this:
If ACTION_STATUS == , then display Active Else If ACTION_STATUS ==0, display Inactive, Else Deleted
I suggest using Dictionary instead of if conditions. In this case may seem easy doing an if condition but that is not the right way if there are several branches.
Dictionary<int, string> data = new Dictionary<int, string>();
data.Add(1, "Active");
data.Add(0, "Inactive");
data.Add(-1, "Deleted");
ViewData["data"]=data;
In View you can get them:
#{
var Dictionary = (Dictionary<int, string>)ViewData["data"];
}
and display it as
<td>#Dictionary[model.ACTION_STATUS]</td>
I think this solution is cleaner.
Edit: To address the edit to the question.
I think the best approach, especially now that there are 3 different status options not just 2 is to do the following (as I recommended in my original answer).
1) Create a dictionary similar to Indrit Kello's answer, but make it "static data" somewhere in your project. Maybe it's loaded from a database table of action statuses. Maybe it is something that you load from a config file, or maybe it's just hard-coded in a static class called "Constants.cs" etc. Depends on the situation. If it were hard coded, you might have a class like:
public static class Constants
{
public static Dictionary<int, string> ActionStatuses = new Dictionary<int, string>(){
{ 1, "Active" },
{ 0, "Inactive"},
//etc. Additional statuses go here. No comma on the final entry...
};
}
2) Add a new property to your view model to decode the display text for this status. You can decorate this with attributes etc. as you can normal VM properties so you can continue to use #Html.DisplayFor etc,
public class MyViewModel
{
// Other properties etc...
public string ActionStatusDisplayText { get { return Constants.ActionStatuses[this.ACTION_STATUS]}; }
}
3) Use this new property in your view.
<td>#Html.DisplayFor(model => model.ActionStatusDisplayText)</td>
4) If you have to do this lookup in multiple different view models, you might want to consider moving the lookup code into the Constants class so that you can share the same code with multiple places. Especially true if you need to implement hte case where as tatus might not be in the dictionary for some reason (value = -99 for somereason etc.)

Should ViewModels have Count properties to simplify the View logic?

Which approach to ViewModels is better:
1. Just have an ICollection<T> in the ViewModel and access it's properties in views, which is pretty much what I'd do in ASP.NET Forms, like this:
public ICollection<Order> Orders {get; set;}
so that in the View I would do something like this
#if(Model.Orders.Count > 0){
or
2. Create a property for the Count of an ICollection so the View can simply reference that value directly.
public ICollection<Order> Orders { get; set; }
public int OrderCount { get { return Orders.Count ; } }
and then in the view
#if(Model.OrderCount > 0) {
or perhaps a boolean property HasOrders to further reduce the logic in the View?
Edit
I'm a bit surprised by the comments, I accept that this is subjective, but then so are questions about whether to use a string property for a date and everyone has to start learning somewhere.
Will I have numerous uses of the OrderCount property? The if and then a label to display the actual count. As such it will be used more frequently than say the customer email address yet I would be astonished if anyone suggested that
public string Email { get; set; }
was taking things too far.
To try to refocus the question a little; what I'm trying to determine is should the ViewModel provide simple properties for everything the view needs - so there is no need to reach down into the Model.Orders to access the Count. Should the View be kept pure and free from logic / 'programming'
3.) Don't use a Collection<T> on a viewmodel, it's probably overkill. Instead, use T[]. Why? Because you shouldn't need .Add, .Remove, and other overhead methods offered by ICollection for an IEnumerable property in a viewmodel. In the end, if you are just using it as a DTO to pass data from a controller to a view, an array is perfectly fine. Nothing will have to be added to or removed from the enumerable during transit to and from the controller. Arrays are generally faster and leaner than Lists and other IEnumerable implementations.
public Order[] Orders { get; set; }
Then, don't use .Count, use .Length. Having a separate property is usually overkill too IMO. Why? Because it just means you end up writing more code where you don't have to. Why add an OrdersCount property when you can just use Orders.Length?
#if (Model.Orders.Length > 0) {
If you are looking for something a little shorter, you can use the .Any() LINQ extension method (note you will have to have using System.Linq; when using this in a viewmodel class, but nothing extra should be needed to use it in a razor view):
#if (Model.Orders.Any()) { // returns true if Model.Orders.Length > 0
One possible exception to this guideline could be if Orders is not set, meaning it is null. In that case, your razor code above would throw a NullReferenceException. For this you could create a HasOrders property on the viewmodel to test against null and .Length. However a simpler solution could be to just initialize the property in a constructor:
public class MyViewModel
{
public MyViewModel()
{
Orders = new Order[0];
}
public Order[] Orders { get; set; }
}
Granted, with the above someone could still set the array to null, so it's your decision of whether to do this, or create a separate property to test against null, or just test against null in your razor code.
using System.Linq;
public class MyViewModel
{
public Order[] Orders { get; set; }
public bool HasOrders { get { return Orders != null && Orders.Any(); } }
}
...or...
#if (Model.Orders != null && Model.Orders.Any()) {
Any way you go, you end up with a little more code in either the consuming class or the consumed class. Use these factors to decide which approach means less code to write:
a.) Is it possible for the property to be null?
b.) How many collection properties are in the viewmodel?
c.) How many times do you have to test against either null or .Length in a razor view?

Prevent selection of a particular item in spark list

I have a Spark List which has a custom itemRenderer for rendering each item in the List.
I wish to prevent an item in that list from being selected (based on some custom logic) by the user.
What is the best way I can achieve this?
Here's how my List is defined:
<s:List id="myList" itemRenderer="com.sample.MyItemRenderer" />
and of course, I have a item renderer defined as the class com.sample.MyItemRenderer.
The selection of items is handled by the list alone as far as I know, so I would say that you can manage it from there. I would have a field on the Objects that are in the list called "selectable" or something like that and when the list item is changing check to see if the new item is actually selectable and if it isn't then you can either have it clear the selection or reset to the previous selection. You can accomplish that by reacting to the "changing" event on the list component and calling "preventDefault" on the IndexChangeEvent as follows:
protected function myList_changingHandler(event:IndexChangeEvent):void {
var newItem:MyObject = myList.dataProvider.getItemAt(event.newIndex) as MyObject;
if(!newItem.selectable) {
event.preventDefault();
}
}
// Jumping ahead ...
<s:List id="myList" changing="myList_changingHandler(event)" // ... continue implementation
The relevant part of the MyObject class is as follows:
public class MyObject {
private var _selectable:Boolean;
public function MyObject(){
}
public function set selectable(value:Boolean):void {
_selectable = value;
}
public function get selectable():Boolean {
return _selectable;
}
}

Hide column in telerik Grid but need to get it

let's say i have an order and order details.
the view will contains the order fields, and a Telerik Grid for the details
i always maintain a reference of the Order in the session.
Session["Order"] = order;
and when the user add an order detail to the grid, I'm saving it in the Order reference.
public ActionResult Grid_AddDetail(OrderDetail orderDetail) {
(Session["order"] as Order).Details.Add(orderDetail);
}
the problem is when i need to update the row, how can i determine which detail in
Order Details has been updated?
public ActionResult Grid_UpdateDetail(OrderDetail orderDetail) {
///how will i compare the element in the details, with the orderDetail?
(Session["order"] as Order).Details.IndexOf(orderDetail) = orderDetail;
}
the problem can be solved by adding a serial number column, and compare the incoming detail with the existed on in my reference, by overriding the Equal:
public overrid Equal(object obj){
return (obj as OrderDetail).Serial == this.Serial;
}
but i want the serial number column to be invisible, but if i do so, it will not be presented in the incomming detail.
If you just want to make the column invisible, I think this should help:
AutoGenerateColumns="false"
That will force you to generate the columns displaying the information, rather than the gridview automatically creating them for you. So now you will need to do something like this to get the order to display
<asp:TemplateField>
<ItemTemplate>
<b><%# DataBinder.Eval(Container.DataItem, "Order") %>:</b>
</ItemTemplate>
</asp:TemplateField>
EDIT:
To access the Serial Number when it is not visible, you will need to use DataKeys:
orderDetail.DataKeyNames = new string[] { "Serial" };
what I did is:
added a column called Serial
made the column width set to 0.
columns.Bound(m => m.Serial).Title("").Sortable(false).Width(0);
and it will be presented in (insert, update)
but the problem in delete is to make him (as Brett said) as a Datakey.
public ActionResult Grid_AddDetail(OrderDetail orderDetail) {
if ((Session["order"] as Order).Details.Count != 0)
item.Serial= (Session["order"] as Order).Details.Max(d => d.Serial) + 1;
(Session["order"] as Order).Details.Add(orderDetail);
}
public ActionResult Grid_UpdateDetail(OrderDetail orderDetail) {
///order detail now contains the serial number.
(Session["order"] as Order).Details.IndexOf(orderDetail) = orderDetail;
}

Resources