Hi im working on MVC project which uses jqgrid. I have an master grid . On click of the row and edit button im goin to CreateOrEdit view . In that view there is a one more grid -Detail grid.
In case of create: the fields for the master grid(labels and respective EditorFor) and the detail grid will be empty and once we populate everything(labels and respective EditorFor and the grid) and click on save .. it gets saved.. This much work is done. Now the problem..
While editing: I have to populate the detail grid depending on the Id selected in the master grid.
I've tried things like:
url:'#Url.Action("PDVs", "GetData", new { pdv = #Model.Details})'
data: JSON.stringify('#(Model.Details)')
and/or specifying the url as:
url:'/PDVs/GetData'
And in the Controller:
public JsonResult GetData(Details details)
So as to receive the model and query it and send the JsonResult back,
but the model details is getting null values for all the fields.
So can someone please give me any ideas of how to solve this problem?
Update: The Details Model is
using System;
using System.Collections.Generic;
using Repository.Pattern.Ef6;
namespace Abc.PROJECTS.Models
{
public partial class PDVDuration:Entity
{
public int PDVDurationId { get; set; }
public Nullable<int> PDVId { get; set; }
public Nullable<System.DateTime> DateFrom { get; set; }
public Nullable<System.DateTime> DateTo { get; set; }
public string Type { get; set; }
public string Remarks { get; set; }
public virtual PDV PDV { get; set; }
}
}
And this is my details grid:
jQuery("#DetailsGrid").jqGrid({
// url: '/PDVs/GetManintenance',
// datatype: 'json',
#*url:'#Url.Action("PDVs", "GetData", new { pdv = #Model.PDVDurations })',*#
mtype: 'GET',
//datatype: "local",
//data: mydata,
//data: JSON.pdv.PDVDuration,
// data:myGridComplete('#Model.PDVDurations'),
height: 100,
width: 900,
colNames: ['DateFrom', 'DateTo', 'Type', 'Remarks'],
colModel: [
{
name: 'DateFrom', index: 'DateFrom', width: 60, editable: true, sorttype: "date",
formatter: 'date',
formatoptions: {
srcformat: 'm/d/Y',
// newformat: 'd/m/Y '
//newformat: 'Y-m-d'
},
editoptions: { dataInit: initDateEdit, size: 14 },
},
{
name: 'DateTo', index: 'DateTo', width: 80, editable: true, sorttype: "date",
formatter: 'date',
formatoptions: {
srcformat: 'm/d/Y',
//newformat: 'd/m/Y'
},
editoptions: { dataInit: initDateEdit, size: 14 },
},
{
name: 'Type', width: 80, editable: true, formatter: "select",
edittype: "select", editoptions: { value: "Open:Open;Maintainance:Maintainance;Closed:Closed", defaultValue: "Open" },
},
{
name: 'Remarks', index: 'Remarks', width: 80, editable: true
}
,
],
#* data: JSON.stringify('#(Model.PDVDurations)'),*#
// data:JSON.stringify(viewModel),
//data:model,
//model:viewModel,
//data: JSON.parse(jsonData),
pager: "#PDVMPager",
loadonce: true,
sortname: 'Client',
ignoreCase: true,
sortorder: 'asc',
gridview: true,
autoencode: true,
rowNum: 10,
rowList: [5, 10, 20, 50],
caption: "Maintenance",
viewrecords: true,
editurl: "clientArray",
gridComplete:function myGridComplete(PDV)
{
return JSON.stringify('#(Model.PDVDurations)')
},
}).jqGrid("navGrid", "#PDVMPager", {}, editSettings, addSettings, {
multipleSearch: true,
overlay: false,
onClose: function () {
$("div#ui-datepicker-div.ui-datepicker").hide();
}
}).jqGrid("filterToolbar", { defaultSearch: "cn" });
});
I think you have problem with Url.Action method if you want url like you wrote you should flip params.
Anyway, i suppose you can try to write like this in your jqgrid init if you need whole model:
url: '#Url.Action("GetData", "PDVs")',
postData: '#Html.Raw(Json.Encode(Model))'
But if you need just some fields you can write like this:
url: '#Url.Action("GetData", "PDVs")',
postData:
{
PDVDurations: #Model.PDVDurations
}
But you should write different signature in controller method. Like this:
public JsonResult GetData(int PDVDurations)
Related
I am developing Asp.Net MVC5 Application with jqGrid.
i have two models University and Religion.
public class University
{
public int UniversityID { get; set; }
public string UniversityName { get; set; }
}
public class Religion
{
public int ReligionID { get; set; }
public string ReligionName { get; set; }
}
I have a model called Student in which the above two classes are nested.
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public DateTime DOB { get; set; }
public string Gender { get; set; }
public University University { get; set; }
public Religion Religion { get; set; }
}
I filled the jqGrid with list of students.
//jqGrid binding through ajax Post
var jsonUnivList = $.parseJSON('#Html.Raw(Json.Encode(Model.Universities))'); //IEnumerable list of Universities
var jsonReligionList = $.parseJSON('#Html.Raw(Json.Encode(Model.Religions))'); // IEnumerable list of Religion
$("#list2").jqGrid({
url: '/Student/StudentGridData',
datatype: "json",
colNames: ['Student Id', 'Student Name', 'Gender', 'DOB', 'University', 'Religion'],
colModel: [
{ name: 'StudentId', index: 'StudentId', width: 70, hidden: true },
{ name: 'StudentName', index: 'StudentName', width: 130, sortable: true, editable: true, formoptions: { label: 'Name *' }, editoptions: { class: "validate[required]", "data-errormessage-value-missing": "*Name Required", "onblur": "$(this).validationEngine('validate');" } },
{
name: 'Gender', index: 'Gender', width: 80, align: "right", sortable: true, editable: true, edittype: 'select',
editoptions:
{
value: { '': '--select gender--', 'M': 'MALE', 'F': 'FEMALE' }
}
},
{ name: 'DOB', index: 'DOB', formatter: 'date', formatoptions: { srcformat: 'd/m/Y', newformat: 'ShortDate' }, width: 150, align: "right", sortable: true, editable: true, formoptions: { label: 'DOB *' }, editoptions: { class: "validate[required]", "data-errormessage-value-missing": "*DOB Required", "onblur": "$(this).validationEngine('validate');" } },
{
name: 'University.UniversityName', index: 'University.UniversityName', width: 150, align: "right", sortable: true, editable: true, edittype: 'select', formoptions: { label: 'Name *' },
editoptions:
{
dataUrl: '',
buildSelect: function (data) {
var s = '<select name="UniversityID" >';
if (jsonUnivList && jsonUnivList.length) {
for (var i = 0, l = jsonUnivList.length; i < l ; i++) {
s += '<option value="' + jsonUnivList[i].UniversityID + '">' + jsonUnivList[i].UniversityName + '</option>';
}
}
return s + "</select>";
},
class: "validate[required]", "data-errormessage-value-missing": "*University Required", "onblur": "$(this).validationEngine('validate');"
}
},
{
name: 'Religion.ReligionName', index: 'Religion.ReligionName', width: 150, align: "right", sortable: true, editable: true, edittype: 'select', formoptions: { label: 'Name *' },
editoptions:
{
dataUrl: '',
buildSelect: function (data) {
var s = '<select name= "ReligionID">';
if (jsonReligionList && jsonReligionList.length) {
for (var i = 0, l = jsonReligionList.length; i < l ; i++) {
s += '<option value="' + jsonReligionList[i].ReligionID + '">' + jsonReligionList[i].ReligionName + '</option>';
}
}
return s + "</select>";
},
class: "validate[required]", "data-errormessage-value-missing": "*Religion Required", "onblur": "$(this).validationEngine('validate');"
}
}
],
rowNum: 10,
rowList: [10, 20, 30],
pager: '#pager2',
sortname: 'StudentId',
mtype: 'POST',
viewrecords: true,
sortorder: "desc",
caption: "Student List",
editurl: '/Student/AddEditStudent'
});
$("#list2").jqGrid('navGrid', '#pager2',
{
edit: true, add: true, del: true, search: true,
searchtext: "Search", addtext: "Add", edittext: "Edit", deltext: "Delete"
}
);
After i clicked on Add option of jqGrid and filled the details (Uiversity and Religion are dropdowns - nested models of student). The filled data will be passed to Controller.
[HttpPost]
public void AddEditStudent(Student StudentModel)
{
}
But when i check the data received in controller,
i recieve the following data:
StudentName
DOB
Gender
But these are coming null:
University
Religion
Note: This problem arise only in the case of jqGrid,
If i submit from page(same textboxes and dropdown) then everything is ok.
This is how I would have modeled my Student Object :
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public DateTime DOB { get; set; }
public string Gender { get; set; }
public int UniversityID { get; set; }
public int ReligionID { get; set; }
}
In JQ Grid I bind options in a slightly different manner than yours. This is how I would bind options to the University column :
editoptions : "1:Stanford;2:Harvard;3:Yale"
Now when you save, the Id of the selected option will be mapped to the corresponding property of Student object
(I am assuming you are using saveRow function of jqGrid to submit data.)
Finally i figured it out
{
name: 'University.UniversityID', index: 'University.UniversityName', jsonmap: "University.UniversityName", width: 150, align: "right", sortable: true, editable: true, edittype: 'select', formoptions: { label: 'University *', name: "University.UniversityID" },
editoptions:
{
value: valUnivList, // List of university as keyvalue pair
class: "validate[required]", "data-errormessage-value-missing": "*University Required", "onblur": "$(this).validationEngine('validate');"
}
}
this is the sample colModel of University,
here the field which we want to submit to controller should be given in name attribute,
and the field which is to be displayed in grid should be given in jsonmap attribute.
I am facing a certain issue while trying to bind the KendoUi Grid with the Json data from the controller. Things seem fine and my Json object contains data but still grid is not showing any thing:
And I am getting this error in chrome JavaScript console:
GET http://localhost:8084/Records?take=5&skip=0&page=1&pageSize=5 500 (Internal Server Error)
In View:
<div id="grid">
</div>
<div>
<script type="text/javascript">
$(document).ready(function () {
$("#grid").kendoGrid({
dataSource: {
type: "json",
serverPaging: true,
pageSize: 5,
groupable: true,
selectable: "row",
transport: { read: { url: "Records", dataType: "json"} }
},
height: 400,
scrollable: true,
sortable: true,
filterable: true,
pageable: true,
columns: [
{ field: "No", title: "No" },
{ field: "Desc", title: "Description" },
{ field: "priority", title: "Priority" },
{ field: "decision", title: "Decision" }
],
dataBound: function () {
this.expandRow(this.tbody.find("tr.k-master-row").first());
}
});
});
</script>
In Controller:
public ActionResult GetRecords()
{
var obj = new User();
var jsnRslt = obj.GetResult(Session["id"].ToString());
//return Json(jsnRslt);
return Json(jsnRslt, JsonRequestBehavior.AllowGet); //Changed as suggested by Dismissile
}
In Model:
public object GetResult(string usrId)
{
…
….
….. try
{
int i = 0;
if (rcrds != null || rcrds.HasRows)
{
jsonWriter.WriteStartObject();
while (rcrds.Read())
{
for (int j = 0; j < rcrds.FieldCount; j++)
{
jsonWriter.WritePropertyName(rcrds.GetName(j));
jsonWriter.WriteValue(rcrds.GetValue(j));
}
i++;
}
jsonWriter.WriteEndObject();
}
}
catch (Exception ex) { }
return jsonWriter;
}
}
Kindly help.
You probably need this in your JSON call:
return Json(jsnRslt, JsonRequestBehavor.AllowGet);
It looks like you are doing a GET call, and by default GET is not allowed on a JSON call.
Try to use the transport attribute within dataSource, something like this:
<script type="text/javascript">
var dataSource = new kendo.data.DataSource({
batch: true,
schema: {
model: {
id: "EmployeeID",
fields: {
EmployeeID: { editable: true, validation: { required: true } },
EmployeeName: { validation: { required: true } }
}
}
},
transport: {
read: {
url: "/Home/GetData",
type: "GET"
},
update: {
url: "/Home/Update",
type: "POST",
contentType: 'application/json'
},
destroy: {
url: "/Home/Destroy",
type: "POST",
contentType: 'application/json'
},
create: {
url: "/Home/Create",
type: "POST",
contentType: 'application/json'
},
pageSize: 1,
parameterMap: function (options, operation) {
if (operation !== "read" && options.models) {
return kendo.stringify(options.models) ;
}
}
}
});
$(document).ready(function () {
$("#grid").kendoGrid({
dataSource: dataSource,
navigatable: true,
pageable: true,
height: 430,
sortable: true,
toolbar: ["create", "save", "cancel"],
columns: [
{ field: "EmployeeID", title: "Employee ID", width: 110 },
{ field: "EmployeeName", title: "Employee Name", width: 110 },
{ command: "destroy", title: "Delete", width: 90 }],
editable: true,
selectable: "multiple row",
groupable: true,
navigatable: true,
filterable: true
});
});
</script>
The controller:
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
var employee = new Employee().GetEmployeeList();
return View(employee);
}
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult GetData()
{
var obj = new Employee();
return Json(obj.GetEmployeeList(), JsonRequestBehavior.AllowGet);
}
[HttpPost]
public JsonResult Update(List<Employee> model)
{
var obj = new Employee();
//return View();
return Json(obj);
}
[HttpPost]
public JsonResult Create(List<Employee> model)
{
var obj = new Employee();
return Json(obj);
}
public ActionResult Destroy(Employee model)
{
return View();
}
}
Return a html view from index method to hold the grid &
I think you also need to add a datasource request in your ActionMethod Parm
public ActionResult GetResult([DatasourceRequest]request, string usrId)
return Json(jsnRslt.ToDatasourceResult(request), JsonRequestBehavior.AllowGet);
Every kendogrid needs this
Try this
transport: { read: { url: "Records", dataType: "jsonp"} }
try with jsonp instead of json.
You are returning an ActionResult, it should be a JsonResult instead.
I am using the free Kendo web controls. I have used the grid view in several places before and decided to use the popup style editing for my current project.
I have most of it working. I have three combo boxes for category, bank account and payee and when I edit an existing item, the model object passed back to my MVC action has the correct values in it. However, when I click on the create button, the three combo box values are returned as null to the controller.
Here is the CSHTML code for this view:
#using System
#using System.Linq
#{
ViewBag.Title = "Transactions";
}
#section Head
{
<link href="~/Content/kendo/kendo.common.min.css" rel="stylesheet" />
<link href="~/Content/kendo/kendo.default.min.css" rel="stylesheet" />
<script src="~/Scripts/kendo/kendo.web.min.js"> </script>
}
#section featured
{
<section class="featured">
<div class="content-wrapper">
<hgroup class="title">
<h1>#ViewBag.Title</h1>
</hgroup>
</div>
</section>
}
<div id="grid"></div>
<script>
$(function() {
$("#grid").kendoGrid({
height: 350,
toolbar: [{ name: "create", text: "Create New Transaction" }],
columns:
[
{ field: "Date", width: "100px", template: '#= kendo.toString(Date,"MM/dd/yyyy") #' },
{ field: "Amount", format: "{0:c}", width: "100px" },
{ field: "Category", width: "80px", editor: categoryDropDownEditor, template: "#=Category.Name#" },
{ field: "BankAccount", title: "Account", width: "80px", editor: bankAccountDropDownEditor, template: "#=BankAccount.Name#" },
{ field: "Payee", width: "80px", editor: payeeDropDownEditor, template: "#=Payee.Name#" },
{ command: ["edit", "destroy"], title: " ", width: "160px" }
],
editable: { mode: "popup", confirmation: "Are you sure you want to delete this transaction?" },
pageable:
{
refresh: true,
pageSizes: true
},
sortable: true,
filterable: false,
dataSource:
{
serverPaging: true,
serverFiltering: true,
serverSorting: true,
pageSize: 7,
schema:
{
data: "Data",
total: "Total",
model:
{
id: "Id",
fields:
{
Id: { editable: false, nullable: true },
Date: { type: "Date" },
Amount: { type: "number", validation: { required: true, min: 0 } },
Category: { validation: { required: true } },
BankAccount: { validation: { required: true } },
Payee: { validation: { required: true } },
Note: { validation: { required: false } }
}
}
},
batch: false,
transport:
{
create:
{
url: "#Url.Action("Create", "Transaction")",
contentType: "application/json",
type: "POST"
},
read:
{
url: "#Url.Action("Read", "Transaction")",
contentType: "application/json",
type: "POST"
},
update:
{
url: "#Url.Action("Update", "Transaction")",
contentType: "application/json",
type: "POST"
},
destroy:
{
url: "#Url.Action("Delete", "Transaction")",
contentType: "application/json",
type: "POST"
},
parameterMap: function(data)
{
return JSON.stringify(data);
}
}
}
});
function categoryDropDownEditor(container, options)
{
$('<input required data-text-field="Name" data-value-field="Id" data-bind="value:' + options.field + '"/>')
.appendTo(container)
.kendoDropDownList(
{
autoBind: true,
dataValueFileld: "Id",
dataTextField: "Name",
dataSource:
{
type: "json",
transport: { read: "#Url.Action("GetCategories", "Transaction")" }
}
});
}
function bankAccountDropDownEditor(container, options)
{
$('<input required data-text-field="Name" data-value-field="Id" data-bind="value:' + options.field + '"/>')
.appendTo(container)
.kendoDropDownList(
{
autoBind: true,
dataValueFileld: "Id",
dataTextField: "Name",
dataSource:
{
type: "json",
transport: { read: "#Url.Action("GetBankAccounts", "Transaction")" }
}
});
}
function payeeDropDownEditor(container, options)
{
$('<input required data-text-field="Name" data-value-field="Id" data-bind="value:' + options.field + '"/>')
.appendTo(container)
.kendoDropDownList(
{
autoBind: true,
dataValueFileld: "Id",
dataTextField: "Name",
dataSource:
{
type: "json",
transport: { read: "#Url.Action("GetPayees", "Transaction")" }
}
});
}
});
</script>
The binding to the kendo combo box must be working, otherwise the edit would fail as well. All I can think is that the object is not created correctly. Also, it selects the first item in the combo box by default, but even so, does not bind the value.
Following is the code for my create and update actions:
[HttpPost]
public ActionResult Create(TransactionModel transactionModel)
{
var transaction = _moneyBO.CreateTransaction();
Mapper.Map(transactionModel, transaction);
_moneyBO.UpdateTransaction(transaction);
return Json(Mapper.Map<TransactionModel>(transaction));
}
public ActionResult Update(TransactionModel transactionModel)
{
var transaction = _moneyBO.Transactions.SingleOrDefault(x => x.Id == transactionModel.Id);
if (transaction == null)
return View("NotFound");
Mapper.Map(transactionModel, transaction);
_moneyBO.UpdateTransaction(transaction);
return Json(Mapper.Map<TransactionModel>(transaction));
}
I have not found a good example using the popup custom edit. The example on the Kendo site works inline, but if you change the example to popup it does not work.
I have a same problem. Write, if you solve it, please
I found, that Kendo think that "null" (default for int?) is ObservableObject (while initialization of ComboBox), thats why it can't be parsed to "number". If you edit item (not create), value id not "null" and model bindind work's fine
Not sure if it's the only issue here but in your code example it looks like the initialization of your dropdown isn't quite correct. You have written dataValueFileld which should be dataValueField
kendoDropDownList({
autoBind: true,
dataValueFileld: "Id", <-- Incorrect spelling
dataTextField: "Name",
dataSource:
{
type: "json",
transport: { read: "#Url.Action("GetPayees", "Transaction")" }
}
});
I want to add grouping to my jqGrid. I have a simple model:
public class ViolationViewModel
{
[JqGridColumnFormatter(JqGridColumnPredefinedFormatters.Date, SourceFormat = "d.m.Y H:i:s", OutputFormat = "d.m.Y H:i")]
public DateTime FixationTime { get; set; }
public string OrderNumber { get; set; }
public string ViolationType { get; set; }
}
This is code in a view:
#{
var grid = new JqGridHelper<ViolationViewModel>("myGrid",
dataType: JqGridDataTypes.Json,
methodType: JqGridMethodTypes.Post,
pager: true,
sortingName: "ViolationType",
sortingOrder: JqGridSortingOrders.Asc,
url: Url.Action("Violation", "Cabinet"),
viewRecords: true,
rowsList: new List<int>() { 10, 20, 30, 50, 100 },
loadOnce: true,
multiSelect: true,
autoWidth: true,
groupingEnabled: true,
groupingView: new JqGridGroupingView { ColumnShow = new[] { false }, Fields = new[] { "ViolationType" }, DataSorted = true},
).FilterToolbar(new JqGridFilterToolbarOptions() { StringResult = true })
.Navigator(new JqGridNavigatorOptions() { Add = false, Delete = false, Edit = false, View = false, Refresh = false, Search = false });
}
I have 16 records, 15 records with the same ViolationType, 1 record have other value .
The problem with that jqGrid create three (must be two) groups, but there are two groups have the same caption. But when I click on any column (change sort) then all works fine and I have two groups.
Where is a problem?
One more question: in the rowList option I have the first value as 10. But when my grid is loaded default value is 20. How to setup it to first value?
You have set DataSorted to true. In this case the data from the initial request should be already properly sorted (by 'ViolationType') on the server side and returned in proper order.
For the second part of your question, just set rowsNumber to 10 for intial value:
#{
var grid = new JqGridHelper<ViolationViewModel>("myGrid",
dataType: JqGridDataTypes.Json,
methodType: JqGridMethodTypes.Post,
pager: true,
sortingName: "ViolationType",
sortingOrder: JqGridSortingOrders.Asc,
url: Url.Action("Violation", "Cabinet"),
viewRecords: true,
rowsList: new List<int>() { 10, 20, 30, 50, 100 },
rowsNumber: 10,
loadOnce: true,
multiSelect: true,
autoWidth: true,
groupingEnabled: true,
groupingView: new JqGridGroupingView { ColumnShow = new[] { false }, Fields = new[] { "ViolationType" }, DataSorted = true},
)
.FilterToolbar(new JqGridFilterToolbarOptions() { StringResult = true })
.Navigator(new JqGridNavigatorOptions() { Add = false, Delete = false, Edit = false, View = false, Refresh = false, Search = false });
}
I have a jqGrid in MVC setup as:
<script type="text/javascript">
jQuery(document).ready(function () {
jQuery("#list").jqGrid({
url: '/Home/DynamicGridData/',
datatype: 'json',
mtype: 'POST',
colNames: ['id', 'note', 'tax', 'PaymentType', 'CreatedByUsername', 'Actions'],
colModel: [
{ name: 'id', index: 'id', hidden: true, key: true, editable: true, editrules:{ required:false } },
{ name: 'note', index: 'note', width: 40,align:'center', editable: true, editrules: { required : true } },
{ name: 'tax', index: 'tax', width: 400, align: 'center', editable: true, editrules: { required : true } },
{ name: 'PaymentTypeId', index: 'PaymentTypeId', width: 400, align: 'center', editable: true, edittype:"select",
editoptions: { dataUrl: '/Home/PaymentTypes/' }},
{ name: 'CreatedByUsername', index: 'CreatedByUsername', hidden: true, editable: true, editrules:{ required:false } },
{ name: 'act',index:'act',width:55,align:'center',sortable:false,formatter:'actions',
formatoptions:{
keys: true, // we want use [Enter] key to save the row and [Esc] to cancel editing.
beforeSubmit:function(postdata, formid) {
alert("Hi");
jQuery("#ValidationSummary").show('slow');
},
},}
],
pager: jQuery('#pager'),
rowNum: 10,
rowList: [5, 10, 20, 50],
sortname: 'Id',
sortorder: "desc",
viewrecords: true,
imgpath: '',
caption: 'My first grid',
editurl: '/Home/Save/'
});
jQuery("#list").navGrid('#pager', { edit: false, search: false, add: true });
});
</script>
When creating on submit it goes to the save method on the controller.
As you can see CreatedByUsername and id are hidden for normal view and edit.
When I add new data though I find that ModelState.IsValid = false.
I've managed to make it so that CreatedByUsername does not added an error to the ModelState even though no data is added for it on insert through editrules:{ required:false }.
The problem is this same piece of code doesn't work for the id and is adding an Error to the ModelState of "The id field is required."
Can anyone please tell me how to prevent this from happening for the id field?
The problem is that your id field has the key attribute and besides your validation rule this field is required. A workaround is to define a custom function for validation an always return true
Can you set the id field in your viewmodel to nullable? Like this:
public int? Id { get; set; }
If the id is not nullable, mvc will automatically create a validation error. If you're not using a viewmodel, but just using parameters on your save action, you can set the id parameter to nullable.