How to update antd table with mobx when observable changes - antd

my antd table is not updating when selectedId property changes:
rowClassName={row => row.id === this.model.selectedId
? row.nameWithTypeAndRunType.type + " " + SELECTED_ROW_CSS_CLASS
: row.nameWithTypeAndRunType.type
}
i have an workaround, where i extract the table to a constant:
const MyTable = (viewModel: MyModel) => (
<Table<MyModel>
........
and add in render() with <MyTable {...this.myModel} />
I'd like to create a RTable, so that i could generally use the antd Table conveniently. I tried this:
export function RTable<RecordType extends object = any>(props: TableProps<RecordType>): JSX.Element {
const t = <Observer>{() => <Table {...props} />}</Observer>
return t
}
Than i use in render() RTable instead of Table tag , but no luck, table does not update.
Any ideas what to do in RTable function to force the update on Table?
Or any other idea how to solve this?

actually, the problem is that rowClassName is a function and mobx can only react to property changes.
So what we did is to pass the selectedId as property, so that mobx detects the change and rerenders the component:
export interface RTableProperties<RecordType extends object = any> extends TableProps<RecordType> {
selectedRecordId: string | undefined
}
export function RTable<RecordType extends object = any>(rTableProps: RTableProperties<RecordType>): JSX.Element {
const rTable = <Table {...rTableProps} />
return rTable
}

Related

Select2 Asp.net MVC - Dropdownlist is not valued with initial data

I am using a select2 using MVC 5 and C#.
I'm having trouble with the dropdownlist (select2) loading with initial data of the model.
The items passed in the corresponding binding field properly valued, but they are not shown in select2!
I mean, despite the list of the ViewModel field correctly valued by the controller, the dropdownlist (select2) is not valued correctly, as if the binding model did not work.
Needless to say, I'm googling for 1,5 days.
Fortunately (:)) I have no problem at the loading of select2 with all items, the dropdownlist works correctly even on the post, even I can take the selected items.
Many Thanks to all
P.s: Now that I'm writing, I have a doubt; Could be that select2 doesn't work with List ?
View
#section scripts{
...
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
}
#Html.DropDownListFor(model => model.MezziStraSelect, Model.MezziStraOrdinari, new { style = "width: 100%", #class = "form-control" })
JS
$(document).ready(function () {
//...
$("#MezziStraSelect").select2({
placeholder: "Select one or more items",
multiple: true,
allowClear: true
});
#if ( Model.MezziStraSelect == null)
{
<text>$("#MezziStraSelect").val(null).trigger("change");</text>
}
}
ViewModel
public Guid[] MezziStraSelect { get; set; }
public MultiSelectList MezziStraOrdinari { get; set; }
Controller
//Load List MezziStraOrdinari
var _stra = m.GetMezziStraordinari().Select(x => new
{
id = x.VoceSpesaID,
desc = x.VoceSpesa
}).ToList();
//view model set field
vm.MezziStraOrdinari = new MultiSelectList(_stra, "id", "desc");
//Load array Mezzi used from item selected
List<Guid> _mezziStraUsati = new List<Guid>();
var elems = dc.ItemSelected.FirstOrDefault(x => x.ItemID.ToString() == _guidSelected);
if (elems!=null)
{
elems.VociSpese.ToList().ForEach(x =>
{
if (x.VociSpesa.Straordinario == true)
_mezziStraUsati.Add(x.VoceSpesaViaggioID); //VoceSpesaViaggioID is GUID
});
if (_mezziStraUsati.Count>0)
vm.MezziStraSelect = _mezziStraUsati.ToArray(); //Guid[]
}
The initial loading of the Select2 unfortunately the binding cannot set the initial values!
So reading the Select2 documentation to this link , I had to proceed with a manual upload via JS.
Practically to try to do an human thing (I hope it is), I created an Extension method in the backend of the Model field
Extension Method
public static string ToSelect2Array<T>(this T[] values)
{
var resp = string.Empty;
values.ToList().ForEach(x => resp += $"'{x.ToString()}',");
if (resp.Length > 0)
resp = resp.Substring(0, resp.Length - 1);
return resp;
}
and then in JS client side, I call it like this:
VIEW (script JS)
#if ( Model.MezziStraSelect != null)
{
<text>
$("#MezziStraSelect").val([#Html.Raw(Model.MezziStraSelect.ToSelect2Array())]);
$("#MezziStraSelect").trigger('change');
</text>
}
else
{
<text>$("#MezziStraSelect").val(null).trigger("change");</text>
}
This is working quite well, but is it possible that there is no way to get it to bind automatically?
I would be curious to know other solutions, more elegant than this I would be grateful for!

Kendo control value in the event is the controls previous value

I am trying to understand why this isn't working. If i set the value of myTextBox to 5 then trigger the change event. the value for my myTextBox is null. If i change the value to 10 then fire the event again, the value will be the previous one (5). I've compared it to one of the many textboxes working on the form and they appear the same. The only difference is the value property in the wrapper object is set in the ones that work but behind in the one that doesn't. Digging into the both objects element property, i see the values are correct and current. Any help would be appreciated.
Model Property
[UIHint("Decimal")]
[Display(Name = "Example")]
public decimal? MyTxt{ get; set; }
Template (Decimal.cshtml):
#model decimal?
#{
var defaultHtmlAttributesObject = new { style = "width:100%" };
var htmlAttributesObject = ViewData["htmlAttributes"] ?? new { };
var htmlAttributes = Html.MergeHtmlAttributes(htmlAttributesObject, defaultHtmlAttributesObject);
}
#(Html.Kendo().NumericTextBoxFor(m => m)
.Format("#.00")
.HtmlAttributes(htmlAttributes)
)
UI Declaration
#Html.EditorFor(m => m.MyTxt, new { htmlAttributes = new { #style="width: 100%", #readonly = "readonly" } })
Javascript:
var myTextBox = $('#MyTxt').data('kendoNumericTextBox');
$(document).on('change', '#foo', function(){
var test = myTextBox.value();
})
Update:
In the document ready function i was binding the change event like this:
$('#MyTxt').change({ source: $('#MyTxt'), destination: someObject, isTwoMan: true, crewType: LaborType.Set }, SomeCalcFunction);
The jQuery change event fires nefore the kendo one thus the delay in getting the correct value. The fix was to read the manual and bind to the event the kendo way
the issue was in the way i was binding to the change event.
$('#MyTxt').change({ source: $('#MyTxt'), destination: someObject, isTwoMan: true, crewType: LaborType.Set }, SomeCalcFunction);
will fire before the kendo event does. Changing it the kendo way fixed my issue
$("#MyTxt").bind("change", function (e) {
var event = jQuery.Event('change', {
source: $("#MyTxt"),
destination: someObject,
isTwoMan: true,
crewType: LaborType.Set
});
SomeCalcFunction(event);
});

Pass variables to fragment container in relay modern

I'm using Relay Modern (compat). I have a fragment that contains a field that has one argument, but I can't find a way of passing the variable value from the parent component:
// MyFragmentComponent.jsx
class MyFragmentComponent extends Component {...}
const fragments = {
employee: graphql`
fragment MyFragmentComponent_employee on Employee {
hoursWorked(includeOvertime: $includeOvertime)
dob
fullName
id
}
`,
}
export default Relay.createFragmentContainer(MyFragmentComponent, fragments)
It will end up saying $includeOvertime is not defined. The context where this component is being rendered looks like this:
// MyRootComponent.jsx
class MyRootComponent extends Component {
render() {
const { employee } = this.props
const includeOvertime = //... value is available here
return (
<div>
<MyFragmentComponent employee={employee} />
</div>
)
}
}
const query = graphql`
query MyRootComponentQuery($employeeId: String!) {
employee(id: $employeeId) {
fullName
...MyFragmentComponent_employee
}
}
`
export default MyUtils.createQueryRenderer(MyRootComponent, query) // this just returns a QueryRenderer
With relay classic you would pass variables this way:
....
employee(id: $employeeId) {
fullName
${MyFragmentComponent.getFragment('employee', variables)}
}
How can I achieve the same with relay modern?
Using #argumentDefinitions and #arguments directives seems to be the way to go. In relay versions before 1.4.0 graphql.experimental had to be used instead of graphql.
In the fragment definition:
const fragments = {
employee: graphql`
fragment MyFragmentComponent_employee on Employee
#argumentDefinitions(includeOvertime: { type: "Boolean", defaultValue: false }) {
hoursWorked(includeOvertime: $includeOvertime)
dob
fullName
id
}
`,
}
If you want the argument to be required:
#argumentDefinitions(includeOvertime: { type: "Boolean!" })
In the parent component you should specify the arguments for the fragment like this:
const query = graphql`
query MyRootComponentQuery($employeeId: String!, $includeOvertime: Boolean) {
employee(id: $employeeId) {
fullName
...MyFragmentComponent_employee #arguments(includeOvertime: $includeOvertime)
}
}
`
In this page in the official relay docs there is an example of directives for defining/passing arguments.
UPDATE:
Since relay version 1.4.0 graphql.experimental was deprecated and now all the features are supported by the regular graphql tag.
UPDATE:
In relay version 1.5.0 graphql.experimental was removed.

How do I access no_monitor in save() function?

I've this configure() function in my form:
public function configure() {
$this->current_user = sfContext::getInstance()->getUser()->getGuardUser();
unset($this['updated_at'], $this['created_at']);
$this->widgetSchema['idempresa'] = new sfWidgetFormInputHidden();
$id_empresa = $this->current_user->getSfGuardUserProfile()->getIdempresa();
$this->setDefault('idempresa', $id_empresa);
$this->widgetSchema['no_emisor'] = new sfWidgetFormDoctrineChoice(array('model' => 'SdrivingEmisor', 'add_empty' => 'Seleccione un Emisor', 'expanded' => false, 'multiple' => false));
$this->validatorSchema['idempresa'] = new sfValidatorPass();
$this->validatorSchema['no_emisor'] = new sfValidatorPass();
}
And I'm need to define a relation data in save() function so I do this:
public function save($con = null) {
$new_machine = parent::save($con);
$relation = new SdrivingMaquinaEmisor();
$relation->setIdmaquina($new_machine);
$relation->setIdemisor();
$relation->save();
return $new_machine;
}
In order the set the Idemisor, how do I access to the selected value when users submit the form? Is this the best way to achieve this?
EDIT
After take the suggestion about how to access no_emisor value now my code looks like:
public function save($con = null) {
$new_machine = parent::save($con);
$relation = new SdrivingMaquinaEmisor();
$relation->setIdmaquina($new_machine);
$relation->setIdemisor($this->values['no_emisor']);
$relation->save();
return $new_machine;
}
But I get this error:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'idmaquina' cannot be null
For some reason $new_machine doesn't return the id of the latest saved element. Maybe I'm doing in the wrong way so what I'm doing wrong?
I think you might want to do this in the form's doUpdateObject instead, since that receives the cleaned values.
http://www.symfony-project.org/api/1_4/sfFormObject#method_doupdateobject
Edit:
Alternatively, $this->values['no_emisor'] should work once the form has been bound.

Persist CheckBox State in Telerik MVC Grid While Paging in ASP.NET MVC Application

I am using Telerik MVC Grid where one of the columns is checkboxes. If I select checkboxes and then go to page 2 and then come back to page 1 all the checkboxes are gone. Which is of course the way HTTP works. Now, I put all the selected checkboxes inside the hidden field but since the grid does some sort of postback my hidden field is cleared next time.
If you're using Client Side data binding you can use the javascript/jquery below to maintain checkbox state.
Maintain checkbox state:
var selectedIds = [];
$(document).ready(function () {
//wire up checkboxes.
$('#YOUR_GRID_ID :checkbox').live('change', function (e) {
var $check = $(this);
console.log($check);
if ($check.is(':checked')) {
//add id to selectedIds.
selectedIds.push($check.val());
}
else {
//remove id from selectedIds.
selectedIds = $.grep(selectedIds, function (item, index) {
return item != $check.val();
});
}
});
});
Restore checkbox state after data binding:
function onDataBound(e) {
//restore selected checkboxes.
$('#YOUR_GRID_ID :checkbox').each(function () {
//set checked based on if current checkbox's value is in selectedIds.
$(this).attr('checked', jQuery.inArray($(this).val(), selectedIds) > -1);
});
}
A more verbose explanation available on my blog:
http://blog.cdeutsch.com/2011/02/preserve-telerik-mvc-grid-checkboxes.html
You need to save the state of the checkboxes to your database, and then retrieve them again from the database when you reload the page.
During paging, you need to reload only those records that pertain to a particular page. You can do that using the Skip() and Take() methods from Linq.
to preserve checked /unchecked checkbox state using telerik grid clientemplate across postbacks and async postbacks and in refreshing grid and (auto)paging, I tried the solution above with no avail and so went up with a bit harder solution; as I could not save the state in db, I used a session variable and an hiddenfield:
first, a way to do ajax postback (see function DoAjaxPostAndMore , courtesy of somebody herearound), where in success we take care of client values of selections, adding and removing as checked /unchecked
I also had to manually check / uncheck the checkboxes inside the manual ajax post
second, an hidden field (see 'hidSelectedRefs') to preserve clientactions, as the Session variable I am using will not be seen clientside in partial rendering
#model IEnumerable<yourInterfaceOrClass>
#{
ViewBag.Title = "Select Something via checkboxes";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Select Something via checkboxes</h2>
<!-- we need a form with an id, action must be there but can be an empty string -->
<form id="frm" name ="frm" action="">
<p>
<!--we need this as Session new values will not be takein in aajax requests clientisde, so it is easier to mange this field, which, in first and subsequent complete postbacks can have the value of the Session variable -->
<input type="hidden" name="hidSelectedRefs" id="hidSelectedRefs" value= '#Session["SelectedReferencesToPrint"]' />
</p>
<br />
<script type="text/javascript">
//ajax manual post to a custom action of controller, passing the id of record and the state of the checkbox
//to adjust the Session value
//data: $form.serialize() will have the single checbox value
//but only if checked. To make my life eaasier, I added the value (is id ) and the checked/unchecked
//state of checkbox (is the $(chkClicked).attr('checked'))
function DoAjaxPostAndMore(chkClicked) {
var $form = $("#frm");
$.ajax({
type: "POST",
url: 'SelectReferences',
data: $form.serialize() + '&id=' + $(chkClicked).val() + '&checked=' + $(chkClicked).attr('checked'),
error: function (xhr, status, error) {
//do something about the error
alert("Sorry, we were not able to get your selection...");
},
success: function (response) {
//I also needed to check / uncheck manually the checkboxes:
$(chkClicked).attr('checked', !$(chkClicked).attr('checked'));
//and now put correct values in hidSelectedRefs hidden field:
if ($(chkClicked).attr('checked')) {
$('input[name=hidSelectedRefs]').val($('input[name=hidSelectedRefs]').val() + '|' + $(chkClicked).val() + '|');
} else {
var tmp = $('input[name=hidSelectedRefs]').val();
$('input[name=hidSelectedRefs]').val(tmp.toString().replace('|' + $(chkClicked).val() + '|', ''));
}
}
});
return false; // if it's a link to prevent post
}
Then I handled the OnRowDataBound, to ensure the checboxes would be correctly checked on postbacks,
function onRowDataBound(e) {
var itemsChecked = $('input[name=hidSelectedRefs]').val();
if (itemsChecked)
{
if (itemsChecked.indexOf('|' + $(e.row).find('input[name=checkedRecords]').val() + '|') >= 0)
{
$(e.row).find('input[name=checkedRecords]').attr('checked', true);
}
}
}
</script>
The telerik mvc Grid is as follows:
(you can see I also handled OnDataBinding and OnDataBound, but thats's only to show a
"Loading" gif. The controller is named "Output" and the action that normally would be called "Index" here is callled "PrintReferences". The correspondenting Ajax action is called "_PrintReferences")
Of interest here is the ClientTemplate for checkbox (cortuesy of someone else herearound, where onclick
we call our custom ajax action (named "SelectReferences") on our Output controller via a call to the
DoAjaxPostAndMore() javascript/jquery function
#(Html.Telerik().Grid<yourInterfaceOrClass>()
.Name("Grid")
.ClientEvents(e => e.OnDataBinding("showProgress").OnDataBound("hideProgress").OnRowDataBound("onRowDataBound"))
.DataBinding(dataBinding =>
{
dataBinding.Server().Select("PrintReferences", "Output", new { ajax = ViewData["ajax"]});
dataBinding.Ajax().Select("_PrintReferences", "Output").Enabled((bool)ViewData["ajax"]);
})
.Columns( columns =>
{
columns.Bound(o => o.ID);
columns.Bound(o => o.ID)
.ClientTemplate(
"<input type='checkbox' name='checkedRecords' value='<#= ID #>' onclick='return DoAjaxPostAndMore(this)' />"
)
.Width(30)
.Title("")
.HtmlAttributes(new { style = "text-align:center; padding: 0px; margin: 0px;" });
columns.Bound(o => o.TITLE);
columns.Bound(o => o.JOBCODE);
columns.Bound(o => o.ORDERCODE );
//columns.Bound(o => o.AUTHOR);
columns.Bound(o => o.STATE);
columns.Command(commands =>
commands
.Custom("Details")
.ButtonType(GridButtonType.Image)
.HtmlAttributes(new { #class = "t-icon-details" })
.DataRouteValues(route => route.Add(o => o.ID)
.RouteKey("ID"))
.Ajax(false)
.Action("Details", "Referenza")
);
})
.Pageable(paging =>
paging.PageSize(10)
.Style(GridPagerStyles.NextPreviousAndNumeric)
.Position(GridPagerPosition.Bottom))
.Sortable()
.Filterable()
.Resizable(resizing => resizing.Columns(true))
.Reorderable(reorder => reorder.Columns(true))
.NoRecordsTemplate("No Reference found. Please review your filters...")
.ColumnContextMenu()
)
</form>
that's all for the View. Now, to the controller:
//Get : this is the usally called "Index" action
//here we can load data, but we also must ensure the Session variable is fine
public ActionResult PrintReferences(bool? ajax, string msgInfo, string selectedRef)
{
if (Session["SelectedReferencesToPrint"] == null)
{
Session["SelectedReferencesToPrint"] = string.Empty;
}
if (string.IsNullOrEmpty(selectedRef))
{
selectedRef = "|0|";
}
string msgOut = string.Empty;
//this is where I get data to show
List<yourInterfaceOrClass> ret = LoadData(out msgOut);
if (!string.IsNullOrEmpty(msgInfo) && !string.IsNullOrEmpty(msgInfo.Trim()))
{
msgOut = msgInfo + ' ' + msgOut;
}
ViewBag.msgOut = msgOut;
ViewData["ajax"] = ajax ?? true;
return View(ret);
}
//GridAction: here is telerik grid Ajax get request for your "_Index"
[GridAction]
public ActionResult _PrintReferences(string msgInfo)
{
//again, we must take care of Session variable
if (Session["SelectedReferencesToPrint"] == null)
{
Session["SelectedReferencesToPrint"] = string.Empty;
}
string msgOut = string.Empty;
List<yourInterfaceOrClass> ret = LoadData(out msgOut);
return View(new GridModel(ret));
}
//Post: this is where our custom ajax post goes
//we are here if a checkbox is cheched or unchecked
//in the FormCollection parameter we get the checkbox value only if checked, and also
//(and always) the parms we passed (id of record and state of checkbox: we cannot simply add,
//we must also subtract unchecked)
[HttpPost]
public ActionResult SelectReferences(FormCollection collection)
{
//we need a session variable
if (Session["SelectedReferencesToPrint"] == null)
{
Session["SelectedReferencesToPrint"] = string.Empty;
}
//use a local variable for calculations
string wholeSelectionToPrint = Session["SelectedReferencesToPrint"].ToString();
//get value passed: id
string selectedRefId = collection["id"];
if (!string.IsNullOrEmpty(selectedRefId))
{
selectedRefId = "|" + selectedRefId + "|";
}
bool cheked = (collection["checked"].ToString()=="checked");
//get vcalue passed :checked or unchecked
if (cheked)
{
//the element is to add
wholeSelectionToPrint += selectedRefId;
}
else
{
//the element is to remove
wholeSelectionToPrint = wholeSelectionToPrint.Replace(selectedRefId, "");
}
//set session variable final value
Session["SelectedReferencesToPrint"] = wholeSelectionToPrint;
return null;
}
//normal postback:
//we will be here if we add a button type submit in our page,
//here we can collect all data from session variable to do
//something with selection
[HttpPost]
public ActionResult PrintReferences(FormCollection collection)
{
//get selected references id
if (Session["SelectedReferencesToPrint"] == null)
{
Session["SelectedReferencesToPrint"] = string.Empty;
}
//use a local variable for calculations
string wholeSelectionToPrint = Session["SelectedReferencesToPrint"].ToString();
wholeSelectionToPrint = wholeSelectionToPrint.Replace("||", "|");
string[] selectdIDs = wholeSelectionToPrint.Split(new char[] { '|' });
foreach (string id in selectdIDs)
{
if (!string.IsNullOrEmpty(id))
{
//do something with single selected record ID
System.Diagnostics.Debug.WriteLine(id);
}
}
//omitted [....]
ViewData["ajax"] = true;
return View(ret);
}

Resources