Skip to content

Commit

Permalink
Changes from Hornstra project including the ability to pass data back…
Browse files Browse the repository at this point in the history
… from success or error callback functions, compatibility with outside CSS grid systems, numerous bug fixes, significant refactoring and optimizations to RepeaterFieldUi, ability to use AutocompleteFieldUi without ProcessWire Pages, ensuring that all field types have values populated through hidden fields even when they are set to read only, Field::siblings() method added to easily select sibling fields in callback functions (especially useful in repeaters), new MarkupFieldUi for adding arbitrary html markup between fields.
  • Loading branch information
thetuningspoon committed Apr 25, 2024
1 parent e14f417 commit 6e298bb
Show file tree
Hide file tree
Showing 52 changed files with 764 additions and 269 deletions.
1 change: 1 addition & 0 deletions AutocompleteField/AutocompleteField.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
top: .5rem;
color: #888;
font-size: .9rem;
display: none;
}
2 changes: 1 addition & 1 deletion AutocompleteField/AutocompleteField.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ $(function () {
$spinner.hide();
},
};
var customSettings = $this.data('settings');

var customSettings = $.parseJSON($this.attr('data-settings'));
var settings = $.extend(defaultSettings, customSettings);

$this.autocomplete(settings);
Expand Down
16 changes: 14 additions & 2 deletions AutocompleteField/AutocompleteField.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

<?php if($readOnly): ?>
<div class="field-readOnly">
<input <?= $formAttribute ?> type="hidden" name="<?= $sanitizer->entities1($name) ?>" value="<?= $sanitizer->entities1($value) ?>" <?= $disabled ? 'disabled' : '' ?> />

<?php if($value): ?>
<?= $sanitizer->entities1($value) ?>
<?php else: ?>
Expand All @@ -10,9 +12,19 @@
</div>
<?php else: ?>
<div class="autocompleteField-inputs">
<i class="autocompleteField-spinner fa fa-spin fa-circle-o-notch hide"></i>
<div class="autocompleteField-spinner"><i class="fa fa-spin fa-circle-o-notch"></i></div>

<input <?= $formAttribute ?> maxlength="<?= $sanitizer->entities1($maxLength) ?>" <?php if($id): ?>id="input_<?= $sanitizer->entities1($id) ?>"<?php endif ?> class="field-input txtBox <?= $error ? 'txtBox_error' : '' ?>" type="text" placeholder="<?= $sanitizer->entities1($placeholder) ?>" value="<?= $sanitizer->entities1($displayValue) ?>" <?php if(count($settings)): ?>data-settings="<?= $sanitizer->entities1(json_encode($settings)) ?>"<?php endif ?> <?= $disabled ? 'disabled' : '' ?> />
<input
<?= $formAttribute ?>
maxlength="<?= $sanitizer->entities1($maxLength) ?>"
<?= $id ? 'id="input_'.$sanitizer->entities1($id).'"' : '' ?>
class="field-input txtBox<?= $error ? ' txtBox_error' : '' ?> <?= $sanitizer->entities1($inputClasses) ?>"
type="text"
placeholder="<?= $sanitizer->entities1($placeholder) ?>"
value="<?= $sanitizer->entities1($displayValue) ?>"
<?php if(count($settings)): ?>data-settings="<?= $sanitizer->entities1(json_encode($settings)) ?>"<?php endif ?>
<?= $disabled ? 'disabled' : '' ?>
/>

<input <?= $formAttribute ?> type="hidden" name="<?= $sanitizer->entities1($name) ?>" class="autocompleteField-value" value="<?= $sanitizer->entities1($value) ?>" <?= $disabled ? 'disabled' : '' ?> />
</div>
Expand Down
72 changes: 43 additions & 29 deletions AutocompleteField/AutocompleteFieldUi.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
*/

class AutocompleteFieldUi extends FieldUi {
public $cssClass = 'autocompleteField';
public $settings = []; // Array of settings for configuring the jquery.autocomplete.js instance.
public $searchCallback; // A callback function that returns search results. Can be used in place of searchSelector if you're not working with ProcessWire Pages or need absolute control. The callback is passed three parameters: $query, $field, $form. The return value should be an array of associative arrays with a 'value' and a 'data' element for each. The 'data' element is itself an associative array with an 'id' and optional 'category'. See jquery.autocomplete documentation for more options. If using this option, you will also need to provide a $validateCallback to validate the input. Using this $searchCallback overrides most of the options below.
public $__displayValue; // If using $searchCallback and populating the field with an initial $value, use $displayValue to show a string different from the value in the input box. For example, showing a customer name instead of the customer id.

// The options below are only relevant if this field is returning ProcessWire Pages and you are not using $searchCallback above
public $searchFields = 'title'; // Pipe separated list of field names to search
public $searchById = true; // Show matching result in the autocomplete when a valid page ID is entered directly
public $__searchSelector; // A selector string to narrow down the pages that will be searched
Expand All @@ -22,25 +28,25 @@ class AutocompleteFieldUi extends FieldUi {
public $resultLimit = 10;
public $searchOperator = '%='; // ProcessWire search operator to use to match results
public $groupBy; // Name of ProcessWire field to group the suggestions by (if grouping the suggestions is desired)
public $cssClass = 'autocompleteField';
public $maxLength;
public $settings = []; // Array of settings for configuring the jquery.autocomplete.js instance.
public $maxLength = 255;

protected function setup() {
$this->headScripts[] = $this->url . 'jquery.autocomplete.js'; // This is not the same as jQueryUI Autocomplete. See https://github.com/devbridge/jQuery-Autocomplete
}

protected function run() {
// Set the initial display value when the field loads. If the value is a valid page, format the display value (results label) according to our preferences.
$this->view->displayValue = $this->value;
$this->view->displayValue = $this->displayValue ?? $this->value;

if(ctype_digit($this->value)) {
$match = $this->pages->get($this->buildValidateSelector());
if($match->id) { // Valid Page
$this->view->displayValue = $this->createLabel($match);
if(!$this->searchCallback) {
if(ctype_digit($this->value)) {
$match = $this->pages->get($this->buildValidateSelector());
if($match->id) { // Valid Page
$this->view->displayValue = $this->createLabel($match);
}
}
}

return parent::run();
}

Expand All @@ -66,16 +72,17 @@ protected function buildValidateSelector() {
}

protected function fieldValidate() {

if($this->allowStringValue == false || ctype_digit($this->value)) {
// Make sure value is an ID that matches a valid page for this field
if(!$this->pages->get($this->buildValidateSelector())->id) {
$this->error = __('Invalid selection.');
$this->view->displayValue = $this->value;
if(!$this->searchCallback) {
if($this->allowStringValue == false || ctype_digit($this->value)) {
// Make sure value is an ID that matches a valid page for this field
if(!$this->pages->get($this->buildValidateSelector())->id) {
$this->error = __('Invalid selection.');
$this->view->displayValue = $this->value;
}
}
else {
$this->value = $this->sanitizer->text($this->value);
}
}
else {
$this->value = $this->sanitizer->text($this->value);
}

if($this->error) return false;
Expand All @@ -97,25 +104,32 @@ protected function createLabel(Page $match) {
}

/**
* Returns matching results (Pages) for the autocomplete jQuery plugin
* Returns matching results (e.g. Pages) for the autocomplete jQuery plugin
*
* Each result is an associative array with a 'value' and a 'data' element. The 'data' element is an associative array with an 'id' and optional 'category'. See jquery.autocomplete documentation for more.
*/
protected function ajax_getMatches() {
$query = $this->input->get->query;

if($query) {
$matches = $this->pages->find($this->buildSearchSelector($query));
if($this->searchCallback) {
$this->ajax['suggestions'] = call_user_func_array($this->searchCallback, [$query, $this, $this->form]);
}
else {
$matches = $this->pages->find($this->buildSearchSelector($query));

$this->ajax['suggestions'] = [];
if($matches->count()) {
foreach($matches as $match) {
$label = $this->createLabel($match);
$this->ajax['suggestions'] = [];
if($matches->count()) {
foreach($matches as $match) {
$label = $this->createLabel($match);

$suggestion = ['value' => $label, 'data' => ['id' => "$match->id"]];
if($this->groupBy) {
$suggestion['data']['category'] = $match->get("{$this->groupBy}");
}
$suggestion = ['value' => $label, 'data' => ['id' => "$match->id"]];
if($this->groupBy) {
$suggestion['data']['category'] = $match->get("{$this->groupBy}");
}

$this->ajax['suggestions'][] = $suggestion;
$this->ajax['suggestions'][] = $suggestion;
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion ButtonField/ButtonField.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.buttonField {
padding-top: 1rem;
/*padding-top: 1rem;*/
}
.buttonField_fullWidth .btn {
width: 100%;
Expand Down
13 changes: 12 additions & 1 deletion ButtonField/ButtonField.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ class="field buttonField<?= $fullWidth ? ' buttonField_fullWidth' : '' ?>"
<?= $sanitizer->entities1($label) ?>
</a>
<?php else: ?>
<button <?= $formAttribute ?> type="<?= $sanitizer->entities1($type) ?>" class="btn <?= $sanitizer->entities1($btnClasses) ?>" name="<?= $sanitizer->entities1($name) ?>" value="<?= $sanitizer->entities1($value) ?>" <?= $attributeString ?> <?= $disabled ? 'disabled' : '' ?>>
<button
<?= $formAttribute ?>
type="<?= $sanitizer->entities1($type) ?>"
class="btn <?= $sanitizer->entities1($btnClasses) ?> <?= $sanitizer->entities1($inputClasses) ?>"
name="<?= $sanitizer->entities1($name) ?>"
value="<?= $sanitizer->entities1($value) ?>"
<?= $disabled ? 'disabled' : '' ?>
<?= $attributeString ?>
<?php foreach($extraAttributes as $attr => $val): ?>
<?= $sanitizer->entities1($attr) ?>="<?= $sanitizer->entities1($val) ?>"
<?php endforeach?>
>
<?php if($icon): ?>
<i class="fa fa-fw fa-<?= $sanitizer->entities1($icon) ?>"></i>
<?php endif ?>
Expand Down
5 changes: 4 additions & 1 deletion CheckboxField/CheckboxField.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
.checkboxField {
padding-top: 1.5rem;
/*padding-top: 1.5rem;*/
}
.checkboxField .field-input {
margin-right: .25em;
}

/* Toggle Switch checkbox replacement */
Expand Down
39 changes: 32 additions & 7 deletions CheckboxField/CheckboxField.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,37 @@

<input <?= $formAttribute ?> type="hidden" class="field-fallback" name="<?= $sanitizer->entities1($name) ?>" value="0" <?= $disabled ? 'disabled' : '' ?> />

<label class="checkboxField-label <?= $toggleSwitch ? 'toggleSwitch' : '' ?>">
<input class="field-input" <?= $formAttribute ?> type="checkbox" name="<?= $sanitizer->entities1($name) ?>" <?php if($id): ?>id="input_<?= $sanitizer->entities1($id) ?>"<?php endif ?> value="1" <?= $value == 1 ? 'checked="checked"' : '' ?> <?= $disabled ? 'disabled' : '' ?> />
<?php if($toggleSwitch): ?>
<span class="toggleSwitch-slider"></span>
<?php endif ?>
<?= $sanitizer->entities1($label) ?>
</label>
<?php if($readOnly): ?>

<div class="field-readOnly">
<input <?= $formAttribute ?> type="hidden" name="<?= $sanitizer->entities1($name) ?>" value="<?= $sanitizer->entities1($value) ?>" <?= $disabled ? 'disabled' : '' ?> />

<?php if($value): ?>
<i class="fa fa-check-square-o"></i>
<?php else: ?>
<i class="fa fa-square-o"></i>
<?php endif ?>
</div>

<?php else: ?>

<label class="checkboxField-label <?= $toggleSwitch ? 'toggleSwitch' : '' ?>">
<input
<?= $formAttribute ?>
type="checkbox"
class="field-input <?= $sanitizer->entities1($inputClasses) ?>"
name="<?= $sanitizer->entities1($name) ?>"
<?= $id ? 'id="input_'.$sanitizer->entities1($id).'"' : '' ?>
value="1"
<?= $value == 1 ? 'checked="checked"' : '' ?>
<?= $disabled ? 'disabled' : '' ?>
/>
<?php if($toggleSwitch): ?>
<span class="toggleSwitch-slider"></span>
<?php endif ?>
<?= $sanitizer->entities1($label) ?>
</label>

<?php endif ?>

<?php include('../Field/footer.php') ?>
4 changes: 3 additions & 1 deletion CheckboxField/CheckboxFieldUi.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
*/
class CheckboxFieldUi extends FieldUi {

public $value = null;
public $value = 0;
public $toggleSwitch = false;
public $cssClass = 'checkboxField';
public $showLabel = false;

public $nonNull = true; // As a boolean, a checkbox can never be unpopulated

protected function setup() {
if($this->columnize) $this->headScripts[] = $this->config->urls->templates . 'library/jquery.columnizer.min.js';
}
Expand Down
42 changes: 30 additions & 12 deletions CheckboxesField/CheckboxesField.php
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
<?php include('../Field/header.php') ?>

<?php if($bulkSelectOptions): ?>
<div class="checkboxesField-bulkSelect">
<button type="button" class="checkboxesField-all"><i class="fa fa-check-square-o"></i> All</button><button type="button" class="checkboxesField-none"> None</button>
<input <?= $formAttribute ?> type="hidden" class="field-fallback" name="<?= $sanitizer->entities1($name) ?>" value="[#]" <?= $disabled ? 'disabled' : '' ?> />

<?php if($readOnly): ?>

<div class="field-readOnly">
<?php foreach($value as $val): ?>
<input <?= $formAttribute ?> type="hidden" name="<?= $sanitizer->entities1($name) ?>[]" value="<?= $sanitizer->entities1($val) ?>" <?= $disabled ? 'disabled' : '' ?> />
<?php endforeach ?>

<?php if($selectedLabel): ?>
<?= $sanitizer->entities1($selectedLabel) ?>
<?php else: ?>
<div class="field-noValue"><i class="fa fa-minus-circle"></i> Not Selected</div>
<?php endif ?>
</div>
<?php endif ?>

<input <?= $formAttribute ?> type="hidden" class="field-fallback" name="<?= $sanitizer->entities1($name) ?>" value="[#]" <?= $disabled ? 'disabled' : '' ?> />
<?php else: ?>

<?php if($bulkSelectOptions): ?>
<div class="checkboxesField-bulkSelect">
<button type="button" class="checkboxesField-all"><i class="fa fa-check-square-o"></i> All</button><button type="button" class="checkboxesField-none"> None</button>
</div>
<?php endif ?>

<ul class="checkboxesField-list <?= $columnize ? 'checkboxesField-columnize' : '' ?>">
<?php foreach($options as $option): ?>
<li class="<?= $columnize ? 'dontsplit' : '' ?>">
<label class="checkboxesField-label"><input <?= $formAttribute ?> type="checkbox" name="<?= $sanitizer->entities1($name) ?>[]" value="<?= $sanitizer->entities1($option['value']) ?>" <?= $value && in_array($option['value'], $value) ? 'checked="checked"' : '' ?> <?= $disabled ? 'disabled' : '' ?> /> <?= $sanitizer->entities1($option['label']) ?></label>
</li>
<?php endforeach ?>
</ul>
<ul class="checkboxesField-list <?= $columnize ? 'checkboxesField-columnize' : '' ?>">
<?php foreach($options as $option): ?>
<li class="<?= $columnize ? 'dontsplit' : '' ?>">
<label class="checkboxesField-label"><input <?= $formAttribute ?> type="checkbox" name="<?= $sanitizer->entities1($name) ?>[]" value="<?= $sanitizer->entities1($option['value']) ?>" <?= $value && in_array($option['value'], $value) ? 'checked="checked"' : '' ?> <?= $disabled ? 'disabled' : '' ?> /> <?= $sanitizer->entities1($option['label']) ?></label>
</li>
<?php endforeach ?>
</ul>

<?php endif ?>

<?php include('../Field/footer.php') ?>
6 changes: 3 additions & 3 deletions DateField/DateField.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ $(function() {

var date = new Pikaday({
field: $input[0],
format: $input.attr('data-date-format'),
minDate: new Date($input.attr('data-min-date')),
maxDate: new Date($input.attr('data-max-date')),
format: $input.data("date-format"),
minDate: new Date($input.attr('data-min-date') + 'T00:00:00'),
maxDate: new Date($input.attr('data-max-date') + 'T00:00:00'),
});

// If the value of the field is changed, automatically trigger setDate on pikaday
Expand Down
29 changes: 23 additions & 6 deletions DateField/DateField.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
<?php include('../Field/header.php') ?>

<?php if($readOnly): ?>
<?php if($value): ?>
<?= $sanitizer->entities1($value) ?>
<?php else: ?>
<span class="field-noValue"><i class="fa fa-minus-circle"></i> No Value</span>
<?php endif ?>
<div class="field-readOnly">
<input <?= $formAttribute ?> type="hidden" name="<?= $sanitizer->entities1($name) ?>" value="<?= $sanitizer->entities1($value) ?>" <?= $disabled ? 'disabled' : '' ?> />

<?php if($value): ?>
<?= $sanitizer->entities1($value) ?>
<?php else: ?>
<span class="field-noValue"><i class="fa fa-minus-circle"></i> No Value</span>
<?php endif ?>
</div>
<?php else: ?>
<input <?= $formAttribute ?> maxlength="<?= $sanitizer->entities1($maxLength) ?>" name="<?= $sanitizer->entities1($name) ?>" <?php if($id): ?>id="input_<?= $sanitizer->entities1($id) ?>"<?php endif ?> class="field-input txtBox <?= $error ? 'txtBox_error' : '' ?>" type="text" placeholder="<?= $sanitizer->entities1($placeholder) ?>" value="<?= $sanitizer->entities1($value) ?>" data-date-format="<?= $sanitizer->entities1($jsDateFormat) ?>" data-min-date="<?= $sanitizer->entities1($minDate) ?>" <?= $disabled ? 'disabled' : '' ?> <?= !$autocomplete ? 'autocomplete="off"' : '' ?> />
<input
<?= $formAttribute ?>
type="text"
maxlength="<?= $sanitizer->entities1($maxLength) ?>"
name="<?= $sanitizer->entities1($name) ?>"
<?= $id ? 'id="input_' . $sanitizer->entities1($id).'"' : '' ?>
class="field-input txtBox <?= $error ? 'txtBox_error' : '' ?> <?= $sanitizer->entities1($inputClasses) ?>"
placeholder="<?= $sanitizer->entities1($placeholder) ?>"
value="<?= $sanitizer->entities1($value) ?>"
data-date-format="<?= $sanitizer->entities1($jsDateFormat) ?>"
data-min-date="<?= $sanitizer->entities1($minDate) ?>"
<?= $disabled ? 'disabled' : '' ?>
<?= !$autocomplete ? 'autocomplete="off"' : '' ?>
/>
<?php endif ?>

<?php include('../Field/footer.php') ?>
10 changes: 7 additions & 3 deletions DateField/DateFieldUi.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<?php namespace ProcessWire;
/*
* NOTE: This field should be provided a value in the same format that it will display to the user (the same format specified in the $jsDateFormat and $phpDateFormat properties below). When saving, you must manually convert the value back to your database format.
*/
class DateFieldUi extends FieldUi {

public $cssClass = 'dateField';
public $jsDateFormat = 'YYYY/MM/DD'; // YYYY-MM-DD Doesn't seem to work with the current version of Pikaday
public $phpDateFormat = 'Y/m/d';
public $jsDateFormat = 'YYYY/MM/DD'; // Note: YYYY-MM-DD Doesn't seem to work with the current version of Pikaday
public $phpDateFormat = 'Y/m/d'; // The php date format must be equivalent to the js format above. These are different because JS and PHP use different character codes to represent the same formats.
public $maxLength;
public $minDate;
// public $minDate;
public $__minDate;
public $maxDate;

public function setup() {
Expand Down
Loading

0 comments on commit 6e298bb

Please sign in to comment.