How to populate dependent dropdowns in angular 7 dynamic forms? - javascript

I have a dynamic form that has more than one dropdown and one of dropdown dependent another. The problem is that I couldn't populate the dependent dropdown.
jobDepartmentDropdownOnChange(e: any) {
this.personnelRecruitmentInfoService.getAllByJobDepartmentId(e.value).subscribe(
res => {
if (res.code === ResponseCode.success) {
res.dataList.map(item => {
this.personnels.push({
value: item.personnel.id.toString(),
label: item.personnel.dataValue
});
});
if (this.formComponent !== undefined) {
this.formComponent.form.controls['personnelId'].patchValue(this.personnels);
}
}
}
);
}
const personnelDropdown = new InputDropdown({
key: 'personnelId',
label: '',
options: this.personnels,
value: '',
disabled: true,
required: false,
multiple: true,
order: 20
});
public personnels: any[] = [];
const jobDepartmentDropdown = new InputDropdown({
key: 'jobDepartmentId',
label: '',
options: this.jobDepartments,
value: '',
onChange: this.jobDepartmentDropdownOnChange.bind(this),
disabled: true,
required: false,
multiple: true,
order: 19
});
export class InputDropdown extends InputBase<string> {
controlType = 'dropdown';
options: { value: string, label: string }[] = [];
showSelect = false;
multiple = false;
constructor(options: {} = {}) {
super(options);
this.options = options['options'] || [];
this.showSelect = options['showSelect'] || false;
this.multiple = options['multiple'] || false;
}
}
How can I populate personnelId dropdown according to the previous dropdown?

You would be way better of using a subject for your personnels over triggering angulars change detection cycle way too often performance wise. But if you simply wonder why your solution doesn't work regardless of performance, you're pushing elements in your personnels object not actually changing the reference held by this.personnels. This means the angular change detection won't be triggered. If you do want to trigger the change detection, alter your "push" like this:
jobDepartmentDropdownOnChange(e: any) {
this.personnelRecruitmentInfoService.getAllByJobDepartmentId(e.value).subscribe(
res => {
if (res.code === ResponseCode.success) {
res.dataList.map(item => {
// Use the spread operator to create a new array hence
// updating the reference in the object angular detects changes upon.
this.personnels = [...this.personnels, {
value: item.personnel.id.toString(),
label: item.personnel.dataValue
}];
});
if (this.formComponent !== undefined) {
this.formComponent.form.controls['personnelId'].patchValue(this.personnels);
}
}
}
);
}

I found solution:
const jobDepartmentDropdown = new InputDropdown({
key: 'jobDepartmentId',
label: '',
options: this.jobDepartments,
value: '',
onChange: this.jobDepartmentDropdownOnChange.bind(this),
disabled: true,
required: false,
multiple: true,
order: 19
});
((this.inputs as InputBase<any>[]).find(i => i.key === 'personnelId') as InputDropdown).options = options;

Related

Filtering out a calculated value in in an object

I tried to only show the relevant objects only and strip as much as possible non-relevant things.
allComponentsFiltered returns a combination of 3 things,
search input
component_group_id
selectedComponent .status
// Desired objective:
I've created a tab with a value of inactive, which does not match a component status status.inactive
The idea is to return in the filter function the components that do not have status.active as true.
const state = {
componentStatusTabs: [
{ name: "All", value: "all", icon: "mdi-all-inclusive" },
{ name: "Starred", value: "starred", icon: "mdi-star" },
{ name: "Modular", value: "modular", icon: "mdi-view-module" },
{ name: "Active", value: "active", icon: "mdi-lightbulb-on" },
{ name: "Inactive", value: "inactive", icon: "mdi-lightbulb-off" }
],
};
// How the component statuses object looks.
selectedComponent = {
component_group_id: 81,
status: {
active: true,
modular: false,
starred: false,
}
}
// Returns the name of the tab name selected within the form field editor
activeComponentEditFormFieldsStatusTabName: state => {
return state.componentStatusTabs[state.activeStatusTab].value;
},
// Returns components that either belong to the selected group, matches the search string or has the corresponding status.
allComponentsFiltered: (state, getters, rootState) => {
if (!getters.hasSelectedSomeGroups) return [];
const search = rootState.application.search.toLowerCase();
return state.allComponents.filter(component => {
return (
(search === "" || component.config.general_config.title.toLowerCase().match(search)) &&
(getters.activeComponentEditFormFieldsStatusTabName === "all" || component.status[getters.activeComponentEditFormFieldsStatusTabName]) &&
state.selectedComponentGroups.some(group => group.id === component.component_group_id)
);
});
}
Answering to my self after I found the solution:
allComponentsFiltered: (state, getters, rootState) => {
if (!getters.hasSelectedSomeGroups) return [];
const search = rootState.application.search.toLowerCase();
return state.allComponents.filter(component => {
return (
(search === "" || component.config.general_config.title.toLowerCase().match(search)) &&
(getters.activeComponentEditFormFieldsStatusTabName === "all" ||
(getters.activeComponentEditFormFieldsStatusTabName === "inactive" && !component.status.active) ||
component.status[getters.activeComponentEditFormFieldsStatusTabName]) &&
state.selectedComponentGroups.some(group => group.id === component.component_group_id)
);
});
},
I added
|| (getters.activeComponentEditFormFieldsStatusTabName === "inactive" && !component.status.active)

Update array of object state in react js

I have the following code snippet from my component where I generate Input field according to the objects in the state.
I can successfully generate the input fields but have been getting error message:
TypeError: Cannot read property 'map' of undefined
Pointing to the method arrayObjToArrary in Utils.js whenever I type in the input field.
How can I update the value of here ??
Main.js
import Input from "../UI/Input";
import {arrayObjToArrary} from "../../utility/Utils.js";
const [profiles, setProfiles] = useState({
controls: [
{
network: {
elementType: "input",
elementConfig: {
type: "text",
label: "Network",
},
value: "Twitter",
},
},
{
username: {
elementType: "input",
elementConfig: {
type: "text",
label: "Username",
},
value: "#john",
},
},
{
url: {
elementType: "input",
elementConfig: {
type: "url",
label: "URL",
},
value: "https://example.xyz",
},
},
],
});
const profilesControls = arrayObjToArrary(profiles.controls);
const arrayInputHandler = (event, index, identifier) => {
const list = [...profiles.controls];
list[index][identifier] = event.target.value;
setProfiles(list);
};
let profileField = profilesControls.map((formElement) => (
<Input
label={formElement.config.elementConfig.label}
key={formElement.index}
type={formElement.config.elementType}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
changed={(event) => arrayInputHandler(event, formElement.index, formElement.id)}
/>
));
Utils.js
export const arrayObjToArrary = (controls) => {
const formElementsArray = controls.map((item,index) =>({
id: Object.keys(item)[0],
index:index,
config: item[Object.keys(item)[0]],
}))
return formElementsArray;
}
You can try this
const arrayInputHandler = (event, index, identifier) => {
const list = [...profiles.controls];
list[index][identifier].value = event.target.value;
setProfiles({ controls: list });
};
check here codesandbox
The issue in how you update your profiles object in arrayInputHandler. When you pass in the list to setProfiles, it changes its structure from an object to array.
Also you must not mutate the original state values. The correct way to update is as below
const arrayInputHandler = (event, index, identifier) => {
const value = event.target.value;
setProfiles(prev => ({
...prev,
controls: profiles.controls.map((controls, i) => {
if(i === index) {
return {
...controls, [identifier]: {
...controls[identifier],
value
}
}
}
return controls
});
}));
};
P.S. You can always solve your problem in a simplistic manner like
const arrayInputHandler = (event, index, identifier) => {
const list = [...profiles.controls];
list[index][identifier] = event.target.value;
setProfiles({profile:list});
};
However its not a correct approach and should be avoided as react relies a lot on immutability for a lot of its re-rendering and other optimizations

RegExp for email validation is not reading string properly (using React, no JSX)

I am trying to get my "submit" button to be enabled or disabled based on the validity of the email that is passed. However, the RegExp expression I am using is not reading the string properly. I've tried testing my isWorking() function with just email.length > 0 to make sure that whatever is being passed to isWorking() is indeed a string, and we are good there but RegExp is always returning false regardless of the input it receives. I'm using React without JSX in this app. Any help at all would be deeply appreciated. Thank you so much!
const validEmailRegex = RegExp(/^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
class Signup extends React.Component {
constructor() {
super();
this.state = {
email: ""
};
this.handleSubmit = this.handleSubmit.bind(this);
}
isWorking () {
const email = event.target;
//if (email.length > 0) {
// return false;
// }
// return true;
if (validEmailRegex.test(email) === true) {
return false;
}
return true;
}
handleSubmit(event) {
event.preventDefault();
if (!event.target.checkValidity()) {
this.setState({
invalid: true,
displayErrors: true,
});
return;
}
const form = event.target;
const data = new FormData(form);
for (let name of data.keys()) {
const input = form.elements[name];
const parserName = input.dataset.parse;
console.log('parser name is', parserName);
if (parserName) {
const parsedValue = inputParsers[parserName](data.get(name));
data.set(name, parsedValue);
}
}
this.setState({
res: stringifyFormData(data),
invalid: false,
displayErrors: false,
});
}
render() {
const { res, invalid, displayErrors } = this.state;
//pass data to the button for disabling or not
const isEnabled = this.isWorking();
return (
React.createElement("div", { className: "container" },
React.createElement("div", { className: "row" },
React.createElement("form", { onSubmit: this.handleSubmit, noValidate: true, className: displayErrors ? 'displayErrors' : '' },
React.createElement("input", { className: "form-control", name: "formEmail", id: "formEmail", type: "email", placeholder: "email"}),
),
React.createElement("span", { className: "span"},
React.createElement("fieldset", { className: "form-group" },
React.createElement(Link, { className: "nav-link", activeClassName: "nav-link-active", to: "/contact" },
React.createElement("button", { className: "button1", disabled: isEnabled, type: "button"}, "next")
),
)
),
),
)
);
}
}
Your isWorking() does not receive event from anywhere. Also, event.target will be an HTML element and definitely not an input value. For a form, you do event.target.elements["<name_of_input>"] (here, name of input if formEmail) to get input value.

ReactJS -> problem with 'clearing' input forms

I've got simple form.
Every time I try to fill a form, the rest of the fields clears.
In addition validation errors are displayed below each field instead of just one. How to fix this?
This how it looks before any action: https://i.imgur.com/zjGsNRL.png
And this how it looks after wrong data: https://i.imgur.com/pSh6rFM.png
My constructor:
constructor(props) {
super(props);
this.state = {
title: {
text: ''
},
foods: [{
text: '',
kcal: '',
protein: '',
carbohydrate: '',
fat: ''
}],
};
Handlers and validation f():
handleKcalChange(event, index) {
const foods = this.state.foods.slice();
const value = event.target.value;
foods[index] = {
kcal: value,
...this.validateParam(value)
}
this.setState({
foods: foods
});
}
handleProteinChange(event, index) {
const foods = this.state.foods.slice();
const value = event.target.value;
foods[index] = {
protein: value,
...this.validateParam(value)
}
this.setState({
foods: foods
});
}
validateParam = (paramNumber) => {
if(paramNumber < 0) {
return {
validateStatus: 'error',
errorMsg: 'Number can`t be smaller than 0.'
}
} else if(paramNumber > 1000) {
return {
validateStatus: 'error',
errorMsg: 'Number is too big!'
}
} else {
return {
validateStatus: 'success',
errorMsg: null
}
}
}
One issue may be here in handleKcalEvent
foods[index] = {
kcal: value,
...this.validateParam(value)
}
you aren't changing the kcal of the object at the index, you're changing the object to a new object that just has kcal has a parameter, thereby erasing all of the other information (text, fat, carbs etc.)
try something like
const current = foods[index];
foods[index] = {...current,
kcal: value,
...this.validateParam(value)
}

javascript: Alter the output of a function to the specified decimal place

I am not very good with my javascript but recently needed to work with a library to output an aggregated table. Was using fin-hypergrid.
There was a part where I need to insert a sum function (rollups.sum(11) in this example)to an object so that it can compute an aggregated value in a table like so:
aggregates = {Value: rollups.sum(11)}
I would like to change this value to return 2 decimal places and tried:
rollups.sum(11).toFixed(2)
However, it gives the error : "rollups.sum(...).toFixed is not a function"
If I try something like:
parseFloat(rollups.sum(11)).toFixed(2)
it throws the error: "can't assign to properties of (new String("NaN")): not an object"
so it has to be a function object.
May I know if there is a way to alter the function rollups.sum(11) to return a function object with 2 decimal places?
(side info: rollups.sum(11) comes from a module which gives:
sum: function(columnIndex) {
return sum.bind(this, columnIndex);
}
)
Sorry I could not post sample output here due to data confidentiality issues.
However, here is the code from the example I follow. I basically need to change rollups.whatever to give decimal places. The "11" in sum(11) here refers to a "column index".
window.onload = function() {
var Hypergrid = fin.Hypergrid;
var drillDown = Hypergrid.drillDown;
var TreeView = Hypergrid.TreeView;
var GroupView = Hypergrid.GroupView;
var AggView = Hypergrid.AggregationsView;
// List of properties to show as checkboxes in this demo's "dashboard"
var toggleProps = [{
label: 'Grouping',
ctrls: [
{ name: 'treeview', checked: false, setter: toggleTreeview },
{ name: 'aggregates', checked: false, setter: toggleAggregates },
{ name: 'grouping', checked: false, setter: toggleGrouping}
]
}
];
function derivedPeopleSchema(columns) {
// create a hierarchical schema organized by alias
var factory = new Hypergrid.ColumnSchemaFactory(columns);
factory.organize(/^(one|two|three|four|five|six|seven|eight)/i, { key: 'alias' });
var columnSchema = factory.lookup('last_name');
if (columnSchema) {
columnSchema.defaultOp = 'IN';
}
//factory.lookup('birthState').opMenu = ['>', '<'];
return factory.schema;
}
var customSchema = [
{ name: 'last_name', type: 'number', opMenu: ['=', '<', '>'], opMustBeInMenu: true },
{ name: 'total_number_of_pets_owned', type: 'number' },
{ name: 'height', type: 'number' },
'birthDate',
'birthState',
'employed',
{ name: 'income', type: 'number' },
{ name: 'travel', type: 'number' }
];
var peopleSchema = customSchema; // or try setting to derivedPeopleSchema
var gridOptions = {
data: people1,
schema: peopleSchema,
margin: { bottom: '17px' }
},
grid = window.g = new Hypergrid('div#json-example', gridOptions),
behavior = window.b = grid.behavior,
dataModel = window.m = behavior.dataModel,
idx = behavior.columnEnum;
console.log('Fields:'); console.dir(behavior.dataModel.getFields());
console.log('Headers:'); console.dir(behavior.dataModel.getHeaders());
console.log('Indexes:'); console.dir(idx);
var treeView, dataset;
function setData(data, options) {
options = options || {};
if (data === people1 || data === people2) {
options.schema = peopleSchema;
}
dataset = data;
behavior.setData(data, options);
idx = behavior.columnEnum;
}
// Preset a default dialog options object. Used by call to toggleDialog('ColumnPicker') from features/ColumnPicker.js and by toggleDialog() defined herein.
grid.setDialogOptions({
//container: document.getElementById('dialog-container'),
settings: false
});
// add a column filter subexpression containing a single condition purely for demo purposes
if (false) { // eslint-disable-line no-constant-condition
grid.getGlobalFilter().columnFilters.add({
children: [{
column: 'total_number_of_pets_owned',
operator: '=',
operand: '3'
}],
type: 'columnFilter'
});
}
window.vent = false;
//functions for showing the grouping/rollup capabilities
var rollups = window.fin.Hypergrid.analytics.util.aggregations,
aggregates = {
totalPets: rollups.sum(2),
averagePets: rollups.avg(2),
maxPets: rollups.max(2),
minPets: rollups.min(2),
firstPet: rollups.first(2),
lastPet: rollups.last(2),
stdDevPets: rollups.stddev(2)
},
groups = [idx.BIRTH_STATE, idx.LAST_NAME, idx.FIRST_NAME];
var aggView, aggViewOn = false, doAggregates = false;
function toggleAggregates() {
if (!aggView){
aggView = new AggView(grid, {});
aggView.setPipeline({ includeSorter: true, includeFilter: true });
}
if (this.checked) {
grid.setAggregateGroups(aggregates, groups);
aggViewOn = true;
} else {
grid.setAggregateGroups([], []);
aggViewOn = false;
}
}
function toggleTreeview() {
if (this.checked) {
treeView = new TreeView(grid, { treeColumn: 'State' });
treeView.setPipeline({ includeSorter: true, includeFilter: true });
treeView.setRelation(true, true);
} else {
treeView.setRelation(false);
treeView = undefined;
delete dataModel.pipeline; // restore original (shared) pipeline
behavior.setData(); // reset with original pipeline
}
}
var groupView, groupViewOn = false;
function toggleGrouping(){
if (!groupView){
groupView = new GroupView(grid, {});
groupView.setPipeline({ includeSorter: true, includeFilter: true });
}
if (this.checked){
grid.setGroups(groups);
groupViewOn = true;
} else {
grid.setGroups([]);
groupViewOn = false;
}
}
you may try:
(rollups.sum(11)).toFixed(2)
enclosing number in parentheses seems to make browser bypass the limit that identifier cannot start immediately after numeric literal
edited #2:
//all formatting and rendering per cell can be overridden in here
dataModel.getCell = function(config, rendererName) {
if(aggViewOn)
{
if(config.columnName == "total_pets")
{
if(typeof(config.value) == 'number')
{
config.value = config.value.toFixed(2);
}
else if(config.value && config.value.length == 3 && typeof(config.value[1]) == 'number')
{
config.value = config.value[1].toFixed(2);
}
}
}
return grid.cellRenderers.get(rendererName);
};

Categories