Skip to main content

Using Posting Requirements Form Widget Separate from Order Journey

If you want to use Contract Channel Posting Requirements form widget on it's own, there are a couple of recipes we can share.

Showing the form with a specific contract ID

In order to show the form with dynamic fields, you need the ID of the contract (contractId in HAPI Backend) and pass it to the form widget as such:

<he-orderjourney-contractchannelpostingrequirements-contract-id contract-id="CONTRACT-ID-YOU-HAVE">
</he-orderjourney-contractchannelpostingrequirements-contract-id>

Normally, this widget is part of the Order Journey but because you will not be showing this as part of a journey but as a standalone widget, there would be some further things to do such as:

  • Adding the contract to the basket
  • Get basket products

Because otherwise getOrderCampaignRequestBody function of Campaign Utilities and contractStepsValidations variable of Order Journey State will not work correctly as these work based on the products/contracts a user has in their basket.


HAPI Elements, to prevent this issue, is automatically adding the contract with the contract-id you pass to user's basket so that you don't have to manually do these.

Getting form values

To get the values user entered into the form, there are two ways:

Using campaignForm variable from Campaign State

Please note that campaignForm is not adhering to the structure HAPI Backend expects therefore getOrderCampaignRequestBody function of Campaign Utilities can be used to transform the form into the structure HAPI Backend expects.

const campaignForm = window.hapi.campaign.state.campaignForm.value
const orderedProductsSpecs = campaignForm.orderedProductsSpecs
/* will output an Object with keys such as
{
"a42e6d6b-90c7-4463-9534-24485081768d": {
"someSpecKey": "someSpecValue",
...rest of the contract posting requirements
}
}
*/
// then you can grab the values of the form as such
const xContractRequirements = orderedProductsSpecs["CONTRACT-ID-YOU-HAVE"]

Using getOrderCampaignRequestBody function from Campaign Utilities

const requestBody = window.hapi.campaign.utils.getOrderCampaignRequestBody()
const orderedProductsSpecs = requestBody.orderedProductsSpecs
/* will output an Array with items such as
{
contractId: "a42e6d6b-90c7-4463-9534-24485081768d",
productId: "dace3b3c-f81a-4f2a-b5f9-e0c89c12d8d1",
utm: "utmCampaign=jobmarketing&utmMedium=jobposting&utmSource=Joblift%20Netherlands"
postingRequirements: {
"someSpecKey": "someSpecValue",
...rest of the contract posting requirements
}
}
*/
// then you can grab the values of the form as such
const xContractSpec = orderedProductsSpecs.find(spec => spec.contractId === "CONTRACT-ID-YOU-HAVE")
if (xContractSpec) {
const xContractRequirements = xContractSpec.postingRequirements
}

Listening to changes on form values

To listen to changes, there are two ways (which are virtually the same but one is more towards the Campaign itself in general whereas the other is for posting requirements):

Listening to changes on the campaignForm variable of Campaign State

window.hapi.campaign.state.campaignForm.onChange((campaignForm) => {
const orderedProductsSpecs = campaignForm.orderedProductsSpecs
/* will output an Object with keys such as
{
"a42e6d6b-90c7-4463-9534-24485081768d": {
"someSpecKey": "someSpecValue",
...rest of the contract posting requirements
}
}
*/
// then you can grab the values of the form as such
const xContractRequirements = orderedProductsSpecs["CONTRACT-ID-YOU-HAVE"]
})

Listening to changes on the contractStepsData variable of Order Journey State

window.hapi.orderJourney.state.contractStepsData.onChange(contractPostingRequirements => {
const stepKey = window.hapi.orderJourney.utils.orderJourneyGetContractStepKey("CONTRACT-ID-YOU-HAVE")
const xContractRequirements = contractPostingRequirements[stepKey]
})

Listening to changes on contractStepsValidators variable of Order Journey State

Some fields show up only when a certain value is selected in a different field. This is handy to keep track of which fields are visible on the form.

window.hapi.orderJourney.state.contractStepsValidators.onChange(validators => {
const stepKey = window.hapi.orderJourney.utils.orderJourneyGetContractStepKey("CONTRACT-ID-YOU-HAVE")
const xContractValidators = validators[stepKey]

const someFieldYouWantToPrefill = "name-of-field"
if (xContractValidators[someFieldYouWantToPrefill] === "isNotEmpty") {
// field became visible
// run your prefill function that prefills "someFieldYouWantToPrefill"
}
})

Setting form values

Posting Requirements form fields are highly dynamic therefore it is extremely hard to prefill these values. It is however possible, for example you have a saved snapshot of the requirement form and you want to restore it, you can do it as such:

const xContractRequirementsSnapshot = {
"someSpecKey": "someSpecValue",
//...rest of the contract posting requirements
}

const campaignForm = window.hapi.campaign.state.campaignForm.value
window.hapi.campaign.state.campaignForm = window.hapiUtils.mergeDeepOverwriteArrays(campaignForm, {
orderedProductsSpecs: {
["CONTRACT-ID-YOU-HAVE"]: xContractRequirementsSnapshot
}
})

Listening to validation result

Whenever the values of the form changes, the validations run. You can listen to validation changes as such:

window.hapi.orderJourney.state.contractStepsValidations.onChange(contractStepsValidations => {
const stepKey = window.hapi.orderJourney.utils.orderJourneyGetContractStepKey("CONTRACT-ID-YOU-HAVE")
const xContractValidations = contractStepsValidations[stepKey]
console.log('xContractValidations', xContractValidations)
/* outputs
{
"validityOrErrorMessages": {
"industry": {
"id": "common.field-is-required",
"defaultMessage": "This field is required"
}
},
"areAllValid": false
}
*/
})

Manually validating the form

Sometimes you may need to validate the form on demand, this may come in handy in cases where for example you have your own "Save Contract Requirements" kind of button and initially, the button should be disabled because the form is empty. IN HAPI Elements, the fields are validated one by one after the field blurs. Until a field blurs, the field itself will most likely have an empty value thus the field validation will be true. The following function assumes that all fields have been blurred so that you don't need to wait for the end user to blur the fields.

const stepKey = window.hapi.orderJourney.utils.orderJourneyGetContractStepKey("CONTRACT-ID-YOU-HAVE")
const validationResult = window.hapi.orderJourney.service.validateStep(stepKey)
console.log('Validation Result', validationResult)
/* outputs
{
"validityOrErrorMessages": {
"type": false,
"industry": false,
"region": false,
"salary_min": false,
"salary_max": false,
"salary_period": false,
"start_date": false,
"relocate_from_eu": false
},
"areAllValid": false
}
*/

Blurring form fields manually so the error messages appear beneath form fields

The validateStep function documented above will validate the step and return you the validation results however in the UI for posting requirements widget, the form fields will not have the error shown beneath them. To actually show those, the form field needs to have been blurred by the user. To manually blur the fields programmatically, you can do:

const ourContractId = "YOUR-CONTRACT-ID"
const stepKey = window.hapi.orderJourney.utils.orderJourneyGetContractStepKey(ourContractId)
const validators = window.hapi.orderJourney.state.contractStepsValidators.value[stepKey]

const fieldsToBlur = {}
Object.keys(validators).forEach(fieldName => {
fieldsToBlur[fieldName] = true
})

const allContractInBasketValidationResults = window.hapi.orderJourney.service.setContractStepsBlurredFields({
[stepKey]: fieldsToBlur
})
const validationResultsForOurContract = allContractInBasketValidationResults[stepKey]

console.log('Validation Results', validationResultsForOurContract)

Validating the campaign form via HAPI Backend

The validations on frontend are very simple. Frontend only checks if value is not empty, if it is a url or an email etc. but HAPI backend does more validations. To get the campaign form validated:

const campaignBody = window.hapi.campaign.utils.getOrderCampaignRequestBody()

const response = await window.hapi.campaign.service.validateCampaign.run(campaignBody);
if (response.has_errors) {
console.log('HAPI Campaign has errors', response.errors)
}

Validating the campaign form contract posting requirements via HAPI Backend

The validations on frontend are very simple. Frontend only checks if value is not empty, if it is a url or an email etc. but HAPI backend does more validations. To get the campaign contract posting requirements validated:

const idOfContractInBasket = "id here"
const campaignBody = window.hapi.campaign.utils.getOrderCampaignRequestBody()

const response = await window.hapi.campaign.service.validateContractPostingRequirementsWithCampaign.run(
idOfContractInBasket,
campaignBody
);
if (response.has_errors) {
console.log('HAPI Campaign has errors', response.errors)
}

Showing errors received from backend only for posting requirements

You can place the Contract Channel Posting Requirements Errors widget above/below your form to show errors received from backend. When there are no errors, the widget won't show up.

Showing custom error messages underneath form fields

To show custom errors, there are two ways;

  • Adding your own translations to translations file
  • Skipping translation mechanism (not recommended)

To set a custom error message you first need to blur the form field as such:

const contractId = "CONTRACT-ID-YOU-HAVE"
const stepKey = window.hapi.orderJourney.utils.orderJourneyGetContractStepKey(contractId)
const fieldNameYouWantToBlur = "postcode"
window.hapi.orderJourney.state.contractStepsBlurredFields = {
...window.hapi.orderJourney.state.contractStepsBlurredFields.value,
[stepKey]: {
...(window.hapi.orderJourney.state.contractStepsBlurredFields.value[stepKey] || {}),
[fieldNameYouWantToBlur]: true
}
}

After blurring the input you can modify the error message:

const contractId = "CONTRACT-ID-YOU-HAVE"
const stepKey = window.hapi.orderJourney.utils.orderJourneyGetContractStepKey(contractId)
const fieldYouWantToShowCustomMessage = "postcode"
window.hapi.orderJourney.state.contractStepsErrorMessages = {
...window.hapi.orderJourney.state.contractStepsErrorMessages.value,
[stepKey]: {
...(window.hapi.orderJourney.state.contractStepsErrorMessages.value[stepKey] || {}),
[fieldYouWantToShowCustomMessage]: {
id: "your-custom-translation-key",
defaultMessage: "your custom translation value"
}
}
}

Normally the error messages appear in these cases:

  • Simple FE validation, that checks for emptiness (or things like "should be more than 0"), when user blurs the input and input is empty
  • BE validation, that may check for all sorts of validation, when user submits the form (tries to order)

When you are using the contract posting requirements form as standalone, it may indicate that you already have an integration with VONQ HAPI Backend thus if you do this, then the form fields should automatically show BE error messages underneath the fields:

Using Service Function

window.hapi.campaign.service.orderCampaign.run()

// the above service function automatically sets the errors if the request fails to
// window.hapi.orderJourney.state.orderErrors

Using your own backend proxy or Elements API function

try {
// POST /campaign/order
// OR
const orderRequestBody = window.hapi.campaign.utils.getOrderCampaignRequestBody()
await window.hapi.campaign.api.orderCampaign(orderRequestBody)
} catch (error) {
window.hapi.orderJourney.state.orderErrors = error?.response?.data || null
}

Using Elements Campaign Service Validation Functions

These service functions do not set orderErrors object and you need to handle that yourself:

Using validateCampaign
try {
const orderRequestBody = window.hapi.campaign.utils.getOrderCampaignRequestBody()
const validationResult = await window.hapi.campaign.service.validateCampaign.run(orderRequestBody)
// if campaign is valid, validationResult output is
/*
{
errors: {
orderedProducts: [],
orderedProductsSpecs: []
},
has_errors: false,
}
*/
// if campaign is NOT valid, validationResult output changes to something like
/*
{
errors: {
// these arrays operate by index of a product/contract in the order of
// orderedProducts array in campaignForm
orderedProducts: [{}, {}],
orderedProductsSpecs: [
{},
{
credentials: {},
posting_requirements: {
"requirement-1": "some error",
"requirement-2": "some other error",
}
}
]
},
has_errors: false,
}
*/

// to show the error messages beneath the fields, set `orderErrors`
window.hapi.orderJourney.state.orderErrors = validationResult.has_errors ? validationResult : null
} catch (error) {
// the validateCampaign function does not fail on order errors, it just returns the validation results
// it only fails when there is an unexpected error
console.log("[HAPI] Something unexpected happened while validating the campaign")
}
Using validateContractPostingRequirementsWithCampaign
try {
const orderRequestBody = window.hapi.campaign.utils.getOrderCampaignRequestBody()
const contractIdToValidate = "some-id-you-have"
const validationResult = await window.hapi.campaign.service.validateContractPostingRequirementsWithCampaign.run(contractIdToValidate, orderRequestBody)

// if contract posting requirements is valid, validationResult output is
/*
{
errors: {
credentials: {},
posting_requirements: {},
},
has_errors: false,
}
*/
// if contract posting requirements is NOT valid, validationResult output changes to show the `errors` of `posting_requirements` and also `has_errors` is true

// the response here needs to be transformed to the orderErrors structure

// to show the error messages beneath the fields, set `orderErrors`
window.hapi.orderJourney.state.orderErrors = validationResult.has_errors ? validationResult : null
} catch (error) {
// the validateCampaign function does not fail on order errors, it just returns the validation results
// it only fails when there is an unexpected error
console.log("[HAPI] Something unexpected happened while validating the campaign channel posting requirements")
}

Since FE only checks for simple validations like emptiness, you may want to show some messages depending on your business logic, even when the form field is actually valid thus no error messages are shown. To do that you can let the FE know that you are handling the error state as such:

// the contract form is lazy thus the `contractStepsValidators` will be empty initially
// this is populated when the contract form is shown in the UI and the contract data has loaded
// therefore we need to add a change event listener to be aware of that
// PLEASE NOTE that when the value of the form field changes, the validators may get updated
// thus you need to keep overwriting them inside onChange
window.hapi.orderJourney.state.contractStepsValidators.onChange((validators) => {
const contractId = "CONTRACT-ID-YOU-HAVE"
const stepKey = window.hapi.orderJourney.utils.orderJourneyGetContractStepKey(contractId)
const validatorsOfContract = validators[stepKey]
if (Object.keys(validatorsOfContract).length) {
// make all field error messages handled by you
const updatedValidators = {}
Object.entries(validatorsOfContract).forEach(([key, value]) => {
updatedValidators[key] = "isHandledByBackend"
})

window.hapi.orderJourney.state.contractStepsValidators = {
...window.hapi.orderJourney.state.contractStepsValidators.value,
[stepKey]: updatedValidators
}

// OR
// make only a certain field handled by you
const updatedValidators2 = {...validatorsOfContract}
updatedValidators2["some-field-name"] = "isHandledByBackend"
window.hapi.orderJourney.state.contractStepsValidators = {
...window.hapi.orderJourney.state.contractStepsValidators.value,
[stepKey]: updatedValidators
}
}
})

Adding your own translations to translations file

You can add your custom translations following the Internalization documentation. In that page, there is a Translation file that contains the default English translations. You can extend existing English translations as such:

window.hapi.language.state.translations = {
...window.hapi.language.translations.value,
en: {
...window.hapi.language.translations.value.en,
"your-custom-translation-key": "your custom translation value"
}
}

If you are providing a different language other than English, at the time you provide the translations, you can do as such:

window.hapi.language.state.translations = {
...window.hapi.language.translations.value,
es: {
// your Spanish translations
"your-custom-translation-key": "your custom translation value"
}
}

You can skip adding any translations and just use the hardcoded message that is not actually translated as such:

const contractId = "CONTRACT-ID-YOU-HAVE"
const stepKey = window.hapi.orderJourney.utils.orderJourneyGetContractStepKey(contractId)
const fieldYouWantToShowCustomMessage = "postcode"
window.hapi.orderJourney.state.contractStepsErrorMessages = {
...window.hapi.orderJourney.state.contractStepsErrorMessages.value,
[stepKey]: {
...(window.hapi.orderJourney.state.contractStepsErrorMessages.value[stepKey] || {}),
[fieldYouWantToShowCustomMessage]: {
id: "fall-back-to-default", //trick system into falling back to defaultMessage
defaultMessage: "your custom translation value"
}
}
}