views:

761

answers:

4

Hi,

I'm trying to write a plugin that creates a ul-list. Got it to working, but I can't figure out how to se if it's already loaded. I believe it's due to that it creates a new instance each time I call it. Is there a way around this.

I use this code for the plugin:

$.fn.addResultList = function (options) {
     var constructor = $('<ul></ul>'),
      offset = $(this).offsetParent();

     constructor.addClass(options.className);
     constructor.append('<li>Lorem ipsum</li>');
     constructor.css({
      backgroundColor : '#fff',
      left : offset.left + 'px',
      padding : '10px',
      position : 'absolute',
      top : offset.top + 'px',
      zIndex : '10'
     });

     $(this).after(constructor);

     modalObjects.push('.' + options.className);

     $('.' + options.className).hover(function() { 
            mouse_is_inside=true; 
        }, function() { 
            mouse_is_inside=false; 
        });


     this.isConstructed = true;

     return this;
    }

And call it like:

$('#related_project_name').addResultList({
        className : 'related_result_list',
        data : $.parseJSON(data),
        callback : function () {

        }
       });

But when I call it the second time it says that this.isConstructed is undefined.

Any ideas?

..fredrik

A: 

don't know if it will work but give it a try:

var constructor;

if(! this.isConstructed){
    constructor = $('<ul></ul>');
}
TheVillageIdiot
+1  A: 

Since this is a jQuery plugin you should always return a jQuery extended object. This might sound unrelated, but it actually should fix your problem. Instead of returning this, return $(this).extend( { constructed: true } );. You'll get back the extended element with the constructed property.

If you're trying to make an object that you can treat like an object in a language like Ruby (eg, you want to be able to say someResultList.reload(); or similar), then you might want to look into John Resig's non-jQuery-dependent-but-complementary JavaScript Inheritance mini-library.

If you give more details on what exactly you're trying to do ('behavior-wise') then more help can appear :)

thenduks
Well what I'm trying to do is creating a plugin that I can attach to any input that will create a list based on what data I send in. When a user clicks outside the list should be hidden, and on focus on the same input, just show it again. Note re-create it. Makes any sense?
fredrik
Perhaps this is a better way to explain it. I would like it to work similar to the dialog widget from jQuery UI.
fredrik
So then I'd go for the first thing I mentioned above. Extend `$(this)` with an object that includes the properties you'll need. Namely, `constructed: true`. Though, I'd suggest rethinking your approach. Why not have the list already on the page and just show/hide it?
thenduks
Just what I did. The reason I didn't include the list already is dude to I don't know where it suppose to be.I used jQuery UI to create what I wanted, I'll paste my code in a new post if anyone is interested.
fredrik
A: 

This is the solution I went with. Works great.

$.widget("ui.addResultList", {
     _init: function() {
      var constructor = $('<ul></ul>'),
       input = $('<input type="hidden" />'),
       offset = $(this.element).offsetParent();

      constructor.addClass(this.options.className);
      input.attr('id', this.options.className + '_inputid');
      input.attr('name', this.options.className + '_inputid');
      constructor.css({
       display: 'none',
       left : offset.left + 'px',
       position : 'absolute',
       top : offset.top + 'px',
       zIndex : '10'
      });

      this.constructor = constructor;
      this.keyListner = false;
      modalObjects.push(this.constructor);

      $(this.constructor).hover(function () { 
             mouse_is_inside = true; 
         }, function () { 
             mouse_is_inside = false; 
         });

      $(this.element).after(input);
      $(this.element).after(constructor);

        },

       _updateList : function (data) {
         var r = data.result,
       l = r.length,
       c = this.constructor,
       obj,
       i;

      c.html('');
      for (i = 0; i < l; (i += 1)){
       obj = r[i];
       if (i === 0) {
        c.append('<li id="result_' + obj.id + '" class="selected"><a href="javascript:void(0)">' + obj.name + '</a></li>');
       } else {
        c.append('<li id="result_' + obj.id + '"><a href="javascript:void(0)">' + obj.name + '</a></li>');
       }
      }
        },

     _addKeyEvents : function () {
      var _this = this,
       old,
       scrollY = 0;
      this.keyListner = true;
      this.element.keydown(function (e) {
       old = _this.constructor.find('.selected');
       scrollY = 42;
       if (_this.constructor.is(':visible') && (e.keyCode === 40 || e.keyCode === 38)) {
        if (e.keyCode === 40) {
         if (old.is(':last-child')) { return; }
         old.next('li').addClass('selected');
         scrollY += old.next('li').height();      
        } else if (e.keyCode === 38) {
         if (old.is(':first-child')) { return; }
         old.prev('li').addClass('selected');
        }
        old.prevAll().each(function (index, elem) {
         scrollY += $(elem).height();
        });

        scrollY -= _this.constructor.height();
        _this.constructor.get(0).scrollTop = scrollY;
        old.removeClass('selected');
       }

       if (e.keyCode === 13) {
        e.preventDefault();
        if (e.stopPropagation) e.stopPropagation();
        $('#' + _this.constructor.attr('class') + '_inputid').val(old.attr('id').split('_')[1]);
        _this.element.val(old.find('a').html());
        _this.close();
        return false;
       }
      });

      this.constructor.find('li a').live('click', function () {
       $('#' + _this.constructor.attr('class') + '_inputid').val($(this.parentNode).attr('id').split('_')[1]);
       _this.element.val($(this).html());
       _this.close();
      });

      this.constructor.find('li a').live('mouseover', function () {
       _this.constructor.find('li').removeClass('selected');
       $(this.parentNode).addClass('selected');
      });
     },

        open : function () {
      this.constructor.get(0).scrollTop = 0;
      if (arguments[0].data.result.length) {
       this._updateList(arguments[0].data);
       if (!this.keyListner) {
        this._addKeyEvents();
       }
       this.constructor.show();
      } else {
       this.close();
      }
        },

        close : function () {
         this.constructor.hide();
        },

        destroy : function() {
           $.widget.prototype.apply(this, arguments); // default destroy
        }
     });

     $.extend($.ui.addResultList, {
      getter: "value length",
        defaults: {
         className: 'related_result_list'
        }
     });

And and a new list with:

$('#related_project_name').addResultList();

And open it with:

$('#related_project_name').keyup(function (e) {
       if (e.keyCode === 13) {
        e.preventDefault();
        if (e.stopPropagation) e.stopPropagation();
        return false;
       }

       if (e.keyCode !== 38 && e.keyCode !== 40) {
        if ($(this).val().length > 1) {
         model.jso.getConstructions($(this).val(), view.related.showProductList);
        } else {
         $('#related_project_name').addResultList('close');
        }
       }
      });

Hope is help to anyone with the same problem. :)

..fredrik

fredrik
A: 

To answer your original question, instead of saving state on the jQuery wrapper object you should use jQuery.data to attach data to a specific element:

$.fn.addResultList = function(options) {
  if (this.data("addResultList.isConstructed"))
    return this;

  // ... code here ...

  this.data("addResultList.isConstructed", true);
  return this;
};
orip