jQuery Snippets

Here are list of JQuery code snippets updated ongoing bases

0. Attach Custom Function to HTML Element
(function ($) {
    $('.image_button').click(function(e){
        e.preventDefault();
        var img_src = $(this).find('img').attr('src');
        $('#focus-image').attr('src',img_src);
    });
})(jQuery);

Here, we put everything in parentheses so it executes on page load while passing jQuery object and assign it to “$” symbol. Afterwards, search the html element with class ‘image_button’ and assign click event with our function. This function grabs src from one element and swaps with another

1. How to find events attached to particular element
$._data($('a[href="/node/56/edit"]')[0], "events");

the first element in the _date() function is the element. In our case, that is a link with an attribute of ‘href’ equal to specific path

or with chrome Developer tools select “inspect element’ on the element and then look at the righ

2. How to assign your custom methods to an DOM element
(function(window, document, $) {

var prototype = $.fn,
	    placeholder;
...
            placeholder = prototype.placeholder = function() {
			return this;
		};
            function customFunciton() {
                    //do something
            }
}(this, document, jQuery));

Here, the prototype refers the global prototype heap, so by assigning function placeholder() to the global prototype we make it available to call on any of the element we wish:

$('textarea').placeholder();

So this will assign the scope with all our custom function, so anyone can all the custom function ‘customFunction’ from now on

3. Ways to call your Custom function
$(window).on('load', function () {
    $('[data-ride="carousel"]').each(function () {
      var $carousel = $(this)
      $carousel.carousel($carousel.data())
    })
  })

Here, onload event we search for dom element and then once found execute our costum method ‘carousel’

4. Stop Event

Once you attach an event to an element(i.e.btn-nav-toggle-responsive) to the link tag, you may want it not to go anywhere. In that case, you prevent the default behaviour

$('.btn-nav-toggle-responsive').click(function(e){
		$('.left-sidebar').toggleClass('show-fullsidebar');
        e.preventDefault();
	});

Here, we passing in the event ‘e’, so that we can call ‘preventDefault() that prevents from going to a different link

5. Trigger Event From Console

To trigger events from console:

var my_e = jQuery.Event("some_event_id");
jQuery("body").trigger(my_e)

This will trigger event ‘some_event_id’ from console
To trigger event from code:

$.event.trigger({
            type: "reset_image_ds"
        });
6. Load JS Library from Console

There are at least two ways:

jQuery.getScript('http://dev-ckeditor/ckeditor/ckeditor.js');

In this approach we use jQuery libarary to load script ckeditor.js. An altertnative, is:

var script= document.createElement('script');
script.type= 'text/javascript';
script.src= 'script.js';
document.head.appendChild(script);

For stylesheets:

document.appendStyleSheet('/path/bootstrap.min.css');
7. Decode String in jQuery

Perhaps, you garbed html text just to learn it is in the encoded form. To decode it back into html tags:

var decodedHtml = jQuery('<textarea/>').html("SOME-STRING-WITH-HTML-CHARACTERS").val();

This will take all html character symbols and convert to appropriate tags names

8. jQuerify HTML

Perhaps, you have raw HTML and you would like to make into jQuery elements, so you can take advantage of jQuery Lib:

var b = jQuery(decodedOldHtml);
var output = b.find('.wrapper-end').html();

This will take each html element and wrap into jQuery objects. Afterwards, you can call jQuery Api as we did here to find certain element(i.e. wrapper-end)

Note: You will get error –

“Uncaught Error: Syntax error, unrecognized expression:”

if the content you are trying to jQuerify(i.e. decodeOldHtml) is not wrapped into any tags. The good practice always wrap into some tags before jQuerify as following:

jQuery('<div>' + decodedOldHtml + '</div>');

This way you will avoid the Syntax error all times for text only content. The more important it will not break on ‘find’ calls by putting it all in one jQuery object. For more, see jquerify-string-to-find-element-fails. At last, ensure the wrapping tags are not ‘body’ because jQuery ignores the ‘body’ tag wrapper

9. Make jQuery Sandbox

It may be too long of name – jQuery to type each time, so what often is done is create jQuery sandbox and pass the “$” as jQuery object:

(function ($) {
       console.log($('find-me').html());
})(jQuery);

Here,we first wrap our code into parentheses, so besides parsing it is also executed. In addition,for our code block, we pass in jQuery object by putting at the end and assigning it to “$” since its the parameter in the function.

Often times your code will do some manipulation on DOM, then you probably like to make sure DOM is loaded before the code in your sandbox is executed:

jQuery(document).ready(function($) {
       console.log($('find-me').html());
});

So, we attach event ‘ready’ before executing the sandbox

At last, it may be the case you will merge your code with other javascript files, then add ‘;’ in front to avoid breaking the execution:

;jQuery(document).ready(function($) {
       console.log($('find-me').html());
})(jQuery);
9. Remove and Replace DOM Elements

Some of the most common manipulations to the DOM is remove & replace. To remove element with its children:

$('.find-me').empty();

This will remove the element ‘.find-me’ and all of the children(the tree) below
To replace an element or often times remove the element but not the children:

var cnt = $('.find-me').contents();
 $('find-me').replaceWith(cnt);

This will take out the element ‘.find-me’ but keep all the children intact.

JS Widget Boilerplate

There are probably several ways to setup and organize code for your JS widget. Here is one I find common and works well for me:

if (!window.VIRTUAL_HOSTY) {
    /**
     * @singleton
     */
    window.VIRTUAL_HOSTY = (function ($) {

        //private variables come here...
        var base_url = window.BASE_URL || '/',
            log_on = false;

        //private function to initiate your widget
        var _init = function () {
            $(window.document).ready(function ($) {
               _parse_url();
            });
        }

        //public interface...the API for your widget
        return {
            set_base: function (some_val) {//function to set private variable
                base_url = some_val;
            },
            get_base: function () {//function to retrieve private variable
                return base_url;
            },
            init: function(){//public function called to initialize widget
              _init();  
            },
            turnOnDebug: function () {//public function to turn on logging
                log_on = true;
            }
        }
    })(jQuery);
}

Here we create global variable VIRTUAL_HOSTY that is the access point of our Widget. All the interaction happens via this variable. The VIRTUAL_HOSTY returns an object. All of the functions specified in this object is public. Everything else is private since we wrapped it into parentheses “()” making the scope execute at the time of load accessible only with the references in the returned object VIRTUAL_HOSTY

Once you have the widget lib file imported you can call on it:

<script>
VIRTUAL_HOSTY.turnOnDebug();
VIRTUAL_HOSTY.init();
</script>

Here,we turn on logging and then initiate our widget VIRTUAL_HOSTY

Importing and Exporting Products in Drupal Commerce

In prevous post Drupal Commerce Install and Setup, we went over on incorporating commerce functionality with Drupal and creating/displaying Products. In this post, we go over how to import/export Drupal commerce “products” as well as “product displays”. Important to note, there are “products” and then there are “product displays”. The “product display” is displaying one to many products referenced by the display. It is plain Drupal node with an extra field of type – product reference. The “product” is specific type provided by Commerce module.

Initially, I was thinking to use features for import and export products in drupal commerce store but it doesn’t have the capability for products to be featured, so the solution for us was using Views data export to export products and Commerce_feeds for importing products

Exporting Commerce Products

So, you have several products that you created with Drupal Commerce product installed and its UI. To export those products, we first create a view containing the products and then export this view via Views data export into CSV file

a) Install views_data_export and dependent modules
drush dl views_data_export
drush dl image_url_formatter
drush en views_data_export image_url_formatter
b) Create a view with products you like to export

Create a view with products you like to export. The view has to list each product field you like to export, so add each product field. Here are other specifics of the view:

  • ‘size’ is set to unlimited
  • give a display name in the Administration settings(in our example ‘theme_export_display’)
  • select image fields with field formatter ‘image_url’ (see module image_url_formatter) with setting ‘nothing’ for linking
    • Image url and ‘URI Path’ for Uri path.
  • ensure price is of type “raw amount”(i.e 8000 for $80.00)
  • ensure product status output format is ‘0/1’
  • Do not include Product ID.
  • keep field labels, so there is header in the export file
  • Format type for taxonomy fields are ‘link’
b) Configure View for export
  1. Add a new “Data export” display to your view.
  2. Change its “Style” to the desired export type. e.g. “CSV file”.
  3. Configure the options (such as name, quote, etc.). You can go back and do
    this at any time by clicking the gear icon next to the style plugin you just
    selected.
  4. Give it a path in the Feed settings such as “path/to/view/csv”.
  5. Attach this view to the view containing the products you like to export by
    updating the “Attach to:” option in feed settings.
  6. Ensure “Items to Display” is set to “Display All”
c) Generate the file via drush command
drush views-data-export --format=csv VIEW-NAME VIEW-DISPLAY-NAME exports/product-exports/theme_export_Jan11_2014.csv --quote-values --strict=0 --header-row

This will take the view and create csv file from it.

Import Commerce Products

Once you have the csv file with commerce products an export generated as described above, we going to use Commerce_feeds for import this products in another Drupal instance

a) install commerce_feeds module and its dependent modules
drush dl feeds
drush dl feeds_ui
drush dl commerce_feeds
drush en feeds feeds_ui commerce_feeds

This install all the necessary artifacts for using commerce_feeds to import Drupal commerce products

b)Create New Feed Importer

Create a new feed importer named “Product Importer” at Administration -> Structure -> Feeds Importer -> Add Importer. Here are specifics of this importer:

  • Change the parser to “CSV Parser”
  • Change processor to “Commerce Product Processor”
  • In “Commerce Product Processor” settings use product type “product” (or whatever your product is) and change the “Author” to your username.
  • ensure Fetcher is of type ‘file upload’
  • In “mapping”, map:
    • SKU -> Product SKU
    • Title -> Product Title
    • Price -> Price: Amount
    • Image -> Image
    • Set ID as unique target.
    • …//so on with other product fields in the .csv file

c) Run import

Go to ‘/import’ url, use the Product Importer and import your products
Note: before importing, make sure you have copied the artifacts into the same location you importing from if there is any custom fields with artifacts

Exporting Product Displays

Everything would be the same if there wouldn’t be product reference field that links the multiple products to the particular display it is for. To import/export ‘product’ field, we will utilize modules ‘feeds_temper’ and ‘feeds_tamper_ui’ modules.

drush dl feeds_tamper
drush en feeds_tamper feeds_tamper_ui

Their responsibility is to take list of SKUs part of ‘products’ field and create multiple product references for each display at the time of import

a)Create a view

Create view with displays to export(i.e. view ‘theme_display_export’ with display name – ‘display_export’). Here are some important details about the view:

  • ensure result size of node is set to unlimited
  • ensure the output formatter for field ‘product’ is set ot ‘SKU no link’
  • ensure label is attached, so it generates csv with a header row

b) Configure View for export
  1. Add a new “Data export” display to your view.
  2. Change its “Style” to the desired export type. e.g. “CSV file”.
  3. Configure the options (such as name, quote, etc.)
  4. Give it a path in the Feed settings such as “path/to/view/csv”.
  5. Attach this view to the view containing the product displays you like to export by
    updating the “Attach to:” option in feed settings.
  6. Ensure “Items to Display” is set to “Display All”
c)Run Export

Export Product Displays into .csv file by calling drush command as following:

drush views-data-export --format=csv theme_display_export display_export exports/display-exports/display_export_Jan11_2014.csv --quote-values --strict=0 --header-row

This will generate .csv file with product displays

Import Product Displays

Once you have the .csv of product displays, we import them in another Drupal instance. Here are the steps:

a)Create Product Display Importer

First, create product display Importer by going to ‘Admin’->’Structure’->’Feeds Importer’ -> ‘Add New Importer’. Some important things to watch for:

  • ensure this is Nodes Processor under the Process
  • ensure Fetcher is set to ‘file upload’
  • create mappings
b)Create Rule for ‘product’ field

Create Rule for ‘product’ field by clicking on the ‘Tamper’ tab and then ‘add plugin’ under the field ‘product’ to ‘Product:SKU’ mapping or however you named the relationship mapping between display to products.
Note: ensure the rule is of type ‘list/explode’

c)Run importer

At last, run importer by going to ‘/importer’ url and selecting the product display importer following the UI

Troubleshooting

1. Unknown option: –header-row. See `drush help views-data-export` for available options.To suppress this error, add the option –strict=0

Make sure to add –strict=0 to the command or in the ‘alias’ configuration file

2. Exported file’s header comes up empty(“”,””,””…)

Make sure to include label for each field, otherwise the header is empty

3. The Images field is coming up empty when exported into csv file

This because there is need to have a special field formatter to format image into url. Module image_url_formatter does exactly that

4. Price is not importing accordingly all zeros

-ensure price is of type “raw amount”(i.e 8000 for $80.00) in the exported CSV file

5. Product Status is always importing into ‘Disable’ State.

Ensure the exported value of product status is in format of ‘1/0’ that can be configured in the view under ‘output format’

6. Images are not displayed in Admin UI.

Ensure that the exporter is set to export image fields into ‘URI path’ output format that is provided by a separate module ‘image_url_formatter’

7. “A product with SKU some_sku could not be found. Please check that the product exists or import it first”(resulting on display only pointing to first product but not all)

Answer: ??? I am not sure…still figuring this out

8. ‘PDOException: SQLSTATE[42S02]: Base table or view not found:’

Make sure cache is cleared after view created/updated. Also ensure your view returns results

9. Missing Feeds plugin FeedsCommerceProductProcessor

This error come up when there was missing ‘commerce_feeds’ module. After installing and enabling it, the error goes away

10. User by id ‘864’…

This happens when running importer with user configured that doesn’t exist on current Drupal instance. Go to admin/structure/feeds/NAME_OF_YOUR_IMPOTER/settings/FeedsNodeProcessor and update user with any user currently present

11. Warning: is_dir(): Unable to find the wrapper “private” – did you forget to enable it when you configured PHP? in file_prepare_directory()

Ensure that the private dir is configured for your Drupal instance (see “Private file system path” at admin/config/media/file-system)

12. Target is missing for Node Processor of Importer

Make sure the field is present fro the content type you are importing

13. Exports exactly 10 items only no matter what the pager is set to

This is happening when the view exported is not of type ‘Data Export’. Ensure exporting view is of type “Data Export”

Reference

  • https://drupal.org/project/commerce_feeds
  • http://drupal.stackexchange.com/questions/87039/how-can-i-import-and-export-commerce-products-in-drupal-7
  • https://drupal.org/node/622698
  • https://drupal.org/project/commerce_feedsmulti
  • http://www.drupalcommerce.org/node/467

Taking CKEditor a Apart

In this post, we dive into the code of CKEditor to learn internals. We needed to do so to understand how the empty tags are being removed by default. This was not acceptable behavior because frameworks like Twitter Bootstrap or Foundation use empty tags part of the layout.

The dev version none minimized CKEditor are located at https://github.com/ckeditor/ckeditor-dev

Boot from Console

When troubleshooting or learning, it always easer to move everything else out of the way but the thing of your interest. In our case, we decided to run CKEditor without Drupal in the browser console for quick, easy changes. In the following are steps to boot CKEditor from console:

1. Disable Drupal

To take Drupal out of way, we disable the CKEditor module

2. Import CKEditor

There were several ways to load CKEditor all of which are listed here

A)From HTML Layout
By inserting the following in top of your page within ‘head’ tag

<script src="http://dev-ckeditor/sites/all/libraries/ckeditor/ckeditor.js?n83a0r"></script>

B) From Drupal API
By executing the following code in hook_preprocess_page() or any other appropriate place for that matter

drupal_add_js('http://dev-ckeditor/sites/all/libraries/ckeditor/ckeditor.js', array('scope' =>; 'header', 'type' =>; 'external'));

C)From JS Console
By calling the following, ckeditor library will be loaded

jQuery.getScript('http://dev-ckeditor/ckeditor/ckeditor.js');

NOTE: In console, I run into errors when ckeditor.js was trying to load its dependences such ad load.js due to unable configure base path. I believe it should work if using the production ckeditor.js instead the development version that is not compiled into one

This will import the library as well as initialize CKeditor after which making the global variable CKEDITOR available and ready, so we can call CKEditor API

Let’s check the status

CKEDITOR.status

It gives status – ‘loaded’ on success and its ready to start ckeditor
There are 4 different status – loaded, basic_load, ???

4. Load Into Textarea

To start up CKEditor in the textarea is as simple as initializing it that can be done from console as following:

CKEDITOR.replaceAll();
//or particular element with certain id
CKEDITOR.replace('id_name');

Here, we start up all of the textareas by calling replaceAll(). In the second, we enable CKEditor on certain element not necessaryly ‘textarea’ with id of ‘id_name’. This should bring up the text editor

Other Stuff

1. Editor Instances
CKEDITOR.instances

This will display all the ckeditor instances

2. See Editor Configuration

From console:

var editor = CKEDITOR.instances.editor1;
alert( editor.config.skin ); // e.g. 'moono'
3. Filters

To see what input is about to be filtered:

CKEDITOR.instances['edit-body-value'].element.$

Setting custom filters:

var filter = new CKEDITOR.filter( 'p strong em br' );
editor.setActiveFilter( filter );
...
...
// Create standalone filter passing 'p' and 'b' elements.
		 *		var filter = new CKEDITOR.filter( 'p b' ),
		 *			// Parse HTML string to pseudo DOM structure.
		 *			fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<p><b>foo</b> <i>bar</i></p>' ),
		 *			writer = new CKEDITOR.htmlParser.basicWriter();
		 *
		 *		filter.applyTo( fragment );
		 *		fragment.writeHtml( writer );
		 *		writer.getHtml(); // -> '<p><b>foo</b> bar</p>'

For hooking into all enable active filters:

editor.on( 'activeFilterChange', function() {
     if ( editor.activeFilter.check( 'cite' ) )
        // Do something when <cite> was enabled - e.g. enable a button.
     else
        // Otherwise do something else.
   } );

To disable filters:

//before the editor is initiated
 CKEDITOR.config.allowedContent = true;
textarea_settings = Drupal.ckeditorLoadPlugins(textarea_settings);
CKEDITOR.replace(textarea_id, textarea_settings);

Here, the textarea_id is the ‘id’ of the element(textarea or div). I am not sure the textarea_settigns???

5. Drupal and CKEditor

In the modules/ckeditor/ckeditor.utils.js, is where the CKEditor instances are created and attached to textarea.
From ckeditor.utils.js, the function CKEditor.replace is called that is defined in the themedui.js that calls CKEDITOR.editor from editor.js

6. Fragmenting in CKEditor
var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<p>foo<b>bar</b>bom</p>' );
 fragment.forEach( function( node ) {
 console.log( node );
} );
		 *		// Will log:
		 *		// 1. document fragment,
		 *		// 2. <p> element,
		 *		// 3. "foo" text node,
		 *		// 4. <b> element,
		 *		// 5. "bar" text node,
		 *		// 6. "bom" text node.
		 *
		 * @since 4.1
		 * @param {Function} callback Function to be executed on every node.
		 * **Since 4.3** if `callback` returned `false` descendants of current node will be ignored.
		 * @param {CKEDITOR.htmlParser.node} callback.node Node passed as argument.
		 * @param {Number} [type] If specified `callback` will be executed only on nodes of this type.
		 * @param {Boolean} [skipRoot] Don't execute `callback` on this fragment.
7. Stop Removing Empty Tags

There is defined list of tags that is going to be removed if empty(see dtd.js and $removeEmpty). To ensure the certain empty tag are not being removed, add attribute ‘data-cke-survive’:

<span data-cke-survive="true" ></span>

or pass the following to the CKEditor configuration file:

CKEDITOR.dtd.$removeEmpty['span'] = 0;
CKEDITOR.dtd.$removeEmpty['TAG-NAME'] = 0;

This make the empty tags to not being removed
NOTE: If you working with the production CKEditor.js, you will have to alter section starting “CKEDITOR.dtd” as following:

 CKEDITOR.dtd=function(){var a=CKEDITOR.tools.extend,e=function(a,d){for(var [...]
span:0//set the 
TAG:0
..

You will have to grep the CKEditor.js file for section starting “CKEDITOR.dtd” and then change value from 1 to 0 for each tags you like to not be removed if empty(see above example for ‘span’ tag)

8. Update Styles

Here is an example for console to update styles:

var editor = CKEDITOR.instances['edit-body-und-0-value'];
editor.document.getBody().setStyle('font-size','30px');

Here, we start by retrieving the CKEditor Instance with id “edit-body-und-0-value”. Afterwards, we set the font-size.

if you like append the whole style sheet than:

editor.document.appendStyleSheet('/sites/all/path/to/css/bootstrap.min.css')
9. Stop Auto Wrap

The default behavior is for CKEditor to wrap text into paragraph tags. To stop that:

//from console
CKEDITOR.config['autoParagraph'] = false

–OR–
In the ckeditor.config.js file:

config.autoParagraph = false

This will stop automatically wrapping every element into paragraph tag. You can also set to add ‘br’ or ‘div’ instead of pargraph tags as following:

CKEDITOR.config['entermode'] = CKEDITOR.ENTER_BR;//or config.entermode=2 in config.js
CKEDITOR.config['entermode'] = CKEDITOR.ENTER_DIV;//or config.entermode=23 in config.js

This will add BR or DIV tag to every element

To stop wrap a certain element such as anchor tag into paragraph tag:

                delete CKEDITOR.dtd.$inline['a'];

This will change the declaration of the anchor tag to not be an inline tag, thus, no any wrapping takes place

Stop remove Empty Anchor tags

To stop removing empty tags, add the attribute – “data-cke-survive” to the anchor tag as following:

<a href="#" data-original-title="github" class="github" data-cke-survive="true">

Or ensure attribute “href” is not present as following:

<a data-original-title="linkedin" class="linkedin">
Stop Wrap UL around LI

By default, CKeditor is wrapping UL element around LI if one is not present. To stop alter CKEDITOR.dtd.$listItem in DTD.js

//OLD:$listItem: { dd: 1, dt: 1, li: 1 },
        $listItem: { dd: 1, dt: 1},

–OR–
By editing CKEditor directly will cause problems sooner or later. Instead, hook into CKeditor and update from outside

if(window.CKEDITOR){
                CKEDITOR.on('instanceCreated', function (ev) {
                  //make sure LI is not wrapped within UL
                    delete CKEDITOR.dtd.$listItem['li'];
                    delete CKEDITOR.dtd.$intermediate['li'];    
                }
}

Here, we hook into the event triggered when CKEditor instance is created to remove “li’ tag from inline tags list.
By removing the LI form the list, you ensure that CKEditor is not wrapping LI elements with additional “UL”. For production, you will have to grep ckeditor.js file to make the change since everything is compiled into one JS file

Hook Into Events

There are lot of events available either on CKEDITOR App variable – CKEDITOR or on each editor instance itself

To listen for event on CKEDITOR APP variable

<script>
CKEDITOR.on( 'instanceReady', function( ev ) {
    editor = ev.editor;
    if(editor.name == 'edit-body-value'){
         console.log( 'appending style - sites/all/themes/metronic/assets/drupal/custom_metronic.css' );
         editor.document.appendStyleSheet('/path/custom_metronic.css');
}
}
</script>      

Here, we sign up for event “InstanceReady” to append custom stylesheets.

To listen for event on editor instance itself

CKEDITOR.on( 'instanceCreated', function( ev ) {
    if(ev.editor.name == 'edit-body-value'){
       ev.editor.on( 'contentDom', function(){
         var ck_dom = ev.editor.document.$;
         var s0= ck_dom.createElement( 'script' );
         s0.type = 'text/javascript';
         s0.onload = function() {
             console.log( 'script /path/jquery-1.11.0.min.js loaded' );
         }
         s0.src = '/path/jquery-1.11.0.min.js';
         s0.$ = s0;
         ck_dom.head.appendChild(s0);
      } );
   }
 });   

Here, we first use event on the CKEDITOR App itself to get handle of the editor obj. Once we got the editor object, we sign up for an event “contentDom” that is triggered once the DOM of editor content is constructed. During this event we are importing JQuery lib

See What’s Going On

To see what is filtered, processed and actually sitting in the editor content(DOM) at any give time from console:

var editor = CKEDITOR.instances['edit-body-value'];
editor.document.$.body //to see what is within BODY tags
editor.document.$.head //to see what is within HEAD tags

Here, we first retrieve the particular textarea of our interest and then navigate to the BODY or HEAD. It will display the DOM in real time

How To Find Which Config.js CKEditor Is Using

To see which config.js the CKEditor is actually using, once the instance is up, go to console and type:

CKEDITOR.config.customConfig
CKEDITOR.instances['edit-body-value'].config.customConfig

This will print out the path and the name of config file CKEditor is using. Note, there may be different config files used at the same time. The CKEDITOR app using one and then each different editor instance(i.e.CKEDITOR.instances.editor1) using another one(perhaps one loaded from the theme folder,etc)

Some Tags are Removed

you can disable the filter with allowedContent configuration as described above. You can also disable processing on a particular tag as following:

  • data-cke-filter=”off”
  • data-cke-processor=”off”
  • autocomplete=”off”

This will apply for the tag and all of its children

Manipulate Editor Content

To remove some element from the the editor:

var element = ev.editor.document.getById( 'ckeditor-wrapper-end' );
element.remove(true);

This will remove element with id ckeditor-wrapper-end while keeping parent and children elements intact.
The better way API for manipulated data of editor is available on “editable” object:

var editable = CKEDITOR.instances['.textId'].editor.editable();
if(editable.find('ckeditor-wrapper-end').count()) editable.find('.ckeditor-wrapper-end').remove();

As you can see, the “editable” API has similar API as jQuery. Please, reference editable.js for complete list of function available.

Update Editor

To update editor content:

var editor = CKEDITOR.instances['TEXTAREA-ID'];
var newHtml = '<p>Hello World!</p>";
editor.setData(newHtml,{
       callback: function() {
         this.updateElement();
        }
});
$('textarea#TEXTAREA-ID').html(newHtml);

Here, we first retrieve editor and then update its content via setData() API. At last, the textarea itself is updated in the DOM

Summary of Events

Event Scope comments
loaded editor triggered once editor is loaded before initiating the editor content
instanceLoaded CKEDITOR triggered once CKEDITOR is loaded
customConfigLoaded editor
configLoaded editor
destroy editor
save editor
key editor
dblclick editor
doubleclick editor
click editor
beforeGetData editor Handle the load/read of editor data/snapshot.
getSnapshot editor sets event data from the editor data
saveSnapshot editor ???
lockSnapshot editor ???
unlockSnapshot editor ???
afterSetData editor sets the data from editor data
loadSnapshot editor takes event data and sets it to the editor data
beforeFocus editor Delegate editor focus/blur to editable.
insertHtml editor takes the data from event and inserts in CKEDITOR.DOM via insertHTML()
insertElement editor takes the data from event and inserts in CKEDITOR.DOM via insertElement()
insertText editor takes the data from event and inserts in CKEDITOR.DOM via insertText()
beforeSetMode editor Before any mode is set on the Editor(i.e. editor.mode)(see plugin.js)
beforeModeUnload editor after mode is set on the editor(i.e. editor.mode)(see plugin.js)
beforeGetModeData editor before getting data after setting the mode(see plugin.js)
afterModeUnload editor passing data to be able update at last step in mode setup(see plugin.js)
toHtml editor at the time data is process including filters applied(see htmlprocessor.js)
dataFiltered editor after some filter applied(see htmldataprocessor.js)
dataReady editor when data is ready before processing and filtering(see htmldataprocessor.js)

To fire any of the events:

editor.fire( 'saveSnapshot' );

Here, we trigger one of the events – saveSnapshot attached to the editor.
To listen an event:

			editor.on( 'saveSnapshot', function( evt ) {
                                //do something with evt.data or evt.data.contentOnly
			} );

Here we listen of event – save Snapshot and then grab the new data via evt.data or evt.data.contentOnly

Encode and Decode

To decode or encode a string:

var result = CKEDITOR.tools.htmlDecode(editor.getData());
var encodedText = CKEDITOR.tools.htmlEncode(result);

Here, the editor data is decoded and encoded. See CKEDITOR.tools for much more utilities

Troubleshooting

1.Resource interpreted as Script but transferred with MIME type text/html: “http://dev-ckeditor/node/add/core/loader.js”

This happened when I was trying to load CKEditor library from console and the CKEditor was trying to load its dependencies. The dependency URL(path) came up wrong resulting in this error. I tried to set CKEditor Base path but it didn’t work because the base path needed to be set before the CKEdiotr library is loaded. I tried that and it didn’t work

The changes doesn’t take an effect

Make sure you clear browser cache as well as Drupal cache for the most current config.js loaded

Adding CKEditor Plugins Manually in Drupal

CKEditor has a lot of new good plugins that doesn’t have supporting Drupal module for easy integration. In this post, we go over how to integrate any native CKEditor plugin without supporting Drupal module present. Specifically, we cover how to install the plugin as part of CKEditor module and how to do the same but as separate custom module. In this post, we have CKEditor installed as a stand alone module not part of Drupals WYSIWYG install. How to install stand alone editor such as CKEditor, please, see post Wysiwyg With Ckeditor In Drupal

To make it more practical, lets install CKEditor plugin – Leaflet that adds map functionality to the editor

Add Plugin Part of CKEditor Module

1.) Download and unzip the plugins here (depending on your setup):

“sites/all/modules/ckeditor/plugins” or “sites/all/modules/contrib/ckeditor/plugins”.

After that, you should have the following folder structures:
“ckeditor/plugins/leaflet”
“ckeditor/plugins/lineutils”
“ckeditor/plugins/widget”

2.) We should now activate the new plugins and add them to the toolbar.

This is done by configuring the Ckeditor Profile Settings, in which by default is located in:
“admin/config/content/ckeditor/edit/Advanced”
“admin/config/content/ckeditor/edit/Full”

A. Activate the Plugins
In EDITOR APPEARANCE >>> Plugins section:
Enable the corresponding checkboxes for the Leaflet, Line Utilities, and Widget plugins.
These are the texts displayed adjacent to their checkboxes:
“Plugin file: leaflet”
“Plugin file: lineutils”
“Plugin file: widget”

B. Add them to the Toolbar
We should make the activated plugins visible in the toolbar,
skipping this step will make the Leaflet plugin inaccessible in the toolbar.

In EDITOR APPEARANCE >>> Toolbar section:
Drag the Leaflet Maps icon (black-colored) from the ‘All Buttons’ section to the ‘Used Buttons’ section.

We need to configure the Leaflet Maps icon only since the Line Utilities and Widget plugins
have no toolbar icons and they will just load in the background.

3.) Then, click the Save button. Clear the Drupal’s overall cache AND clear the browser’s cache.

Clearing the browser’s cache is also very important since the CKEditor’s JS and CSS assets/components are cached also in the browser.

As indicated above, using the “sites/all/modules/ckeditor/plugins” or “sites/all/modules/contrib/ckeditor/plugins”
will work with no additional custom hooks programming since by default CKEditor utilize that folder
in the CKEditor Global Profile Settings: “admin/config/content/ckeditor/editg”.

Adding Plugins As Stand Alone Module

The above approach requires the plugins to be part of the CKEditor Module, but it may not be desirable especially in case for distributing your custom module that requires one of the CKEditor plugin. In that case, its good idea to keep CKEditor plugins in stand alone module part of your custom module perhaps. Here is how to do so:

Lets say your custom module name is ‘custom-module’ and you are going to keep CKEditor plugins in sub-folder ‘ckeditor-plugins’, then the destination location would be:
“sites/all/modules/custom-module/ckeditor-plugins”

After that, you should have the following folder structures:
“sites/all/modules/custom-module/ckeditor-plugins/leaflet”
“sites/all/modules/custom-module/ckeditor-plugins/lineutils”
“sites/all/modules/custom-module/ckeditor-plugins/widget”

2.) Hook Plugins Into CKEditor

Next, lets hook the plugins into CKEditor via hook_ckeditor_plugin as following in custom-module.module file:

/********CKEditor plugins********/
function custom-module_ckeditor_plugin()
{
    $module_path = drupal_get_path('module', 'custom-module');
    $plugins     = array();

    $plugins['leaflet'] = array(
        'name'    => 'leaflet',
        'desc'    => t('DesignsSquare.com: CKEditor: Leaflet plugin'),
        'path'    => $module_path . '/ckeditor-plugins/leaflet/',
        'default' => 't',
        'buttons' => array(
            'leaflet' => array(
                'label' => 'Leaflet Map',
                'icon'  => 'icons/leaflet.png',
            ),
        ),
    );

    $plugins['lineutils'] = array(
        'name'    => 'lineutils',
        'desc'    => t('DesignsSquare.com: CKEditor: LineUtils plugin'),
        'path'    => $module_path . '/ckeditor-plugins/lineutils/',
        'default' => 't'
    );

    $plugins['widget'] = array(
        'name'    => 'widget',
        'desc'    => t('DesignsSquare.com: CKEditor: Widget plugin'),
        'path'    => $module_path . '/ckeditor-plugins/widget/',
        'default' => 't'
    );

    return $plugins;
}

Here, the variable ‘name’ is as specified in the plugin.js that is part of all CKEditor Plugins (look for – CKEDITOR.plugins.add(“NAME”‘) – in the plugin.js for each plugin). The button index(highlight line) has to be exactly as specified in the plugin.js(look for ‘ui.addButton(“BUTTON-NAME”). The variable ‘icon’ specifies the location for the image of the button. Ensure it is there.

Afterwards, the plugins will show up in the CKEditor Configuration section(admin/config/content/ckeditor/edit/Full(or Advance). The rest is the same from the first approach where you would check the plugins in ‘Plugins’ section at admin/config/content/ckeditor/edit/Full(or Advance) and then move the icon into Toolbar.

Troubleshooting

1. Unable To Add Icons in Toolbar

Turn off the toolbar and add icons manually. To turn off the toolbar go to the admin/config/content/ckeditor/editg and select ‘disable’ for ‘Use toolbar Drag&Drop feature’. I am guessing that the toolbar misbehaves because CKEditor Module imports JQuery UI sortable lib that is already imported once by the core. As result, it causes js fatal errors. I am out of time to verify.

2. Uncaught TypeError: Cannot read property ‘icons’ of null

Ensure the name of the plugin as specified in the plugin.js(i.e. CKEDITOR.plugins.add(‘Here-comes-name’,) is the same used in the hook_ckeditor_plugin(). Set a break point with condition of ‘s === null’ where s is the variable the attribute ‘icons’ is called on(i.e s.icons) to help identified which plugin is causing issue in ckeditor.js

References:

  • http://docs.cksource.com/CKEditor_for_Drupal/Enterprise/Drupal_7/Plugins
  • https://github.com/ranelpadon/ckeditor-leaflet/blob/master/Installation%20Guide.txt

Solving Menu Import Issue when Featuring Sample Data in Drupal

[UPDATE] – alias menu import module was rolled into “Sample Data” module that is of newer version. it is listed on Drupal site
Here is post about it – http://margotskapacs.com/2014/07/importexport-sample-data-and-assets-for-kickstart-all-via-features/

Currently, if you would like to export and import the sample data( like kickstart) via features, then the menus for your data breaks because it uses the hard coded path(i.e. node/id) and the new instance has different path for the nodes imported. To my understanding there is a work in place to fix this in future release of features by using the path uuid but of January, 2014 that is not available at least in stable version

The solution presented here utilizes ‘pathauto’ and its generated alias path for each node to do the mapping between the same nodes on two different Drupal instances. In another words, we export existing menu structure with alias path as reference. Once these nodes are imported(before menu import script is run) on the another Drupal instance, it automatically generates the default alias path(for custom alias, please, section ‘If Custom Path Alias…’ below). Since these alias path are the same between the drupal instances, the installation script looks up the mapping when structuring and building menu.

To maintain the existing menu structure, you will have to run a script that will generate the structure used by the menu importer script as showed in this post. This may be improved in future but author didn’t have time at this time and may not be a value since there is path uuid project in progress that will be the better solution in long run

In addition, the path for nodes has to be unique as well.

Requirement:

  1. Pathauto module has to be installed and enabled
  2. Each node have to have path alias or setting ‘Generate automatic URL alias’ selected under ‘Url path settings'(this is default behavior once ‘pathauto’ enabled, so you should need to do anything unless you have nodes present and you just installed/enabled ‘pathauto’. In that case, you have to go into each node and check the setting ‘Generate automatic URL alias’)
  3. The nodes has to be already imported before running the menu_import on the new Drupal instance
  4. (Recommended) When importing Nodes via features, ensure the StrongArm variable or configuration of ‘pathauto_node_pattern’ is also imported. This ensures that for imported nodes the generated alias path is the same as the one in current Drupal instance. This is especially important if the alias pattern for nodes is not the default one

The solution consists of two parts:

Part I: Build Exportable Menu

To build current menu structure for menu of your choice run the following script in “PHP Execute” block on Drupal instance you want to export the menu from(copy/past and then specify menus to export on line 3):

include_once DRUPAL_ROOT . '/includes/utility.inc';
//specify menues to export
$menus = array('menu-bottom-menu', 'main-menu');
$mainMenuArray = array();
//mlid to alias map
$alias_map = array();
$menu_items = array();

$output = "function _menus_altered(){ \n";
$output .= "return array (\n";

foreach ($menus as $menu) {
    $links = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC))
        ->fields('ml')
        ->condition('ml.menu_name', $menu)
        ->condition('ml.hidden', '0')
        ->orderBy('weight')
        ->execute()
        ->fetchAll();

    foreach ($links as $key => $link) {
        $links[$key]['options'] = unserialize($link['options']);
    }
    $mainMenuArray = array_merge($mainMenuArray, $links);

    $mlid_ins = db_select('menu_links', 'ml')
        ->fields('ml', array('mlid', 'link_path'))
        ->condition('ml.menu_name', $menu)
        ->condition('ml.hidden', '0')
        ->execute()->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);

    $menu_items = $menu_items + $mlid_ins;

    $menu_ins = menu_load($menu);
    if ($menu_ins) {
        $menu_item = 'array(';
        $menu_item .= "'menu_name' => '" . $menu_ins['menu_name'] . "',";
        $menu_item .= "'title' => '" . str_replace("'", "\'", $menu_ins['title']) . "',";
        $menu_item .= "'description' => '" . str_replace("'", "\'", $menu_ins['description']) . "'";
        $menu_item .= ")";
        $output .= $menu_item . ",\n";
    } else {
        watchdog(WATCHDOG_NOTICE, "menu: $menu not found");
    }
}
$output .= "    );\n";
$output .= "}\n\n\n";

//generate the map between mlid and alias path
foreach ($menu_items as $mlid => $options) {
    $alias_map[$mlid] = drupal_get_path_alias($options['link_path']);
}
$output .= "function _menu_installed_items() {\n";
$output .= "return array (\n";
foreach ($mainMenuArray as $key => $link) {
    $alias_path = drupal_get_path_alias($link['link_path']);

//prepare link to pass in menu_link_save()
    $link_export = $link;
    $link_export['link_path'] = $alias_path;
    unset($link_export['mlid']);
    unset($link_export['router_path']);
    unset($link_export['options']['identifier']);
    $link_export['plid'] = ($link_export['plid']) ? $alias_map[$link_export['plid']] : $link_export['plid'];
    $link_export['p1'] = ($link_export['p1']) ? $alias_map[$link_export['p1']] : $link_export['p1'];
    $link_export['p2'] = ($link_export['p2']) ? $alias_map[$link_export['p2']] : $link_export['p2'];
    $link_export['p3'] = ($link_export['p3']) ? $alias_map[$link_export['p3']] : $link_export['p3'];

    $output .= "'" . $alias_path . "' => " . drupal_var_export($link_export) . ",\n";
}
$output .= ");\n";
$output .= "}\n";

drupal_set_message("<textarea rows=100 style=\"width: 100%;\">" . $output . '</textarea>');

First copy and past the above code in to “PHP Execute” block. Second, specify the menus you like to export in the line 3 variable ‘$menus’
This script generates two arrays encapsulated in functions _menu_installed_items() and _menus_altered() that you will have to supply in your custom module installation file in next step

Part – II : Build Custom Module to Import Menus

Create a custom block with the following .install script:

function MODULE_NAME_install(){
    $t = get_t();

   // clear menus and create new ones if doesn't exist
    foreach(_menus_altered() as $menu){
        //remove menu links for clean install
        menu_delete_links($menu);
        drupal_set_message($t('Links deleted from menu - ' . $menu['menu_name']));

       if(!menu_load($menu['menu_name'])){
           //doesn't exist...lets create one
           drupal_set_message($t('Missing menu '.$menu['menu_name'].'...creating'));
           menu_save($menu);
       }
    }

    //map containing the structure of the menu
    $alias_map = array();

    //shuffle so that parents are always above the children menu items
    //this is important so when building the $alias_map child is not being inserted without parent already present
    $all_items = _menu_installed_items();
    $parents_p1 = array();
    $parents_p2 = array();
    $parents_p3 = array();
    $parents_p4 = array();
    $parents_p5 = array();

    foreach($all_items as $key => $item){
        if($key == $item['p1']){
            $parents_p1[$key] = $item;
        }
        if($key == $item['p2']){
            $parents_p2[$key] = $item;
        }
        if($key == $item['p3']){
            $parents_p3[$key] = $item;
        }
        if($key == $item['p4']){
            $parents_p4[$key] = $item;
        }
        if($key == $item['p5']){
            $parents_p5[$key] = $item;
        }
    }

    $sorted_menu_list = array_merge($parents_p1,$parents_p2,$parents_p3,$parents_p4,$parents_p5);

    //add links
    foreach ($sorted_menu_list as $key => $item) {
        $alias_link_path = $item['link_path'];
        //look up node based on the alias path of existing drupal instance
        $item['link_path'] = drupal_get_normal_path($alias_link_path);

        //drupal_set_message('plid: '.$item['plid']. ' alias_map:' . $alias_map[$item['plid']] );
        $item['plid'] = ($item['plid'] !== 0 && !empty($alias_map[$item['plid']])) ? $alias_map[$item['plid']] : $item['plid'] ;
        $item['p1'] = ($item['p1'] !== 0 && !empty($alias_map[$item['p1']])) ? $alias_map[$item['p1']] : $item['p1'] ;
        $item['p2'] = ($item['p2'] !== 0 && !empty($alias_map[$item['p2']]) ) ? $alias_map[$item['p2']] : $item['p2'] ;
        $item['p3'] = ($item['p3'] !== 0 && !empty($alias_map[$item['p3']])) ? $alias_map[$item['p3']] : $item['p3'] ;

        $installed = menu_link_save($item);
        if($installed !== FALSE) {
            drupal_set_message($t('Menu Item : '. $key . ' installed'));
            //drupal_set_message('INSERT: link_path: '.$alias_link_path. ' mlip:' . $installed );
            $alias_map[$alias_link_path] = $installed;
        }else{
            drupal_set_message($t('Menu Item : '. $key . ' was not installed'));
        }
    }
    menu_link_save($item);
    menu_cache_clear_all();
    drupal_set_message($t('Installed Menu'));
}

//ADD THE TWO FUNCTIONS FROM PART I HERE

Make sure you add the two functions generated in Part I (i.e._menus_altered(),_menu_installed_items() ) in the .install file where it says ‘//ADD THE TWO FUNCTIONS FROM PART I HERE’. These are used by the script to build the new menus

Thats all….in future we hope to automize the above process by creating module if the problem is not solved soon by the features,uuid modules.

If Custom Path Alias…

The features module currently does not export/import path_alias for nodes. This is okey if you are using path alias of the node that were generated by default by pathauto module, however. If not, then your custom alias path for nodes are not exported/imported via features and the above menu export/import solution will not work. We have created module to export/import path alias published at https://github.com/kapasoft-config-scripts/designssquare_alias_path. By enabling this module, your custom path alias is going to be exported and then imported for all nodes.

Troubleshooting

All of the menu items does not import

Make sure all the menu items imported are pointing to path alias that are unique. If not unique, then you have to break into more than one menu import to work.

How To Implement Custom Field Types In Drupal

In this post, we go over on how to create, handle and display a custom fields in Drupal. In addition, we look on situations when your custom field contains an artifacts that requires an additional care

The hook_field_schema() defines the columns. The table will be only added after an actual field is created(attached to content type). So, if you like to see if the data is being added for the custom field, look into table ‘field_xxx’

The hook_field_shema() needs to be define and declared in the module .install file as following:

function NAME-OF-SCHEMA_field_schema($field) {
    $schema = array();
    $schema['columns']['class'] = array(
        'type' => 'varchar',
        'length' => 50,
        'not null' => FALSE
    );
  
    $schema['columns']['image'] = array(
        'description' => 'The {file_managed}.fid being referenced in this field.',
        'type' => 'int',
        'not null' => FALSE,
        'unsigned' => TRUE,
    );

    $schema['foreign keys'] = array(
        'image_fid' => array(
            'table' => 'file_managed',
            'columns' => array('image' => 'fid'),
        ),
    );

    return $schema;
}

In the above example, a schema of 2 fields are created. One of the fields are a file upload, so there is additional foreign constrain defined.

Test from PHP Execute block

$field = field_info_field('field_designssquare_slider_layer');
module_load_install('field_designssquare_slider_layer');
$schema = (array) module_invoke('field_designssquare_slider_layer', 'field_schema', $field);

Look into Watchdog logs for a message. You should see the message if the install was performed

The columns can also be checked in the table ‘field_NAME_OF_FIELD’ in database

Specify Display

You may like to create a display format for the custom field. The field display gives user ability to select display for the field. Developer can take it and create separate layout via custom theme functions wrapped into template file

To declare display:

/**
 * Implements hook_field_formatter_info().
 */
function videojs_field_formatter_info() {
  return array(
    'videojs' => array(
      'label' => t('Video.js : HTML5 Video Player'),
      'field types' => array('file', 'media', 'link_field'),
      'description' => t('Display a video file as an HTML5-compatible with Flash-fallback video player.'),
      'settings'  => array(
        'width' => NULL,
        'height' => NULL,
        'posterimage_field' => NULL,
        'posterimage_style' => NULL,
      ),
    ),
  );
}

The ‘field types’ specify to which types the display is available. The ‘settings’ is custom parameter passed to the display(will cover later). So, this declares the display available for user to specify from UI. Next, lets implement the display:

/**
 * Implements hook_field_formatter_view().
 */
function videojs_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  if ($display['type'] !== 'videojs') {
    return array();
  }
  if (empty($items)) {
    return array();
  }

  if ($field['type'] == 'link_field') {
    //for field type of 'link_field' do things differently
  }

  $settings = $display['settings'];
  $attributes = array();
  if (!empty($settings['width']) && !empty($settings['height'])) {
    $attributes['width'] = intval($settings['width']);
    $attributes['height'] = intval($settings['height']);
  }


  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
  return array(
    array(
      '#theme' => 'videojs',
      '#items' => $items,
      '#player_id' => 'videojs-' . $id . '-' . str_replace('_', '-', $instance['field_name']),
      '#attached' => videojs_add(FALSE),
      '#entity' => $entity,
      '#entity_type' => $entity_type,
      '#attributes' => $attributes,
      '#posterimage_style' => !empty($settings['posterimage_style']) ? $settings['posterimage_style'] : NULL,
    ),
  );
}

The display formatter calls a custom function declared as following:

function videojs_theme() {
  return array(
    'videojs' => array(
      'variables' => array('items' => NULL, 'player_id' => NULL, 'attributes' => NULL, 'entity' => NULL, 'entity_type' => NULL, 'posterimage_style' => NULL),
      'template' => 'theme/videojs',
    ),
}

Here, the theme custom function used for rendering display is declared. This theme custom function uses a template file ‘theme/videojs’ to render output, but before that the hook_preprocess_CUSTOM-THEME-FUNC is called, so the variables can be preprocessed.

Render the Display

To render specified display as following:

$video_render_array = field_view_field(
                        'node',
                        $entity,
                        'field_video_file',
                        array(
                            'type' => 'videojs',
                            'label' => 'hidden',
                            'settings' => array(
                                  'width' => '300',
                                  'height' => '400',
                                  'posterimage_field' => NULL,
                                  'posterimage_style' => NULL,
                            ),
                        )
                    );
 $content = render($video_render_array);

Here the field ‘field_video_file’ uses a display ‘jw_player’ that takes one parameter jwplayer_preset. Afterwards, the renderable array returned from the display is being rendered.

Create Custom Field Type

To declare custom field type use hook_field_info() as following:

function MODULE-NAME_field_info() {
 return array(
        'OUR_CUSTOM_FIELD_NAME' => array(
            'label' => t('Slide Layer'),
            'description' => t('field containing info about one of the layers part of the Rev Slider'),
            'settings' => array('max_length' => 255),
            'instance_settings' => array(
                'text_processing' => 0,
            ),
            'default_widget' => 'OUR_CUSTOM_FIELD_NAME_widget',
            'default_formatter' => 'OUR_CUSTOM_FIELD_NAME_formatter',
        ),
    );
}

Besides name and description, the default widget and formatter is specified. Next, lets declare and implement the widget for this field type :

/**
 * Implements hook_field_widget_info().
 * Expose Field API widget types.
 */
function MODULE-NAME_field_widget_info() {
    return array(
        'OUR_CUSTOM_WIdGET_NAME' => array(
            'label' => t('Revolution Slide Layer'),
            'field types' => array('OUR_CUSTOM_FIELD_NAME'),
        ),
    );
}

The widget name is given and assigned to our custom field type – OUR_CUSTOM_FIELD_NAME via ‘field types’. After declaring the widget, lets specify the form for user to enter values:

/**
 * Implements hook_field_widget_form().
 */
function MODULE-NAME_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
    $element += array(
        '#type' => $instance['widget']['type'],
        '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
    );
    return $element;
}

/**
 * Implements hook_element_info().
 * Declare the field Form API element types and specify their default values.
 */
function MODULE-NAME_element_info() {
    $elements = array();
    $elements['OUR_CUSTOM_WIdGET_NAME'] = array(
        '#input' => TRUE,
        '#process' => array('designssquare_rev_slider_layer_field_process')
    );
    return $elements;
}

In the function hook_field_widget_form(), it specifies the widget type, so we can use hook_element_info for specifying form. Afterwards, the hook_element_info() is used to specify function returning the renderable array with columns of the schema from the .install file

At last, the widget form is specified:

function designssquare_rev_slider_layer_field_process($element, $form_state, $complete_form) {
    $element['layer_header'] = array(
        'layer_header' => array(
            '#type' => 'markup',
            '#weight' => 0,
            '#markup' => '<p>' . t('Slide Layer '.($element['#delta'] + 1)) . '</p>',
        ),
    );
$element['image'] = array(
        '#title' => t('Image'),
        '#type' => 'managed_file',
        '#weight' => 8,
        '#description' => t('Upload a file, allowed extensions: jpg, jpeg, png, gif'),
        '#default_value' => isset($element['#value']['image']) ? $element['#value']['image'] : '',
        '#upload_location' => REV_SLIDER_DEST.'/img',
        '#states' => array(
            'visible' => array(
//                ':input[title="content_choice_'.$element['#delta'].'"]' => array('value' => 'image'),
                ':input[name="content_choice['.$element['#delta'].']"]' => array('value' => 'image'),
            ),
        ),
        '#upload_validators' => array(
            'file_validate_extensions' => array('jpg jpeg png gif'),
            // Pass the maximum file size in bytes
            'file_validate_size' => array(MAX_SIZE_LIMIT_DS*1024*1024),
        ),

    );
    // To prevent an extra required indicator, disable the required flag on the
    // base element since all the sub-fields are already required if desired.
    $element['#required'] = FALSE;

    return $element;
}

We still have to declare and implement hook_field_is_empty($item, $field), so it know when the field is considered to be empty

function MODULE-NAME_field_is_empty($item, $field)
{
    return !isset($item);
}

Here, we tell that empty is when no instance of our custom field is present

Validating Custom Field Values

To validate the custom field, we first register our custom validation function via hook_formt_alter():

function MODULE-NAME_form_alter(&$form, &$form_state, $form_id)
{
    switch ($form_id) {
        case 'OUR-FORM-ID':
            //assign validation
            $form['#validate'][] = 'some_custom_validate';
            //assign submition
            $form['#submit'][] = 'some_custom_submit';
            break;
    }
}

Since this is a general hook, we filter out only the form of our interest. (For finding Form ID, please, see post ‘Forms In Drupal Overview‘). Afterwards, we specify our custom function for validating the form

Next, the validation logic is declared:

function some_custom_validation(){
 if(empty($form_state['values']['some_field']['und'])){
         form_set_error('some_field[und]', t("error message comes here"));
}

On validation, our custom validation function is being called in which we perform validation for our custom field values

Handling Artifacts For Custom Field

If your custom field contains files, then there is extra care of keeping them permanent as well as removed on deletion. In the above section “Validating Custom Field Values”, we registered our submission processor function. By default all artifacts via file_manage is saved temporary. Next, we ensure the artifacts are permanent on creation and deleted on the remove event:

function some_custom_submit($form, &$form_state)
{
  
        if ((isset($form_state['clicked_button']) && $form_state['clicked_button']['#value'] == "Delete")) {
            //custom field needs to be deleted
            //remove artifact image
            $file = file_load(['values']['our_custom_field']['image']);
            if ($file) {
                file_delete($file);
                file_usage_delete($file, 'MODULE-NAME');
            }
        } else {
            //custom field needs to be added
            //make permanent image file
            $file = file_load(['values']['our_custom_field']['image']);
            if ($file) {
                // Change status to permanent.
                $file->status = FILE_STATUS_PERMANENT;
                //all permanent files needs an entry in the 'file_usage' table
                $id = (isset($form_state['node']->nid)) ? $form_state['node']->nid : 0;
                file_usage_add($file, 'MODULE-NAME', $form_state['node']->type, $id);
                // Save.
                file_save($file);
            }
        }
    }
}

First, we check to see if the node is being deleted or created. Afterwards, we load the artifact and make it permanent or remove it if being deleted

See post ‘Handling Assets For Custom Module In Drupal’ for how to automize artifact import/export

Handling Multiple Instances of Custom Field

In case, user chooses your custom field to be more than one time for specific node, then there is module field_remove_item that allows easy for them to remove any of the instance, however. You will have to handle the artifacts again. That easy to do via hook_field_validate()

function MODULE-NAME_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors)
{
    //remove artifacts  on 'remove item' bottom
            foreach ($items as $key => $item) {
               //see if any of the layers is selected to be deleted
                if ($item['field_remove_item']) {
                    //remove image
                    $file = file_load($item['image']);
                    if ($file) {
                        file_delete($file);
                        file_usage_delete($file, 'MODULE-NAME');
                    }
                }
            }
}

Here, we loop through each instance of your custom field since we don’t know exactly which of the one is being removed. The instance with field ‘field_remove_item’ set, is the one we have to remove artifacts, so we have if statement following with standard way of removing asset managed with manage_file

Uninstalling Custom Fields

If you developed a custom field and installed as a separate module than it will create a lock where you will not be able to uninstall the module. There are two solutions that i am aware. The quick and dirty is to update table ‘fields_config’ to remove the dependency. Another solution is to create a separate module for the purpose to run uninstall hook during which it removes the field, thus, removing the lock so it becomes available to be uninstalled. Lets look at the last solution

Here is the hook_uninstall() for uninstalling the custom module:

function MODULE-NAME_uninstall()
{
    //remove fields from content types
    foreach(_get_bundles_for_field_type('CUSTOM-FIELD-TYPE') as $bundle){
        field_attach_delete_bundle('node', $bundle);
        drupal_set_message('uninstalled field for bundle:'.$bundle);
    }

    //remove tables
    drupal_uninstall_schema('CUSTOM-FIELD-SCHEMA');

    // remove variables
    db_query("DELETE FROM {variable} WHERE name LIKE 'CUSTOM-FIELD-SCHEMA_%'");

    // Remove assets.
//    file_unmanaged_delete_recursive(file_default_scheme() . '://slider');

    // Clear the cache.
    field_info_cache_clear();
}

The function ‘_get_bundles_for_field_type’ returns all the bundles referencing the custom field. Afterwards, we use field_attach_delete_bundle API To remove our custom field type as well as all of its instances. Next the schema is removed, the variables deleted and files removed. At last, we clear the cache

Here is the implementation of _get_bundles_for_field_type():

//returns all the bundles of entity instances that references a field instance with a given type
function _get_bundles_for_field_type($field_type)
{
    $field_map = field_info_field_map();
    $bundles = array();
    foreach ($field_map as $field_name => $item) {
        if ($item['type'] == $field_type) {
            foreach ($item['bundles'] as $bundle_list) {
                foreach ($bundle_list as $bundle) {
                    $bundles[] = $bundle;
                }
            }
        }
    }
    return array_unique($bundles);
}

Render The Field

Troubleshooting

1. PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry ‘public://some/path/img.jpg’ for key ‘uri’: INSERT INTO {file_managed}

This issue came up when our custom field was a file and by reinstalling the field it created a duplicate entry in the manage_file table. The solution was clean file_manage table when instance with our custom field was delete

//clean file_manage database on field instance delete
function MODULE-NAME_node_delete($node){
    if($node->type == 'CERTAIN-TYPE'){
        //removes images from file_manage/file_usage tables
        $field_instance = field_get_items('node', $node, 'field_FIELED-NAME');
        foreach($field_instance as $key => $instance){
            $file = file_load($instance['image']);
            file_delete($file);
            file_usage_delete($file, 'MODULE-NAME');
  ...
}

The hook-node_delete($node) is called when any node is being deleted. Here, in line 5, we look up our custom fields for the node being deleted and then clean the file entry in file_manage and file_usage tables.

This also removes the images from Drupal public directory(by default sites/default/files). We did made files being copied when module is being enabled:

function MODULE-NAME_uuid_node_features_rebuild_alter(&$node, $module){
...
            $source = _get_source_file($final_file->uri);
            file_unmanaged_copy($source, $final_file->uri);
...
}

function _get_source_file($uri){
    $source = drupal_get_path('module','MODULE-NAME').'/imports/public/'.substr($uri, 9);
    if(!file_exists($source)){
        watchdog(WATCHDOG_NOTICE, 'MODULE-NAME Import: Image does not exists at '.$source);
    }
    return $source;
}

The hook_uuid_node_features_rebuild_alter is covered in the article Make Custom Field With File Exportable In Drupal Features. We just extend it with file transfer when module feature is being enabled. In highlighted line, we copy from predefined location to a Drupal public folder.

See also post “Handling Artifacts for Custom Module In Drupal” for solution on handling assets such as images, files

2. Drupal claim “Field type(s) in use”

This message is displayed when you would like to disable the module but Drupal doesn’t permit. To solve problem, go to ‘field_config’ table and update the ‘type’ and ‘module’ text for your custom field. More info on the issue https://drupal.org/node/1284380

Fields pending deletion

This comes up after the custom field is removed but the chrom job run only once which is not sufficient. It needs to run several times.

Reference:

  • http://clikfocus.com/blog/how-set-custom-field-type-using-drupal-7-fields-api
  • http://drewpull.drupalgardens.com/blog/drupal-7-field-api-sample
  • http://drupaltutorial4u.blogspot.com/2011/04/how-to-create-custom-field-for-drupal-7.html
  • https://drupal.org/project/examples
  • http://drewpull.drupalgardens.com/blog/drupal-7-custom-field-formatter
  • http://drupal.stackexchange.com/questions/49958/hook-field-schema-does-not-work
  • http://nodesforbreakfast.com/article/2012/02/24/create-custom-form-api-elements-hookelementinfo-drupal-7
  • https://api.drupal.org/api/drupal/includes%21database%21schema.inc/group/schemaapi/7
  • http://www.slideshare.net/zugec/fields-in-core-how-to-create-a-custom-field

Activate Context To Rebuild Page More Than Once Per Page Load in Drupal

There was a situation where we needed to rebuild a page content and do it from Page scope(hook_preprocess_page). Most of the time the page content is altered via hook_page_alter(), however. This adds overhead of setting up a separate function, filtering request and other things all of which creates another dependency to the logic already handled from the hook_preprocess_page(). In this post, we cover how to accomplish the same thing from Page scope(hook_preprocess_page) to force rebuild content after making changes to it.

Here, we use Context module to manage blocks.

1. Activate Context

The context is what sets which blocks to display and in which region. Depending on the request, we activate a different context as following:

function MODULE-NAME_preprocess_page(&$vars){
      if(module_exists('context')){
            $context = context_load('shop');
            (isset($context->name)) ? context_set('context', $context->name, $context) : watchdog(WATCHDOG_NOTICE, 'Context with name - shop does not exist');
     }
}

Here, the context – shop is loaded and then set to be active.

The changes are made, however, the blocks and regions are already built and set in the $vars[page] variable ready to be rendered in the template file.

2. Rebuild the Blocks and Regions

Although we activated certain context, the blocks of this context will not show up unless we rebuild the blocks and regions for the content:

function MODULE-NAME_preprocess_page(&$vars){
...
//rebuild blocks and regions
//            block_page_build($vars['page']);
            context_page_build($vars['page']);

            //set the new changes in cache
            drupal_set_page_content($vars['page']);
...
}

Here, first we rebuild blocks managed by context in the line 5. In the line 4, we are including uncommitted line if the changes would have been made for blocks managed by Block module. Afterwards, the cache for the content is reset with our new changes.
Thats all, now the content is displayed with the new changes

Setting Up and Managing Multiple SSH Accounts for Drush on Mac/Linux

It may be the case, you have multiple ssh connections some for github accounts and some for one or more virtual boxes for Drupal sites. In this post, we cover how to generate, configure and manage multiple SSH accounts for drush. In addition, we go over how to overcome the error ‘ssh-copy-id: command not found ‘ that happens for Mac machines when trying to setup ssh keys via drush command ‘pushkey’

Generating Public/Private SSH Key Pair

For each different ssh account, you would generate ssh public/private key pair as following:

ssh-keygen -t rsa -C "user@hostname.com"

By default, this will store the generated key pairs(two files one with .pub extensions) in the directory you run the command from. We moved it into $HOME/.ssh directory and we will be referring to this directory in this post

Manage SSH Keys

You can manage multiple SSH keys by creating host aliases for each ssh key pair. The host aliases are defined in the $HOME/.ssh/config file and all they do is reference the keys per host alias as following

Host virtual-box-main
        HostName hostdomain.com
        User user-name
        IdentityFile ~/.ssh/id_rsa_virtual_box

Host another-host-alias
        HostName some-remote-box.com
        User ssh-user
        IdentityFile ~/.ssh/private_key_gen_above

Here the host alias is ‘virtual-box-main’. The ‘hostname’ is actual domain name to the server you like to ssh. The ‘user’ is the actual ssh user account that will be used to ssh into the server. At last, the ssh key pair is referenced via ‘IdentityFile’ that was generated in the above step.

So, you can repeat the above steps to generate ssh key pairs for each ssh remote account. Then configure host alias by adding another entry as specified above for each different ssh login you need.

Setup Drush

Once you have the ssh host alias setup, then to configure drush via the alias(one location $HOME/.drush/aliases.drushrc.php) as following:

$aliases['ds.prod'] = array(
	'remote-host' => 'virtual-box-main',
	'remote-user' => 'root',
	'root' => '/home/websites/site',
	'uri' => 'http://site.com',
    );

Here the host alias configure in ssh is specified ‘remote-host’, so when drush makes the ssh connection it will look it up the alias to identify ssh key to use for login

If you try to connect with the current setup:

drush @ds.prod st

You will get following error:
Permission denied (publickey,password).

This is because the remote machine needs to have the ssh public key in order to be able authenticate. Lets push the key:

drush pushkey @ds.prod

This function copies the ssh key into the server for ssh authentication.

For Mac, this will result into the following error because Mac doesn’t have the function ‘ssh-copy-id’
ssh-copy-id: command not found
So, solution is to copy ssh key manually

Copy Public SSh Key Manually

To copy the public ssh key, run the following:

cat id_rsa_virtual_box.pub | ssh user@virtualbox.com 'cat >> .ssh/authorized_keys' 

This pipes the ssh key and then logs into the virtual server and pasts the key into authorized_keys file. You may be missing the .ssh folder on the remote server. In that case, you make it create one as following:

cat id_rsa_virtual_box.pub | ssh user@virtualbox.com 'umask 077; mkdir -p .ssh ; cat >> .ssh/authorized_keys'

Now, running:

drush @some.alias st

It should be displaying the status of the site on remote server

Troubleshooting

1. Test SSH Authentication

To test any of your SSH Aliases setups:

ssh virtual-box-main

Here, we test the alias ‘virtual-box-main’. This will prompt for password if logging in for the first time. All other times it logs you in without password
Note: for ssh services that has turned off interactive mode such as Git, you use -T flag:

ssh -T git-ssh-alias

This will confirm if your ssh is setup correctly or not

2. Permission denied (publickey,password).

You will get this message if you haven’t set up the ssh as described in this post

3. ‘pushkey’ could not be found drush

This is because the push key function is addition and needs to be downloaded as following:

drush dl drush_extras

This will download and install drush_extras module that includes “pushkey” command

4. “sudo: no tty present and no askpass program specified”

This is issue comes up when you open ssh without tty session. It can be solved in 2 ways depending on how much access you have on remote instance or whatever the remote instance has the capability of NOPASSWORD

  1. Turn on NOPASSWORD on remote instance as described here:
    http://askubuntu.com/questions/192050/how-to-run-sudo-command-with-no-password/443071#443071
  2. Configure Drush to pass in credentials at time of ssh-ing. Its being done by editing .drush/drushrc.php and adding following line:
    $options['ssh-options'] = '-o PasswordAuthentication=no -i $HOME/.ssh/id_rsa/lamp_vbox';
    

    Here the lamp_vbox is the private key generated by ssh-keygen

5. WARNING: UNPROTECTED PRIVATE KEY FILE!

The permissions need to be reset:

sudo chmod 600 /path/.ssh/id_rsa/lamp_vbox
sudo chmod 600 /path/.ssh/id_rsa/lamp_vbox.pub
sudo chmod 644 /path/.ssh/known_hosts
sudo chmod 755 /path/.ssh
References

http://mail-archives.apache.org/mod_mbox/hadoop-mapreduce-user/201210.mbox
http://blogs.uoregon.edu/developments/2012/06/21/bash-function-ssh-copy-id-for-mac/
http://www.drush.org/sites/default/files/attachments/DGD7-Drush.pdf
http://stackoverflow.com/questions/3844393/what-to-do-about-pty-allocation-request-failed-on-channel-0

How to upgrade jQuery UI in Drupal

In this post, we cover how to upgrade jQuery UI lib for Drupal. The jquery_update module provides ability to switch the JQuery newest version but not JQuery UI, so we do it manually

1. Load jQuery and set version

Enable jquery_update module, so the jquery is loaded

2. Load Newest UI libs

Important to note, that the jquery_update module contains the newest version of JQuery UI but is not loading at least at the time of writing this article. So, We use hook_js_alter() to swap jquery UI libs to newer version as following

function MODULE-NAME_js_alter(&$javascript){
if($jQuery_ver = variable_get('jquery_update_jquery_version', '1.8') === '1.10'){
        //use jquery_updates ui libraries instead the drupal core
        foreach($javascript as $key => $js_path){
            $token_path = explode("/", $key);
            $current_js_lib = $token_path[count($token_path) - 1];
            $new_path = drupal_get_path('module','jquery_update').'/replace/ui/ui/minified/'.$current_js_lib;

            if($token_path[0] === 'misc' && $token_path[1] === 'ui'){
                //avoid duplicates
                if(!array_key_exists($new_path,$js_path)){
                    $data = $javascript[$key];
                    $data['version'] = "1.10.2";
                    $data['data'] = $new_path;
                    unset($javascript[$key]);
                    $javascript[$new_path] = $data;
                }

            }
        }
    }
}

Here, it takes all js being included from ‘misc/ui folder and replace their path to the current version of jQuery ui libs

2.B CSS styles seem to be handled by jQuery_update module so nothing to do here
3. Include Legacy Support

There is JQuery Lib – jquery-migrate.js available to provide legacy support for JQuery that many of Drupal core modules use, so we include it also via hook_js_alter() as following:

    function MODULE-NAME_js_alter(&$javascript){
      ...
      $migrate_js = '';
    if ($jQuery_ver = variable_get('jquery_update_jquery_version', '1.08') > '1.07') {
        //use jquery_updates ui libraries instead the drupal core
        foreach ($javascript as $key => $js_path) {
            $token_path = explode("/", $key);
            $current_js_lib = $token_path[count($token_path) - 1];

            //add legacy support for other modules still using jQuery legacy library
            if ($current_js_lib == 'jquery.min.js' || $current_js_lib == 'jquery.js') {
                $new_path = 'http://code.jquery.com/jquery-migrate-1.2.1.min.js';
                $migrate_js = $js_path;
                $migrate_js['version'] = "1.2.1";
                $migrate_js['data'] = $new_path;
                $migrate_js['weight'] = $js_path['weight'] + 0.01;
                $migrate_js['type'] = 'external';
                $javascript[$new_path] = $migrate_js;
            }
        }
    }
}

In the if statement, the jquery-migrate.min.js is imported right after the jquery library. The jquery-migrate.min.js lib adds all the legacy support for modules still using jQuery old version. To ensure it comes right after, we take the weight of the Jquery lib and decrement.

Video in Drupal

In this post, we cover how to install and configure Drupal site to play video files using either JW player or Video.js player. The post is divided into three sections. In the first part, we describe how to install and configure Video module necessary for JW video player to play the video. In the second part, we cover how to install and configure JW video player. Next, we show how to incorporate the video file to test the setup. At last, we cover how to play videos with only Video.js player.

Required modules: video, libraries, jw_player, videojs

Part I – Install and Configure Video Module

a) Download and Enable Video module
If you use drush, then:

drush dl video
drush en video, video_ui

This downloads video module and enables video as well as video_ui modules

b)Configure Video module
To configure video module, go to ‘admin/config/media/video’ and turn off transcending in ‘transcend’ tab. In addition, select ‘html 5 player’ for all of the video types or the video type of files you are going to play

This completes video module install. Next, we need a player to play the video files. In this post we demonstrate how to install/configure two different video players but you only need one to play the video.

Part II – Installing and Configuring JW Player

a) Install Drupal module – jw_player
With drush:

drush dl jw_player
drush en jw_player

b) Install JW Player Library
Go to http://www.jwplayer.com/about-jwplayer/ and download the JW player.You will have to create account in order to get the jw player and there licensing restriction all of which makes the other player vidoe.js covered next a more appealing alternative.

Once you download the library, copy it into the libraries folder – ‘sites/all/libraries/jw_player’

c) Configure JW Player
The only configuration necessary is to set video presets that is size for the video window. To do so, go to admin/config/media/jw_player and create jw_player presets

Part II – Installing and Configuring Video.js Player

An alternative to the JW Player is Video.js Player. The Video.js Player doesn’t require Drupal Video module.

a) Install Drupal module – videojs
With drush:

drush dl videojs
drush en videojs

b) Install Video.js Library
Go to videojs.com and download the Video.js player. Once you download the library, copy it into the libraries folder – ‘sites/all/libraries/video-js’ which is default location but you can be changed under videojs configurations

c) Configure Video.js Player
There is nothing to configure except make sure that the library(sites/all/libraries/video-js) contains video.min.js file

This completes the back end work for playing the video

Part 3 – Incorporate Video in your Drupal site

For the front end, you would create a field of type ‘file’ to incorporate the video on the page. For this field, go to ‘manage display’ and select ‘HTML 5: Video Player’ for Video.js player or ‘JW Player’ for JW Player. You would also specify the type of video files to play part of standard Drupal file field configuration

You are done. Now, create new instance that has this field and upload the video file.

For developers, to render the field in template file as following:

            $video_render_array = field_view_field(
                'node',
                $node,
                'field_video_file',
                array(
                    'type' => 'jw_player',
                    'label' => 'hidden',
                    'settings' => array(
                        'jwplayer_preset' => 'video_small_preset',
                    ),
                )
            );

            vars['some_var'] = render($video_render_array);

To render a field of VideoJs:

$video_render_array = field_view_field(
                'node',
                $node,
                'field_video_file',
                array(
                    'type' => 'videojs',
                    'label' => 'hidden',
                    'settings' => array(
                        'width' => '400',
                        'height' => '400',
                        'posterimage_field' => NULL,
                        'posterimage_style' => NULL,
                    ),
                )
            );

            vars['some_var'] = render($video_render_array);

Troubleshooting

1. Look into ‘admin/reports/status’ for errors
2. Check the module documentation (README file in module folder)

Issue

ERROR 1: Invalid argument supplied for foreach() in video_players_admin_settings() (line 326…

This may be because the field type is ‘video’ when it needs to be a ‘file’ with display format ‘JW Player’

ERROR 2: Please select a player to play Flash videos

This may be because the JW player is not present in ‘libraries’ folder or the field type is configured to be a video type when its need to be a simple ‘file’ type

ERROR 3: No video with supported format and MIME type found

Make sure the Drupal video module is installed and configure if using JW Player. For Video.js, make sure that there exists the video.min.js file in the sites/all/libraries/video-js folder

References

Play and Pause Buttons for YouTube and Vimeo Videos (via Their APIs)