0
\$\begingroup\$

Over the past year I've found myself moving from back-end development to something more akin to full-stack development. I've created the following jQuery plugin as a test of my hopefully-not-so-fledgling JavaScript/jQuery skills. The plugin is used to inject <option> tags into the element(s) specified, and should follow the guidelines for creating basic plugins posted here.

(function($) {
  $.fn.injectOptions = function(opts) {
    opts = opts.map(function(opt) {
      if (typeof opt === 'string')
        return '<option>'+opt+'</option>';
      return '<option value="'+opt.key+'">'+opt.val+'</option>';
    });
    var optHTML = opts.reduce(function(html, opt) { return html+opt; });
    this.each(function() { $(this).html(optHTML); });
    return this;
  };
}(jQuery));

//example:
var fooArr = ["foo", "bar", "baz", "faz"];
var quxArr = [
  { key: 1, val: "qux"},
  { key: 2, val: "quux"},
  { key: 3, val: "quuux"},
];

$(".foo-options").injectOptions(fooArr);
$(".qux-options").injectOptions(quxArr);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select class="foo-options">
</select>
<select class="qux-options">
</select>
<select class="qux-options">
</select>

Any/all suggestions are welcome!

\$\endgroup\$
0

2 Answers 2

1
\$\begingroup\$

This code does not seem right:

var optHTML = opts.reduce(function(html, opt) { return html+opt; });
this.each(function() { $(this).html(optHTML); });

Here your .reduce() function is aggregating ALL the HTML for ALL of the options, which I think is an appropriate use of reduce function. But I do not understand why there is call to each() here as this can only refer to a single element. In fact I am guessing this doesn't even have an each() method on it , since this is an Element object. Shouldn't this just be:

var optHTML = opts.reduce(function(html, opt) { return html+opt; }, '');
$(this).html(optHTML);

I am not seeing great utility in separate map and reduce steps. This causes you to iterate your input options an extra time that could be considered removed if you wanted to simply consolidate all this logic into the reduce step. If the use cases are for small lists, maybe this performance isn't important and the added "clarity" of having two separate steps is acceptable.


Does it truly makes sense to support this extension on an arbitrary jQuery collection? Right now, as this code is written, if I had HTML like this:

<select class="foo-options">
</select>
<select class="foo-options">
</select>

And I called your extension on $('.foo-options'), you would end up with duplicated options for these two different selects. Is this desirable behavior?


Rather than writing HTML strings and having to hold the aggregated string in memory, you should give some consideration to actually creating the DOM Nodes and appending them.

Along with suggestion on removing redundant iterations over options, might have you function looking like this:

$.fn.injectOptions = function(opts) {
  // not shown - perhaps validate opts is array with at least 1 element
  // and fail out if this is not the case

  // simply iterate the passed options appending each element as you go
  opts.forEach( opt => {
      var val, txt;
      if (typeof opt === 'string') {
          val = opt;
          txt = opt;
      } else {
          val = opt.key;
          txt = opt.val;
      }
      $(this).append( $.('<option>', { value: val, text: txt }) );
}
\$\endgroup\$
1
  • \$\begingroup\$ this would refer to a jQuery object already, so $(this) would be superfluous, however that also means that the line reduces to this.html(optHTML); (no each necessary), which I'd failed to consider. \$\endgroup\$
    – Conduit
    Oct 19, 2016 at 22:28
0
\$\begingroup\$

I've aggregated my own edits with the other solutions here - attribution and motivations for my edits are below.

(function($) {
  $.fn.injectOptions = function(items, settings) {
    settings= $.extend({}, $.fn.injectOptions.defaults, settings);
    return this.append(items.map(item => {
      return $("<option>", {
        value: item[settings.value],
        disabled: item[settings.disabled] ? "disabled": undefined,
        text: (typeof item === 'string') ? item : item[settings.text]
      });
    }));
  };
  $.fn.injectOptions.defaults = {
    value: "value",
    disabled: "disabled",
    text: "text"
  };
}(jQuery));

The use of append and combination of steps used to create the option objects comes from Mike's answer.

The choice of properties used to populate the option objects is now configurable through the settings optionally passed in on the second argument. Default property choices are kept in $.fn.injectOptions.defaults, which can also be configured. The disabled state can be set in addition to the text and value of the option objects.

The following snippet illustrates the different ways the code can be used:

(function($) {
  $.fn.injectOptions = function(items, settings) {
    settings= $.extend({}, $.fn.injectOptions.defaults, settings);
    return this.append(items.map(item => {
      return $("<option>", {
        value: item[settings.value],
        disabled: item[settings.disabled] ? "disabled": undefined,
        text: (typeof item === 'string') ? item : item[settings.text]
      });
    }));
  };
  $.fn.injectOptions.defaults = {
    value: "value",
    disabled: "disabled",
    text: "text"
  };
}(jQuery));

//using an array of strings
var strArray = ["foo", "bar", "baz"];
$(".str-options").injectOptions(strArray);

//using an array of objects (properties match defaults)
var defArray = [
  { value: 1, text: "foo"},
  { value: 2, text: "bar", disabled: "true"},
  { value: 3, text: "baz"}
];
$(".def-options").injectOptions(defArray);

//using an array of objects (properties match user-defined defaults)
var userDefArray = [
  { key: 1, val: "foo"},
  { key: 2, val: "bar", off: "false"},
  { key: 3, val: "baz"}
];
$.fn.injectOptions.defaults.value = "key";
$.fn.injectOptions.defaults.text = "val";
$.fn.injectOptions.defaults.disabled = "off";
$(".udef-options").injectOptions(userDefArray);

//manually specifying the object properties in the settings argument
var userArray = [
  { num: 1, data: "foo"},
  { num: 2, data: "bar", inactive: true},
  { num: 3, data: "baz"}
];
$(".user-options").injectOptions(userArray, { value: "num", text: "data", disabled: "inactive"});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select class="str-options">
</select>
<select class="def-options">
</select>
<select class="udef-options">
</select>
<select class="user-options">
</select>

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.