This extension adds the possibility to create a forms (for e.g. a contact form). Use the drag & drop form builder to create any form you’ll ever want or need.
Form Fields - Frontend form fields views can be customized in framework-customizations/extensions/forms/form-builder/items/{item-type}/views/view.php
.
All built-in form builder item types can be found in framework/extensions/forms/includes/option-types/form-builder/items/
directory.
For e.g. to overwrite the view for item type text
(which is located in framework/extensions/forms/includes/option-types/form-builder/items/text
)
create framework-customizations/extensions/forms/form-builder/items/text/views/view.php
.
Form Fields Container - The view for the container that wraps the form fields can be customized in framework-customizations/extensions/forms/form-builder/views/items.php
.
<form>
can be customized in framework-customizations/extensions/forms/extensions/contact-forms/views/form.php
.framework-customizations/extensions/forms/extensions/contact-forms/views/email.php
.First, make sure you understand how the base builder works.
The Forms extension have a built-in form-builder
option type (that can be found in the framework-customizations/extensions/forms/form-builder/
directory)
which is used by the Contact Forms sub-extension. To create an item type for form-builder
you have to look in its
method called item_type_is_valid()
to see what class you must extend in order to be accepted by the builder.
class FW_Option_Type_Form_Builder
{
...
/**
* @param FW_Option_Type_Builder_Item $item_type_instance
* @return bool
*/
protected function item_type_is_valid($item_type_instance)
{
return is_subclass_of($item_type_instance, 'FW_Option_Type_Form_Builder_Item');
}
}
So you have to extend the FW_Option_Type_Form_Builder_Item
class and register it as a builder item type.
Below is explained how to create a simple Yes/No
question radio input.
Create the framework-customizations/extensions/forms/includes/builder-items/yes-no
directory.
Create framework-customizations/extensions/forms/includes/builder-items/yes-no/class-fw-option-type-form-builder-item-yes-no.php
with the following contents:
class FW_Option_Type_Form_Builder_Item_Yes_No extends FW_Option_Type_Form_Builder_Item
{
/**
* The item type
* @return string
*/
public function get_type()
{
return 'yes-no';
}
/**
* The boxes that appear on top of the builder and can be dragged down or clicked to create items
* @return array
*/
public function get_thumbnails()
{
return array(
array(
'html' =>
'<div class="item-type-icon-title">'.
' <div class="item-type-icon"><span class="dashicons dashicons-editor-help"></span></div>'.
' <div class="item-type-title">'. __('Yes/No Question', 'unyson') .'</div>'.
'</div>',
)
);
}
/**
* Enqueue item type scripts and styles (in backend)
*/
public function enqueue_static()
{
$uri = fw_get_template_customizations_directory_uri('/extensions/forms/includes/builder-items/yes-no/static');
wp_enqueue_style(
'fw-form-builder-item-type-yes-no',
$uri .'/backend.css',
array(),
fw()->theme->manifest->get_version()
);
wp_enqueue_script(
'fw-form-builder-item-type-yes-no',
$uri .'/backend.js',
array('fw-events'),
fw()->theme->manifest->get_version(),
true
);
wp_localize_script(
'fw-form-builder-item-type-yes-no',
'fw_form_builder_item_type_yes_no',
array(
'l10n' => array(
'item_title' => __('Yes/No', 'unyson'),
'label' => __('Label', 'unyson'),
'toggle_required' => __('Toggle mandatory field', 'unyson'),
'edit' => __('Edit', 'unyson'),
'delete' => __('Delete', 'unyson'),
'edit_label' => __('Edit Label', 'unyson'),
'yes' => __('Yes', 'unyson'),
'no' => __('No', 'unyson'),
),
'options' => $this->get_options(),
'defaults' => array(
'type' => $this->get_type(),
'options' => fw_get_options_values_from_input($this->get_options(), array())
)
)
);
fw()->backend->enqueue_options_static($this->get_options());
}
/**
* Render item html for frontend form
*
* @param array $item Attributes from Backbone JSON
* @param null|string|array $input_value Value submitted by the user
* @return string HTML
*/
public function frontend_render(array $item, $input_value)
{
return '<pre>'. print_r($item, true) .'</pre>';
}
/**
* Validate item on frontend form submit
*
* @param array $item Attributes from Backbone JSON
* @param null|string|array $input_value Value submitted by the user
* @return null|string Error message
*/
public function frontend_validate(array $item, $input_value)
{
return 'Test error message';
}
private function get_options()
{
return array(
array(
'g1' => array(
'type' => 'group',
'options' => array(
array(
'label' => array(
'type' => 'text',
'label' => __('Label', 'unyson'),
'desc' => __('The label of the field that will be displayed to the users', 'unyson'),
'value' => __('Yes/No', 'unyson'),
)
),
array(
'required' => array(
'type' => 'switch',
'label' => __('Mandatory Field?', 'unyson'),
'desc' => __('If this field is mandatory for the user', 'unyson'),
'value' => true,
)
),
)
)
),
array(
'g2' => array(
'type' => 'group',
'options' => array(
array(
'default_value' => array(
'type' => 'radio',
'label' => __('Default Value', 'unyson'),
'choices' => array(
'' => __('None', 'unyson'),
'yes' => __('Yes', 'unyson'),
'no' => __('No', 'unyson'),
),
)
)
)
)
),
);
}
}
FW_Option_Type_Builder::register_item_type('FW_Option_Type_Form_Builder_Item_Yes_No');
Create framework-customizations/extensions/forms/includes/builder-items/yes-no/static/backend.js
:
fwEvents.one('fw-builder:'+ 'form-builder' +':register-items', function(builder){
var currentItemType = 'yes-no';
var localized = window['fw_form_builder_item_type_yes_no'];
var ItemView = builder.classes.ItemView.extend({
template: _.template(
'<div class="fw-form-builder-item-style-default fw-form-builder-item-type-'+ currentItemType +'">'+
'<div class="fw-form-item-controls fw-row">'+
'<div class="fw-form-item-controls-left fw-col-xs-8">'+
'<div class="fw-form-item-width"></div>'+
'</div>'+
'<div class="fw-form-item-controls-right fw-col-xs-4 fw-text-right">'+
'<div class="fw-form-item-control-buttons">'+
'<a class="fw-form-item-control-required dashicons<% if (required) { %> required<% } %>" data-hover-tip="<%- toggle_required %>" href="#" onclick="return false;" >*</a>'+
'<a class="fw-form-item-control-edit dashicons dashicons-edit" data-hover-tip="<%- edit %>" href="#" onclick="return false;" ></a>'+
'<a class="fw-form-item-control-remove dashicons dashicons-no-alt" data-hover-tip="<%- remove %>" href="#" onclick="return false;" ></a>'+
'</div>'+
'</div>'+
'</div>'+
'<div class="fw-form-item-preview">'+
'<div class="fw-form-item-preview-label">'+
'<div class="fw-form-item-preview-label-wrapper"><label data-hover-tip="<%- edit_label %>"><%- label %></label> <span <% if (required) { %>class="required"<% } %>>*</span></div>'+
'<div class="fw-form-item-preview-label-edit"><!-- --></div>'+
'</div>'+
'<div class="fw-form-item-preview-input">'+
'<label><input type="radio" disabled <% if (default_value === \'yes\') { %>checked<% } %>> <%- yes %></label><br/>'+
'<label><input type="radio" disabled <% if (default_value === \'no\') { %>checked<% } %>> <%- no %></label>'+
'</div>'+
'</div>'+
'</div>'
),
events: {
'click': 'onWrapperClick',
'click .fw-form-item-control-edit': 'openEdit',
'click .fw-form-item-control-remove': 'removeItem',
'click .fw-form-item-control-required': 'toggleRequired',
'click .fw-form-item-preview .fw-form-item-preview-label label': 'openLabelEditor',
'change .fw-form-item-preview-input input': 'updateDefaultValueFromPreviewInput'
},
initialize: function() {
this.defaultInitialize();
// prepare edit options modal
{
this.modal = new fw.OptionsModal({
title: localized.l10n.item_title,
options: this.model.modalOptions,
values: this.model.get('options'),
size: 'small'
});
this.listenTo(this.modal, 'change:values', function(modal, values) {
this.model.set('options', values);
});
this.model.on('change:options', function() {
this.modal.set(
'values',
this.model.get('options')
);
}, this);
}
this.widthChangerView = new FwBuilderComponents.ItemView.WidthChanger({
model: this.model,
view: this
});
this.labelInlineEditor = new FwBuilderComponents.ItemView.InlineTextEditor({
model: this.model,
editAttribute: 'options/label'
});
},
render: function () {
this.defaultRender({
label: fw.opg('label', this.model.get('options')),
required: fw.opg('required', this.model.get('options')),
default_value: fw.opg('default_value', this.model.get('options')),
toggle_required: localized.l10n.toggle_required,
edit: localized.l10n.edit,
remove: localized.l10n.delete,
edit_label: localized.l10n.edit_label,
yes: localized.l10n.yes,
no: localized.l10n.no
});
if (this.widthChangerView) {
this.$('.fw-form-item-width').append(
this.widthChangerView.$el
);
this.widthChangerView.delegateEvents();
}
if (this.labelInlineEditor) {
this.$('.fw-form-item-preview-label-edit').append(
this.labelInlineEditor.$el
);
this.labelInlineEditor.delegateEvents();
}
},
openEdit: function() {
this.modal.open();
},
removeItem: function() {
this.remove();
this.model.collection.remove(this.model);
},
toggleRequired: function() {
var values = _.clone(
// clone to not modify by reference, else model.set() will not trigger the 'change' event
this.model.get('options')
);
values.required = !values.required;
this.model.set('options', values);
},
openLabelEditor: function() {
this.$('.fw-form-item-preview-label-wrapper').hide();
this.labelInlineEditor.show();
this.listenToOnce(this.labelInlineEditor, 'hide', function() {
this.$('.fw-form-item-preview-label-wrapper').show();
});
},
updateDefaultValueFromPreviewInput: function() {
var values = _.clone(
// clone to not modify by reference, else model.set() will not trigger the 'change' event
this.model.get('options')
);
values.default_value = this.$('.fw-form-item-preview-input input').val();
this.model.set('options', values);
},
onWrapperClick: function(e) {
if (!this.$el.parent().length) {
// The element doesn't exist in DOM. This listener was executed after the item was deleted
return;
}
if (!fw.elementEventHasListenerInContainer(jQuery(e.srcElement), 'click', this.$el)) {
this.openEdit();
}
}
});
var Item = builder.classes.Item.extend({
defaults: function() {
var defaults = _.clone(localized.defaults);
defaults.shortcode = fwFormBuilder.uniqueShortcode(defaults.type +'_');
return defaults;
},
initialize: function() {
this.defaultInitialize();
this.modalOptions = localized.options;
this.view = new ItemView({
id: 'fw-builder-item-'+ this.cid,
model: this
});
}
});
builder.registerItemClass(Item);
});
Create framework-customizations/extensions/forms/includes/builder-items/yes-no/static/backend.css
:
/* controls */
.fw-option-type-form-builder .fw-form-builder-item-type-yes-no .fw-form-item-controls .fw-form-item-control-buttons {
display: none;
}
.fw-option-type-form-builder .fw-form-builder-item-type-yes-no:hover .fw-form-item-controls .fw-form-item-control-buttons {
display: inline-block;
}
.fw-option-type-form-builder .fw-form-builder-item-type-yes-no .fw-form-item-controls .fw-form-item-control-buttons > a,
.fw-option-type-form-builder .fw-form-builder-item-type-yes-no .fw-form-item-controls .fw-form-item-control-buttons > a:hover {
text-decoration: none;
}
.fw-option-type-form-builder .fw-form-builder-item-type-yes-no .fw-form-item-controls .fw-form-item-control-buttons a.fw-form-item-control-required {
color: #999999;
}
.fw-option-type-form-builder .fw-form-builder-item-type-yes-no .fw-form-item-controls .fw-form-item-control-buttons a.fw-form-item-control-required.required {
color: #ff0000;
}
/* end: controls */
/* preview */
.fw-option-type-form-builder .fw-form-builder-item-type-yes-no .fw-form-item-preview {
padding: 5px 0;
}
.fw-option-type-form-builder .fw-form-builder-item-type-yes-no .fw-form-item-preview .fw-form-item-preview-label {
padding: 5px 0 10px;
}
.fw-option-type-form-builder .fw-form-builder-item-type-yes-no .fw-form-item-preview .fw-form-item-preview-label span {
display: none;
}
.fw-option-type-form-builder .fw-form-builder-item-type-yes-no .fw-form-item-preview .fw-form-item-preview-label span.required {
display: inline;
color: #ff0000;
}
/* end: preview */
Include the item type by creating the framework-customizations/extensions/forms/hooks.php
file with the following contents:
<?php if (!defined('FW')) die('Forbidden');
/** @internal */
function _action_theme_fw_ext_forms_include_custom_builder_items() {
require_once dirname(__FILE__) .'/includes/builder-items/yes-no/class-fw-option-type-form-builder-item-yes-no.php';
}
add_action('fw_option_type_form_builder_init', '_action_theme_fw_ext_forms_include_custom_builder_items');
At this point the item is working only in backend. If you save the form, add it in a page (or post) using Page Builder and open that page in frontend, you will see the item attributes array.
To make item working in frontend, follow the instructions below:
frontend_render()
method:class FW_Option_Type_Form_Builder_Item_Yes_No extends FW_Option_Type_Form_Builder_Item
{
...
public function frontend_render(array $item, $input_value)
{
if (is_null($input_value)) {
$input_value = $item['options']['default_value'];
}
return fw_render_view(
$this->locate_path(
// Search view in 'framework-customizations/extensions/forms/form-builder/items/yes-no/views/view.php'
'/views/view.php',
// Use this view by default
dirname(__FILE__) .'/view.php'
),
array(
'item' => $item,
'input_value' => $input_value
)
);
}
}
framework-customizations/extensions/forms/includes/builder-items/yes-no/view.php
:<?php if (!defined('FW')) die('Forbidden');
/**
* @var array $item
* @var array $input_value
*/
$options = $item['options'];
?>
<div class="<?php echo esc_attr(fw_ext_builder_get_item_width('form-builder', $item['width'] .'/frontend_class')) ?>">
<div class="field-radio input-styled">
<label><?php echo fw_htmlspecialchars($item['options']['label']) ?>
<?php if ($options['required']): ?><sup>*</sup><?php endif; ?>
</label>
<div class="custom-radio">
<div class="options">
<?php
foreach (array('yes' => __('Yes', 'unyson'), 'no' => __('No', 'unyson')) as $value => $label): ?>
<?php
$choice_attr = array(
'value' => $value,
'type' => 'radio',
'name' => $item['shortcode'],
'id' => 'rand-'. fw_unique_increment(),
);
if ($input_value === $value) {
$choice_attr['checked'] = 'checked';
}
?>
<input <?php echo fw_attr_to_html($choice_attr) ?> />
<label for="<?php echo esc_attr($choice_attr['id']) ?>"><?php echo $label ?></label>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
frontend_validate()
method:class FW_Option_Type_Form_Builder_Item_Yes_No extends FW_Option_Type_Form_Builder_Item
{
...
public function frontend_validate(array $item, $input_value)
{
$options = $item['options'];
$messages = array(
'required' => str_replace(
array('{label}'),
array($options['label']),
__('This {label} field is required', 'unyson')
),
'not_existing_choice' => str_replace(
array('{label}'),
array($options['label']),
__('{label}: Submitted data contains not existing choice', 'unyson')
),
);
if ($options['required'] && empty($input_value)) {
return $messages['required'];
}
// check if has not existing choices
if (!empty($input_value) && !in_array($input_value, array('yes', 'no'))) {
return $messages['not_existing_choice'];
}
}
}
Now the field will be displayed in frontend as a radio box and the validation will work. The submitted value will be used by the form type you chose when created the form, for e.g. the Contact Forms sub-extensions will send the value in email.
You can inspect the built-in form items to learn what possibilities for customization are available (for e.g. what methods from the extended class you can overwrite).