xamarin forms picker binding selected value to NgModel like angular - forms

I have a dynamic model of the user entity with JSON type. I need to show a form to select the value foreach property with dropdownlist then insert to the db. Like a entity of user has property sex and age(property can be edit by endpage by customer) .
the example result like below:
[
{
"bindValue": null,
"title": "sex",
"code":"sex",
"property": {
"option": [
"male",
"female"
]
}
},
{
"bindValue": null,
"code":"grade",
"property": {
"option": [
"2",
"3"
]
}
}
]
with angular ionic project i can code with this:
<ng-container *ngFor="let x of dynamicdroplist">
<ion-item>
<ion-label fixed>{{x.title}}</ion-label>
<ion-select [(ngModel)]="x.bindValue">
<ion-select-option *ngFor="let y of x.property.option" value="{{y}}">{{y}}</ion-select-option>
</ion-select>
</ion-item>
</ng-container>
when user change the select value, it'll bind "ngModel" to x.bindvalue, i can filter the code and the binvalue data to send to api.
But with xamarin forms i don't know how to bind select value to bindvalue
foreach(var item in field){
StackLayout layout = new StackLayout().LoadFromXaml("<StackLayout></StackLayout>");
layout.Children.Add(new Label().LoadFromXaml("<Label Text=\"" + item.Title + "\"></Label>"));
var picker = new Picker { Title = item.Title };
foreach (var e in item.Property.option)
{
picker.Items.Add(e);
}
layout.Children.Add(picker);
_stackLayout.Children.Add(layout);
}
I want to know how can i code this with xamarin forms?

You can bind the Picker.SelectedItem to bindValue in TwoWay, then if user change the select value, the bindValue in the model will update:
picker.BindingContext = item;
picker.SetBinding(Picker.SelectedItemProperty, "bindValue",mode:BindingMode.TwoWay);
Here is the a sample code you can refer:
public partial class MainPage : ContentPage
{
StackLayout _stackLayout;
List<RootObject> field = new List<RootObject>();
public MainPage()
{
InitializeComponent();
_stackLayout = new StackLayout();
field.Add(new RootObject() { bindValue = "", title = "sex", code ="sex", property = new Property() { option = new List<string>() { "male","female"} } });
field.Add(new RootObject() { bindValue = "", title = "grade", code = "grade", property = new Property() { option = new List<string>() { "2", "3" } } });
foreach (RootObject item in field)
{
StackLayout layout = new StackLayout();
layout.Children.Add(new Label() { Text = item.title });
var picker = new Picker { Title = item.title };
//set the BindingContext here
picker.BindingContext = item;
foreach (string e in item.property.option)
{
picker.Items.Add(e);
//bind the value here
picker.SetBinding(Picker.SelectedItemProperty, "bindValue",mode:BindingMode.TwoWay);
}
layout.Children.Add(picker);
picker.SelectedIndexChanged += Picker_SelectedIndexChanged;
_stackLayout.Children.Add(layout);
}
Content = _stackLayout;
}
private void Picker_SelectedIndexChanged(object sender, EventArgs e)
{
//check the value change in field
RootObject r1 = field[0];
Console.WriteLine(r1.bindValue);
RootObject r2 = field[1];
Console.WriteLine(r2.bindValue);
}
}
public class Property
{
public List<string> option { get; set; }
}
public class RootObject
{
public string bindValue { get; set; }
public string title { get; set; }
public string code { get; set; }
public Property property { get; set; }
}
Refer: data-binding

If you need the value programatically, just access it using the picker variable you declared;
picker.SelectedItem
Otherwise if you want to bind it to the UI, you can;
myLabel.SetBinding(Label.TextProperty, new Binding("SelectedItem", source: picker));

Related

How can we pass input1 as a list of objects and check a specific property from all objects?

How can I pass input1 as a List of objects and check specific property not null in all objects of that list in microsoft-RulesEngine https://github.com/microsoft/RulesEngine ? Or we have to pass objects one by one using a loop?
public class TestClass
{
public string Country { get; set; }
public int loyaltyFactor { get; set; }
public int TotalPurchasesToDate { get; set; }
}
List<TestClass> rules = new List<TestClass>() { new TestClass() {
Country = "India"
},
new TestClass() {
Country = "US"
},
new TestClass() {
Country = null
}
};
var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "Test.json", SearchOption.AllDirectories);
if (files == null || files.Length == 0)
throw new Exception("Rules not found.");
var fileData = File.ReadAllText(files[0]);
var workflow = JsonConvert.DeserializeObject<List<Workflow>>(fileData);
var bre = new RulesEngine.RulesEngine(workflow.ToArray(), null);
var inputs = new dynamic[]
{
input1
};
List<RuleResultTree> resultList = bre.ExecuteAllRulesAsync("Test", inputs).Result;
Test.json:
[
{
"WorkflowName": "Discount",
"Rules": [
{
"RuleName": "CountryNotNull",
"ErrorMessage": "Country should be null.",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.country != null"
}
]
}
]
I am not able to create mirosoft-RulesEngine tag so that's why using a random rule-engine tag.

Updating related Phone entities with custom tag helper

As my application currently sits, each AppUser may (or may not) have 3 phone numbers (UserPhones). One of each type (Mobile, Home, Other).
The following Tag Helper works great (Thanks #itminus).
Calling code from Razor Page:
<user-phones phones="#Model.UserPhones"
asp-for="#Model.UserPhones"
prop-name-to-edit="PhoneNumber"
types-to-edit="new EnumPhoneType[] { EnumPhoneType.Mobile,
EnumPhoneType.Other }" />
Code:
public class UserPhonesTagHelper : TagHelper
{
private readonly IHtmlGenerator _htmlGenerator;
private const string ForAttributeName = "asp-for";
[HtmlAttributeName("expression-filter")]
public Func<string, string> ExpressionFilter { get; set; } = e => e;
public List<UserPhones> Phones { get; set; }
public EnumPhoneType[] TypesToEdit { get; set; }
public string PropNameToEdit { get; set; }
[ViewContext]
public ViewContext ViewContext { set; get; }
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; }
public UserPhonesTagHelper(IHtmlGenerator htmlGenerator)
{
_htmlGenerator = htmlGenerator;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = null; //DO NOT WANT AN OUTTER HTML ELEMENT
for (int i = 0; i < Phones.Count(); i++)
{
var props = typeof(UserPhones).GetProperties();
var pType = props.Single(z => z.Name == "Type");
var pTypeVal = pType.GetValue(Phones[i]);
EnumPhoneType eType = (EnumPhoneType) Enum.Parse(typeof(EnumPhoneType), pTypeVal.ToString());
string lVal = null;
switch (eType)
{
case EnumPhoneType.Home:
lVal = "Home Phone";
break;
case EnumPhoneType.Mobile:
lVal = "Mobile Phone";
break;
case EnumPhoneType.Other:
lVal = "Other Phone";
break;
default:
break;
}
//LOOP ALL PROPERTIES
foreach (var pi in props)
{
var v = pi.GetValue(Phones[i]);
var expression = this.ExpressionFilter(For.Name + $"[{i}].{pi.Name}");
var explorer = For.ModelExplorer.GetExplorerForExpression(typeof(IList<UserPhones>), o => v);
//IF REQUESTED TYPE AND PROPERTY SPECIFIED
if (pi.Name.NormalizeString() == PropNameToEdit.NormalizeString() && TypesToEdit.Contains(eType))
{
TagBuilder gridItem = new TagBuilder("div");
gridItem.Attributes.Add("class", "rvt-grid__item");
gridItem.InnerHtml.AppendHtml(BuildLabel(explorer, expression, lVal));
gridItem.InnerHtml.AppendHtml(BuildTextBox(explorer, expression, v.ToString()));
output.Content.AppendHtml(gridItem);
}
else //ADD HIDDEN FIELD SO BOUND PROPERLY
output.Content.AppendHtml(BuildHidden(explorer, expression, v.ToString()));
}
}
}
private TagBuilder BuildTextBox(ModelExplorer explorer, string expression, string v)
{
return _htmlGenerator.GenerateTextBox(ViewContext, explorer, expression, v, null, new { #class = "form-control" });
}
public TagBuilder BuildHidden(ModelExplorer explorer, string expression, string v)
{
return _htmlGenerator.GenerateHidden(ViewContext, explorer, expression, v, false, new { });
}
public TagBuilder BuildLabel(ModelExplorer explorer, string expression, string v)
{
return _htmlGenerator.GenerateLabel(ViewContext, explorer, expression, v, new { });
}
}
My Question:
Lets assume this AppUser only has one related Mobile phone number listed currently. So AppUser.UserPhones (count = 1 of type Mobile). So the code above, as-is, will only render an input for Mobile phone.
Since types-to-edit calls for both Mobile and Other, I want both inputs to be rendered to the screen. And IF the user adds a phone number to the Other input, then it would be saved to the related UserPhones entity on the Razor Pages OnPostAsync method. If the user does NOT provide a number for the "Other" input, then the related UserPhones record of type "Other" should NOT be created.
Can you help?
Thanks again!!!!
TagHelper
As my application currently sits, each AppUser may (or may not) have 3 phone numbers (UserPhones). One of each type (Mobile, Home, Other).
If I understand correctly, an AppUser might have 3 phone numbers and the count of each phone type for every user will be zero or one.
If that's the case, we can simply use PhoneType as an index, in other words, there's no need to use a custom index to iterate through the Phones property, and the ProcessAsync() method could be :
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = null; //DO NOT WANT AN OUTTER HTML ELEMENT
var props = typeof(UserPhones).GetProperties();
// display editable tags for phones
foreach (var pt in this.TypesToEdit) {
var phone = Phones.SingleOrDefault(p=>p.Type == pt);
var index = (int) pt;
foreach (var pi in props)
{
// if phone==null , then the pv should be null too
var pv = phone==null? null: pi.GetValue(phone);
var tag = GenerateFieldForProperty(pi.Name, pv, index, pt);
output.Content.AppendHtml(tag);
}
}
// generate hidden input tags for phones
var phones= Phones.Where(p => !this.TypesToEdit.Contains((p.Type)));
foreach (var p in phones) {
var index = (int)p.Type;
foreach (var pi in props) {
var pv = pi.GetValue(p);
var tag = GenerateFieldForProperty(pi.Name,pv,index,p.Type);
output.Content.AppendHtml(tag);
}
}
}
Here the GenerateFieldForProperty is a simply helper method to generate tag builder for particular property:
private TagBuilder GenerateFieldForProperty(string propName,object propValue,int index, EnumPhoneType eType )
{
// whether current UserPhone is editable (check the PhoneType)
var editable = TypesToEdit.Contains(eType);
var expression = this.ExpressionFilter(For.Name + $"[{index}].{propName}");
var explorer = For.ModelExplorer.GetExplorerForExpression(typeof(IList<UserPhones>), o => propValue);
//IF REQUESTED TYPE AND PROPERTY SPECIFIED
if (pi.Name.NormalizeString() == PropNameToEdit.NormalizeString() && editable)
{
TagBuilder gridItem = new TagBuilder("div");
gridItem.Attributes.Add("class", "rvt-grid__item");
var labelText = this.GetLabelTextByPhoneType(eType);
gridItem.InnerHtml.AppendHtml(BuildLabel(explorer, expression, labelText));
gridItem.InnerHtml.AppendHtml(BuildTextBox(explorer, expression, propValue?.ToString()));
return gridItem;
}
else //ADD HIDDEN FIELD SO BOUND PROPERLY
return BuildHidden(explorer, expression, propValue?.ToString());
}
private string GetLabelTextByPhoneType(EnumPhoneType eType) {
string lVal = null;
switch (eType)
{
case EnumPhoneType.Home:
lVal = "Home Phone";
break;
case EnumPhoneType.Mobile:
lVal = "Mobile Phone";
break;
case EnumPhoneType.Other:
lVal = "Other Phone";
break;
default:
break;
}
return lVal;
}
When posted to server, if someone doesn't input a phone number for the other PhoneType, the actual payload will be something like:
AppUser.UserPhones[0].UserPhoneId=....&AppUser.UserPhones[0].PhoneNumber=911&....
&AppUser.UserPhones[2].UserPhoneId=&AppUser.UserPhones[2].PhoneNumber=&AppUser.UserPhones[2].Type=&AppUser.UserPhones[2].AppUserId=&AppUser.UserPhones[2].AppUser=
&AppUser.UserPhones[1].UserPhoneId=...&AppUser.UserPhones[1].PhoneNumber=119&....
Since we use phone type as the index, we can conclude that the UserPhones[0] will be used as an Mobile phone and the UserPhones[2] will be treated as an Home phone.
page handler or action method
And the model binder on server side will create a empty string for each UserPhone.
To remove those empty inputs and prevent overposting attack, we could use Linq to filter UserPhones so that we can create or update UserPhone records without empty Phones:
var editables = new[] {
EnumPhoneType.Mobile,
EnumPhoneType.Other,
};
AppUser.UserPhones = AppUser.UserPhones
.Where(p => !string.IsNullOrEmpty(p.PhoneNumber)) // remove empty inputs
.Where(p => editables.Contains(p.Type) ) // remove not editable inputs
.ToList();
// now the `UserPhones` will be clean for later use
// ... create or update user phones as you like
Let's say you want to create phones :
public IActionResult OnPostCreate() {
var editables = new[] {
EnumPhoneType.Mobile,
EnumPhoneType.Other,
};
AppUser.UserPhones = AppUser.UserPhones
.Where(p => !string.IsNullOrEmpty(p.PhoneNumber))
.Where(p => editables.Contains(p.Type) )
.Select(p => { // construct relationship for inputs
p.AppUser = AppUser;
p.AppUserId = AppUser.Id;
return p;
})
.ToList();
this._dbContext.Set<UserPhones>().AddRange(AppUser.UserPhones);
this._dbContext.SaveChanges();
return Page();
}
Test Case :
<form method="post">
<div class="row">
<user-phones
phones="#Model.AppUser.UserPhones"
asp-for="#Model.AppUser.UserPhones"
prop-name-to-edit="PhoneNumber"
types-to-edit="new EnumPhoneType[] { EnumPhoneType.Mobile, EnumPhoneType.Other}"
>
</user-phones>
</div>
<button type="submit">submit</button>
</form>
User1 who has Mobile phone and Home phone number:
User2 who wants to create a new Mobile phone number :

Entity Framework is not providing me IDs when calling back

I'm using a ViewModel (RoleVM) with a collection of ViewModels (RolePermissionVM) for this particular edit view. The view displays the RoleVM fields, and a checkbox list of RolePermissionVM. Each row in the checkbox list has a hiddenFor for the ID of the RolePermission.
When I save the form, my controller correctly writes the data to the database, adding or updating records. However, I would like the user to remain on the page, so I call the View again, but trying to get an updated model so that I have the IDs for any newly created RolePermissionVM objects. I am not getting the new IDs into the HiddenFor fields.
Here's my class:
public class RolePermissionVM
{
public int? RolePermissionId { get; set; }
public int RoleId { get; set; }
public int PermissionId { get; set; }
public string PermissionName { get; set; }
public bool IsActive { get; set; }
}
My controller code:
private RoleVM GetRoleVm(int id)
{
var thisRoleVm = (from r in db.Role
where r.RoleId == id
select new RoleVM
{
RoleId = r.RoleId,
RoleName = r.RoleName,
RoleDescription = r.RoleDescription,
OwnerId = r.OwnerId,
IsActive = r.IsActive
}).FirstOrDefault();
thisRoleVm.RolePermission = (from p in db.Permission
join rPerm in
(from rp in db.RolePermission
where rp.RoleId == id
select rp)
on p.PermissionId equals rPerm.PermissionId into pp
from rps in pp.DefaultIfEmpty()
select new RolePermissionVM
{
RolePermissionId = (int?)rps.RolePermissionId,
RoleId = id,
PermissionId = p.PermissionId,
PermissionName = p.PermissionName,
IsActive = (rps.IsActive == null ? false : rps.IsActive)
})
.OrderBy(p => p.PermissionName).ToList();
return thisRoleVm;
}
[HttpPost, ActionName("_roleedit")]
[ValidateAntiForgeryToken]
public ActionResult _RoleEdit(RoleVM editedRole)
{
//...
if (ModelState.IsValid)
{
var dbRole = db.Role.Find(editedRole.RoleId);
dbRole.RoleName = editedRole.RoleName;
dbRole.RoleDescription = editedRole.RoleDescription;
dbRole.OwnerId = editedRole.OwnerId;
foreach (var thisPerm in editedRole.RolePermission) // RolePermission here is the ViewModel, not the actual model
{
if (thisPerm.RolePermissionId != null && thisPerm.RolePermissionId > 0)
{
// We have a record for this, let's just update it
var thisRolePerm =
dbRole.RolePermission.FirstOrDefault(rp => rp.RolePermissionId == thisPerm.RolePermissionId);
thisRolePerm.IsActive = thisPerm.IsActive;
db.Entry(thisRolePerm).State = EntityState.Modified;
}
else
{
if (thisPerm.IsActive)
{
// New and active, so we add it
dbRole.RolePermission.Add(new RolePermission
{
RoleId = editedRole.RoleId,
PermissionId = thisPerm.PermissionId,
IsActive = true
});
}
}
}
db.Entry(dbRole).State = EntityState.Modified;
db.SaveChanges(User.ProfileId);
var newEditedRole = GetRoleVm(editedRole.RoleId); // We don't get the new IDs here, but I would like to
newEditedRole.ResponseMessage = "Saved Successfully";
return View(newEditedRole); // This should have the new RolePermissionId values, but it doesn't.
}
editedRole.ResponseMessage = "Error Saving";
return View(editedRole);
}
The partial view used for each row of the CheckBox list:
#using PublicationSystem.Tools
#model PublicationSystem.Areas.Admin.Models.RolePermissionVM
<li class="editorRow ui-state-default removable-row">
#using (Html.BeginCollectionItem("RolePermission"))
{
<div class="row">
#Html.HiddenFor(model => model.RolePermissionId)
#Html.HiddenFor(model => model.RoleId)
#Html.HiddenFor(model => model.PermissionId)
#Html.HiddenFor(model => model.PermissionName)
<div class="col-md-7">
#Html.DisplayFor(model => model.PermissionName, new {htmlAttributes = new {#class = "form-control"}})
</div>
<div class="col-md-3">
#Html.CheckBoxFor(model => model.IsActive, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
}
</li>
var newEditedRole = GetRoleVm(editedRole.RoleId); should be calling the database to get the updated IDs, but it does not. I think the issue is the DBContext is using a cached copy.
So, why do the new database generated IDs not get pulled back? How can I fix that? Is there a more efficient way to do this?
You have to call return RedirectToAction("ViewName") instead of return View(newEditedRole);
Another way is removing the value from the ModelState, so it will be updated on view:
ModelState.Remove("RoleId")
model.RoleId = dbRole.RoleId
I think return RedirectToAction("ViewName") is better/more reliable choice.

Get image files from Local Folder to display in the UI at runtime fails

I used below codes to retrieve image files from LocalFolder
Total images files is 20 in LocalFolder
The problem:
Only 8 images are displayed and the rest is blank.
Why the rest can not be displayed?
Can bind the BitmapImage file to the image Control as below base on MVVM ?
Example : imageURL ="ms-appdata:///local/imgfile.jpg"
---- In XAML : PhotoView.xaml
<Image x:Name="Img" Source="{Binding ImageUrl}" Stretch="UniformToFill"/>
<TextBlock FontSize="23" Text="{Binding Unit_Price}" Height="23" Margin="3,1,3,0"/>
<TextBlock FontSize="23" Text="{Binding Description}" Height="23" Width="300" Margin="1,1,1,0/>
--- In code behind: PhotoView
ItemsViewModel itemsViewModel = null;
ObservableCollection items = null;
itemsViewModel = new ItemsViewModel();
items = itemsViewModel.GetItems();
//-- GridViewControl
ItemsViewSource.Source = items;
ItemsGridView.SelectedItem = null;
-------------MVVM
--------- Model :
class ItemViewModel : ViewModelBase
{
private string imageurl = string.Empty;
public string ImageUrl
{
get
{ return imageurl; }
set
{
if (imageurl == value)
{ return; }
imageurl = value;
isDirty = true;
RaisePropertyChanged("ImageUrl");
}
}
private decimal unit_price = 0;
public decimal Unit_Price
{
get
{ return unit_price; }
set
{
if (unit_price == value)
{ return; }
unit_price = value;
isDirty = true;
RaisePropertyChanged("Unit_Price");
}
}
}
---------- View Model
class ItemsViewModel : ViewModelBase
{
private ObservableCollection items;
public ObservableCollection Items
{
get
{
return items;
}
set
{
items = value;
RaisePropertyChanged("Items");
}
}
public ObservableCollection GetItems()
{
items = new ObservableCollection();
using (var db = new SQLite.SQLiteConnection(App.DBPath))
{
var query = db.Table().Where(c=> c.CompanyName == Company).OrderBy(c => c.No);
foreach (var _item in query)
{
var item = new ItemViewModel()
{
No = _item.No,
ImageUrl = "ms-appdata:///local/" + _item.PictureFilename,
Unit_Price = _item.Unit_Price,
Description = _item.Description
};
items.Add(item);
}
}
return items;
}
Try to bind ImageSource (not string)
Try to copy the Image folder in the Folder wherever you are using or Register the Every image in Register.resx and call the image by its name given in the Register

Binding dropdown when ajax form in submitted in mvc2 asp.net

My requirement is simple, there are two dropdowns in my view. First dropdown is filled be default and second dropdown has to be filled based on values from first dropdown.
My view is
<script type='text/javascript'>
$(function() {
$('#ddlRoles').change(function() {
$(this).parents('form').submit();
});
});
< % using (Ajax.BeginForm("UpdateDropdownForm", new AjaxOptions { UpdateTargetId = "txtDropdownValue" }))
{ %>
<%= Html.DropDownList("ddlRoles", (IEnumerable<SelectListItem>)ViewData["RolesData"])%>
<%= Html.DropDownList("ddlUsers", (IEnumerable<SelectListItem>)ViewData["UsersList"])%>
<% } %>
My controller is
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["RolesData"] = GetDropdownList();
List<SelectListItem> usersList = new List<SelectListItem>();
usersList.Add(new SelectListItem() { Text = "Select" });
ViewData["UsersList"] = usersList;
TempData["UsersList"] = usersList;
return View();
}
public IEnumerable<SelectListItem> UpdateDropdownForm(string ddlRoles)
{
ViewData["UsersList"] = GetUsersList();
TempData["UsersList"] = GetUsersList();
return GetUsersList();
}
public List<SelectListItem> GetDropdownList()
{
List<SelectListItem> list = new List<SelectListItem>();
list.Add(new SelectListItem() { Text = "Admin", Value = "1" });
list.Add(new SelectListItem() { Text = "Employee", Value = "2" });
list.Add(new SelectListItem() { Text = "Manager", Value = "3" });
return list;
}
public List<SelectListItem> GetUsersList()
{
List<SelectListItem> list = new List<SelectListItem>();
list.Add(new SelectListItem() { Text = "Administrator", Value = "10" });
list.Add(new SelectListItem() { Text = "Ramesh", Value = "20" });
list.Add(new SelectListItem() { Text = "Satish", Value = "30" });
return list;
}
}
Here, im able to post the first dropdown data, and im setting the second dropdown data in viewdata/tempdata and in view im binding the data to second dropdown. But this is not working.
For now, when first dropdown is changed, im just binding second dropdown. but what if i want to fill a dropdown and display data in two text boxes. What do i have to do in that case?
How do i solve this.
please help.
You may find the following answer useful for generating cascading dropdown lists.