Understanding module design patterns in javascript - javascript

I am trying to understand module patterns in Javascript so that i can separate my code into different modules and use them where required.
var messageHandler = (function(){
var el;
var display = function(a){
if(a=='error'){
el = $('.error');
el.css('display','block');
}
else if (a==='success'){
el = $('.success');
el.css('display','block');
}
else if (a=='warning'){
el = $('.warning');
el.css('display','block');
}
else if (a=='danger'){
el = $('.danger');
el.css('display','block');
}
registerClick(el.find('.close'));
return this;
}
function registerClick(p_el){
p_el.bind('click',function(){
hide();
});
}
var hide = function(){
el.css('display','none');
}
return {
display: display,
hide: hide
}
})();
window.messageHandler = messageHandler;
messageHandler.display('warning');
So, I have four different classes in css for different types of messages.The close class is for a small cross button on the top right to close the message.
This works fine till i call the function only once.When i do this
messageHandler.display('warning');
messageHandler.display('success');
Now both the messages close button have been bind to the success close button because el gets overwritten.
How to achieve it keeping the code reusable and concise.

The problem here is that you have a closure variable el that you are overwriting every time display() is called. The hide() function uses whatever is the current value of el at the time it is called, so overwriting el is a problem.
If you want to have "static" functionality like this display() method, you need to avoid shared state.
As #Bergi points out in the comments, you can eliminate the shared el and modify hide() to take an element as input:
var messageHandler = (function(){
var el; // delete this
var display = function(a){
var el; // add this
function registerClick(el){
el.bind('click', function(){
hide(p_el);
});
}
function hide(el){
el.css('display','none');
}
You could also modify hide to make use of the current event properties, and then just have:
function registerClick(el){
el.bind('click', hide);
}
function hide(event){
$(event.target).css('display','none');
}
Cleaned up version including the auto-hide discussed in the comments:
var messageHandler = (function(){
var display = function(a){
var el = $('.' + a);
el.css('display', 'block');
var hideAction = function () { el.css('display', 'block'); };
var token = setTimeout(hideAction, 5000);
el.find('.close').bind('click', function () {
hideAction();
clearTimeout(token);
});
return this;
}
return {
display: display
}
})();

Related

jQuery plugin instances variable with event handlers

I am writing my first jQuery plugin which is a tree browser. It shall first show the top level elements and on click go deeper and show (depending on level) the children in a different way.
I got this up and running already. But now I want to implement a "back" functionality and for this I need to store an array of clicked elements for each instance of the tree browser (if multiple are on the page).
I know that I can put instance private variables with "this." in the plugin.
But if I assign an event handler of the onClick on a topic, how do I get this instance private variable? $(this) is referencing the clicked element at this moment.
Could please anyone give me an advise or a link to a tutorial how to get this done?
I only found tutorial for instance specific variables without event handlers involved.
Any help is appreciated.
Thanks in advance.
UPDATE: I cleaned out the huge code generation and kept the logical structure. This is my code:
(function ($) {
$.fn.myTreeBrowser = function (options) {
clickedElements = [];
var defaults = {
textColor: "#000",
backgroundColor: "#fff",
fontSize: "1em",
titleAttribute: "Title",
idAttribute: "Id",
parentIdAttribute: "ParentId",
levelAttribute: "Level",
treeData: {}
};
var opts = $.extend({}, $.fn.myTreeBrowser.defaults, options);
function getTreeData(id) {
if (opts.data) {
$.ajax(opts.data, { async: false, data: { Id: id } }).success(function (resultdata) {
opts.treeData = resultdata;
});
}
}
function onClick() {
var id = $(this).attr('data-id');
var parentContainer = getParentContainer($(this));
handleOnClick(parentContainer, id);
}
function handleOnClick(parentContainer, id) {
if (opts.onTopicClicked) {
opts.onTopicClicked(id);
}
clickedElements.push(id);
if (id) {
var clickedElement = $.grep(opts.treeData, function (n, i) { return n[opts.idAttribute] === id })[0];
switch (clickedElement[opts.levelAttribute]) {
case 1:
renderLevel2(parentContainer, clickedElement);
break;
case 3:
renderLevel3(parentContainer, clickedElement);
break;
default:
debug('invalid level element clicked');
}
} else {
renderTopLevel(parentContainer);
}
}
function getParentContainer(elem) {
return $(elem).parents('div.myBrowserContainer').parents()[0];
}
function onBackButtonClick() {
clickedElements.pop(); // remove actual element to get the one before
var lastClickedId = clickedElements.pop();
var parentContainer = getParentContainer($(this));
handleOnClick(parentContainer, lastClickedId);
}
function renderLevel2(parentContainer, selectedElement) {
$(parentContainer).html('');
var browsercontainer = $('<div>').addClass('myBrowserContainer').appendTo(parentContainer);
//... rendering the div ...
// for example like this with a onClick handler
var div = $('<div>').attr('data-id', element[opts.idAttribute]).addClass('fct-bs-col-md-4 pexSubtopic').on('click', onClick).appendTo(subtopicList);
// ... rendering the tree
var backButton = $('<button>').addClass('btn btn-default').text('Back').appendTo(browsercontainer);
backButton.on('click', onBackButtonClick);
}
function renderLevel3(parentContainer, selectedElement) {
$(parentContainer).html('');
var browsercontainer = $('<div>').addClass('myBrowserContainer').appendTo(parentContainer);
//... rendering the div ...
// for example like this with a onClick handler
var div = $('<div>').attr('data-id', element[opts.idAttribute]).addClass('fct-bs-col-md-4 pexSubtopic').on('click', onClick).appendTo(subtopicList);
// ... rendering the tree
var backButton = $('<button>').addClass('btn btn-default').text('Back').appendTo(browsercontainer);
backButton.on('click', onBackButtonClick);
}
function renderTopLevel(parentContainer) {
parentContainer.html('');
var browsercontainer = $('<div>').addClass('fct-page-pa fct-bs-container-fluid pexPAs myBrowserContainer').appendTo(parentContainer);
// rendering the top level display
}
getTreeData();
//top level rendering! Lower levels are rendered in event handlers.
$(this).each(function () {
renderTopLevel($(this));
});
return this;
};
// Private function for debugging.
function debug(debugText) {
if (window.console && window.console.log) {
window.console.log(debugText);
}
};
}(jQuery));
Just use one more class variable and pass this to it. Usually I call it self. So var self = this; in constructor of your plugin Class and you are good to go.
Object oriented way:
function YourPlugin(){
var self = this;
}
YourPlugin.prototype = {
constructor: YourPlugin,
clickHandler: function(){
// here the self works
}
}
Check this Fiddle
Or simple way of passing data to eventHandler:
$( "#foo" ).bind( "click", {
self: this
}, function( event ) {
alert( event.data.self);
});
You could use the jQuery proxy function:
$(yourElement).bind("click", $.proxy(this.yourFunction, this));
You can then use this in yourFunction as the this in your plugin.

Accordion Next Button

I have tried searching for what I am trying to accomplish, however I have not found what I am looking for.
I am looking to create a Next and Previous button inside the content of the Spry Accordion provided with Dreamweaver CS6. I have searched the SpryAccordion.js and found this code below:
Spry.Widget.Accordion.prototype.openNextPanel = function()
{
return this.openPanel(this.getCurrentPanelIndex() + 1);
};
Spry.Widget.Accordion.prototype.openPreviousPanel = function()
{
return this.openPanel(this.getCurrentPanelIndex() - 1);
};
So I attempted to do this with "#acc-step-1-next" being my "Next" button in Panel 1.
<script>
$(document).ready(function(){
$("#acc-step-1-next").click(function(){
Spry.Widget.Accordion.prototype.openNextPanel = function(){
return ('#Accordian1').openPanel(this.getCurrentPanelIndex() + 1);
};
});
});
</script>
I was wondering if doing it this way might make it easy! How would I go about applying this? Would this work or not?
Also, with the "Next" button, could I just make it ".acc-step-next" and use it universally, instead of individually assigning new ID's?
EDIT:
Sorry, yes I read your answer incorrectly. I have tried searching for the init property, however have had no success.
This is what starts in the Accordion JS file:
(function() { // BeginSpryComponent
if (typeof Spry == "undefined") window.Spry = {}; if (!Spry.Widget) Spry.Widget = {};
Spry.Widget.Accordion = function(element, opts)
{
this.element = this.getElement(element);
this.defaultPanel = 0;
this.hoverClass = "AccordionPanelTabHover";
this.openClass = "AccordionPanelOpen";
this.closedClass = "AccordionPanelClosed";
this.focusedClass = "AccordionFocused";
this.enableAnimation = true;
this.enableKeyboardNavigation = true;
this.currentPanel = null;
this.animator = null;
this.hasFocus = null;
this.previousPanelKeyCode = Spry.Widget.Accordion.KEY_UP;
this.nextPanelKeyCode = Spry.Widget.Accordion.KEY_DOWN;
this.useFixedPanelHeights = false;
this.fixedPanelHeight = 0;
Spry.Widget.Accordion.setOptions(this, opts, true);
if (this.element)
this.attachBehaviors();
};
Which I added this after, but still no luck:
var acc_next = document.getElementById("acc-step-next");
var acc_prev = document.getElementById("acc-step-prev");
$("acc_next").click(function(){
accordion.openNextPanel();
});
$("acc_prev").click(function() {
accordion.openPreviousPanel();
});
I have never worked with Spry.Widget.Accordion, but I would try something like the following.
Search for the code, where your accordion is initialized, it should look something like that:
var accordion = new Spry.Widget.Accordion("Accordian1",{});
And add this just below:
$(".acc-step-next").click(function(){
accordion.openNextPanel();
});
Together it could look something like that:
<script type="text/javascript">
$(document).ready(function(){
var accordion = new Spry.Widget.Accordion("Accordian1",{});
// Add a click handler to all buttons with the class 'acc-step-next' (yes you can do that)
$(".acc-step-next").click(function(){
// when the button is clicked, call the openNextPanel method of the accordion instance we saved above
accordion.openNextPanel();
});
});
</script>

Define a javascript variable under conditions with jquery

Like the title says, I would like to fill a variable up under some conditions
I thought I could do like that but no :
var content = $(function() {
if ($('#content').length) {
return $('#content');
}
if ($('#content_no_decoration').length) {
return $('#contenu_no_decoration');
}
if ($('#full_content').length) {
return $('#full_content');
}
if ($('#full_content_no_decoration').length) {
return $('#full_content_no_decoration');
}
});
So I thought that the javascript variable 'content' would be one of the jquery object representing an element in the dom. But it seems that 'content' is the function.
I guess you imagine what i want to do.. What is the syntax with JQuery ?
Thank you
$(function() { }) is short-code for the DOMReady event. You need to explicitly define a function, and then assign the return value to your variable.
For example:
function getObj()
{
if($('#content').length)
{
return $('#content');
}
if($('#content_no_decoration').length)
{
return $('#contenu_no_decoration');
}
if($('#full_content').length)
{
return $('#full_content');
}
if($('#full_content_no_decoration').length)
{
return $('#full_content_no_decoration');
}
}
You can then assign the value as :
var content = getObj();
You will need to call the assignment when the DOM is ready though, otherwise the selectors will not trigger as expected. For example:
$(function() {
var content = getObj();
});
You are only declaring the function, so content contains a pointer to the function.
Execute it and you are fine:
var content = function() {
if ($('#content').length) {
return $('#content');
}
if ($('#content_no_decoration').length) {
return $('#contenu_no_decoration');
}
if ($('#full_content').length) {
return $('#full_content');
}
if ($('#full_content_no_decoration').length) {
return $('#full_content_no_decoration');
}
}();
But you don't really need a function here. If the script tag is at the bottom of the page (right before the closing </body>-tag), or the assignment is within a load handler you could use:
var content = $('#content').length
? $('#content')
: $('#content_no_decoration').length
? $('#content_no_decoration')
: $('#full_content').length
? $('#full_content')
: $('#full_content_no_decoration').length
? $('#full_content_no_decoration')
: undefined;
Or use jQuery to your advantage and keep things really short:
var content =
$('#content,#content_no_decoration,#full_content,#full_content_no_decoration')
.get(0);
// if none of the elements exist, content will be undefined, otherwise
// it will contain [a JQuery Object of] the first existing element
why you don't do like that ?
function thatsAGoodName() {
if ($('#content').length) {
return $('#content');
}
if ($('#content_no_decoration').length) {
return $('#contenu_no_decoration');
}
if ($('#full_content').length) {
return $('#full_content');
}
if ($('#full_content_no_decoration').length) {
return $('#full_content_no_decoration');
}
}
var content = thatsAGoodName();
The function
$(function() {
// DOM safe to use do stuff
})
Is shorthand for the document ready event. This tells you the coder that the dom is safe to use.
You would not really return anything from this event.
content is an object because you're setting it to a object here:
var content = $(function() {
What you probably intended was:
var content;
if ($('#content').length) {
content = $('#content');
}
if ($('#content_no_decoration').length) {
content = $('#contenu_no_decoration'); // Is #contenu a typo???
}
if ($('#full_content').length) {
content = $('#full_content');
}
if ($('#full_content_no_decoration').length) {
content = $('#full_content_no_decoration');
}
Note, that this will have a reference to an element now. If you want the actual content you'll need to pull it out with something like html() or val().
You are using the shorthand for the jQuery ready event ($(function() {. What I believe you want is a self invoking function:
// remove the call to jQuery
var content = (function() {
if ($('#content').length) {
return $('#content');
}
// ... more
})(); // invoke the function, which should return a jQuery object
You may need to wrap this in a document.ready, depending on where your script is executed.
Rearrange it a little bit and it should work:
$(function () {
var content = (function() {
var regularContent = $('#content');
if (regularContent.length !== 0) {
return regularContent;
}
var contentNoDecoration = $('#content_no_decoration');
if (contentNoDecoration.length !== 0) {
return contentNoDecoration;
}
var fullContent = $('#full_content');
if (fullContent.length !== 0) {
return fullContent;
}
var fullContentNoDecoration = $('#full_content_no_decoration');
if (fullContentNoDecoration.length !== 0) {
return fullContentNoDecoration;
}
}());
});
This code is basically saying once the DOM is ready (the $(function () { ... }); part), run this anonymous function (the (function () { ... }()); part) and assign its return value to content.
Edit: Also, you're losing efficiency by running each of your selectors twice instead of just once.
It's true that content is the function, but you can use that function. Like:
var result = content();
Edit:
Remove the $() around var content = $({/* code */}) and it works.

How can i optimize my Jquery code?

I've created some JavaScript using Jquery, for the page animation :
I trying to optimize it since i repeat the same thing for subtab1, subtab2, subtab3.
The same function is executed for all of them, and the only thing is changes is variable i iterating on?
Any suggestion?
<script type="text/javascript">
$(document).ready(function () {
var $defensivo = $('#defensivoimg');
var $equilibrado = $('#equilibradoimg');
var $activo = $('#activoimg');
var $defensivoSubTab = $('#subtab1');
var $equilibradoSubTab = $('#subtab2');
var $activoSubTab = $('#subtab3');
var $fundosdiponiveis = $('#fundosdiponiveis');
var $fundosdiponiveisTab = $('#tabs1');
$defensivo.live('click', function () {
$fundosdiponiveis.removeClass("subshow show").addClass("hide");
$defensivoSubTab.removeClass("hide");
$defensivoSubTab.show();
});
$equilibrado.live('click', function () {
$fundosdiponiveis.removeClass("subshow show").addClass("hide");
$equilibradoSubTab.removeClass("hide");
$equilibradoSubTab.show();
});
$activo.live('click', function () {
$fundosdiponiveis.removeClass("subshow show").addClass("hide");
$activoSubTab.removeClass("hide");
$activoSubTab.show();
});
});
</script>
For a while:
var $fundosdiponiveis = $('#fundosdiponiveis');
This is my default div.
var $defensivoSubTab = $('#subtab1');
var $equilibradoSubTab = $('#subtab2');
var $activoSubTab = $('#subtab3');
That divs apears when i clicking on one of the following tabs:
var $defensivo = $('#defensivoimg');
var $equilibrado = $('#equilibradoimg');
var $activo = $('#activoimg');
And that button hides and changes style"display" to none, on click, of my three #subtab's
var $fundosdiponiveisTab = $('#tabs1');
Any suggestion?
You could write a function that returns the proper function:
function createShowTabFunc(tab) {
return function () {
$fundosdiponiveis.removeClass("subshow show").addClass("hide");
tab.removeClass("hide");
tab.show();
}
}
Then assign your click handlers:
$defensivo.live('click', createShowTabFunc($defensivoSubTab));
$equilibrado.live('click', createShowTabFunc($equilibradoSubTab));
$activo.live('click', createShowTabFunc($activoSubTab));
Have a common class attribute to all the tab's and you just need to write $('.class').click() and in this get the id of the corresponding tab and according to the id fetched by attr function, you can have an if else to define your variables inside the if else and execute your code block.

How to add values on Javascript?

I'm a very beginner on this and wondering is there anyway that I can add these two values
maxScreenWidth: 480,
menuTitle: 'Menu:'
into this script.
function DropDown(el) {
this.mainNav = el;
this.initEvents();
}
DropDown.prototype = {
initEvents: function () {
var obj = this;
obj.mainNav.on('click', function (event) {
$(this).toggleClass('active');
event.stopPropagation();
});
}
}
$(function () {
var mainNav = new DropDown($('#mainNav'));
$(document).click(function () {
// all dropdowns
$('.dropdown-menu').removeClass('active');
});
});
thanks in advance.
In addition, this is the dropdown menu that I'm applying to my website.
http://tympanus.net/Tutorials/CustomDropDownListStyling/index2.html
I'm trying to apply this menu only for phone layout but it maintains its form no matter what screen size is. It is supposed be disappeared when it's more 480px but it isn't.
Thank you so much for your help.
If you want to add those properties, just add them like below:
function DropDown(el, width, title) {
this.mainNav = el;
this.maxScreenWidth = width; //added
this.menuTitle = title; //added
this.initEvents();
}
Now they are part of your constructor as passable arguments
Then when you call you constructor, just pass what those values should be
$(function () {
var mainNav = new DropDown($('#mainNav'), 480, 'Menu'); //See, pass them here.
$(document).click(function () {
// all dropdowns
$('.dropdown-menu').removeClass('active');
});
});
You can add this values as DropDown parameters as Christoper wrote, also you can create global variables:
var maxScreenWidth = 480;
var menuTitle = 'Menu:';
function DropDown(el) {
this.mainNav = el;
this.initEvents();
}
//...
But then, if you write it in your js file, other code could access and change your global variables (they are global after all :) ), so this technique exists:
(function ($) {
var maxScreenWidth = 480;
var menuTitle = 'Menu:';
function DropDown(el) {
this.mainNav = el;
this.initEvents();
}
//...
}(jQuery));
In the last example you'r creating function with 'private scope', so your 'private' variables ain't accessible from other js code. Also you should note that you couldn't access DropDown from other code in you application, only within this function.

Categories

Resources