My question is in regards to the "Compelling Example" given for ReactiveUI where as a person types in a search bar, the search occurs asynchronously. Suppose though I wanted to provide my user with a way to refresh the current search results. I could just ask them to backspace in the search bar and retype their last character. However, they are asking for a "Refresh" button because it's not obvious to them how to refresh the current results.
I can't think of how to do this within the context of the example:
public class TheViewModel : ReactiveObject
{
private string query;
private readonly ObservableAsPropertyHelper<List<string>> matches;
public TheViewModel()
{
var searchEngine = this.ObservableForProperty(input => input.Query)
.Value()
.DistinctUntilChanged()
.Throttle(TimeSpan.FromMilliseconds(800))
.Where(query => !string.IsNullOrWhiteSpace(query) && query.Length > 1);
var search = searchEngine.SelectMany(TheSearchService.DoSearchAsync);
var latestResults =
searchEngine.CombineLatest(search, (latestQuery, latestSearch) => latestSearch.Query != latestQuery ? null : latestSearch.Matches)
.Where(result => result != null);
matches = latestResults.ToProperty(this, result => result.Matches);
}
public string Query
{
get
{
return query;
}
set
{
this.RaiseAndSetIfChanged(ref query, value);
}
}
public List<string> Matches
{
get
{
return matches.Value;
}
}
}
Does anyone have any suggestions on how I could capture a command from a button and re-execute the existing search without clearing out their current search text?
You can merge the existing observable of Query changes with a new observable that returns the current Query when the refresh button is pressed.
First a command for the refresh button:
public ReactiveCommand<Unit, String> Refresh { get; private set; }
Then you create the command and assign it, and create a merged observable of the two observables:
Refresh = ReactiveCommand.Create<Unit, String>(() => Query);
var searchEngine = Observable.Merge(
this.ObservableForProperty(input => input.Query).Value().DistinctUntilChanged(),
Refresh)
.Throttle(TimeSpan.FromMilliseconds(800))
.Where(query => !string.IsNullOrWhiteSpace(query) && query.Length > 1);
The rest can stay unchanged.
Related
I got a simple WinForm application with a couple of textboxes and a confirm button, I'm using ReactiveUI.
This is my ViewModel:
public CurrencyViewModel()
{
editCurrency = new Currency();
this.ValidationRule(
viewModel => viewModel.IsoCode,
isoCode => !string.IsNullOrWhiteSpace(isoCode),
"error");
this.ValidationRule(
viewModel => viewModel.Name,
name => !string.IsNullOrWhiteSpace(name),
"error");
NewCommand = ReactiveCommand.Create(() => NewItem());
SaveCommand = ReactiveCommand.Create(() => Save(), this.IsValid());
}
public string IsoCode
{
get => isoCode;
set
{
editCurrency.IsoCode = value;
this.RaiseAndSetIfChanged(ref isoCode, value);
}
}
public string Name
{
get => name;
set
{
editCurrency.Name = value;
this.RaiseAndSetIfChanged(ref name, value);
}
}
private void NewItem()
{
IsoCode = string.Empty;
Name = string.Empty;
Symbol = string.Empty;
}
I then bind my validation and my save command in the view:
this.BindValidation(ViewModel, vm => vm.IsoCode, v => v.errorLabelIsoCode.Text).DisposeWith(disposables);
this.BindValidation(ViewModel, vm => vm.Name, v => v.errorLabelName.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCommand, v => v.sfButtonOk, nameof(sfButtonOk.Click)).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.NewCommand, v => v.sfButtonNew, nameof(sfButtonNew.Click)).DisposeWith(disposables);
My issue is that sfButtonOk stays enabled when i first launch the application even if isValid() is false, the command doesn't fire as intended so it's just a grapichal problem it seems. The button is disabled only if I write valid text and then cancel it.
It seems that the button is disabled only when isValid goes from true to false
The issue here is probably related to a view model being initialized too late, or due to the view model property not sending change notifications on the view side in time. Make sure you assign the view model to the IViewFor.ViewModel property before a call to WhenActivated, or otherwise implement INotifyPropertyChanged on the view side (also you probably don't need a WhenActivated at all because WinForms doesn't have dependency properties that might introduce a potential for a memory leak)
Additionally worth noting, that we have evergreen sample apps targeting various UI frameworks including Windows Forms in the ReactiveUI.Validation core repository https://github.com/reactiveui/ReactiveUI.Validation/blob/d5089c933e046c5ee4a13149491593045cda161a/samples/LoginApp/ViewModels/SignUpViewModel.cs#L43 Just tested the Winforms sample app, and the button availability seems to perform as we'd expect.
Lets say i have multiple public functions in a single controller, i am trying to fine a way to display 1 option per page for example, first page user selects a schoolYear then hit submit, next page the user selects a school and hit submit, 3rd page the user selects a term and with a final page of all the selected fields being shown and a delete button. i am starting to test my page now and am wondering what is the best way to accomplish this?
class SchoolYearsController extends AppController
{
public function index()
{
}
public function chooseYear()
{
$schoolYearsTable = $this->loadModel('SchoolYears');
$schoolYears = $schoolYearsTable->find()->order(['SchoolYears.endYear'=>'desc']);
$this->set('schoolYears',$schoolYears);
$this->request->getSession()->write('App.schoolYear',$this->request->getData('schoolYear'));
return $this->redirect(['controller'=>'SchoolYears', 'action'=>'chooseSchool']);
}
public function chooseSchool()
{
$schoolListsTable = $this->loadModel('Schools');
$schoolYear = $this->session->read('App.schoolYear');
$schools = $schoolListsTable->find()
->where([
'fiscalYear' => $schoolYear,
'districtCode' => 'MA',
])
;
$this->set('schools',$schools);
return $this->redirect(['controller'=>'SchoolYears', 'action'=>'chooseTerm']);
}
public function chooseTerm()
{
$chosenTerm = $this->request->getData();
}
}
My entity
/**
* #ORM\ManyToOne(targetEntity="Estat", inversedBy="temes")
*/
private $estat;
public function setEstat(\Ncd\ForumBundle\Entity\Estat $estat = null)
{
$this->estat = $estat;
return $this;
}
My admin
protected function configureListFields(ListMapper $listMapper)
{
//$estats=$this->getEstatsPossibles()->toArray();
$estats=array();
foreach($this->getEstatsPossibles() as $estat)
{
$estats[$estat->getId()]=$estat->getNom();
}
$listMapper
->add('estat', 'choice',['editable' => true,'choices'=> $estats])
I'd like to make estat field editable in the list grid. Doing it on this way I get make it editable, a combobox appears but when I chose an option I get an exception because setEstat function of my entity does not recive an Estat entity, but a string (the array's key).
Trying
->add('estat', 'many_to_one',['editable' => true,'choices'=> $estats])
Only appears a link to the entity without any possibility to change.
Is it possible?
Waiting for a better and cleaner solution I'v solved this injecting an entityManager in my entity following the solution of this answer:
Get entityManager inside an Entity
Then, in my entity I've changed setEstat function:
public function setEstat( $estat = null)
{
if (is_object($estat) && get_class($estat)=='Ncd\ForumBundle\Entity\Estat')
{
$this->estat=$estat;
} else {
$estat_o=$this->em->getRepository('Ncd\ForumBundle\Entity\Estat')->find((int)$estat);
$this->estat = $estat_o;
}
return $this;
}
I have a form with about 20 fields. I have a ListBox that is populated with Customers from the Model when the page loads. Once the user picks one of those Customers from the ListBox, I want to post to the Controller the selected Customer, get customer's info, return it to same view, and populate some of the fields with the Customer's info.
Here is what I am trying now, but it might not be the best way. Also, the onclick gets called on page load, which causes an infinite loop.
View - CreateUser
#Html.ListBoxFor(x => x.Id,
Model.Customers.Select(
x => (new SelectListItem {
Text = x.Name,
Value = x.Value.ToString(),
Selected = x.IsSelected})).OrderBy(x => x.Text),
htmlAttributes new {
onclick = #Html.Action("GetCustomerInfo", "Customer", Model)
})
Controller - Customer
[ChildActionOnly]
public ActionResult GetCustomerInfo(CustomerModel Model)
{
// populate model with customer info
return View("CreateUser", Model);
}
Also, if there is a better way for this solution, I would love to hear any ideas. I am trying to avoid loading all Customers and then just using Angular to change the text fields based on selected Customer, since there is going to be over 1,000 customers and it would be slow to initially load all of them.
#Html.Action() is razor code and is parsed on the server so GetCustomerInfo() is called before the page is sent to the client. The fact its associated with the onclick event of a control is irrelevant. The infinite loop is because the view returned by GetCustomerInfo is the same view your trying to render - it contains the same #Html.Action() so GetCustomerInfo is called again, which returns a view with the same #Html.Action() so GetCustomerInfo is called again and so on.
You can use ajax to update the DOM with the selected customers details.
View models
public class SelectCustomerVM
{
[Display(Name="Select customer to display details")]
public int? CustomerID { get; set; }
public SelectList CustomerList { get; set; }
}
public class CustomerVM
{
public int ID { get; set; }
public string Name { get; set; }
// other properties of customer
}
Controller
public ActionResult Index()
{
SelectCustomerVM model = new SelectCustomerVM();
model.CustomerList = new SelectList(db.Customers, "ID", "Name");
return View(model);
}
public ActionResult Details(int ID)
{
CustomerVM model = new CustomerVM();
// get customer from database and map properties to CustomerVM
return PartialView(model);
}
Index.cshtml
#model SelectCustomerVM
#Html.LabelFor(m => m.CustomerID)
#Html.DropDownListFor(m => m.CustomerID, Model.CustomerList, "--Please select--")
<div id=customerdetails></div>
<script type="text/javascript">
$('#CustomerID').change(function() {
var customerID = $(this).val();
if(customerID) {
$.get('#Url.Action("Details", "Customer")', { ID: customerID }, function(data) {
$('#customerdetails').html(data);
});
} else {
$('#customerdetails').empty();
}
});
</script>
GetCustomer.cshtml (partial view)
#model CustomerVM
#DisplayFor(m => m.ID)
#DisplayFor(m => m.Name)
....
Some best practices to note. Don't pollute your view with code to construct SelectList's - that's the responsibility of the controller; and use Unobtrusive javascript - don't mix content and behavior.
I've Created a Model which have few custom validation. These custom validation I've annotated at property by below code
[CustomValidation(typeof(ItemmasterModel), "ValueTextMaxLenghtValidate")]
public decimal Valuetextmaxlength
{
get
{
return _Valuetextmaxlength;
}
set
{
ValidateProperty("Valuetextmaxlength",value);
_Valuetextmaxlength = value;
RaisePropertyChanged(() => Valuetextmaxlength);
}
}
public static ValidationResult ValueTextMaxLenghtValidate(object obj, ValidationContext context)
{
var itmmstr = (ItemmasterModel)context.ObjectInstance;
if (itmmstr.SelectedValuetypeDd != null)
{
string vtype = itmmstr.SelectedValuetypeDd.Key.ToString();
if (vtype.Equals("C"))
{
if (itmmstr.SelectedItemValueCodeTypesDd != null)
{
string vcode = itmmstr.SelectedItemValueCodeTypesDd.Key.ToString();
if (vcode.Equals("T"))
{
if (itmmstr.Valuetextmaxlength == null || itmmstr.Valuetextmaxlength == 0)
{
return new ValidationResult("Value Max Length is not Entered",
new List<string> { "Valuetextmaxlength" });
}
}
}
}
else if (vtype.Equals("T"))
{
if (itmmstr.Valuetextmaxlength == null || itmmstr.Valuetextmaxlength == 0)
{
return new ValidationResult("Value Max Length is not Entered",
new List<string> { "Valuetextmaxlength" });
}
}
}
return ValidationResult.Success;
}
Now this validation code depend on other property. scenerio When User select a value from dropdown it makes 1 checkbox selected automatically and User should enter the value in texbox also.
Issue:
Validation is working. checkbox is selected at first time then also it comes with error popup.
untill user doesn't make changes into this checkbox or texbox it is with error only. 1 time it says error even value has been entered .Next time it goes even user have not entered anything but during final full object validation is again comes with error.
Why this even ambiguity is happening. How to solve this.
Need more code let me know. I'll Post. Code is in Silverlight 5, MVVM Light
During my Custom validation I was using ValidateProperty("Valuetextmaxlength",value); before setting the value of property so It was giving me issue but the moment I set the value first and then Use my ValidateProperty("Valuetextmaxlength",value); everything worked smooth. Still I don't know the reason why but It worked for me.