How can I add rows or delete row dynamically in Lightening data table - apex

//component
<aura:component controller="LookupRelatedContactC"
implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
<aura:attribute name="selectedLookUpRecord" type="sObject" default="{}"/>
<aura:attribute name="data" type="Object"/>
<aura:attribute name="columns" type="List"/>
<aura:attribute name="selectedRows" type="List"/>
<aura:attribute name="rowIndex" type="String"/>
<aura:attribute name="options" type="List" default="[
{'label': 'Contacts', 'value': 'option1'},
{'label': 'Opportunities', 'value': 'option2'}]"/>
<aura:attribute name="value" type="String" default="option1"/>
<aura:handler name="change" value="{!v.selectedLookUpRecord}" action="{!c.selectedLookupChanged}"/>
<lightning:card title = 'Search by Account Name' >
<c:LookupReusable_Parent objectAPIName="account" IconName="standard:account"
selectedRecord="{!v.selectedLookUpRecord}"
label="Accounts"/>
<lightning:radioGroup name="radioGroup"
label="Select any one of these"
options="{! v.options }"
value="{! v.value }"
type="radio"/>
<br/><br/>
<lightning:datatable columns="{! v.columns }"
data="{! v.data }"
keyField="id"
hideCheckboxColumn="true"
onrowaction="{!c.removeRecord}"/><br></br>
<lightning:button variant="brand" label="Save" title="Brand action" onclick="{! c.handleClick}" />
<lightning:button variant="brand" label="Add Row" title="Brand action" onclick="{! c.handleClick}" />
</lightning:card>
</aura:component>
//js controller
({
selectedLookupChanged:function(component, event, helper) {
component.set('v.columns', [
{label: 'Contact Name',
fieldName: 'Name',
editable: true,
type: 'text'},
{label: 'Phone',
fieldName: 'Phone',
type: 'phone'},
{label: "Action",type: "button", typeAttributes: {
label: 'Delete',
name: 'delete',
title: 'Delete',
disabled: false,
value: 'delete',
iconPosition: 'left'
}},
]);
var aId = component.get("v.selectedLookUpRecord").Id;
var action=component.get('c.getCon');
action.setParams({accId : aId});
action.setCallback(this, function(response){
var state = response.getState();
if (state === "SUCCESS") {
var rows1 = response.getReturnValue();
component.set("v.data", rows1);
}
});
$A.enqueueAction(action);
}
})
//apex class
public class LookupRelatedContactC {
#AuraEnabled
public static List<Contact> getCon(List<String> accId) {
return [SELECT Id, Name, Phone FROM Contact WHERE AccountId =:accId];
}
}
In the above code I am stuck at the point where I am not able to add rows or delete any row dynamically in the lightning data table what I have found on the internet mostly people I have done with it aura iteration but not with data table. So when I press on delete it should fire js event or when I add row then it should fire js I dont want to call server side method again and again to add or delete. So, once I done with the delete or add row then after pressing Save button then it should fire server side method once and should save into the database Please help

Related

React-hook-form + dynamic form: Render element upon dropdown selection

I am working in form using react-hook-form. This form use useFieldArray, it has to be dynamic.
Right now is very simple, it contains a react-select component with a few options and a textfield that get rendered depending on the option that the user select on the select component.
The problem I have is that the textfield component renders when the state updates, which is correct until I add a new group of element to the form. Since the textfield is listening to the same state it doesn't matter which select I use to render the textfield element, it gets rendered in all groups.
I am looking a way to specify which textfield should be rendered when the user change the select.
I the sandbox you can see what I have done. To reproduce the problem click on the "Add"-button and you will see two areas, each one with a select component.
When you choose "Other" in the select component a textfield appears, but not only in the area where the select was changed but in all areas.
How can I avoid that behavior?
https://codesandbox.io/s/vibrant-fast-381q0?file=/src/App.tsx
Extract:
const [isDisabled, setIsDisabled] = useState<boolean>(true);
const { control, handleSubmit, getValues } = useForm<IFormFields>({
defaultValues: {
managerialPositions: [
{
authority: 0,
chiefCategory: 0,
title: 0,
otherTitle: ""
}
]
}
});
useFieldArray implementation:
const {
fields: managerialPositionsFields,
append: managerialPositionsAppend,
remove: managerialPositionsRemove
} = useFieldArray({
name: "managerialPositions",
control
});
Here i update the state when the user select "Other title" in the select component:
const watchChange = (value?: number, i?: number) => {
let values: any = getValues();
if (values.managerialPositions[i].title === 3) {
setIsDisabled(false);
}
};
And here is where I render the button to create a new group of elements and the select component and the textfield that should be rendered if "isDisabled" is false.
{managerialPositionsFields.map((field, index) => {
return (
<Stack className="sectionContainer" key={field.id}>
<Stack horizontal horizontalAlign="space-between">
<StackItem>
<CommandBarButton
iconProps={{ iconName: "AddTo" }}
text="Add"
type="button"
onClick={() => {
managerialPositionsAppend({
authority: 0,
chiefCategory: 0,
title: 0,
otherTitle: ""
});
}}
/>
</StackItem>
</Stack>
<Stack horizontal tokens={{ childrenGap: 20 }}>
<StackItem>
<Label className="select-label requiredIkon">Title</Label>
<Controller
control={control}
name={`managerialPositions.${index}.title`}
render={({ field: { onChange, value, ref } }) => (
<>
<Select
className="react-select-container authoritySelect"
classNamePrefix="react-select"
placeholder="Select title"
options={titelList}
id={`managerialPositions.${index}.title`}
value={
titelList.find((g) => g.value === value)
? titelList.find((g) => g.value === value)
: null
}
onChange={(val) => {
onChange(val.value);
watchChange(val.value, index);
}}
/>
{
// this input is for select validation
<input
tabIndex={-1}
autoComplete="off"
style={{ opacity: 0, height: 0 }}
value={
titelList.find((g) => g.value === value)
? titelList
.find((g) => g.value === value)
.toString()
: ""
}
required={true}
//Without this console will get an error:
onChange={() => {}}
/>
}
</>
)}
/>
</StackItem>
{!isDisabled && (
<StackItem className="">
<Controller
name={`managerialPositions.${index}.otherTitle`}
control={control}
render={({
field: { onChange, name: fieldName, value }
}) => (
<TextField
label="Other title"
name={fieldName}
onChange={(e) => {
onChange(e);
}}
value={value}
/>
)}
/>
</StackItem>
)}
</Stack>
</Stack>
);
})}

Getting asynchronous values from ant-design vue form using onValuesChange

I have a form that submits and send data to the backend using ant-design-vue. However, what I would like to achieve is give the user some form of feedback so while they type in the field they get to see the value {fullname placeholder} updated immediately, and clicking on the submit button sends it to the backend altogether.
{{ fullname || 'Your Name' }}
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }">
<a-form-item label="Full Name">
<a-input
type="text"
placeholder="Your Name"
:disabled="toggleEdit === 'edit'"
v-decorator="[
'fullname',
{
initialValue: this.fullname || '',
rules: [{ required: true, message: 'Name is required!' }],
},
]"
autocomplete="name"
/> </a-form-item
></a-col>
So the {{ fullname }} at the top updates immediately the user types Similar to v-model. But I would like to know how I can achieve this in ant-design-vue form with the onValuesChange method.
You need to use v-model to bind your value on inputfirstly.
meanwhile, use #click on button with submit method
<a-input
type="text"
v-model="inputValue" <---here
placeholder="Your Name"
:disabled="toggleEdit === 'edit'"
v-decorator="['fullname',
{
initialValue: this.fullname || '',
rules: [{ required: true, message: 'Name is required!' }],
},
]"
autocomplete="name"/>
<button #click="submit"></button>
...
data(){
return{
inputValue: ""
}
}
methods:{
submit(){
// I send keyword with a object which include this inputValue by POST method
// you can change it to yours, and keyword as well
fetch("api-url").post({ keyword: this.inputValue })
.then(response.json())
.then(res => { //dosomthing when you get res from back-end })
}
}
Does above code is your requirement?

React-Admin: <ReferenceField> is stuck in loading state, even though data fetch succeeds

I have problem with <ReferenceField>.
On my <List> page, I have rows with UserId and ToolId that I need reference. I want to show user name and tool code.
But these two columns get stuck in loading state, even though the dataProvider, GET_MANY request succeeded for users and tools.
When I console.log(data), it the data is all there but somehow it's not rendered into <ReferenceField>. And It no error is logged at all.
Any idea what am I doing wrong? Or what's happening?
ReferenceFields still loading
EDIT:
I have updated react-admin and now its not displaying loading bar, but just display nothing.
Here is code of List:
export const B_toolList = props => (
<List {...props}>
<Datagrid rowClick="edit">
<TextField source="id" />
<NumberField source="active" />
<NumberField source="time" />
<DateField source="createdAt" />
<DateField source="updatedAt" />
<ReferenceField source="UserId" reference="Users">
<TextField source="name" />
</ReferenceField>
<ReferenceField source="ToolId" reference="Tools">
<TextField source="code" />
</ReferenceField>
</Datagrid>
</List>
);
I also find out that if i try to inspect ReferenceField element with react-dev-tool, There are no values but just "Loading..." note.
But I dont know why... It looks to me that API response is OK.
API res:
Btools fetch:
(2) [{…}, {…}]
0:
ToolId: 1
UserId: 1
active: 1
createdAt: "2020-04-08T16:00:03.000Z"
id: 1
time: 1
updatedAt: "2020-04-08T16:00:03.000Z"
__proto__: Object
1:
ToolId: 1
UserId: 2
active: 1
createdAt: "2020-04-08T16:00:03.000Z"
id: 2
time: 1
updatedAt: "2020-04-08T16:00:03.000Z"
__proto__: Object
length: 2
__proto__: Array(0)
Than Users and Tools fetch:
(2) [{…}, {…}]
0: {id: 1, name: "Tomáš Princ", email: "p***#seznam.cz", createdAt: "2020-04-08T15:59:54.000Z", updatedAt: "2020-04-08T15:59:54.000Z"}
1: {id: 2, name: "jhfgjhfg", email: "admin#admin.cz", createdAt: "2020-04-08T18:44:16.000Z", updatedAt: "2020-04-08T18:44:16.000Z"}
length: 2
__proto__: Array(0)
[{…}]
0: {id: 1, code: "guhf", name: "jhfh", state: "ghf", free: 1, …}
length: 1
__proto__: Array(0)
I log this json data in DataProvider and return it like data: json
switch (type) {
case GET_LIST:
case GET_MANY:
case GET_MANY_REFERENCE:
console.log(json)
console.log(type)
if (!headers.has('content-range')) {
throw new Error(
'The Content-Range header is missing in the HTTP Response. The simple REST data provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?'
);
}
console.log((headers.get('content-range')))
return {
data: json,
total:
//json.length,
parseInt(
headers
.get('content-range')
.split('/')
.pop(),
10
),
};
So it look like app have all data it needs, but for some reason still loading and waiting for something.
EDIT 2:
I tried little workaround by creating custom element, that load userId and toolId and fetches classic query get user/tool by ID for every row. It works.
const ToolCode = ({ record = {} }) => {
const dataProvider = useDataProvider();
const [tool, setTool] = useState();
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
useEffect(() => {
dataProvider.getOne('tools', { id: record.ToolId })
.then(({ data }) => {
setTool(data);
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
})
}, []);
if (loading) return <Loading />;
if (error) return <Error />;
if (!tool) return null;
console.log("fetch tools")
return (
tool.code
)
};
const UserName = ({ record = {} }) => {
const dataProvider = useDataProvider();
const [user, setUser] = useState();
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
useEffect(() => {
dataProvider.getOne('users', { id: record.UserId })
.then(({ data }) => {
setUser(data);
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
})
}, []);
if (loading) return <Loading />;
if (error) return <Error />;
if (!user) return null;
return (
user.name
)
};
const B_toolFilter = (props) => (
<Filter {...props}>
<TextInput label="Search" source="q" alwaysOn />
<TextInput label="Title" source="title" defaultValue="Hello, World!" />
</Filter>
);
export const B_toolList = props => (
<List {...props}>
<Datagrid rowClick="edit">
<TextField source="id" />
<NumberField source="active" />
<NumberField source="time" />
<DateField source="createdAt" />
<DateField source="updatedAt" />
<UserName source="UserId" />
<ToolCode source="ToolId" />
</Datagrid>
</List>
);
Now I can get tool code and user name by IDs form B_Tools, but its much less efficient than GET_MANY method used by ReferenceField..
Nwm. Whole time it was just about capital letters of Tools and Users in
<ReferenceField source="UserId" reference="Users">
and
<ReferenceField source="ToolId" reference="Tools">
I changes it to
<ReferenceField source="UserId" reference="users">
and
<ReferenceField source="ToolId" reference="tools">
Now it works!
I just still dont get why it fetched right resources... It is like it is not capital sensitive when it fetches data, but it is capital sensitive when it renders. I dont know.
Anyway, I would expect some kind of error message with mistake like this... not just right response and empty render.

Bootstrap-vue: Auto-select first hardcoded <option> in <b-form-select>

I'm using b-form-select with server-side generated option tags:
<b-form-select :state="errors.has('type') ? false : null"
v-model="type"
v-validate="'required'"
name="type"
plain>
<option value="note" >Note</option>
<option value="reminder" >Reminder</option>
</b-form-select>
When no data is set for this field I want to auto-select the first option in the list.
Is this possible? I have not found how to access the component's options from within my Vue instance.
your v-model should have the value of the first option.
example
<template>
<div>
<b-form-select v-model="selected" :options="options" />
<div class="mt-3">Selected: <strong>{{ selected }}</strong></div>
</div>
</template>
<script>
export default {
data() {
return {
selected: 'a',
options: [
{ value: null, text: 'Please select an option' },
{ value: 'a', text: 'This is First option' },
{ value: 'b', text: 'Selected Option' },
{ value: { C: '3PO' }, text: 'This is an option with object value' },
{ value: 'd', text: 'This one is disabled', disabled: true }
]
}
}
}
</script>
You can trigger this.selected=${firstOptionValue} when no data is set.
what if we don't know what the first option is. The list is generated?
if you have dynamic data, something like this will work.
<template>
<div>
<b-form-select v-model="selected" :options="options" />
<div class="mt-3">Selected: <strong>{{ selected }}</strong></div>
</div>
</template>
<script>
export default {
data() {
return {
selected: [],
options: [],
};
},
mounted: function() {
this.getOptions();
},
methods: {
getOptions() {
//Your logic goes here for data fetch from API
const options = res.data;
this.options = res.data;
this.selected = options[0].fieldName; // Assigns first index of Options to model
return options;
},
},
};
</script>
If your options are stored in a property which is loaded dynamically:
computed property
async computed (using AsyncComputed plugin)
through props, which may change
Then you can #Watch the property to set the first option.
That way the behavior of selecting the first item is separated from data-loading and your code is more understandable.
Example using Typescript and #AsyncComputed
export default class PersonComponent extends Vue {
selectedPersonId: string = undefined;
// ...
// Example method that loads persons data from API
#AsyncComputed()
async persons(): Promise<Person[]> {
return await apiClient.persons.getAll();
}
// Computed property that transforms api data to option list
get personSelectOptions() {
const persons = this.persons as Person[];
return persons.map((person) => ({
text: person.name,
value: person.id
}));
}
// Select the first person in the options list whenever the options change
#Watch('personSelectOptions')
automaticallySelectFirstPerson(persons: {value: string}[]) {
this.selectedPersonId = persons[0].value;
}
}

Create autocomplete box in Syncfusion

How to create autocomplete box when edit mode is inlineFormTemplate
for eg:
<script id="template" type="text/template">
<input type="text" name="test" value="{{:test}}"/>
//here i need autocomplete textbox like this
<ej-autocomplete id="search1" filter-type="Contains" highlight-search="true" show-rounded-corner="true" enable-auto-fill="true"
enable-distinct="true" show-popup-button="true" watermark-text="Country name" items-count="20" min-character="2"
width="150" popup-width="500px" popup-height="250px"
template="<div width='5%'>${CountryName} ${CountryId}</div>">
<e-autocomplete-fields key="CountryId" text="CountryName" />
<e-datamanager adaptor="UrlAdaptor" url="/country/SelectCountry"></e-datamanager>
</ej-autocomplete>
</script>
We would like to inform you that, In Asp.Net Core, control has been rendered initially. When using the render Template concepts, the control will not be created. To handle this, we have achieved your requirement by rendering the Autocomplete control from client side.Please find code of the sample in the Grid to use the Autocomplete in a Grid column when Editing.
Code:
<ej-grid id="Edittemplate" allow-paging="true">
<e-datamanager url="//mvc.syncfusion.com/Services/Northwnd.svc/Orders/?$top=45" offline="true"></e-datamanager>
<e-edit-settings allow-adding="true" allow-deleting="true" allow-editing="true" edit-mode="Normal" />
<e-toolbar-settings show-toolbar="true" toolbar-items='new List<string>() { "add", "edit", "delete", "update", "cancel", "search" }' />
<e-columns>
<e-column field="OrderID" is-primary-key="true" header-text="Order ID" text-align="Right" width="70"></e-column>
<e-column field="CustomerID" header-text="Customer ID" width="80">
<e-edit-template create="create" read="read" write="write"></e-edit-template>
</e-column>
<e-column field="EmployeeID" header-text="Employee ID" text-align="Left" width="75"></e-column>
<e-column field="Freight" header-text="Freight" text-align="Right" format="{0:C2}" width="75"></e-column>
<e-column field="OrderDate" header-text="Order Date" text-align="Right" width="80" format="{0:MM/dd/yyyy}"></e-column>
<e-column field="ShipCity" header-text="Ship City" width="110"></e-column>
</e-columns>
</ej-grid>
<script type="text/javascript">
function create() {
return $("<input>");
}
function write(args) {
obj = $('#Edittemplate').ejGrid('instance');
args.element.ejAutocomplete({
width: "100%", dataSource: obj.model.dataSource,
query: ej.Query().from("Suppliers").select("CustomerID"),
filterType: "contains",
multiColumnSettings: {
stringFormat: "{0} ({2}) ({1})",
enable: true,
showHeader: true,
columns: [{
field: "CustomerID",
headerText: "CustomerID",
},
{
field: "OrderID",
headerText: "OrderID"
},
{
field: "EmployeeID",
headerText: "EmployeeID"
},
{
field: "ShipCity",
headerText: "ShipCity"
}
]
}, value: args.rowdata !== undefined ? args.rowdata["CustomerID"] : ""
});
}
function read(args) {
args.ejAutocomplete('suggestionList').css('display', 'none');
return args.ejAutocomplete("getValue");
}
$("#Edittemplate").keyup(function (e) {
if (e.keyCode == 40 && $(e.target).hasClass("e-autocomplete")) {
var autocomp = $("#EdittemplateCustomerID").ejAutocomplete("instance")
if (autocomp.getValue() != "" && autocomp.getActiveText() != "No suggestions")
$(e.target).val(autocomp.getActiveText());
}
});