Saturday, December 24, 2011

Just another object-oriented approach to jQuery plugins

It has been almost a year since I have been working primarily in JavaScript. During this time I have written three jQuery plugins and loads of other scripts.This is the story of how my approach to writing jQuery plugins has evolved.

I was working on my first plugin, which was supposed to be a large (in LOC) and went through the authoring mentioned on the jQuery site. In the beginning- it was great, a few exposed methods, well organized code, private functions, everything looked pretty. Soon the code reached some 1000 lines and it started becoming messy for me. To clarify, I am basically a java developer. I accept that the coding practices will obviously differ in every language, but for me, object oriented approach to code seems much more understandable and tidier than 'functions everywhere' thing!

I began searching for object oriented approaches people take in writing jQuery plugins. There are many, but mostly at the cost of some other flexibility. Some approaches allow only one public method, claiming only one namespace is must but more control was needed in the calendar. Some allow complete public access to the options object, but additional control was needed. There were one time calculations based on options that were necessary for the required functionality. Now making the options object public won't give me that control, will it? But apart from that, I could not understand the requirement of making it fully public. Pardon me, this statement is not to question those who follow these approaches, but this is what was thought.

A better approach was needed, where all the flexibility of making it a 'functions everywhere' is retained and a little more organization is achieved in the code. So there emerged a merger. A merger that:

  • Allows multiple methods to be made available.
  • Claims only one namespace.
  • Does not make options simply public.
  • Keeps a context, maintains a state.
  • And follows every other requirement mentioned as a guideline while writing plugins by the jQuery authors.
That merger has now evolved into a simple, precise plugin template! All you need is a case-sensitive, replace-all and you are ready with a working plugin, set with the basic features ready for more, organized plugin...

Here's the code:
/**
 * Plugin comments
 */
(function($, undefined){
 var MyPlugin = function(element, options){
  
  /*
   * *************************** Variables ***************************
   */
  var defaults = {
   defaultValue : '2'
  }; //default options
  
  /*
   * *************************** Plugin Functions ***************************
   */
  
  /*
   * Initializes plugin.
   */
  function initialize(options){
   extendOptions(options);
   sl.log("Got Options- initialize: ");
   sl.log(options);
  }
  
  /*
   * Updates plugin.
   */
  function update(options){
   sl.log("Got Options- update: ");
   sl.log(options);
  }
  
  /*
   * Destroy plugin changes
   */
  function destroy(options){
   // Remove all added classes.
   // Remove all bound methods.
   
   // Remove plugin data
   element.removeData('myplugin');
  }
  
  /*
   * Updates plugin options after plugin has been initialized.
   */
  function setOptions(options){
   extendOptions(options);
  }
  
  //expose plugin functions
  this.initialize = initialize;
  this.update = update;
  this.destroy = destroy;
  this.setOptions = setOptions;
  
  /*
   * *************************** Utility Methods ***************************
   */
  /*
   * Extend the default options using the passed options.
   */
  function extendOptions(options){
   if (options) {
    $.extend(true, defaults, options);
   }
  }
 };
 
 var mP = $.myPlugin = {version: "0.01"};
 $.fn.myPlugin = function(options){
  var args = arguments; // full argument array passed to the plugin.
  
  // Available methods in plugin
  var pMethods = {
   init : function(options){
    // Get the plugin data
    if (this.data('myplugin')) return;
    // Initialize the plugin
    var myplugin = new MyPlugin(this, options);
    // Add plugin data to the element
    this.data('myplugin', myplugin);
    myplugin.initialize(options);
   },
   update : function(options){
    // Get the plugin data
    var myplugin = this.data('myplugin');
    if (!myplugin) return; // do nothing if plugin is not instantiated.

    myplugin.update(options);
   },
   destroy : function(options){
    // Get the plugin data
    var myplugin = this.data('myplugin');
    if (!myplugin) return; // do nothing if plugin is not instantiated.
    
    // destroy data and revert all plguin changes.
    myplugin.destroy(options);
   },
   setOptions : function(options){
    // Get the plugin data
    var myplugin = this.data('myplugin');
    if (!myplugin) return; // do nothing if plugin is not instantiated.
    
    // Update the plugin options
    myplugin.setOptions(options);
   }
  };
  
  // For each element, check and invoke appropriate method passing the options object
  return this.each(function(i, tElement){
   var element = $(tElement);
   
   if (pMethods[options]){
    pMethods[options].call(element, args[1]);
   } else if (typeof options === 'object' || !options){
    pMethods['init'].call(element, args[0]);
   } else {
    $.error( 'Method ' +  options + ' does not exist in jQuery.myplugin' );
   }
  });
 };
})(jQuery);

Now what you need to get going is the replace-all, this is what you replace:

MyPlugin : PluginName
myPlugin : Plugin JQuery Method Name/pluginName
myplugin : Data Name/Variable Name
mp       : pN
pMethods : pluginNameMethods
defaults : defaulsObjectName


That's it!
You are ready with a working plugin!
Oh yes, that sl there in the code is actually the SmartLogger. Read about it here.
This is a quick post and I plan to update the post with more explanation of the code, do visit again!

Let me know how you find it in the comments.

Saturday, December 10, 2011

Habit-Firebug Saver: SmartLogger



How many of the web developers do not depend on Firebug or the chrome's console... Just wondering..

BTW, its plain fun to work with firebug, makes life a lot easier.. Its a different matter all together that the other browser that you have to develop for does not have a powerful enough tool. (Name deliberately avoided to avoid the eminent flame-war!) Yes the current versions have a quite powerful debug and development tools but (hopefully) few developers working on products still have to consider some 10 year old versions (namely 6, 6.5 and 7). Ah, the pain.. Anyways, we are not discussing that..

What we are talking about is the issues that we face when testing our changes to a thousand lines JavaScript code on multiple browsers, especially after we are accustomed to the ease of firebug. :)

I spent much of my time commenting my console.log() statements before I could dare to open the page in IE. Well, fear not, the days have passed! The pain drove me to write a logger object that can not only sense presence of console object but can do much more than that, like ability to assert, selective logging and more..

I call it the SmartLogger.


//Global Logger Object, for use during development, configurable logger.
var SmartLogger = function(options) {
 
 var sl = {}; // Logger Object
 
 // Accepting passed params.
 options = options || {};
 sl.enableLogger = options.enableLogger!==undefined?options.enableLogger:true;
 sl.enableAssert = options.enableAssert!==undefined?options.enableAssert:true;
 sl.loggerOutput = options.loggerOutput!==undefined?options.loggerOutput:undefined; //'console', 'alert', undefined
 sl.selectiveEnable = options.selectiveEnable!==undefined?options.selectiveEnable:'';
 sl.selectiveDisable = options.selectiveDisable!==undefined?options.selectiveDisable:'';
 
 // Logger properties
 sl.name = "SmartLogger";
 sl.whoami = function(){ return "SmartLogger_"+sl.enableLogger+"_"+sl.enableAssert+"_"+sl.loggerOutput+"_"+sl.selectiveEnable+"_"+sl.selectiveDisable;}
 sl.version = '0.7';
 
 // Checks if console object is defined. Checked only at the time of instantiation.
 var hasConsole = (typeof console === "object");
 
 // Checks if logging should be done to console.
 function logToConsole(){
  if (sl.loggerOutput){
   if (sl.loggerOutput === 'console') return true;
  } else {
   if(hasConsole) return true;
  }
  return false;
 }
 
 // Handles the logging intelligence
 function handleLogging(logMethod, logString, strId){
  if(!sLog(strId)) {return;}
  // Decides if to log and logs or alerts appropriately.
  if(sl.enableLogger){
   if (logToConsole()){ // && hasConsole
    if(hasConsole)console[logMethod](logString);
   } else {
    alert(logString);
   }
  }
 };
 
 // Handles the selective logging functionality
 function sLog(strId){
  var allowLog = true;
  if (sl.selectiveEnable) {
   allowLog = strId === sl.selectiveEnable;
  } else if (sl.selectiveDisable) {
   allowLog = !(strId === sl.selectiveDisable);
  }
  
  return allowLog;
 };
 
 // Returns a formatted object structure with current values to complete depth.
 function printString(obj, name, str, strEnd){
  var stringified;
  name = name?name:"Object",
  str = str?str:"";
  strEnd = strEnd?strEnd:"";
  stringified = str+name+" : {\n";
  for (var a in obj){
   if (typeof obj[a] === 'object'){
    stringified+= printString(obj[a],a,"\t",",");
   } else {
    stringified+= str+"\t"+a +" : "+obj[a]+",\n";
   }
  }
  stringified += str+"}"+strEnd+"\n";
  return stringified;
 };
 
 // Exposed methods of the object
 //log a string to console/alert
 sl.log = function(str, strId){
  handleLogging('log', str, strId);
 };
 
 //debug logging a string to console/alert
 sl.debug = function(str, strId){
  handleLogging('debug', str, strId);
 };
 
 //write an information string to console/alert
 sl.info = function(str, strId){
  handleLogging('info', str, strId);
 };
 
 //throw error string to console/alert
 sl.error = function(str, strId){
  handleLogging('error', str, strId);
 };
 
 //Assert an assumption
 sl.assert = function(str, strId){
  if(sl.enableAssert){
   handleLogging('log', 'Assumption: true', strId);
   if(!str){
    handleLogging('error', 'Assumption failed!', strId);
    debugger;
   }
  }
 };
 
 // Logs the formatted object structure with current values to console/alert
 sl.stringToConsole = function(obj, str){
  sl.log(printString(obj, str));
 };
 
 return sl;
};

var sl = new SmartLogger();

Features:

  • Multiple logging profiles can be maintained at the same time with different properties.
var sl = new SmartLogger();
var sl2 = new SmartLogger({selectiveEnable: 'block1'});
  • Proprieties can be set at the time of instantiation or even later.
var sl = new SmartLogger();
sl.loggerOutput = 'console';
var sl2 = new SmartLogger({loggerOutput: 'console'});
  • name, version number and whoami to identify the logger with a string of its current properties.
var sl = new SmartLogger();
sl.name // SmartLogger.
sl.version // 0.7
sl.whoamI() // Returns a string of its properties with the name of the object in a specific sequence:
// "SmartLogger_"+ enableLogger +"_"+ enableAssert+"_"+ loggerOutput+"_"+ selectiveEnable+"_"+ selectiveDisable;
// Example: SmartLogger_true_true_console__b
// We will see what these properties are in some time..
  • Enable or disable logging altogether: enableLogger controls if the statements should ever be logged.
var sl = new SmartLogger();
sl.log('gets logged');
sl.enableLogger = false;
sl.log('never gets logged');
  • Intelligently decides where the logging statements should go...
sl.loggerOutput = undefined; //default
/* Decides based on presence of 'console' object.
If console is present statements will be logged to console,
else like in case of IE, will be 'alerted' to the user.
Now at times this can get messy, with loads of log statements alerting on our face..
But wait, we have ways to handle that.*/

sl.loggerOutput = 'console';
// Plain instruction, no intelligence, all statements will always go to console.
// If console is not present statements will just be eaten-up.

sl.loggerOutput = 'alert';
// Another plain instruction, all statements will always be alerted.
// Will not bother to check if console exists or not.
  • Log formatted objects to console. Now you wont need that much with firebug but to see the entire contents of the object, well formatted you can just say stringToConsole.
// Just a sample object with unknown properties.
var obj = {prop1: 'value',functProp:function(){return "this is a function that returns me!";}, propObj:{prop2:'value2'}};
sl.stringToConsole(obj); // You say this.

// On console or in the alert prompt, you get this
Object : {
 prop1 : value,
 functProp : function () {
    return "this is a function that returns me!";
},
 propObj : {
  prop2 : value2,
 },
}

  • Assert your assumptions. Checks that the assumption is true, if yes, logs so. If assumption fails, will write out an error on the console and invoke the debugger so the user can check in the stack exactly where the assumption failed and why.
sl.assert(Obj.str===Obj2.str)
sl.assert(1==1); // logs 'Assumption: true' to console.
sl.assert(1==2); // Logs error 'Assumption: failed!' and invoke debugger to the assert line in SmartLogger.

//Now you can go and check in the stack and watch to panels to check value and call stack.
  • Has a wrapper for 4 of the logging APIs from firebug and adding new is not much of a task. What it already has:
    • log
    • debug
    • info
    • error
  • Has ability of selective logging.
Now this thing is a live saver. The properties selectiveEnable and selectiveDisable control what statements to log. While these are not mandatory inputs to all the wrappers but I suggest you set them always. These are logging context that can be used to selectively enable logs for only partial of the code, the code that currently interests you..

// Suppose we were working on a defect number 101 and now we are developing a functionality for
// automating welcome messages to users and are asked to urgently fix defect 203.
// Ah, complex scenario, but it will only help understand the purpose.

// When we are working on defect 101, we had the logger configured as statements as:
sl.log("reached here"); // worst way to log: who knows wheres here! but just an example.

// Now we are working on the functionality and
// we would not want those 10 logging statements added while we were working on the defect.
// We can remove them or simply enable the 'selective logger'!
sl.selectiveEnable = 'welcomer';
sl.log("fetching message", "welcomer");
// And voila, only the 'welcomer' messages will be logged.

// Now we get the next urgent defect.
sl.selectiveEnable = 'defect203';
sl.log("value in Obj1.str"+Obje1.str, "defect203");
// We get only the defect203 logs!

// Now some of our new changes depend on the changes we made in defect101, but we cant get the logs from those..
// What do we do? If someone did not enable selective logger and removed the statements, please add them back (:p),
// remove statements for 'welcomer' functionality. Or simply, disable 'welcomer' messages..!
sl.selectiveEnable = '';
sl.selectiveDisable = 'welcomer';
sl.log("value in Obj1.str"+Obje1.str, "defect203");
sl.log("value in Obj2.varInt1"+Obj2.varInt1, "defect101");
// Ha ha! Log statements for 'welcomer' gone and we get the rest!

While using the SmartLogger, I suggest you always pass the string identifier, so that you can control the logs at any point of time later.

What can you expect next in SmartLogger:

  • Use assert from firebug itself.
  • Check that the function exists in the logger before calling it.
  • Make the selective logger take arrays.
  • In stringToConsole, handle functions too to remove that glitch in no closing bracket.

Let me know what you think about the SmartLogger, if you would like any additions to its behaviour and also if you find any defects in the comments below.

Tuesday, December 6, 2011

How to add a nice code block to blogger

While there are awesome syntax highlighting and styling libraries  with nicely written tutorials for using them with blogger, a simple 'code' tag can suffice at times...

For such times:

.myCodeHighlighter {
    border-left: 4px solid #FF9933;
    display: block;
    font-family: Courier,"Courier New",monospace;
    margin: 15px;
    overflow: auto;
    padding: 15px;
    text-align: left;
    white-space: nowrap;
}


And use as:

<code class="myCodeHighlighter">
//the code codes here..
</code>


A preview of how it will make your code tags look like? You are looking right at it! :)

Creating a Local Version Control System

Now this is a post that is going to be updated time and again.. The very post that made me abandon my previous blog to move on to a Google site..
Requirement:
Need to setup a local repository.. a version control system.. Given that I use VSS (I know, please dont make faces..!) at my work place, I am used to the 'ease'.. However it may be, it sure is simple to use...! I need the repository to be platform independent, as I dual boot into Windows 7 and Ubuntu, secondly need it to be local.. Now thats the background, next is a journal...

Which one to use?
Tried:
CVS: on Ubuntu 11.4 and on windows 7.. dint find it so impressive..
TortoiseCVS: Dont know why but the options in the context menu never appeared...! :S
Mercurial: confused about its use.. yet to be tried..
Trying out git.. many friends suggest this.. seems interesting.. simple to learn, they say.. lets see!

Git setup
Alright, what helped me understand it whole was two great articles, one form tiredblogger and another from a harvard site.
Do go through them both... (And you don't have to read this page any further! :) )

Rest is for my reference, for my own understanding..
It was simple to setup..
Download and install git from the ubuntu download center... Download and install git for windows from Google code project.. Done.. Both OS set.
In windows, left almost all settings unchanged during installation..

All I need to do is to create a bare repository to start with, its better they say.. so
in a new folder:
git init --bare
Then clone it into a new repo.. That's the working folder I guess...
so..
git clone <prev folder> <new folder name>
Done..
Then create and add new file to this new folder.. Thats the first source file..
Then, add it to repo..
git add .
Commit the repo..
git commit -a -m "a message"
Push the updates to the main repo, which would otherwise have been a remote repository, the original one from which the working was cloned.
git push origin master
Cool enough.. Lets try new things..

Working on multiple OS
The local repository cant be directly used from both the OS.. so What I am gonna have to try is to work as if I am 2 different persons contributing to a project.. haven't very well understood it all yet! lets see...!

Change only the origin url!

Alright.. Now, all I need to do is change only the path / url in the git config file for the head origin! Once done, the push was fine...! :) so...
Is there a way to avoid this manual work???
like having 2 origins? remotes I mean..
In manual it is said that the remote path can also be passed from a file in $GIT_DIR/remotes
But does that create new branches? If so, that'll clutter the repository unnecessarily...
lets see...


This post will be updated as and when I learn new things..!

Monday, December 5, 2011

mGSD With CheckboxPlugin

    To give an introduction, TiddlyWiki is a wiki in a single html page, a must have tool for every developer, what you use it for is subjective. And mGSD is a TiddlyWiki variant to aid in GTD.

    I believe, CheckboxPlugin is a must have in a tiddly wiki that can even remotely be used to track things! In spite of warnings that it 'damages your mGSD' I haven't been able to damage mine nor the data in it for a while now.. Thought should share it with you..
    But I must remind that it can 'potentially' damage the file or the data in it if proper precaution is not taken. So please backup your wikis if you plan to proceed on the steps I mention here. Do proceed only when you are sure of what you are doing and that you have a copy of the file safe somewhere.
    I did damage a few of the mGSDs when I first tried out using CheckboxPlugin, the auto-backups saved me. What I have found out is that the file is damaged or the data is corrupted only when the CheckboxPlugin-specific constructs are used in mGSD-specific tiddlers!

mGSD-specific tiddlers: By this, I mean the type of tiddlers specific to mGSD, like actions, projects, references, various lists of actions, anything that is not in tiddly wiki but in mGSD.( and where mGSD has its own checkboxes!)
CheckboxPlugin-specific constructs: These refer to [ ], [_], [x] and [X], the constructs used to denote checkbox states in CheckboxPlugin.

installation and use:
  1. Backup the file! (just reminding..)
  2. Ensure that you do not have CheckboxPlugin-specific constructs are used in mGSD-specific tiddlers, simply install the plugin, restart mGSD and check that everything is fine. A rare chance of failure I hope, the constructs are not so very common to be used otherwise. (You have a backup, just in case)
  3. If something fails, find and remove the CheckboxPlugin-specific constructs from the tiddlers.
  4. When you want to add a list for tracking, do not add it in the action directly. Create a tiddler, simple Plain-Old-Tiddler, by going to : 'More... --> TW --> New Tiddler'. name it appropriately and add a link to it in the action, something like: [[MyTracker]].

One Rule: Keep the 'CheckboxPlugin-specific constructs' out of 'mGSD-specific tiddlers' and you should be just fine!

Eclipse: Same Workspace in windows and linux

I believe almost all dual booters wish for something like this.. The eclipse workspace should be independent of the operating system they are logged in..! well.. I have found a way to do it..

Issue:
    The problem with using the same workspace is that 'it doesn't work!' :D well.. the configuration of a workspace is stored in .metadata folder inside the working directory.. it has all the paths.. Most importantly, the path to JRE/JDK..! If a workspace from Windows is opened in Linux/ubuntu the java projects wont compile cause of the build path issues.. If you set it right in ubuntu, it wont work in windows..

Solution:
Well..One of the solutions that works or at least worked for me is:
    Having 2 ws with all projects pointing to an external folder..!

There may be other solutions, but this is how it is done:
    Say you have a workspace under windows, now you need it to work under ubuntu..
Well.. leave alone the original workspace.. Create a new one and 'import' the projects from the original one.. What needs to be taken care of is: dont copy them to their default locations, that is the new workspace... You can also choose to copy the layout and other settings as well.. That way the projects will still be in the original workspace, pointed to from your new one, with all your operating specific data stored in the new workspace..! Works! You can do this for the Windows as well, keeping the 'projects folder' completely independent..

Problems with the approach:
The problem with this approach is that when you create a new project you need to be careful to create it in the original workspace i.e. where all other projects are.. Thats not all that difficult.. And even if you forget once, you can copy this project to the central projects location! Simple...?

What did I try it with:
Windows 7 & Ubuntu 10.10 & 11.4.
Eclipse 3.6.2 Helios

Thats it.. And you have the OS independent Workspace!! :)

Explanation of the name...

Hello!
It's not that this is my first attempt at blogging but my previous attempts never went past a couple of posts... This precisely is the 3rd!
This time, I was serious to make it a habit to blog. The idea is, when you want to blog, it has to be about something new, something you tried 'new', in effect, you keeping doing something new. One of my attempts to keep up experimenting.

This time wanted to make it different, a name that has a real meaning, for both the worlds: the one I belong to and the one I came from...
So, was thinking about a name for the blog... 

The beginning of the expression...

The beginning:
As all my computer literate readers will understand, The Slash (/) stands for the root! The Beginning in this context.

The Expression:
The word 'prot' here stands for proteins.Thinking further, what are proteins? Now the life sciences students will may begin with 'Proteins are biochemical compounds blah blah blah' (read that on Wikipedia), but at an higher level than the biochemical definition, they are what the genes express! The Expression...

 
In short, slashprot ('/prot' to make use of some symbols)!