0

I feel as if this is really simple and I'm having a "wood for the trees" moment, but that moment has lasted over a day now, so it's time to get help! I've Googled this and read lots of SO answers, but again, I'm having trouble applying the answers I've seen to my exact problem, no doubt due to my own shortcomings.

To illustrate the problem, I have this sample jQuery plugin (I tried to create a fiddle for this but don't know how to include a plugin external to the 'page' in a fiddle):

(function ($) {

    $.fn.test = function (options) {
        var settings = $.extend({
            onItemClick: function () { },
            itemArray: new Array(),
        }, options);

        for (i = 0; i < settings.itemArray.length; i++) {
            var $item = $("<li></li>");
            $item.html(settings.itemArray[i].itemLabel);
            $item.click(function () {
                settings.onItemClick.call(settings.itemArray[i]);
            });
            this.append($item);
        }

        return this;

    };

}(jQuery));

As you can see, I'm accepting a function as a variable - onItemClick. I've left out the test that this exists and is a function for the sake of brevity.

My example page looks like this:

<body>
    <div> 
        <ul id="testList"></ul>
    </div>
</body>
</html>

<script type="text/javascript">
    $(function () {

        var testDataArray = new Array();

        var itemLabel = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"];
        var itemDate = ["2017-01-03", "2015-08-03", "2016-06-21", "2016-12-20", "2013-09-07"];

        for (i = 0; i < itemDate.length; i++) {
            var testData = {};
            testData["itemLabel"] = itemLabel[i];
            testData["itemDate"] = itemDate[i];

            testDataArray[i] = testData;
        };

        $("#testList").test({
            itemArray: testDataArray,
            onItemClick: function (item) {                
                alert(item.itemLabel + ": " + item.item.Date);
            }
        });
    })
</script>

So, when I call the plugin from my page, I want to be able to pass a function as a parameter and be able to include variables in that function which will be populated with real values by the plugin. That last point is where I'm getting stuck.

The code I've written doesn't work as is. Unsurprisingly, it throws the error

Cannot read property 'itemDate' of undefined

because settings.itemArray is not known within the scope of the function I'm trying to attach to the click event of the item. I understand that, I just don't understand what the correct way is to do this!

1
  • create plunker example Commented Jan 14, 2017 at 14:50

1 Answer 1

1

here's a working snippet.

you were on the right path;

  1. I'd added an argument 'item' to the onItemClick callback function in the plugin
  2. when you call a function from a plugin in this way the first argument should be undefined as in settings.onItemClick.call(undefined, itemToSend);
  3. your plugin creates a number of list items in the dom, but the original click function has no way to differentiate between them when the array item is chosen. for this reason all were coming back as arrayitem 5, therefore in this version the index of the list item clicked is checked, and this is used to pick the array item, alternatively the array item content could be attached to the dom element via the data attribute (as commented)
  4. your alert function called item.item.Date instead of item.itemDate
  5. The click function has been moved outside of the for-loop and the selector is associated with the plugin's main selector, to allow for multiple lists

hope this assists.

(function($) {

  $.fn.test = function(options) {
    var settings = $.extend({
      onItemClick: function(item) {

      },
      itemArray: [],
    }, options);


    var arrayItems = settings.itemArray;

    for (i = 0; i < arrayItems.length; i++) {
      var $item = $("<li></li>");
      $item.html(arrayItems[i].itemLabel);

      // $item.html(arrayItems[i].itemLabel).data('arrayItems', arrayItems[i]); 

      this.append($item);
    }
    $('li', this).click(function() {
      var indx = $(this).index(),
        itemToSend = arrayItems[indx];
      //  var itemToSend = $(this).data('arrayItems');
      settings.onItemClick.call(undefined, itemToSend);
    });
    return this;

  };

}(jQuery));

// copy everything above to a separate file and call via html


$(function() {

  var testDataArray = [];

  var itemLabel = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"];
  var itemDate = ["2017-01-03", "2015-08-03", "2016-06-21", "2016-12-20", "2013-09-07"];

  for (i = 0; i < itemDate.length; i++) {
    var testData = {};
    testData.itemLabel = itemLabel[i];
    testData.itemDate = itemDate[i];

    testDataArray[i] = testData;
  };

  $("#testList").test({
    itemArray: testDataArray,
    onItemClick: function(item) {
      console.log(item.itemLabel + ": " + item.itemDate);
    }
  });
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<!--    <script src="/test.js"></script>   -->

<div>
  <ul id="testList"></ul>
</div>

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks very much for this. This solution produces the desired effect, with the click event of the items writing the values of the object in the array to the console. However, I don't think it answers the question I was trying to ask - I probably didn't explain it clearly! In my example, the code which displays the alert is in the page, not the plugin, which is critical. What I want is for the plugin to return the item as a variable which the developer writing the page could use as they saw fit. This is an example of what I'm trying to do: fullcalendar.io/docs/selection/select_callback
answer edited to reflect. no change to the code, you just need to move the plugin script to a different file and call via html. The item variable data is passed back and forth as required. your onItemClick function in the page script is the same as the callback function you're after I believe. your plugin is already written and structured as a private function, so whether it's in the same page or called from another file it will work as long as it's loaded before the plugin is called.
Thanks again. I realised that I've over-simplified my real-life problem to formulate this question, to the point where your answer, whilst it perfectly addresses the problem as I've posed it, doesn't really work in my actual code. However, you definitely put one the right track - it was key to realise that I could attach the click handler after rendering the elements and reference the object from the original array. What I've ended up doing is adding the object from the array related to each element ("item", in this example) to the .data() of the DOM element, making it easy to reference later.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.