Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC / POC: Repeatable Settings Fields #70

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/class.settings-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ class WeDevs_Settings_API {
*/
protected $settings_fields = array();

/**
* Tracks whether the repeatable JS has been loaded to prevent multiple calls enqueue_script
* @var bool
*/
protected $repeatable_loaded = false;

public function __construct() {
add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
}
Expand Down Expand Up @@ -132,6 +138,7 @@ function admin_init() {
'std' => isset( $option['default'] ) ? $option['default'] : '',
'sanitize_callback' => isset( $option['sanitize_callback'] ) ? $option['sanitize_callback'] : '',
'type' => $type,
'children' => isset( $option['children'] ) ? $option['children'] : null,
);

add_settings_field( $section . '[' . $option['name'] . ']', $option['label'], array( $this, 'callback_' . $type ), $section, $section, $args );
Expand Down Expand Up @@ -385,6 +392,77 @@ function callback_color( $args ) {
echo $html;
}


/**
* Displays a repeatable for a settings field. Sanitization, is applied to the final field.
*
* @param array $args settings field args
*/
function callback_repeatable( $args ) {

if (!$this->repeatable_loaded) {
$url = plugins_url("js/jquery.repeatable.js",__FILE__);
wp_enqueue_script("jquery.repeatable", $url, array("jquery"));
}

// Add the repeatable
$ex_data = json_decode($this->get_option( $args['id'], $args['section'], $args['std']), true);
$ex_keys = array_keys($ex_data);

$html = '<div class="repeatable-container">';

if (count($ex_keys) > 0) {
foreach ($ex_data[$ex_keys[0]] as $i=>$v) {
$html .= '<div class="field-group">';
foreach ($args['children'] as $child) {
$html .= '<label for="'.$child['name'].'_{?}">'.$child['label'].'</label>';
$html .= '<input type="text" name="'.$child['name'].'_ex'.$i.'" value="'.$ex_data[$child['name']][$i].'" id="'.$child['name'].'_ex'.$i.'" data-parent="'.$args['id'].'" data-child="'.$child['name'].'"/>';
}
$html .= '<input type="button" class="delete" value="X" />';
$html .= '</div>';
}
}

$html .= '</div><input type="button" value="Add" class="add" />';
$html .= '<input type="hidden" name="'.$args["section"].'['.$args['id'].']" id="'.$args['section'].'_'.$args["id"].'"/>';
echo $html;

// Add the field group template
$template = '<script type="text/template" id="'.$args["id"].'-template"><div class="field-group">';

foreach ($args['children'] as $child) {
$template .= '<label for="'.$child['name'].'_{?}">'.$child['label'].'</label>';
$template .= '<input type="text" name="'.$child['name'].'_{?}" value="" id="'.$child['name'].'_{?}" data-parent="'.$args['id'].'" data-child="'.$child['name'].'"/>';

}
$template .= '<input type="button" class="delete" value="X" />';
$template .= '</div></script>';

echo $template;

// Push in the JS to make the repeatable go.
echo '<script>jQuery(function($) {$(".repeatable-container").repeatable({template: "#'.$args['id'].'-template"});});</script>';

// On save grab all the values and insert them into the hidden field -- -- this needs to be improved
?>
<script>
jQuery("form").on("submit", function () {
var data = {};
<?php foreach ($args["children"] as $child):?>
data['<?php echo $child['name'];?>'] = [];
jQuery("[data-child=<?php echo $child['name'];?>").each(function(idx,el){
console.log(el);
data['<?php echo $child["name"];?>'].push(jQuery(el).val());
});
<?php endforeach;?>

jQuery("#<?php echo $args["section"];?>_<?php echo $args['id'];?>").val(JSON.stringify(data));
});
</script>
<?

}

/**
* Sanitize callback for Settings API
*/
Expand Down
143 changes: 143 additions & 0 deletions src/js/jquery.repeatable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*-------------------------------------------
* This is basically just a cut and paste of the below
* https://github.com/jenwachter/jquery.repeatable
*------------------------------------------*/

(function ($) {

$.fn.repeatable = function (userSettings) {

/**
* Default settings
* @type {Object}
*/
var defaults = {
addTrigger: ".add",
deleteTrigger: ".delete",
max: null,
startWith: 0,
template: null,
itemContainer: ".field-group",
beforeAdd: function () {},
afterAdd: function () {},
beforeDelete: function () {},
afterDelete: function () {}
};

/**
* Iterator used to make each added
* repeatable element unique
* @type {Number}
*/
var i = 0;

/**
* DOM element into which repeatable
* items will be added
* @type {jQuery object}
*/
var target = $(this);

/**
* Blend passed user settings with defauly settings
* @type {array}
*/
var settings = $.extend({}, defaults, userSettings);

/**
* Total templated items found on the page
* at load. These may be created by server-side
* scripts.
* @return null
*/
var total = function () {
return $(target).find(settings.itemContainer).length;
}();


/**
* Add an element to the target
* and call the callback function
* @param object e Event
* @return null
*/
var addOne = function (e) {
e.preventDefault();
settings.beforeAdd.call(this);
createOne();
settings.afterAdd.call(this);
};

/**
* Delete the parent element
* and call the callback function
* @param object e Event
* @return null
*/
var deleteOne = function (e) {
e.preventDefault();
settings.beforeDelete.call(this);
$(this).parents(settings.itemContainer).first().remove();
total--;
maintainAddBtn();
settings.afterDelete.call(this);
};

/**
* Add an element to the target
* @return null
*/
var createOne = function() {
getUniqueTemplate().appendTo(target);
total++;
maintainAddBtn();
};

/**
* Alter the given template to make
* each form field name unique
* @return {jQuery object}
*/
var getUniqueTemplate = function () {
var template = $(settings.template).html();
template = template.replace(/{\?}/g, "new" + i++); // {?} => iterated placeholder
template = template.replace(/\{[^\?\}]*\}/g, ""); // {valuePlaceholder} => ""
return $(template);
};

/**
* Determines if the add trigger
* needs to be disabled
* @return null
*/
var maintainAddBtn = function () {
if (!settings.max) {
return;
}

if (total === settings.max) {
$(settings.addTrigger).attr("disabled", "disabled");
} else if (total < settings.max) {
$(settings.addTrigger).removeAttr("disabled");
}
};

/**
* Setup the repeater
* @return null
*/
(function () {
$(settings.addTrigger).on("click", addOne);
$("form").on("click", settings.deleteTrigger, deleteOne);

if (!total) {
var toCreate = settings.startWith - total;
for (var j = 0; j < toCreate; j++) {
createOne();
}
}

})();
};

})(jQuery);