I developed this short script but I'm wondering what is the best way to make $ul
, $el
and some functions e.g. select
private. At the moment these are part of public interface but I would like to hide these. Wrap into another function returning DropDown object maybe? What would be the proper way to do this? Code below:
namespace = {};
namespace.DropDown = function(el) {
this.$el = $(el)
this.$trigger = this.$el.find(".trigger");
this.$ul = this.$el.find("ul");
this.$li = this.$ul.find("li");
this.$trigger.text(this.$ul.find(".selected").text());
this.$trigger.on("click", $.proxy(this.open, this));
}
namespace.DropDown.prototype.open = function(e) {
e.stopImmediatePropagation();
this.$ul.addClass("open");
// position selected element in the middle
var scrollUp,
panelCenter = this.$ul.scrollTop() + (this.$ul.innerHeight() / 2),
selectedPositionTop = this.$ul.scrollTop() + this.$ul.find(".selected").position().top;
if (selectedPositionTop > panelCenter) {
scrollUp = selectedPositionTop - panelCenter;
this.$ul[0].scrollTop = this.$ul[0].scrollTop + scrollUp;
} else {
scrollUp = panelCenter - selectedPositionTop;
this.$ul[0].scrollTop = this.$ul[0].scrollTop - scrollUp;
}
// position elements whole container (list container)
var triggerTop = this.$trigger.offset().top + (parseInt(this.$trigger.css("padding-top")) || 0) + (parseInt(this.$trigger.css("border-top") || 0)),
t = Math.abs(triggerTop - this.$ul.find(".selected").offset().top);
this.$ul.css("top", -t + "px");
this.$li.one("click", $.proxy(this.select, this));
$(document).one("click", $.proxy(this.close, this));
}
namespace.DropDown.prototype.close = function() {
this.$li.off("click");
this.$ul.removeClass("open");
this.$ul.css("top", "0px");
}
namespace.DropDown.prototype.select = function(e) {
$(document).off("click");
e.stopImmediatePropagation();
this.$li.removeClass("selected");
$(e.target).addClass("selected");
this.$trigger.text(this.$ul.find(".selected").text());
this.close(e);
}
$(function() {
new namespace.DropDown($(".dropdown")[0]);
new namespace.DropDown($(".dropdown")[1]);
new namespace.DropDown($(".dropdown")[2]);
});
EDIT:
I managed to wrap it into another function so I could get the properties and functions e.g. $ul, select off the protootype and make private via closure. It does work and I am able to keep guts of the object private but is this the best way to got? it seems overly complicated to me. Also I'm sure I'm not the first to come up with this and so is there a name for this pattern? Modified code below:
namespace = {};
namespace.DropDown = function(el) {
var $el = $(el),
$trigger = $el.find(".trigger"),
$ul = $el.find("ul"),
$li = $ul.find("li");
DropDown = function() {
$trigger.text($ul.find(".selected").text());
$trigger.on("click", $.proxy(this.open, this));
}
function select(e) {
$(document).off("click");
e.stopImmediatePropagation();
$li.removeClass("selected");
$(e.target).addClass("selected");
$trigger.text($ul.find(".selected").text());
this.close(e);
}
DropDown.prototype.open = function(e) {
e.stopImmediatePropagation();
$ul.addClass("open");
// position selected element in the middle
var scrollUp,
panelCenter = $ul.scrollTop() + ($ul.innerHeight() / 2),
selectedPositionTop = $ul.scrollTop() + $ul.find(".selected").position().top; //- $ul.find(".selected").outerHeight();
if (selectedPositionTop > panelCenter) {
scrollUp = selectedPositionTop - panelCenter;
$ul[0].scrollTop = $ul[0].scrollTop + scrollUp;
} else {
scrollUp = panelCenter - selectedPositionTop;
$ul[0].scrollTop = $ul[0].scrollTop - scrollUp;
}
// position elements whole container (list container)
var triggerTop = $trigger.offset().top + (parseInt($trigger.css("padding-top")) || 0) + (parseInt($trigger.css("border-top") || 0)),
t = Math.abs(triggerTop - $ul.find(".selected").offset().top);
$ul.css("top", -t + "px");
$li.one("click", $.proxy(select, this));
//$ul[0].scrollTop = this.scrollPos;
$(document).one("click", $.proxy(this.close, this));
}
DropDown.prototype.close = function() {
$li.off("click");
$ul.removeClass("open");
$ul.css("top", "0px");
}
return new DropDown();
};
$(function() {
new namespace.DropDown($(".dropdown")[0]);
new namespace.DropDown($(".dropdown")[1]);
new namespace.DropDown($(".dropdown")[2]);
});
EDIT 2:
I should have mentioed that I want to keep the methods on prototype
rather than directly on the object itself via this.functionname
. I want to avoid method duplication as this will happen if these are attached to this
directly. For this simple reason this question is not duplicate.