To have a bindable checkbox-list in iOS with MvvmCross, I created a view-class that holds a checkbox element, that is bound to the item viewmodel:
public class CheckListItemView : BaseView
{
public CheckListItemView()
{
var set = this.CreateBindingSet<CheckListItemView, CheckListItemViewModel>();
var item = new CheckboxElement();
set.Bind(item).For(v => v.Value).To(vm => vm.IsChecked).TwoWay();
set.Apply();
this.CheckboxElement = item;
}
public new CheckListItemViewModel ViewModel
{
get { return base.ViewModel.As<CheckListItemViewModel>(); }
set { base.ViewModel = value; }
}
public Element CheckboxElement { get; set; }
}
Unfortunately, in this case the binding is not too reliable: I have a button that checks all items checked and one that un-checks all boxes.
foreach (var checkListItemViewModel in checkList)
{
checkListItemViewModel.IsChecked = value;
}
If I keep pressing those two buttons for a time, at some point the UI does not update the checked state correctly. The values in the background are correct, though.
Related
I need to generate a list of years to select from, with the current year being selected by default. Nothing complex. I'm setting the first item in the list as "selected". The list shows the first item has Selected = true when stepping through the code, but when the view loads, the current year is not selected, and the attribute isn't added to the HTML. Here's the code to generate the year list:
public int CurrentYear { get { return DateTime.Now.Year; } }
public int FiscalYear { get; set; }
public List<SelectListItem> FiscalYearList
{
get
{
var yearList = new List<SelectListItem>();
for (var i = 0; i <= 5; i++)
{
yearList.Add(new SelectListItem
{
Text = (CurrentYear - i).ToString(),
Value = (CurrentYear - i).ToString(),
Selected = (i == 0)
});
}
return yearList;
}
}
}
And here is the cshtml code:
<select id="FiscalYearList" asp-for="FiscalYear" asp-items="Model.FiscalYearList" class="form-control">
<option>---Select---</option>
</select>
The answer to this was unintuitive. I set the "default" selected year as the value of "asp-for", and got rid of the manual setting of the selected item value.
public int FiscalYear { get { return DateTime.Now.Year; } }
public List<SelectListItem> FiscalYearList
{
get
{
var yearList = new List<SelectListItem>();
for (var i = 0; i <= 5; i++)
{
yearList.Add(new SelectListItem
{
Text = (FiscalYear - i).ToString(),
Value = (FiscalYear - i).ToString()
});
}
return yearList;
}
}
<select id="FiscalYearList" asp-for="FiscalYear" asp-items="Model.FiscalYearList" class="form-control">
<option>---Select---</option>
</select>
This set the value of "FiscalYear" as the selected item in the drop-down. So setting this value explicitly in code was ignored, but it works like this. Strange.
I have a button to change my mvxspinner, but in my view model I can't change the value displayed in my mvxspinner. For Example :
I have the mvxspinner and button in a layout :
<MvxSpinner
style="#style/SpinnerField.Filter"
android:id="#+id/filter_listacliente_spinner_bairro"
local:MvxBind="ItemsSource ListaBairro;SelectedItem FiltroBairro;"
local:MvxItemTemplate="#layout/filter_spinner_bairro"
local:MvxDropDownItemTemplate="#layout/filter_item_spinner_bairro" />
<Button
android:id="#+id/filter_listacliente_btlimpar"
android:theme="#style/DefaultButton"
local:MvxBind="Click LimparFiltros"
style="#style/DefaultButtonFilter"/>
In my ICommand, I tried changing my value to "Estado" and ID to "-1":
public ICommand LimparFiltros
{
get
{
return new MvxCommand(() =>
{
FiltroEstado = new EstadoDto { IdEstado = -1, Descricao = "Estado" };
});
}
}
My SelectedItem :
var _filtroEstado = new EstadoDto();
public EstadoDto FiltroEstado
{
get { return _filtroEstado; }
set
{
_filtroEstado = value;
RaisePropertyChanged(() => FiltroEstado);
}
}
Result for this: Nothing change in my display :(.
Expected result: My display have a description "Estado" selected
You are addressing the backing field _filtroEstado which will not cause the RaisePropertyChanged in your FiltroEstado property set to fire an INotifyPropertyChanged event. So the view with never get notified of the change.
return new MvxCommand(() =>
{
_filtroEstado = new EstadoDto { IdEstado = -1, Descricao = "Estado" };
});
Additionally, the reference for the SelectedItem needs to come form the bound ItemsSource list. Therefore, you could do something like:
return new MvxCommand(() =>
{
FiltroBairro = ListaBairro.FirstOrDefault(x => x.IdEstado == -1);
});
You're setting your private variable, _filtroEstado, inside LimparFiltros. As a result, the set on the public FiltroEstado property isn't firing, and neither is the RaisePropertyChanged.
The code I have below works. It's pulls in a list of every item in my One Repository.
When I add my second table to pull all the items out of THAT table I get the following error, on my DataTwo I can't figure out why it's throwing this error as the first one is programmed the exact same way.
"A specified Include path is not valid. The EntityType does not declare a navigation property with the name"
View Model
public IList<OneVM> Ones { get; set; }
public IList<TwoVM> Twos { get; set; }
public ViewModelVM()
{
this.Ones = new List<OneVM>();
this.Twos = new List<TwoVM>();
}
Working Original Code Below (Controller)
public ActionResult Directory()
{
var vm = new ViewModelVM();
var datas = _OneRepository.GetData();
vm.Datas = _mapper.Map<IList<DataVM>>(datas.OrderBy(i => i.Name));
return View(vm);
}
Desired Broken Code Below (Controller)
public ActionResult Directory()
{
var vm = new FormDirectoryVM();
var datas = _OneRepository.GetData();
var datasTwo= _TwoRepository.GetMoreData();
vm.Datas = _mapper.Map<IList<DataVM>>(datas.OrderBy(i => i.Name));
return View(vm);
vm.DatasTwo= _mapper.Map<IList<DataTwoVM>>(datasTwo);
return View(vm);
}
The problem was my Repository. I was including something that didn't need to be.
public IEnumerable<Two> GetMoreData()
{
return _context.Twos
.Include(i => i.Title) // I don't need this line
.Include(i => i.Description) // I don't need this line either
.Include(i => i.Keywords)
.Include(j => j.Text) // Or this Line
.Where(i => !i.IsDeleted)
;
}
I have three dropdown list:
#Html.DropDownListFor(m => m.Edit.SelectCountryId, Model.Edit.Countries, "Please select", new { id = "CountryID", #class = "form-control", #onchange = "LoadRegions();" })
#Html.DropDownListFor(m => m.Edit.SelectRegionId,Model.Edit.Region, "Please select", new { id = "RegionID", #class = "form-control", #onchange = "LoadCities();" })
#Html.DropDownListFor(m => m.Edit.SelectCityId, Model.Edit.City, "Please select", new { id = "CityID", #class = "form-control" })
I populate second one depending on first one and third depending on second...but when i want to display those dropdown lists for some specific user im able to display just countries
My model:
public int SelectCountryId { get; set; }
public int SelectRegionId { get; set; }
public int SelectCityId {get; set;}
public System.Web.Mvc.SelectList City { get; set; }
public System.Web.Mvc.SelectList Region{ get; set; }
public System.Web.Mvc.SelectList Country{ get; set; }
My controller:
model.Edit.SelectCountryId = user.ContactInformation.First().City.Region.Country.Id;
model.Edit.SelectRegionId = user.ContactInformation.First().City.Region.Id;
model.Edit.SelectCityId = user.ContactInformation.First().City.Id;
I get values on controller but i cant get display them in view
EDIT:
My scripts:
var region = $('#RegionID');
var urlCountries = "/Account/GetRegions";
$.getJSON(urlCountries, { countryId: $('#CountryID').val() }, function (response) {
// clear and add default (null) option
region.empty().append($('<option></option>').val('').text('Please select'));
for (var i = 0; i < response.length; i++) {
region.append($('<option></option>').val(response[i].Id).text(response[i].Name));
}
});
var city = $('#CityID');
var url = "/Account/GetCities"; // use the helper (dont hard code)
$.getJSON(url, { regionId: $('#RegionID').val() }, function (response) {
// clear and add default (null) option
city.empty().append($('<option></option>').val('').text('Please select'));
for (var i = 0; i < response.length; i++) {
city.append($('<option></option>').val(response[i].Id).text(response[i].Name));
}
});
My methods for GetCity() and GetRegions()
public IEnumerable<IRegion> GetRegions(int countryId)
{
Model.Administration.Region[] modelRegions = Model.Administration.Region.FindAll(DetachedCriteria.For<Model.Administration.Region>().Add(Restrictions.Eq("Country.Id", countryId)));
return modelRegions.Select(Region.ConvertFromModel).Cast<IRegion>().ToList();
}
public IEnumerable<ICity> GetCities(int regionId)
{
CityLimited[] modelCities = CityLimited.FindAll(DetachedCriteria.For<CityLimited>().Add(Restrictions.Eq("Region.Id", regionId)));
return modelCities.Select(City.ConvertFromModel).Cast<ICity>().ToList();
}
You need to pass an additional value indicating the selected option in the json data your returning to the view. Your GetRegions() method should look like
public JsonResult GetRegions(int countryId) // return a JsonResult
{
Model.Administration.Region[] modelRegions = Model.Administration.Region.FindAll(DetachedCriteria.For<Model.Administration.Region>().Add(Restrictions.Eq("Country.Id", countryId)));
var options = modelRegions.Select(Region.ConvertFromModel).Cast<IRegion>().ToList();
var selection = 1; // set the value of the selected option here - must match the `ID` property of one of one of the `IRegion` your returning
return Json(new { options = options, selection = selection }, JsonRequestBehavior.AllowGet);
}
Side note: Its not clear what modelRegions.Select(Region.ConvertFromModel).Cast<IRegion>().ToList(); returns but if IRegion contains more that just the ID and Name properties you need for the view, consider creating a collection of anonymous objects so you don't send unnecessary data across the wire
Then the corresponding script should be
var url = '#Url.Action("GetRegions", "Account")'; // use the helper (dont hard code)
var regions = $('#RegionID'); // cache the element
$('#CountryID').change(function() {
$.getJSON(url, { id: $(this).val() }, function(response) {
// clear and add default (null) option
regions.empty().append($('<option></option>').val('').text('Please select'));
$.each(response.options, function(index, item) {
cities.append($('<option></option>').val(item.Value).text(item.Text));
});
regions.val(response.selection); // set the selected value
});
});
and remove the #onchange = "LoadRegions(); from you markup
Ditto for the GetCities() controller method and the corresponding script
When you create a SelectList you can optionally pass in the SelectedValue property for which the documentation says
// selectedValue:
// The selected value. Used to match the Selected property of the corresponding
// System.Web.Mvc.SelectListItem.
However, if you pass it a value object which is not contained in the list of items, it still sets the selected value. Try this:
using System.Web.Mvc;
class SomeItem
{
public int id { get; set; }
public string text { get; set; }
}
class CreateSelectList
{
public static SelectList CreateSelectList()
{
List<SomeItem> items = new List<SomeItem>();
for (int i = 0; i < 3; i++)
{
items.Add(new SomeItem() { id = i, text = i.ToString() });
}
// 5 is not in the list of items yet the property SelectedValue does = 5
return new SelectList(items, "id", "text", 5);
}
}
My questions are:
Since I want to lazily set my selected value only if it exists, I just want to pass in a value and have it ignored when it does not exist in the list, but how? (is this a bug or a design feature), or
If you create a SelectList without the SelectedValue, after you have constructed it, how can you set the SelectedValue (again when it exists in the list) ?
If your code is near to your real scenario, you could use something like this
// check if there is any element with id = 5
if (items.Any(i => i.id == 5))
{
// there is an element with id = 5 so I set the selected value
return new SelectList(items, "id", "text", 5);
}
else
{
// there is no element with id = 5 so I don't set the selected value
return new SelectList(items, "id", "text");
}