Introducing Relax Accordion: Accordion with drag-and-drop and callback functionality

I’m happy to release a jQuery plugin: Relax accordion. Relax accordion was made using jQuery and some jQuery ui components, the sortable library and the droppable library.

This plugin lives on github.

Setup

You’ll need to clone this repository to use the files here.

git clone git@github.com:alabid/relaxaccordion.git

on your unix/linux command line.

To use relax accordion, you need three core js files:

  • jQuery.js — the new wave JavaScript library. The other two .js files depend on this.
  • relax.js — JavaScript file containing the implementation of the relax accordion.
  • jQuery.draggable.droppable.custom.min.js — it contains the draggable and droppable jquery ui libraries.

If you are going to use any more of the jQuery ui libraries, I’d advise that you just download and include the jQuery ui libraries you’d use with your application together with relax.js. If you do that then you wouldn’t have to include (iii) anymore. But make sure that you have the draggable, droppable, and sortable jquery ui libraries coupled with your downloads.

Download jquery ui libraries here.

After downloading (i), (ii), (iii), you can include these files in your html file like this (and preferably in this order):

 <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="jquery.draggable.droppable.custom.min.js" 

<script type=”text/javascript” src=”relax.js”></script>
<script type=”text/javascript” src=”myscript.js”

Then you can call the relax plugin in your own script like this:

$("ul.menu").relax();

The selector ul.menu specifies the list where the relax function will act upon and make a RELAX accordion.

Usage

The .relax() function takes in an object that you could use to specify some options for the relax accordion:

 // default values $("ul.menu") .relax({ "animate" : "fast", "activate-links" : false, // or "deactivate-links" : true "openondrag" : false, ondrop: function() {}, // or null opacity: 0.7 }); 

animate option specifies how fast the accordion menus should slide up or slide down. Available options: “fast”, “slow”, “normal”.

activate-links option applies to only accordion that use <a> tags in them (which is most accordions). It deactivates all the links. You can activate the links again by simply setting this option to true.

openondrag specifies if a sublist in the relax accordion menu should be open when a drag operation is being performed. If openondrag == false, then whenever you start to drag an <li> from one container to another, any open sublist closes.

ondrop is a callback that will be called whenever an <li> is moved from one sublist to another.

For example:

ondrop: function(dropped, into, contained) { log("I am "); log(dropped); log("dropped into "); log(into); log("in this menu: "); log(contained); } 

dropped is the last list item (<li>) that was dropped before the ondrop callback was called.

into is the list <ul> into which dropped was dropped into.

contained is the container/menu that encapsulates both dropped and into.

Examples


Examples of usage of relax.js are at http://vidmuster.com. Check out these ones:

  1. Two sortable accordion menus. One uses <a> elements and the other uses <p> elements in the accordion tabs. Example 1.
  2. Two sortable accordion menus that communicate with each other. You can drag from one menu to the other and also specify callbacks that will execute just immediately after you’re done moving an <li>Example 2.

Feedback, Bugs, Suggestions

I’d really like your feedback, comments, and bug reports sent to me somehow preferably by filing an issue (github feature).

Most Important Regular Expression for parsing HTML

The usefulness of Regexes (Regular Expressions) is ineffable. Especially in parsing documents, it’s a well-suited and indispensable tool. All good HTML and XML Parsers basically use Regexes to extract cardinal information in HTML documents like names of tags, whether the tag being examined is well-formed, empty, or even malformed by checking the tags against a bunch of rules. Of all the regular expressions needed by a HTML parser, the most important/complex is the one that matches the start tag of an element.

There are a lot of Regular Expression solutions on how to parse HTML tags and attributes. The most popular one (on the Internet that I’ve seen so far) is something like this: "<(\/?)(\w+)[^>]*(\/?)>." This is a non-greedy regular expression that matches both a start and an end tag. For example, it will match a "<pre>" and a "</pre>." It will also match a "<br/>" which is an Empty element. This Regex is so inefficient because it fails to consider a bunch of cases: How would the parser know if the tag is an empty, block, or inline element? How would it know if the tag has attributes and how would it handle these attributes? These questions cannot be answered by using this overly simplistic Regex.

I found a very efficient Regular Expression to parse HTML tags and attributes: /^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/. This Regex makes it easier to not only identify empty tags but also parse the attributes in these tags.This intelligent piece of Regex was in John Resig’s “HTMLparser.js”–a simple HTML parser made by John Resig. At first sight, it seems complicated. But if you look more closely into this Regex, you will notice it simply extracts necessary information about the tag: its tag name, its attributes, and its type (empty or not).

BREAK DOWN OF THE REGEX

There are three main groups:

  • The first group – (\w+) – captures the name of the tag being examined.
  • The second group is the largest of the three. It contains sub-groups, most of which are non-matching (groups with ?: are non-matching which means that information about the group wouldn’t be available after the group is matched). This second group matches the attributes in a tag (if there exists any). The group checks for quotes (double or single) around an attribute’s value. It also handles the case where there are  no quotes around the attribute’s value: [^>\s]+ . This also makes sure that the regular expression matching is non-greedy ([^>]* prevents capturing more than one right-angled bracket).
  • The third group is (\/?) and it simply checks if the tag being examined is an empty element (like <br/>’s and <hr/>’s).

Python script to mirror a directory on another (unix/linux/mac)

For some time now, I’ve been trying to figure how to mirror a directory recursively so that all files (in any depth of the source directory) in the source directory will be in the destination direction. In addition, if any files on the source directory changed, we could update the destination directory just by running the program again.

Precondition:
For the program to run, you need to make the variables, from_dir and to_dir, point to the source and destination directories respectively.

At the moment, the script works excellently well and is well commented to elucidate the workings of the script and my intentions. On the other hand, I believe there can be some improvements to the script like:

  • Enabling input facilities in the python script so that the client will only need to enter the values of from_dir and to_dir variables.
  • Make it cleaner by commenting more.
  • Or even package it into a class.
  • Or if we (or you or I) feel very challenged (or less lazy), we/you/I could distribute it as a python third-party package.

Please modify as you please! This code is in the public domain.

#!/usr/bin/env python

# Python script to compare two directories, from_dir and to_dir
# and copy or files from from_dir that are not in to_dir
# it does this recursively as it walks on directories
# author Daniel Alabi
# date
import os
import os.path
import re
import subprocess

# change from_dir and to_dir as appropriate
from_dir = "/home/daniel/jquery/"
to_dir = "/home/daniel/jquery-alias/"

# getExtDir gets the remaning path of the full
# path when "from_dir" has been removed from
# dir
def getExtDir(dir):
    if re.search(from_dir, dir):
    return re.sub(from_dir, "", dir)

# getUpperLevelDir gets the full path of the present
# directory (".")
def getUpperLevelDir(dirString):
# handle the case where dirString already has a
# trailing "/"
    if (dirString[-1] == "/"):
        dirString = dirString[0:-1]
    to = dirString.rfind("/")
    return dirString[0:to] + "/"

# updateDirs recursively updates to_here until
# it has the same files (up-to-date ones) and
# structure as from_here
def updateDirs(from_here, to_here):
    os.chdir(from_here) # cd to from_here

    # walk the from_here directory
    for file_or_dir in os.listdir(from_here):
        to_file_or_dir = to_here + file_or_dir
        from_file_or_dir = from_here + file_or_dir

        # check if to_file_or_dir is a dir by first determining if it
        # is supposed to be a dir
        # if it is a dir, first of all attach a trailing forward slash
        # this will make it easier for the remaining part of
        # the program to deal with the directories, from_here and to_here
        if (os.path.isdir(file_or_dir)):
            to_file_or_dir += "/"
            from_file_or_dir += "/"

            # check if to_file_or_dir (as a dir) exists
            # if it doesn't make the directory
            if (not os.path.exists(to_file_or_dir)):
                os.mkdir(to_file_or_dir)

            upperleveldir = getUpperLevelDir(from_file_or_dir)

            # now we are sure that to_file_or_dir
            # exists and is a dir; recurse into it
            updateDirs(from_file_or_dir, to_file_or_dir)

            # cd back to where you came from
            os.chdir(upperleveldir)
        else:
            # it is a file

            # check if the file exists
            if (os.path.exists(to_file_or_dir)):
                # get the times when they were modified last
                modified_to = os.stat(to_file_or_dir).st_mtime
                modified_from = os.stat(from_file_or_dir).st_mtime

                # here we check if the modified times of to_file_or_dir
                # is the same as that of from_file_or_dir
                # Since we just want to make sure that the file
                # in to_here (the directory we are going to) mirrors
                # the one in from_here, we just check that the modified
                # times are equal
                # if they aren't we copy the one from from_here
                # to the one in to_here and remove
                # the already existing one in to_here 

                if (modified_from > modified_to):
                    copyString = from_file_or_dir + " " + getUpperLevelDir(\
                        to_file_or_dir)
                # print useful messages to screen about the update
                # i'm about to make
                    print from_file_or_dir, "has been modified and",\
                         to_here, "not updated"
                    print "Copying" , from_file_or_dir, "from", \
                         from_here, "to", to_here

                    subprocess.Popen('cp ' + copyString, shell=True)
                    os.remove(to_file_or_dir)

            else:
                # the file to_file_or_dir does not exist
                # so we copy the file from from_here
                # to to_here
                copyString = from_file_or_dir + " " + getUpperLevelDir(\
                      to_file_or_dir)

                # print useful message to screen about copying
                # from_file_or_dir to to_here
                print from_file_or_dir, "does not exist in",\
                      to_here
                print "Copying" , from_file_or_dir, "from", \
                      from_here, "to", to_here

                subprocess.Popen('cp ' + copyString, shell=True)

# traditional main that does calls
# updateDirs -- the recursive function
def main():
    print "***Updating files in", to_dir, "to mirror the ones in", \
         from_dir, "***\n"

    updateDirs(from_dir, to_dir)

    print "***FINISHED***"

if __name__ == "__main__":
    main()

End of Program

Note: There might be some indentation mistakes in the above script which might have occurred in the process of copying the source from my editor to the WordPress post textArea. Bear with me!