Object-Oriented Javascript – Simple Definition and Inheritance

For some reason, I’ve recently become enamored of the idea of Object-oriented Javascript (OOJS).  Probably because I’ve been wrestling with the strongly-typed complexities of C# / Visual Studio after having been working for so long with the more relaxed PHP, and the quick-and-dirty nature of Javascript is so appealing.  Plus you don’t need any complicated development environments to play with it!

That being said, I was recently at a great Meetup where we talked about OOJS and code structuring tips, which built upon what I had just read by these fantastic articles:

Basic object-oriented principles in JS and its followup regarding inheritance, and another inheritance helper from one of the jQuery guys.

I thought I’d take a stab at it with the following fiddle: http://jsfiddle.net/9CUGG/6/

I went for a very simplistic, contained, and easy to use method to define and inherit classes.  The important parts are the Define and Inherit functions.

// helper macro to assist in class definition
Define = function(Class, methods) {
    for(var method in methods) {
        // only explicitly defined methods
        if( methods.hasOwnProperty(method) ) {
            Class.prototype[method] = methods[method];
        }
    }

    return Class;
};

This lets you easily define a class and attach public methods (vs private or privileged) like so:

// sample class with privileged and/or private properties
function Car(name) {
    var randomIdentifier = (new Date()).getMilliseconds();
    this.name = name || "Car " + randomIdentifier;
    this.wheels = 4;
    this.color = "red";
}

// attach methods to prototype; note that private not accessible
Define(Car, {
    drive: function(){
        _log('driving on '+this.wheels+' wheels: ', this.name);
    }
    ,
    showColor: function(){
        _log( this.name, ' is ', this.color );
    }
    ,
    showName: function(){
        _log('My car is ', this.name);
    }
});

where line 2 is a “regular class” definition with privileged and private properties, and line 9 defines class-wide public methods shared among all instances of Car.

We can then instantiate new car objects and use their functionality with:

// instantiate
var mycar1 = new Car();
var mycar2 = new Car('Miata');

// implement
mycar1.showName();     // >> My car is Car 574
mycar1.showColor();    // >> Car 574 is red
mycar1.drive();        // >> driving on 4 wheels: Car 574
mycar2.showName();     // >> My car is Miata
mycar2.showColor();    // >> Miata is red
mycar2.drive();        // >> driving on 4 wheels: Miata
// helper macro to assist in inheritance; via http://phrogz.net/js/classes/OOPinJS2.html
Inherit = function(parentClass, initialDefinition) {
    var childClass = initialDefinition || function() {}
    childClass.prototype = new parentClass(); // where the inheritance occurs
    childClass.prototype.constructor = childClass; // Otherwise instances of child would have a constructor of parent
    childClass.prototype.parent = parentClass.prototype; // expose super
    return childClass;
};

Note that in line 3, if you don’t specify the “constructor” you get a blank object, as opposed to automatically inheriting the parent’s. I think Resig’s .init trick gets around this.

So, you then inherit like so:

// instead of:  Suv = Object.create(Car);
Suv = Inherit(Car, function Suv(name) {
    this.name = name || "Car " + (++Uid);
    this.wheels = 4;
    this.color = "brown";
});

which lets you re-/define public Suv methods and instantiate objects as before.

Check out the full source at the fiddle, play around, and let me know what you think!

(And, I know I could attach Define and Inherit to the Function prototype, but I left them separate for this example.)

How to Inherit Specific Node Fields in Drupal 7

Content (nodes) in Drupal 7 is generally atomic, in the sense that it’s not by nature related to other content. There are various mechanisms to assign relationships, and one of the more easily understood is the menu hierarchy — e.g. this page is the parent of that page. However, when accessing nodes they don’t tell you about this relationship out-of-the-box, and so how do you set up a section of your site where specific fields are shared or inherited within that section?

An example of this would be that you have multiple departments, and each department’s logo should be displayed across all of its pages (“About”, “Current Events”, etc). There are, of course, many ways to accomplish this — You could set up multiple blocks, one for each logo, or create a special “logo” content type and use taxonomies + views to pull the appropriate logo for related content. For me (and the purposes of this example) it makes the most sense to create a “logo” field for the “department” content type, and if a department logo isn’t specifically set it should fallback to using its parent’s field.

To do this, we need several things (and please correct me if I missed something, because it was wacky):

  1. get the current node
  2. check for a specific field on a node
  3. get a node’s parent, according to the menu

Combining these functions will let us recursively march up the menu tree until we have a value for the specific field. So let’s start with getting the current node:


$node =& menu_get_object();

[source]

Then we need to get a specific field from a given node. Rather than looping through all the properties of the node and their languages, there are two functions I’ve found, each returning slightly different information:

field_get_items('node', $entity, $field_key);

which will return the direct field values from the node, or:

field_view_field('node', $entity, $field_key);

which will return the renderable array of the field instead.

[source 1] [source 2] [source 3]

We can package this into a flexible function (note the examples as given are actually part of a “HELPER” class, so don’t copy/paste them directly):

/**
 * Get a field property from an entity (usually node)
 *
 * Wrapper for a couple drupal functions to get a language-independent value from a node
 * See references:
 * - http://dominiquedecooman.com/blog/drupal-7-tip-get-field-values-entity-objects-nodes-users-taxonomy
 * - http://drupal7ish.blogspot.com/2011/03/getting-field-data-out-of-entities.html (or use field_extract module)
 * - http://drupal.org/node/250700#comment-4557864
 *
 * @requires D7
 * @param string $field_key the field identifier (machine name)
 * @param object $entity {default FALSE} if provided, use the given entity (node); if not given will try to get the current node from menu_get_object
 * @param array $options {default: entity_type = 'node' (what is $entity), fetch = 0 (the nth item), property = 'safe_value' (what value prop), `return_type` = field|renderable } extra options
 *
 * @return the field value or field values, depending on the options (default is single value)
 */
static function get_node_field($field_key, $entity = false, array $options = array() ) {
	$options = array_merge( array(
		'fetch' => 0	// what to fetch; syn: 'single', 'first'
		, 'entity_type' => 'node'
		, 'property' => 'safe_value'
		, 'return_type' => 'field'	// 'renderable'
	), $options);

	// default to "current object" - node
	if( ! $entity ) $entity = & menu_get_object();

	// if still nothing, skip
	if( ! $entity ) return NULL;

	if( 'field' == $options['return_type'] )
		$field = field_get_items($options['entity_type'], $entity, $field_key);
	elseif( 'renderable' == $options['return_type'] )
		$field = field_view_field($options['entity_type'], $entity, $field_key);

	if( !isset( $field )) return NULL;	//no value

	// are we getting a specific index or all
	if( !isset($options['fetch']) || 'all' === $options['fetch'] ) {
		return $field;
	}

	// if we want all the properties...
	if( !isset($options['property']) || 'all' == $options['property'] ) {
		return $field[  $options['fetch']  ];
	}
	// get the requested property for the index
	return $field[  $options['fetch']  ][  $options['property']  ];
}//--	fn	get_node_field

Now comes the dumb part – in order to find the hierarchical relationship, the only reliable way I’ve found is to get the entire menu structure and recursively search it for a specific node, passing the parent back up by reference.

/**
 * Look for the menu item corresponding to the node identifier in the given menu structure
 *
 * @param int|string $identifier either the node id, or the node title
 * @param array $menu the menu structure, usually through either menu_get_active_trail() or menu_tree_all_data()
 * @param array $attr special attributes/settings
 *
 * @return the menu index/item as an array(index, item)
 */
static function menu_find_node($identifier, &$menu, $attr = array() ){
	if( empty($menu) ) return false;

	// look for either node id or link title
	// if we've found it in this menu structure, then it means our parent item is one level up
	foreach( $menu as $menu_key => $menu_item ) {
		if( isset($attr['menu_source']) && 'menu_trail' == $attr['menu_source'] ) {
			$menu_link = &$menu_item;
		}
		else {
			$menu_link = &$menu_item['link'];
		}

		if( isset( $menu_link['link_path'] )
			&&
			(
				( is_numeric($identifier) && 'node/'.$identifier == $menu_link['link_path'] )
				||
				( isset( $menu_link['link_title'] ) && $identifier == $menu_link['link_title'] )
			)
		) {
			return array($menu_key, $menu_item);
		}
	}
	return false;
}//--	fn	menu_find_node
/**
 * Scan a menu object for the given child - if present, return true
 *
 * Iterates down through a menu object (as returned from menu_tree_all_data), looking for a given menu
 *
 * @param object $menu the menu as returned from menu_tree_all_data
 * @param string|int $child_identifier either the node id or the node title to search for
 * @param array $result the menu parent item, to be passed up by reference through the recursion
 * @param bool $as_node TRUE to pass the result as a node, FALSE to pass the menu item
 *
 * @return TRUE if current menu structure contains the identifier; used by recursion for success state
 */
static function menu_get_parent(&$menu, $child_identifier, &$result, $as_node = true) {
	// look for either node id or link title
	// if we've found it in this menu structure, then it means our parent item is one level up
	if( false !== self::menu_find_node($child_identifier, $menu) ) return true;

	// otherwise, check each 'below' recursively for the identifier
	foreach( $menu as $menu_key => $menu_item ) {
		if( isset($menu_item['below']) && true === self::menu_get_parent($menu_item['below'], $child_identifier, $result, $as_node) ) {
			// if we want the menu item, return it
			if( ! $as_node ) {
				if(!isset($result)) $result = $menu_item;	//pass it up the chain, only if it hasn't already been found
				return true;
			}
			// otherwise, turn it into a node
			if(!isset($result)) {
				$nid = str_replace('node/', '', $menu_item['link']['link_path'] );
				$result = node_load($nid);	//pass it up the chain, only if it hasn't already been found
			}
			return true;
		}
	}

	return null;	//failure
}//--	fn	menu_get_parent

We combine the previous functions into a wrapper call:

/**
 * Get a field property from a node, marching up/down the menu hierarchy until a field is provided
 *
 * Wrapper for a couple drupal functions to get a language-independent value from a node;
 * checking up/down the menu hierarchy until a relative has this field
 * See references:
 * - http://dominiquedecooman.com/blog/drupal-7-tip-get-field-values-entity-objects-nodes-users-taxonomy
 * - http://drupal7ish.blogspot.com/2011/03/getting-field-data-out-of-entities.html (or use field_extract module)
 * - http://drupal.org/node/250700#comment-4557864
 *
 * @requires D7
 * @param string $field_key the field identifier (machine name)
 * @param object $relative {default FALSE} if provided, use the given entity (node); if not given will try to get the current node from menu_get_object; in recursive calls, used for marching up/down the menu tree
 * @param array $options {default: entity_type = 'node' (what is $entity), fetch = 0 (the nth item), property = 'safe_value' (what value prop) } extra options
 *
 * @return the field value or field values, depending on the options (default is single value)
 */
static function get_node_field__recursive($field_key, $relative = false, $options = array() ){
	// loop starts with current node (via get_node_field where $relative = false),
	//	continues as long as we keep finding parents/children and the $feed_key isn't found
	while( ! isset( $field_value ) || NULL !== $field_value || ( isset($options['ignore']) && in_array($field_value, $options['ignore']) ) ) :
		$field_value = self::get_node_field($field_key, $relative, $options);

		if( isset($options['relation']) && 'child' == $options['relation'] ) {
			throw new EntityFieldQueryException('Method not implemented - child relation');
		}
		else /* if ('parent' == $options['relation']) */ {
			$relative = self::get_node_parent($relative);
		}

		// stop if we've run out of menu items
		if( ! $relative ) break;

	endwhile;	// no value given, or default (which we don't want)

	return $field_value;
}//--	fn	get_node_field__recursive

Then you can fetch the potentially inherited value using the following:

$whatever = HELPER::get_node_field__recursive('field_WHATEVER', false
	, array(
		'fetch' => 'all' // 'all' | 0 | a "#"
		// , 'property' => 'safe_value'
		, 'relation'=>'parent'
		, 'ignore'=>array('SOME DEFAULT VALUE')
		)
	);

Please note that there are probably modules that do this already, like Node Hierarchy, but I wanted to try this from scratch instead.

Google Maps – Random References

MicroMVC Framework

A while ago I ran across the MicroMVC framework, which purports to be a super light-weight module-oriented MVC framework.  I was tinkering around with the production version for a multi-touch analytics project, and I was really impressed with how Xeoncross set it up.  Highly recommend it.
I had to make some modifications to the core automagic (loader) to allow it to work for a subdirectory installation, and because I was unfortunately using PHP < 5.3 I had to rewrite a bunch of the ORM code, but otherwise it was really fun to use.

Dropdown Menus [Drupal]

How to add dropdown menus to Drupal. Easiest way I’ve found is to use the Nice Menus module (D6 & D7).

Make sure to replace instances in page.tpl.php of theme(..., $primary_links...) with theme('nice_menus_primary_links')

If using the Zen theme, look for theme(array('links__system_main_menu', 'links'), $primary_links)

Additionally, if you need to wrap links in <span> tags:

Basically, just override theme function THEME_menu_item_link($link), as it's used in Zen theme core.

For Drupal 7 (D7) use print render(menu_tree('main-menu'));. This will give you a nested menu structure, which you can theme to your heart’s content to mimic sliders, dropdowns, etc.

How to theme search button as image [Drupal]

Hook to the search form alter function to turn the search button into an image. Uses drupal_get_path to add theme-relative URL for button image.

/**
* Implements hook_form_alter().
*
* @see https://gist.github.com/751854
*/
function MYTHEME_form_search_block_form_alter(&$form, &$form_state) {
	$form['actions']['submit']['#type'] = 'image_button';
	$form['actions']['submit']['#src'] = drupal_get_path('theme', 'MYTHEME') . '/images/button-search.png';
}

From Make Drupal 7 search button an image button.

Migrate another Site into Drupal (thoughts)

The following is summarized/copied from the article Direct Drupal 5 to Drupal 7 Migration in 24hrs:
  1. Install a clean Drupal 7 site, clean database.
    You have to start somewhere, so download Drupal 7 RC2 along with essential modules for your site: Views (and CTools), Webform, WYSIWYG, Mollom, and Devel.
  2. Rebuild the basics.
    This includes building out Taxonomy vocabularies, Content types, and all the fields on those types. Fortunately with modules like Select options, ImageField, and FileField built into core, this goes much quicker than you might expect.
  3. Scale back unneeded modules.
    Date, Link >> simple text fields using the now-provided core Text module. Other things that made sense (in original author’s case) included converting Upload module to D7′s File into a “field_files” field. More on that in step 6.
  4. Throw away your theme.
    API differences in the theme layer make existing tpl.php files pretty much worthless.
  5. Write a custom upgrade module.
    The biggest step in moving from D5 to D7 like this is getting everything that can’t be rebuilt manually (like content, files, and comments) over to the new site. Can use Batch API to migrate existing nodes and comments to the new site. Basic idea – D7 access to two databases in settings.php, “default” and “legacy”. This module would manually select data from the D5 (legacy) site, construct nodes, files, comments, etc. into objects and call node_save() D7 db, thus letting Drupal do the work.  (see module in source link).
  6. Rebuild your Views.
    Views 1 (D5) >> Views 2 (D6) has tool to reconstruct, but Views 3 (D7) may not work.
  7. Work through the bugs.
    D7 still work in progress – be aware of conflicts with modules (see source for example issue with Views)

Other references:

Get Relative URL [Drupal]

How to get a theme-relative URL in Drupal. A wrapper function for Drupal (D6) to get a relative URL to a theme directory file; optionally specifying something other than your theme when called. Note – easiest to use if placed in template.php file, and manually replace MYTHEME with your theme name.

/**
 * Get frontend-path to something, defaulting to theme-relative path
 * @param string $relativePath the path to the file (omit beginning slash), including filename, relative to whatever drupal path indicated
 * @param string $type defaults to 'theme' to retrieve relative to current theme directory
 * @param string $name default to the theme name, for use with interal call to drupal_get_path
 */
function get_drupal_url($relativePath, $type = 'theme', $name = 'MYTHEME'){
      return sprintf('/%s/%s', drupal_get_path($type, $name), $relativePath);

Change Exposed Views Filter to Dropdown [Drupal]

How to change exposed filters in Drupal Views. Override exposed filters — change regular text field to dropdown. Useful in changing date filter from manual input to something like “1, 3 or 6 months ago”.

Add a function overriding HOOK_preprocess_views_exposed_form. This function will:

  • check that we’re overriding the appropriate form by checking the form id ($vars['form']['#id']) - lines 12 to 20
  • change the properties of the requested “filter” field to a dropdown and its options - lines 24 to 32
  • “flag” the form to be re-rendered by unsetting the #printed property - line 35
  • Trigger re-rendering with core drupal_render on the “filter” field, updating the fields associated “widget” - line 36
/**
 * Alter specific exposed forms to change the textfield into a select item
 * @param $vars
 * @param $hook
 * @see http://drupal.org/node/320992
 * @seealso http://drupalsn.com/learn-drupal/drupal-questions/question-7534
 */
function MYTHEME_preprocess_views_exposed_form(&$vars, $hook) {
	//http://snipplr.com/view/17307/hookformalter-for-views-exposed-filter-form/

	//filter behavior for specified forms, by id
	$allowed_forms = array(
		'views-exposed-form-blog-page-1'
		,'views-exposed-form-newsletters-page-1'
		,'views-exposed-form-press-releases-page-1'
		,'views-exposed-form-reports-page-1'
	);

	// filter
	if ( in_array($vars['form']['#id'], $allowed_forms) ) {
		// which form field to override
		$filter = 'created';

		$vars['form'][$filter]['#type'] = 'select'; //change type
		$vars['form'][$filter]['#size'] = '1'; //update size to be a dropdown?
		$vars['form'][$filter]['#options'] = array(
			'' => t('Show All')
			,'-1 Month' => t('Last Month')
			,'-3 Months' => t('Last 3 Months')
			,'-6 Months' => t('Last 6 Months')
			,'-1 Year' => t('Last Year')
		);

		//tricks the Views into rerendering the filter
		unset($vars['form'][$filter]['#printed']);
		$vars['widgets']['filter-'.$filter]->widget = drupal_render($vars['form'][$filter]);
	}
}//--	fn	MYTHEME_preprocess_views_exposed_form

Sources:

  1. DrupalSN – [sic] how do replacement in view filter expose label for other text. – asks how to change the label, but the re-rendering trick is applicable
  2. How to use hook_form_alter for views exposed filter form – again, changing the label, but has some nice logic for generic overriding