Skip to content

Instantly share code, notes, and snippets.

@seangwright
Last active April 15, 2024 21:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seangwright/21c9dac51882309b2fc8f1387770396b to your computer and use it in GitHub Desktop.
Save seangwright/21c9dac51882309b2fc8f1387770396b to your computer and use it in GitHub Desktop.
Xperience by Kentico - Embedded structured content and the power of custom data types
namespace DancingGoat;
public class Address
{
public const string LIST_FIELD_TYPE = "addresslist";
public string Street { get; set; } = "";
public string City { get; set; } = "";
public string StateProvince { get; set; } = "";
public string PostalCode { get; set; } = "";
public string Country { get; set; } = "";
public string Phone { get; set; } = "";
}
public class AddressListFormComponent : FormComponent<
AddressListFormComponentProperties,
AddressListFormComponentClientProperties,
IEnumerable<Address>>
{
public const string IDENTIFIER = "DancingGoat.FormComponent.AddressList";
public override string ClientComponentName => "@acme/web-admin/AddressList";
}
public class AddressListFormComponentProperties
: FormComponentProperties { }
public class AddressListFormComponentClientProperties
: FormComponentClientProperties<IEnumerable<Address>>
{
public Address NewAddress { get; } = new Address();
}
import { FormComponentProps } from "@kentico/xperience-admin-base";
import { Input } from "@kentico/xperience-admin-components";
import React, { ChangeEvent, useState } from "react";
type Address = {
street: string;
city: string;
stateProvince: string;
postalCode: string;
country: string;
phone: string;
};
interface AddressListFormComponentProps extends FormComponentProps {
newAddress: Address;
value: Address[];
}
export const AddressListFormComponent = (
props: AddressListFormComponentProps
) => {
const [addresses, setAddresses] = useState(
props.value ?? [{ ...props.newAddress }]
);
const handleFieldChange = (
index: number,
event: ChangeEvent<HTMLInputElement>
) => {
if (props.onChange) {
const field = event.target.name.replace(`${index}-`, "") as keyof Address;
const updatedAddress = {
...addresses[index],
[field]: event.target.value,
};
const updatedAddresses = addresses.map((a, i) =>
i === index ? updatedAddress : a
);
setAddresses(updatedAddresses);
props.onChange(updatedAddresses);
}
};
const handleDeleteAddress = (index: number) => {
if (props.onChange) {
const updatedAddresses = addresses.filter((a, i) => i !== index);
setAddresses(updatedAddresses);
props.onChange(updatedAddresses);
}
};
const handleAddressAdd = () => {
if (props.onChange) {
const updatedAddresses = [...addresses, { ...props.newAddress }];
setAddresses(updatedAddresses);
props.onChange(updatedAddresses);
}
};
const fieldStyle = { marginTop: ".5rem" };
return (
<div>
{addresses.map((address, index) => (
<div
key={index}
style={{
marginTop: "2rem",
color: "var(--color-text-default-on-light)",
}}
>
<label>Address {index + 1}</label>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "1rem",
}}
>
<div style={fieldStyle}>
<Input
label="Street"
name={`${index}-street`}
value={address.street}
onChange={(e) => handleFieldChange(index, e)}
/>
</div>
<div style={fieldStyle}>
<Input
label="City"
name={`${index}-city`}
value={address.city}
onChange={(e) => handleFieldChange(index, e)}
/>
</div>
<div style={fieldStyle}>
<Input
label="State/Province"
name={`${index}-stateProvince`}
value={address.stateProvince}
onChange={(e) => handleFieldChange(index, e)}
/>
</div>
<div style={fieldStyle}>
<Input
label="Postal Code"
name={`${index}-postalCode`}
value={address.postalCode}
onChange={(e) => handleFieldChange(index, e)}
/>
</div>
<div style={fieldStyle}>
<Input
label="Country"
name={`${index}-country`}
value={address.country}
onChange={(e) => handleFieldChange(index, e)}
/>
</div>
<div style={fieldStyle}>
<Input
label="Phone"
name={`${index}-phone`}
value={address.phone}
onChange={(e) => handleFieldChange(index, e)}
/>
</div>
<div style={fieldStyle}>
<button
onClick={() => handleDeleteAddress(index)}
style={{ marginTop: "10px" }}
>
Remove
</button>
</div>
</div>
</div>
))}
<button onClick={handleAddressAdd} style={{ marginTop: "20px" }}>
Add Address
</button>
</div>
);
};
using DancingGoat;
[assembly: RegisterModule(typeof(CustomDataTypeModule))]
namespace DancingGoat;
public class CustomDataTypeModule : Module
{
public CustomDataTypeModule() : base(nameof(CustomDataTypeModule)) { }
protected override void OnPreInit(ModulePreInitParameters parameters)
{
DataTypeManager.RegisterDataTypes(
new DataType<IEnumerable<Address>>(
sqlType: "nvarchar(max)",
fieldType: Address.LIST_FIELD_TYPE,
schemaType: "xs:string",
conversionFunc: JsonDataTypeConverter.ConvertToModels,
dbConversionFunc: JsonDataTypeConverter.ConvertToString,
textSerializer: new DefaultDataTypeTextSerializer(Address.LIST_FIELD_TYPE))
{
TypeAlias = "Address",
SqlValueFormat = DataTypeManager.UNICODE,
DbType = SqlDbType.NVarChar,
DefaultValueCode = "[]",
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment