View file jQuery-gridmanager-master/dist/js/jquery.gridmanager.js

File size: 59.9Kb
/*! gridmanager - v0.3.1 - 2014-09-22
* http://neokoenig.github.io/jQuery-gridmanager/
* Copyright (c) 2014 Tom King; Licensed MIT */
(function($  ){

      /**
      * Main Gridmanager function
      * @method gridmanager
      * @returns gridmanager
     * @class gridmanager
     * @memberOf jQuery.fn
     */
    $.gridmanager = function(el, options){
        var gm = this;
        gm.$el = $(el);
        gm.el = el;
        gm.$el.data("gridmanager", gm);

         /**
         * API
         * @method appendHTMLSelectedCols
         * @param {string} html - HTML to append to selected columns
         * @returns null
         */
        gm.appendHTMLSelectedCols = function(html) {
          var canvas=gm.$el.find("#" + gm.options.canvasId);
          var cols = canvas.find(gm.options.colSelector);
          $.each(cols, function(){
            if($(this).hasClass(gm.options.gmEditClassSelected)) {
              $('.'+gm.options.gmEditRegion, this).append(html);
            }
          });
        };
         /**
         * INIT - Main initialising function to create the canvas, controls and initialise all click handlers
         * @method init
         * @returns null
         */
        gm.init = function(){
            gm.options = $.extend({},$.gridmanager.defaultOptions, options);
            gm.log("INIT");
            gm.addCSS(gm.options.cssInclude);
            gm.rteControl("init");
            gm.createCanvas();
            gm.createControls();
            gm.initControls();
            gm.initDefaultButtons();
            gm.initCanvas();
            gm.log("FINISHED");
        };

/*------------------------------------------ Canvas & Controls ---------------------------------------*/


        /**
         * Build and append the canvas, making sure existing HTML in the user's div is wrapped. Will also trigger Responsive classes to existing markup if specified
         * @method createCanvas
         * @returns null
         */
        gm.createCanvas = function(){
          gm.log("+ Create Canvas");
           var html=gm.$el.html();
                gm.$el.html("");
                $('<div/>', {'id': gm.options.canvasId, 'html':html }).appendTo(gm.$el);
                // Add responsive classes after initial loading of HTML, otherwise we lose the rows
                if(gm.options.addResponsiveClasses) {
                   gm.addResponsiveness(gm.$el.find("#" + gm.options.canvasId));
                }
                // Add default editable regions: we try and do this early on, as then we don't need to replicate logic to add regions
                if(gm.options.autoEdit){
                   gm.initMarkup(
                   gm.$el.find("#" + gm.options.canvasId)
                         .find("."+gm.options.colClass)
                         .not("."+gm.options.rowClass)
                      );
                }

        };

        /**
         * Looks for and wraps non gm commented markup
         * @method initMarkup
         * @returns null
         */
        gm.initMarkup = function(cols){
        var cTagOpen = '<!--'+gm.options.gmEditRegion+'-->',
            cTagClose = '<!--\/'+gm.options.gmEditRegion+'-->';

               // Loop over each column
               $.each(cols, function(i, col){
                    var hasGmComment = false,
                        hasNested = $(col).children().hasClass(gm.options.rowClass);

                      // Search for comments within column contents
                      // NB, at the moment this is just finding "any" comment for testing, but should search for <!--gm-*
                      $.each($(col).contents(), function(x, node){
                        if($(node)[0].nodeType === 8){
                            hasGmComment = true;
                        }
                      });

                    // Apply default commenting markup
                    if(!hasGmComment){
                        if(hasNested){
                           // Apply nested wrap
                           $.each($(col).contents(), function(i, val){
                             if($(val).hasClass(gm.options.rowClass)){
                                var prev=Array.prototype.reverse.call($(val).prevUntil("."+gm.options.rowClass)),
                                    after=$(val).nextUntil("."+gm.options.rowClass);

                                if(!$(prev).hasClass(gm.options.gmEditRegion)){
                                    $(prev).first().before(cTagOpen).end()
                                           .last().after(cTagClose);
                                }
                                if(!$(after).hasClass(gm.options.gmEditRegion)){
                                    $(after).first().before(cTagOpen).end()
                                            .last().after(cTagClose);
                                }
                             }
                           });

                        }
                        else {
                           // Is there anything to wrap?
                            if($(col).contents().length !== 0){
                              // Apply default comment wrap
                              $(col).html(cTagOpen+$(col).html()+cTagClose);
                            }
                        }
                      }
                  });
                  gm.log("initMarkup ran");
            };

        /*
          Init global default buttons on cols, rows or both
         */

        gm.initDefaultButtons = function(){
          if(gm.options.colSelectEnabled) {
            gm.options.customControls.global_col.push({callback: gm.selectColClick, loc: 'top', iconClass: 'fa fa-square-o', title: 'Select Column'});
          }
          if(gm.options.editableRegionEnabled) {
            gm.options.customControls.global_col.push({callback: gm.addEditableAreaClick, loc: 'top', iconClass: 'fa fa-edit', title: 'Add Editable Region'});
          }
        };


        /**
         * Add missing reponsive classes to existing HTML
         * @method addResponsiveness
         * @param {} html
         * @returns CallExpression
         */
        gm.addResponsiveness = function(html) {
          if(html === '') { return; }
          var desktopRegex = gm.options.colDesktopClass+'(\\d+)',
              tabletRegex = gm.options.colTabletClass+'(\\d+)',
              phoneRegex = gm.options.colPhoneClass+'(\\d+)',
              desktopRegexObj = new RegExp(desktopRegex,'i'),
              tabletRegexObj = new RegExp(tabletRegex, 'i'),
              phoneRegexObj = new RegExp(phoneRegex, 'i');
              //new_html = '';
          return $(html).find(':regex(class,'+desktopRegex+'|'+tabletRegex+'|'+phoneRegex+')').each(function(i, el) {
            var elClasses = $(this).attr('class'), colNum = 2;
            var hasDesktop = desktopRegexObj.test(elClasses), hasPhone = phoneRegexObj.test(elClasses), hasTablet = tabletRegexObj.test(elClasses);

            colNum = (colNum = desktopRegexObj.exec(elClasses))? colNum[1] : ( (colNum = tabletRegexObj.exec(elClasses))? colNum[1] : phoneRegexObj.exec(elClasses)[1] );

            if(!hasDesktop) {
              $(this).addClass(gm.options.colDesktopClass+colNum);
            }
            if(!hasPhone) {
              $(this).addClass(gm.options.colPhoneClass+colNum);
            }
            if(!hasTablet) {
              $(this).addClass(gm.options.colTabletClass+colNum);
            }
            // Adds default column classes - probably shouldn't go here, but since we're doing an expensive search to add the responsive classes, it'll do for now.
            if(gm.options.addDefaultColumnClass){
              if(!$(this).hasClass(gm.options.colClass)){
                $(this).addClass(gm.options.colClass);
              }
            }
          });
        };

        /**
         * Build and prepend the control panel
         * @method createControls
         * @returns null
         */
        gm.createControls = function(){
          gm.log("+ Create Controls");
            var buttons=[];
            // Dynamically generated row template buttons
            $.each(gm.options.controlButtons, function(i, val){
              var _class=gm.generateButtonClass(val);
              buttons.push("<a title='Add Row " + _class + "' class='" + gm.options.controlButtonClass + " add" + _class + "'><span class='" + gm.options.controlButtonSpanClass + "'></span> " + _class + "</a>");
              gm.generateClickHandler(val);
            });

         /*
          Generate the control bar markup
        */
         gm.$el.prepend(
              $('<div/>',
                  {'id': gm.options.controlId, 'class': gm.options.gmClearClass }
              ).prepend(
                    $('<div/>', {"class": gm.options.rowClass}).html(
                       $('<div/>', {"class": gm.options.colDesktopClass + gm.options.colMax}).addClass(gm.options.colAdditionalClass).html(
                          $('<div/>', {'id': 'gm-addnew'})
                          .addClass(gm.options.gmBtnGroup)
                          .addClass(gm.options.gmFloatLeft).html(
                            buttons.join("")
                          )
                        ).append(gm.options.controlAppend)
                     )
                  )
              );
            };

        /**
         * Adds a CSS file or CSS Framework required for specific customizations
         * @method addCSS
         * @param {} myStylesLocation
         * @returns string
         */
        gm.addCSS = function(myStylesLocation) {
          if(myStylesLocation !== '') {
            $('<link rel="stylesheet" href="'+myStylesLocation+'">').appendTo("head");
          }
        };

        /**
         * Clean all occurrences of a substring
         * @method cleanSubstring
         * @param {} regex
         * @param {} source
         * @param {} replacement
         * @returns CallExpression
         */
        gm.cleanSubstring = function(regex, source, replacement) {
          return source.replace(new RegExp(regex, 'g'), replacement);
        };

        /**
         * Switches the layout mode for Desktop, Tablets or Mobile Phones
         * @method switchLayoutMode
         * @param {} mode
         * @returns null
         */
        gm.switchLayoutMode = function(mode) {
          var canvas=gm.$el.find("#" + gm.options.canvasId), temp_html = canvas.html(), regex1 = '', regex2 = '', uimode = '';
          // Reset previous changes
          temp_html = gm.cleanSubstring(gm.options.classRenameSuffix, temp_html, '');
          uimode = $('div.gm-layout-mode > button > span');
          // Do replacements
          switch (mode) {
            case 768:
              regex1 = '(' + gm.options.colDesktopClass  + '\\d+)';
              regex2 = '(' + gm.options.colPhoneClass + '\\d+)';
              gm.options.currentClassMode = gm.options.colTabletClass;
              gm.options.colSelector = gm.options.colTabletSelector;
              $(uimode).attr('class', 'fa fa-tablet').attr('title', 'Tablet');
              break;
            case 640:
              regex1 = '(' + gm.options.colDesktopClass  + '\\d+)';
              regex2 = '(' + gm.options.colTabletClass + '\\d+)';
              gm.options.currentClassMode = gm.options.colPhoneClass;
              gm.options.colSelector = gm.options.colPhoneSelector;
              $(uimode).attr('class', 'fa fa-mobile-phone').attr('title', 'Phone');
              break;
            default:
              regex1 = '(' + gm.options.colPhoneClass  + '\\d+)';
              regex2 = '(' + gm.options.colTabletClass + '\\d+)';
              gm.options.currentClassMode = gm.options.colDesktopClass;
              gm.options.colSelector = gm.options.colDesktopSelector;
              $(uimode).attr('class', 'fa fa-desktop').attr('title', 'Desktop');
          }
          gm.options.layoutDefaultMode = mode;
          temp_html = temp_html.replace(new RegExp((regex1+'(?=[^"]*">)'), 'gm'), '$1'+gm.options.classRenameSuffix);
          temp_html = temp_html.replace(new RegExp((regex2+'(?=[^"]*">)'), 'gm'), '$1'+gm.options.classRenameSuffix);
          canvas.html(temp_html);
        };



        /**
         * Add click functionality to the buttons
         * @method initControls
         * @returns null
         */
        gm.initControls = function(){
          var canvas=gm.$el.find("#" + gm.options.canvasId);
           gm.log("+ InitControls Running");

           // Turn editing on or off
           gm.$el.on("click", ".gm-preview", function(){
               if(gm.status){
                gm.deinitCanvas();
                 $(this).parent().find(".gm-edit-mode").prop('disabled', true);
              } else {
                gm.initCanvas();
                 $(this).parent().find(".gm-edit-mode").prop('disabled', false);
              }
              $(this).toggleClass(gm.options.gmDangerClass);

            // Switch Layout Mode
            }).on("click", ".gm-layout-mode a", function() {
              gm.switchLayoutMode($(this).data('width'));

            // Switch editing mode
            }).on("click", ".gm-edit-mode", function(){
              if(gm.mode === "visual"){
                 gm.deinitCanvas();
                 canvas.html($('<textarea/>').attr("cols", 130).attr("rows", 25).val(canvas.html()));
                 gm.mode="html";
                 $(this).parent().find(".gm-preview, .gm-layout-mode > button").prop('disabled', true);
              } else {
                var editedSource=canvas.find("textarea").val();
                 canvas.html(editedSource);
                 gm.initCanvas();
                 gm.mode="visual";
                 $(this).parent().find(".gm-preview, .gm-layout-mode > button").prop('disabled', false);
              }
              $(this).toggleClass(gm.options.gmDangerClass);

            // Make region editable
            }).on("click", "." + gm.options.gmEditRegion + ' .'+gm.options.gmContentRegion, function(){
              //gm.log("clicked editable");
                if(!$(this).attr("contenteditable")){
                    $(this).attr("contenteditable", true);
                    gm.rteControl("attach", $(this) );
                }

            // Save Function
            }).on("click", "a.gm-save", function(){
                gm.deinitCanvas();
                gm.saveremote();

            /* Row settings */
            }).on("click", "a.gm-rowSettings", function(){
                 var row=$(this).closest(gm.options.rowSelector);
                 var drawer=row.find(".gm-rowSettingsDrawer");
                    if(drawer.length > 0){
                      drawer.remove();
                    } else {
                      row.prepend(gm.generateRowSettings(row));
                    }

            // Change Row ID via rowsettings
            }).on("blur", "input.gm-rowSettingsID", function(){
                var row=$(this).closest(gm.options.rowSelector);
                    row.attr("id", $(this).val());

            // Remove a class from a row via rowsettings
            }).on("click", ".gm-toggleRowClass", function(){
                var row=$(this).closest(gm.options.rowSelector);
                var theClass=$(this).text().trim();
                    row.toggleClass(theClass);
                    if(row.hasClass(theClass)){
                        $(this).addClass(gm.options.gmDangerClass);
                    } else {
                        $(this).removeClass(gm.options.gmDangerClass);
                    }

             /* Col settings */
            }).on("click", "a.gm-colSettings", function(){
                 var col=$(this).closest(gm.options.colSelector);
                 var drawer=col.find(".gm-colSettingsDrawer");
                    if(drawer.length > 0){
                      drawer.remove();
                    } else {
                      col.prepend(gm.generateColSettings(col));
                    }

              // Change Col ID via colsettings
            }).on("blur", "input.gm-colSettingsID", function(){
                 var col=$(this).closest(gm.options.colSelector);
                    col.attr("id", $(this).val());

            // Remove a class from a row via rowsettings
            }).on("click", ".gm-togglecolClass", function(){
                 var col=$(this).closest(gm.options.colSelector);
                var theClass=$(this).text().trim();
                    col.toggleClass(theClass);
                    if(col.hasClass(theClass)){
                        $(this).addClass(gm.options.gmDangerClass);
                    } else {
                        $(this).removeClass(gm.options.gmDangerClass);
                    }

            // Add new column to existing row
            }).on("click", "a.gm-addColumn", function(){
                $(this).parent().after(gm.createCol(2));
                gm.switchLayoutMode(gm.options.layoutDefaultMode);
                gm.reset();

            // Add a nested row
            }).on("click", "a.gm-addRow", function(){
               gm.log("Adding nested row");
               $(this).closest("." +gm.options.gmEditClass).append(
                  $("<div>").addClass(gm.options.rowClass)
                            .html(gm.createCol(6))
                            .append(gm.createCol(6)));
               gm.reset();

            // Decrease Column Size
            }).on("click", "a.gm-colDecrease", function(){
              var col = $(this).closest("." +gm.options.gmEditClass);
              var t=gm.getColClass(col);
                   if(t.colWidth > parseInt(gm.options.colResizeStep, 10)){
                       t.colWidth=(parseInt(t.colWidth, 10) - parseInt(gm.options.colResizeStep, 10));
                       col.switchClass(t.colClass, gm.options.currentClassMode + t.colWidth, 200);
                   }

            // Increase Column Size
            }).on("click", "a.gm-colIncrease", function(){
               var col = $(this).closest("." +gm.options.gmEditClass);
               var t=gm.getColClass(col);
                if(t.colWidth < gm.options.colMax){
                    t.colWidth=(parseInt(t.colWidth, 10) + parseInt(gm.options.colResizeStep, 10));
                    col.switchClass(t.colClass, gm.options.currentClassMode + t.colWidth, 200);
                }

            // Reset all teh things
            }).on("click", "a.gm-resetgrid", function(){
                canvas.html("");
                gm.reset();

            // Remove a col or row
            }).on("click", "a.gm-removeCol", function(){
               $(this).closest("." +gm.options.gmEditClass).animate({opacity: 'hide', width: 'hide', height: 'hide'}, 400, function(){$(this).remove();});
                 gm.log("Column Removed");

            }).on("click", "a.gm-removeRow", function(){
               gm.log($(this).closest("." +gm.options.colSelector));
               $(this).closest("." +gm.options.gmEditClass).animate({opacity: 'hide', height: 'hide'}, 400, function(){
                  $(this).remove();
                 // Check for multiple editable regions and merge?

                });
                 gm.log("Row Removed");

            // For all the above, prevent default.
            }).on("click", "a.gm-resetgrid, a.gm-remove, a.gm-removeRow, a.gm-save, button.gm-preview, a.gm-viewsource, a.gm-addColumn, a.gm-colDecrease, a.gm-colIncrease", function(e){
               gm.log("Clicked: "   + $.grep((this).className.split(" "), function(v){
                 return v.indexOf('gm-') === 0;
               }).join());
               e.preventDefault();
            });

        };

        /**
         * Add any custom buttons globally on all rows / cols
         * returns void
         * @method initGlobalCustomControls
         * @returns null
         */
        gm.initGlobalCustomControls=function(){
          var canvas=gm.$el.find("#" + gm.options.canvasId),
              elems=[],
              callback = null,
              btnClass = '';

          $.each(['row','col'], function(i, control_type) {
            if(typeof gm.options.customControls['global_'+control_type] !== 'undefined') {
              elems=canvas.find(gm.options[control_type+'Selector']);
              $.each(gm.options.customControls['global_'+control_type], function(i, curr_control) {
                // controls with no valid callbacks set are skipped
                if(typeof curr_control.callback === 'undefined') { return; }

                if(typeof curr_control.loc === 'undefined') {
                  curr_control.loc = 'top';
                }
                if(typeof curr_control.iconClass === 'undefined') {
                  curr_control.iconClass = 'fa fa-file-code-o';
                }
                if(typeof curr_control.btnLabel === 'undefined') {
                  curr_control.btnLabel = '';
                }
                if(typeof curr_control.title === 'undefined') {
                  curr_control.title = '';
                }

                btnClass = (typeof curr_control.callback === 'function')? (i+'_btn') : (curr_control.callback);

                btnObj = {
                  element: 'a',
                  btnClass: 'gm-'+btnClass,
                  iconClass: curr_control.iconClass,
                  btnLabel: curr_control.btnLabel,
                  title: curr_control.title
                };

                $.each(elems, function(i, current_elem) {
                  gm.setupCustomBtn(current_elem, curr_control.loc, 'window', curr_control.callback, btnObj);
                });
              });
            }
          });
        };

        /**
         * Add any custom buttons configured on the data attributes
         * returns void
         * @method initCustomControls
         * @returns null
         */
        gm.initCustomControls=function(){
          var canvas=gm.$el.find("#" + gm.options.canvasId),
              callbackParams = '',
              callbackScp = '',
              callbackFunc = '',
              btnLoc = '',
              btnObj = {},
              iconClass = '',
              btnLabel = '';

          $( ('.'+gm.options.colClass+':data,'+' .'+gm.options.rowClass+':data'), canvas).each(function(){
            for(prop in $(this).data()) {
              if(prop.indexOf('gmButton') === 0) {
                callbackFunc = prop.replace('gmButton','');
                callbackParams = $(this).data()[prop].split('|');
                // Cannot accept 0 params or empty callback function name
                if(callbackParams.length === 0 || callbackFunc === '') { break; }

                btnLoc =    (typeof callbackParams[3] !== 'undefined')? callbackParams[3] : 'top';
                iconClass = (typeof callbackParams[2] !== 'undefined')? callbackParams[2] : 'fa fa-file-code-o';
                btnLabel =  (typeof callbackParams[1] !== 'undefined')? callbackParams[1] : '';
                callbackScp = callbackParams[0];
                btnObj = {
                  element: 'a',
                  btnClass: ('gm-'+callbackFunc),
                  iconClass:  iconClass,
                  btnLabel: btnLabel
                };
                gm.setupCustomBtn(this, btnLoc, callbackScp, callbackFunc, btnObj);
                break;
              }
            }
          });
        };



        /**
         * Configures custom button click callback function
         * returns bool, true on success false on failure
         * @container - container element that wraps the toolbar
         * @btnLoc - button location: "top" for the upper toolbar and "bottom" for the lower one
         * @callbackScp - function scope to use. "window" for global scope
         * @callbackFunc - function name to call when the user clicks the custom button
         * @btnObj - button object that contains properties for: tag name, title, icon class, button class and label
         * @method setupCustomBtn
         * @param {} container
         * @param {} btnLoc
         * @param {} callbackScp
         * @param {} callbackFunc
         * @param {} btnObj
         * @returns Literal
         */
        gm.setupCustomBtn=function(container, btnLoc, callbackScp, callbackFunc, btnObj) {
          var callback = null;

          // Ensure we have a valid callback, if not skip
          if(typeof callbackFunc === 'string') {
            callback = gm.isValidCallback(callbackScp, callbackFunc.toLowerCase());
          } else if(typeof callbackFunc === 'function') {
            callback = callbackFunc;
          } else {
            return false;
          }
          // Set default button location to the top toolbar
          btnLoc = (btnLoc === 'bottom')? ':last' : ':first';

          // Add the button to the selected toolbar
          $( ('.'+gm.options.gmToolClass+btnLoc), container).append(gm.buttonFactory([btnObj])).find(':last').on('click', function(e) {
            callback(container, this);
            e.preventDefault();
          });
          return true;
        };

        /*
          Clears any comments inside a given element

          @elem - element to clear html comments on

          returns void
         */

        gm.clearComments = function(elem) {
          $(elem, '#'+gm.options.canvasId).contents().filter(function() {
            return this.nodeType === 8;
          }).remove();
        };

        /**
         * Checks that a callback exists and returns it if available
         * returns function
         * @callbackScp - function scope to use. "window" for global scope
         * @callbackFunc - function name to call when the user clicks the custom button
         * @method isValidCallback
         * @param {} callbackScp
         * @param {} callbackFunc
         * @returns callback
         */
        gm.isValidCallback=function(callbackScp, callbackFunc){
          var callback = null;

          if(callbackScp === 'window') {
            if(typeof window[callbackFunc] === 'function') {
              callback = window[callbackFunc];
            } else { // If the global function is not valid there is nothing to do
              return false;
            }
          } else if(typeof window[callbackScp][callbackFunc] === 'function') {
            callback = window[callbackScp][callbackFunc];
          } else { // If there is no valid callback there is nothing to do
            return false;
          }
          return callback;
        };

      /**
         * Get the col-md-6 class, returning 6 as well from column
         * returns colDesktopClass: the full col-md-6 class
         * colWidth: just the last integer of classname
         * @col - column to look at
         * @method getColClass
         * @param {} col
         * @return ObjectExpression
         */
        gm.getColClass=function(col){
            var colClass=$.grep(col.attr("class").split(" "), function(v){
                return v.indexOf(gm.options.currentClassMode) === 0;
                }).join();
            var colWidth=colClass.replace(gm.options.currentClassMode, "");
                return {colClass:colClass, colWidth:colWidth};
        };

        /*
          Run (if set) any custom init/deinit filters on the gridmanager canvas
            @canvasElem - canvas wrapper container with the entire layout html
            @isInit - flag that indicates if the method is running during init or deinit.
                      true - if its running during the init process, or false - during the deinit (cleanup) process

            returns void
         */

        gm.runFilter=function(canvasElem, isInit){
          if(typeof gm.options.filterCallback === 'function') {
            gm.options.filterCallback(canvasElem, isInit);
          }
          if(gm.options.editableRegionEnabled) {
            gm.editableAreaFilter(canvasElem, isInit);
          }
        };

        /**
         * Turns canvas into gm-editing mode - does most of the hard work here
         * @method initCanvas
         * @returns null
         */
        gm.initCanvas = function(){
          // cache canvas
          var canvas=gm.$el.find("#" + gm.options.canvasId);
          gm.switchLayoutMode(gm.options.layoutDefaultMode);
          var cols=canvas.find(gm.options.colSelector);
          var rows=canvas.find(gm.options.rowSelector);
           gm.log("+ InitCanvas Running");
              // Show the template controls
              gm.$el.find("#gm-addnew").show();
              // Sort Rows First
              gm.activateRows(rows);
              // Now Columns
              gm.activateCols(cols);
              // Run custom init callback filter
              gm.runFilter(canvas, true);
              // Get cols & rows again after filter execution
              cols=canvas.find(gm.options.colSelector);
              rows=canvas.find(gm.options.rowSelector);
              // Make Rows sortable
              canvas.sortable({
                items: rows,
                axis: 'y',
                placeholder: gm.options.rowSortingClass,
                handle: ".gm-moveRow",
                forcePlaceholderSize: true,   opacity: 0.7,  revert: true,
                tolerance: "pointer",
                cursor: "move"
               });
              /*
              Make columns sortable
              This needs to be applied to each element, otherwise containment leaks
              */
              $.each(rows, function(i, val){
                  $(val).sortable({
                  items: $(val).find(gm.options.colSelector),
                  axis: 'x',
                  handle: ".gm-moveCol",
                  forcePlaceholderSize: true,   opacity: 0.7,  revert: true,
                  tolerance: "pointer",
                  containment: $(val),
                  cursor: "move"
                });
              });
              /* Make rows sortable
              cols.sortable({
                items: gm.options.rowSelector,
                axis: 'y',
                handle: ".gm-moveRow",
                forcePlaceholderSize: true,   opacity: 0.7,  revert: true,
                tolerance: "pointer",
                cursor: "move"
              }); */
            gm.status=true;
            gm.mode="visual";
            gm.initCustomControls();
            gm.initGlobalCustomControls();
            gm.initNewContentElem();
        };

        /**
         * Removes canvas editing mode
         * @method deinitCanvas
         * @returns null
         */
        gm.deinitCanvas = function(){
          // cache canvas
          var canvas=gm.$el.find("#" + gm.options.canvasId);
          var cols=canvas.find(gm.options.colSelector);
          var rows=canvas.find(gm.options.rowSelector);

           gm.log("- deInitCanvas Running");
              // Hide template control
              gm.$el.find("#gm-addnew").hide();
              // Sort Rows First
              gm.deactivateRows(rows);
              // Now Columns
              gm.deactivateCols(cols);
              // Clean markup
              gm.cleanup();
              gm.runFilter(canvas, false);
              gm.status=false;
        };

        /**
         * Push cleaned div content somewhere to save it
         * @method saveremote
         * @returns null
         */
        gm.saveremote =  function(){
        var canvas=gm.$el.find("#" + gm.options.canvasId);
            $.ajax({
              type: "POST",
              url:  gm.options.remoteURL,
              data: {content: canvas.html()}
            });
            gm.log("Save Function Called");
        };


/*------------------------------------------ ROWS ---------------------------------------*/
        /**
         * Look for pre-existing rows and add editing tools as appropriate
         * @rows: elements to act on
         * @method activateRows
         * @param {object} rows - rows to act on
         * @returns null
         */
        gm.activateRows = function(rows){
           gm.log("++ Activate Rows");
           rows.addClass(gm.options.gmEditClass)
               .prepend(gm.toolFactory(gm.options.rowButtonsPrepend))
               .append(gm.toolFactory(gm.options.rowButtonsAppend));
        };

         /**
         * Look for pre-existing rows and remove editing classes as appropriate
         * @rows: elements to act on
         * @method deactivateRows
         * @param {object} rows - rows to act on
         * @returns null
         */
        gm.deactivateRows = function(rows){
           gm.log("-- DeActivate Rows");
           rows.removeClass(gm.options.gmEditClass)
               .removeClass("ui-sortable")
               .removeAttr("style");
        };

        /**
         * Create a single row with appropriate editing tools & nested columns
         * @method createRow
         * @param {array} colWidths - array of css class integers, i.e [2,4,5]
         * @returns row
         */
        gm.createRow = function(colWidths){
          var row= $("<div/>", {"class": gm.options.rowClass + " " + gm.options.gmEditClass});
             $.each(colWidths, function(i, val){
                row.append(gm.createCol(val));
              });
                gm.log("++ Created Row");
          return row;
        };

        /**
         * Create the row specific settings box
         * @method generateRowSettings
         * @param {object} row - row to act on
         * @return MemberExpression
         */
        gm.generateRowSettings = function(row){
         // Row class toggle buttons
          var classBtns=[];
              $.each(gm.options.rowCustomClasses, function(i, val){
                  var btn=$("<button/>")
                  .addClass("gm-toggleRowClass")
                  .addClass(gm.options.controlButtonClass)
                  .append(
                    $("<span/>")
                    .addClass(gm.options.controlButtonSpanClass)
                  ).append(" " + val);

                   if(row.hasClass(val)){
                       btn.addClass(gm.options.gmDangerClass);
                    }
                   classBtns.push(btn[0].outerHTML);
             });
          // Row settings drawer
          var html=$("<div/>")
              .addClass("gm-rowSettingsDrawer")
              .addClass(gm.options.gmToolClass)
              .addClass(gm.options.gmClearClass)
              .prepend($("<div />")
                .addClass(gm.options.gmBtnGroup)
                .addClass(gm.options.gmFloatLeft)
                .html(classBtns.join("")))
              .append($("<div />").addClass("pull-right").html(
                $("<label />").html("Row ID ").append(
                $("<input>").addClass("gm-rowSettingsID").attr({type: 'text', placeholder: 'Row ID', value: row.attr("id")})
                )
              ));

          return html[0].outerHTML;
        };

         /**
         * Create the col specific settings box
         * @method generateColSettings
         * @param {object} col - Column to act on
         * @return MemberExpression
         */
        gm.generateColSettings = function(col){
         // Col class toggle buttons
          var classBtns=[];
              $.each(gm.options.colCustomClasses, function(i, val){
                  var btn=$("<button/>")
                  .addClass("gm-togglecolClass")
                  .addClass(gm.options.controlButtonClass)
                  .append(
                    $("<span/>")
                    .addClass(gm.options.controlButtonSpanClass)
                  ).append(" " + val);
                   if(col.hasClass(val)){
                       btn.addClass(gm.options.gmDangerClass);
                    }
                   classBtns.push(btn[0].outerHTML);
             });
          // col settings drawer
          var html=$("<div/>")
              .addClass("gm-colSettingsDrawer")
              .addClass(gm.options.gmToolClass)
              .addClass(gm.options.gmClearClass)
              .prepend($("<div />")
                .addClass(gm.options.gmBtnGroup)
                .addClass(gm.options.gmFloatLeft)
                .html(classBtns.join("")))
              .append($("<div />").addClass("pull-right").html(
                $("<label />").html("col ID ").append(
                $("<input>")
                  .addClass("gm-colSettingsID")
                  .attr({type: 'text', placeholder: 'col ID', value: col.attr("id")})
                )
              ));

          return html[0].outerHTML;
        };

/*------------------------------------------ COLS ---------------------------------------*/



        /**
         * Look for pre-existing columns and add editing tools as appropriate
         * @method activateCols
         * @param {object} cols - elements to act on
         * @returns null
         */
        gm.activateCols = function(cols){
         cols.addClass(gm.options.gmEditClass);
            // For each column,
            $.each(cols, function(i, column){
              $(column).prepend(gm.toolFactory(gm.options.colButtonsPrepend));
              $(column).append(gm.toolFactory(gm.options.colButtonsAppend));
            });
           gm.log("++ Activate Cols Ran");
        };

        /**
         * Look for pre-existing columns and removeediting tools as appropriate
         * @method deactivateCols
         * @param {object} cols - elements to act on
         * @returns null
         */
        gm.deactivateCols = function(cols){
           cols.removeClass(gm.options.gmEditClass)
               .removeClass(gm.options.gmEditClassSelected)
               .removeClass("ui-sortable");
           $.each(cols.children(), function(i, val){
            // Grab contents of editable regions and unwrap
            if($(val).hasClass(gm.options.gmEditRegion)){
              if($(val).html() !== ''){
                $(val).contents().unwrap();
              } else {
                // Deals with empty editable regions
                $(val).remove();
              }
            }
           });
           gm.log("-- deActivate Cols Ran");
        };

        /**
          * Create a single column with appropriate editing tools
          * @method createCol
          * @param {integer} size - width of the column to create, i.e 6
          * @returns null
          */
         gm.createCol =  function(size){
         var col= $("<div/>")
            .addClass(gm.options.colClass)
            .addClass(gm.options.colDesktopClass + size)
            .addClass(gm.options.colTabletClass + size)
            .addClass(gm.options.colPhoneClass + size)
            .addClass(gm.options.gmEditClass)
            .addClass(gm.options.colAdditionalClass)
            .html(gm.toolFactory(gm.options.colButtonsPrepend))
            .prepend(gm.toolFactory(gm.options.colButtonsPrepend))
            .append(gm.toolFactory(gm.options.colButtonsAppend));
            gm.log("++ Created Column " + size);
            return col;
        };


/*------------------------------------------ Editable Regions ---------------------------------------*/

        /*
          Callback called when a the new editable area button is clicked

            @container - container element that wraps the select button
            @btn       - button element that was clicked

            returns void
         */
        gm.addEditableAreaClick = function(container, btn) {
          var cTagOpen = '<!--'+gm.options.gmEditRegion+'-->',
              cTagClose = '<!--\/'+gm.options.gmEditRegion+'-->',
              elem = null;
          $(('.'+gm.options.gmToolClass+':last'),container)
          .before(elem = $('<div>').addClass(gm.options.gmEditRegion+' '+gm.options.contentDraggableClass)
            .append(gm.options.controlContentElem+'<div class="'+gm.options.gmContentRegion+'"><p>New Content</p></div>')).before(cTagClose).prev().before(cTagOpen);
          gm.initNewContentElem(elem);
        };

          /*
          Prepares any new content element inside columns so inner toolbars buttons work
          and any drag & drop functionality.
            @newElem  - Container of the new content element added into a col
            returns void
         */

        gm.initNewContentElem = function(newElem) {
          var parentCols = null;

          if(typeof newElem === 'undefined') {
            parentCols = gm.$el.find('.'+gm.options.colClass);
          } else {
            parentCols = newElem.closest('.'+gm.options.colClass);
          }

          $.each(parentCols, function(i, col) {
            $(col).on('click', '.gm-delete', function(e) {
              $(this).closest('.'+gm.options.gmEditRegion).remove();
              gm.resetCommentTags(col);
              e.preventDefault();
            });
            $(col).sortable({
              items: '.'+gm.options.contentDraggableClass,
              axis: 'y',
              placeholder: gm.options.rowSortingClass,
              handle: "."+gm.options.controlMove,
              forcePlaceholderSize: true, opacity: 0.7, revert: true,
              tolerance: "pointer",
              cursor: "move",
              stop: function(e, ui) {
                gm.resetCommentTags($(ui.item).parent());
              }
             });
          });

        };

        /*
          Resets the comment tags for editable elements
          @elem - Element to reset the editable comments on
          returns void
         */

        gm.resetCommentTags = function(elem) {
          var cTagOpen = '<!--'+gm.options.gmEditRegion+'-->',
              cTagClose = '<!--\/'+gm.options.gmEditRegion+'-->';
          // First remove all existing comments
          gm.clearComments(elem);
          // Now replace these comment tags
          $('.'+gm.options.gmEditRegion, elem).before(cTagOpen).after(cTagClose);
        };

        /*
          Callback called when a the column selection button is clicked
            @container - container element that wraps the select button
            @btn       - button element that was clicked
            returns void
         */

        gm.selectColClick = function(container, btn) {
          $(btn).toggleClass('fa fa-square-o fa fa-check-square-o');
          if($(btn).hasClass('fa-check-square-o')) {
            $(container).addClass(gm.options.gmEditClassSelected);
          } else {
            $(container).removeClass(gm.options.gmEditClassSelected);
          }
        };


        /*
          Filter method to restore editable regions in edit mode.
         */
        gm.editableAreaFilter = function(canvasElem, isInit) {
          if(isInit) {
            var cTagOpen = '<!--'+gm.options.gmEditRegion+'-->',
                cTagClose = '<!--\/'+gm.options.gmEditRegion+'-->',
                regex = new RegExp('(?:'+cTagOpen+')\\s*([\\s\\S]+?)\\s*(?:'+cTagClose+')', 'g'),
                html = $(canvasElem).html(),
                rep = cTagOpen+'<div class="'+gm.options.gmEditRegion+' '+gm.options.contentDraggableClass+'">'+gm.options.controlContentElem +'<div class="'+gm.options.gmContentRegion+'">$1</div></div>'+cTagClose;

            html = html.replace(regex, rep);
            $(canvasElem).html(html);
            gm.log("editableAreaFilter init ran");
          } else {
            $('.'+gm.options.controlNestedEditable, canvasElem).remove();
            $('.'+gm.options.gmContentRegion).contents().unwrap();

            gm.log("editableAreaFilter deinit ran");
          }
        };

/*------------------------------------------ BTNs ---------------------------------------*/
        /**
         * Returns an editing div with appropriate btns as passed in
         * @method toolFactory
         * @param {array} btns - Array of buttons (see options)
         * @return MemberExpression
         */
        gm.toolFactory=function(btns){
           var tools=$("<div/>")
              .addClass(gm.options.gmToolClass)
              .addClass(gm.options.gmClearClass)
              .html(gm.buttonFactory(btns));
           return tools[0].outerHTML;
        };

        /**
         * Returns html string of buttons
         * @method buttonFactory
         * @param {array} btns - Array of button configurations (see options)
         * @return CallExpression
         */
        gm.buttonFactory=function(btns){
          var buttons=[];
          $.each(btns, function(i, val){
            val.btnLabel = (typeof val.btnLabel === 'undefined')? '' : val.btnLabel;
            val.title = (typeof val.title === 'undefined')? '' : val.title;
            buttons.push("<" + val.element +" title='" + val.title + "' class='" + val.btnClass + "'><span class='"+val.iconClass+"'></span>&nbsp;" + val.btnLabel + "</" + val.element + "> ");
          });
          return buttons.join("");
        };

        /**
         * Basically just turns [2,4,6] into 2-4-6
         * @method generateButtonClass
         * @param {array} arr - An array of widths
         * @return string
         */
        gm.generateButtonClass=function(arr){
            var string="";
              $.each(arr, function( i , val ) {
                    string=string + "-" + val;
              });
              return string;
        };

        /**
         * click handlers for dynamic row template buttons
         * @method generateClickHandler
         * @param {array} colWidths - array of column widths, i.e [2,3,2]
         * @returns null
         */
        gm.generateClickHandler= function(colWidths){
          var string="a.add" + gm.generateButtonClass(colWidths);
          var canvas=gm.$el.find("#" + gm.options.canvasId);
              gm.$el.on("click", string, function(e){
                gm.log("Clicked " + string);
                canvas.prepend(gm.createRow(colWidths));
                gm.reset();
                e.preventDefault();
            });
        };


/*------------------------------------------ RTEs ---------------------------------------*/
        /**
         * Starts, stops, looks for and  attaches RTEs
         * @method rteControl
         * @param {string} action  - options are init, attach, stop
         * @param {object} element  - object to attach an RTE to
         * @returns null
         */
        gm.rteControl=function(action, element){
          gm.log("RTE " + gm.options.rte + ' ' +action);

          switch (action) {
            case 'init':
                if(typeof window.CKEDITOR !== 'undefined'){
                    gm.options.rte='ckeditor';
                    gm.log("++ CKEDITOR Found");
                    window.CKEDITOR.disableAutoInline = true;
               }
                if(typeof window.tinymce !== 'undefined'){
                    gm.options.rte='tinymce';
                    gm.log("++ TINYMCE Found");
                }
                break;
             case 'attach':
                switch (gm.options.rte) {
                    case 'tinymce':
                    if(!(element).hasClass("mce-content-body")){
                      element.tinymce(gm.options.tinymce.config);
                    }
                    break;

                    case 'ckeditor':
                      $(element).ckeditor(gm.options.ckeditor);

                    break;
                    default:
                        gm.log("No RTE specified for attach");
                }
                break; //end Attach
            case 'stop':
                switch (gm.options.rte) {
                    case 'tinymce':
                      // destroy TinyMCE
                      window.tinymce.remove();
                         gm.log("-- TinyMCE destroyed");
                    break;

                    case 'ckeditor':
                      // destroy ckeditor
                         for(var name in window.CKEDITOR.instances)
                        {
                          window.CKEDITOR.instances[name].destroy();
                        }
                         gm.log("-- CKEDITOR destroyed");
                    break;

                    default:
                        gm.log("No RTE specified for stop");
                }
              break; //end stop

              default:
                  gm.log("No RTE Action specified");
            }
        };


/*------------------------------------------ Useful functions ---------------------------------------*/

        /**
         * Quick reset - deinit & init the canvas
         * @method reset
         * @returns null
         */
        gm.reset=function(){
            gm.log("~~RESET~~");
            gm.deinitCanvas();
            gm.initCanvas();
        };

        /**
         * Remove all extraneous markup
         * @method cleanup
         * @returns null
         */

        gm.cleanup =  function(){

          var canvas,
              content;

              // cache canvas
              canvas = gm.$el.find("#" + gm.options.canvasId);

              /**
               * Determine the current edit mode and get the content based upon the resultant
               * context to prevent content in source mode from being lost on save, as such:
               *
               * edit mode (source): canvas.find('textarea').val()
               * edit mode (visual): canvas.html()
               */
              content = gm.mode !== "visual" ? canvas.find('textarea').val() : canvas.html();

              // Clean any temp class strings
              canvas.html(gm.cleanSubstring(gm.options.classRenameSuffix, content, ''));

              // Clean column markup
              canvas.find(gm.options.colSelector)
                  .removeAttr("style")
                  .removeAttr("spellcheck")
                  .removeClass("mce-content-body").end()
              // Clean img markup
                  .find("img")
                  .removeAttr("style")
                  .addClass("img-responsive")
                  .removeAttr("data-cke-saved-src")
                  .removeAttr("data-mce-src").end()
              // Remove Tools
                  .find("." + gm.options.gmToolClass).remove();
              // Destroy any RTEs
                  gm.rteControl("stop");
              gm.log("~~Cleanup Ran~~");
        };

        /**
         * Generic logging function
         * @method log
         * @param {object} logvar - The Object or string you want to pass to the console
         * @returns null
         * @property {boolean} gm.options.debug
         */
        gm.log = function(logvar){
          if(gm.options.debug){
            if ((window['console'] !== undefined)) {
              window.console.log(logvar);
              }
            }
        };
        // Run initializer
        gm.init();
    };



    /**
     Options which can be overridden by the .gridmanager() call on the requesting page------------------------------------------------------
    */
    $.gridmanager.defaultOptions = {
     /*
     General Options---------------
    */

        debug: 0,

        // Are you columns selectable
        colSelectEnabled: true,

        // Can add editable regions?
        editableRegionEnabled: true,

        // Should we try and automatically create editable regions?
        autoEdit: true,

        // URL to save to
        remoteURL: "/replace-with-your-url",

        // Custom CSS to load
        cssInclude: "//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css",

        // Filter callback. Callback receives two params: the template grid element and whether is called from the init or deinit method
        filterCallback: null,
  /*
     Canvas---------------
    */
        // Canvas ID
        canvasId: "gm-canvas",

  /*
     Control Bar---------------
  */
        // Top Control Row ID
        controlId:  "gm-controls",

        // Move handle class
        controlMove: 'gm-move',

        // Editable element toolbar class
        controlNestedEditable: 'gm-controls-element',

        // Array of buttons for row templates
        controlButtons: [[12], [6,6], [4,4,4], [3,3,3,3], [2,2,2,2,2,2], [2,8,2], [4,8], [8,4]],

        // Custom Global Controls for rows & cols - available props: global_row, global_col
        customControls: { global_row: [], global_col: [] },

        // Default control button class
        controlButtonClass: "btn  btn-xs  btn-primary",

        // Default control button icon
        controlButtonSpanClass: "fa fa-plus-circle",

        // Control bar RH dropdown markup
        controlAppend: "<div class='btn-group pull-right'><button title='Edit Source Code' type='button' class='btn btn-xs btn-primary gm-edit-mode'><span class='fa fa-code'></span></button><button title='Preview' type='button' class='btn btn-xs btn-primary gm-preview'><span class='fa fa-eye'></span></button>     <div class='dropdown pull-left gm-layout-mode'><button type='button' class='btn btn-xs btn-primary dropdown-toggle' data-toggle='dropdown'><span class='caret'></span></button> <ul class='dropdown-menu' role='menu'><li><a data-width='auto' title='Desktop'><span class='fa fa-desktop'></span> Desktop</a></li><li><a title='Tablet' data-width='768'><span class='fa fa-tablet'></span> Tablet</a></li><li><a title='Phone' data-width='640'><span class='fa fa-mobile-phone'></span> Phone</a></li></ul></div>    <button type='button' class='btn  btn-xs  btn-primary dropdown-toggle' data-toggle='dropdown'><span class='caret'></span><span class='sr-only'>Toggle Dropdown</span></button><ul class='dropdown-menu' role='menu'><li><a title='Save'  href='#' class='gm-save'><span class='fa fa-save'></span> Save</a></li><li><a title='Reset Grid' href='#' class='gm-resetgrid'><span class='fa fa-trash-o'></span> Reset</a></li></ul></div>",

        // Controls for content elements
        controlContentElem: '<div class="gm-controls-element"> <a class="gm-move fa fa-arrows" href="#" title="Move"></a> <a class="gm-delete fa fa-times" href="#" title="Delete"></a> </div>',
   /*
     General editing classes---------------
  */
        // Standard edit class, applied to active elements
        gmEditClass: "gm-editing",

        // Applied to the currently selected element
        gmEditClassSelected: "gm-editing-selected",

        // Editable region class
        gmEditRegion: "gm-editable-region",

        // Editable container class
        gmContentRegion: "gm-content",

        // Tool bar class which are inserted dynamically
        gmToolClass: "gm-tools",

        // Clearing class, used on most toolbars
        gmClearClass: "clearfix",

        // generic float left and right
        gmFloatLeft: "pull-left",
        gmFloatRight: "pull-right",
        gmBtnGroup:  "btn-group",
        gmDangerClass: "btn-danger",


  /*
     Rows---------------
  */
        // Generic row class. change to row--fluid for fluid width in Bootstrap
        rowClass:    "row",

        // Used to find rows - change to div.row-fluid for fluid width
        rowSelector: "div.row",

        // class of background element when sorting rows
        rowSortingClass: "alert-warning",

        // Buttons at the top of each row
        rowButtonsPrepend: [
                {
                 title:"Move",
                 element: "a",
                 btnClass: "gm-moveRow pull-left",
                 iconClass: "fa fa-arrows "
              },
                {
                   title:"New Column",
                   element: "a",
                   btnClass: "gm-addColumn pull-left  ",
                   iconClass: "fa fa-plus"
                },
                 {
                   title:"Row Settings",
                   element: "a",
                   btnClass: "pull-right gm-rowSettings",
                   iconClass: "fa fa-cog"
                }

            ],

        // Buttons at the bottom of each row
        rowButtonsAppend: [
                {
                 title:"Remove row",
                 element: "a",
                 btnClass: "pull-right gm-removeRow",
                 iconClass: "fa fa-trash-o"
                }
            ],


        // CUstom row classes - add your own to make them available in the row settings
        rowCustomClasses: ["example-class","test-class"],

  /*
     Columns--------------
  */
        // Column Class
        colClass: "column",

        // Class to allow content to be draggable
        contentDraggableClass: 'gm-content-draggable',

        // Adds any missing classes in columns for muti-device support.
        addResponsiveClasses: true,

        // Adds "colClass" to columns if missing: addResponsiveClasses must be true for this to activate
        addDefaultColumnClass: true,

       // Generic desktop size layout class
        colDesktopClass: "col-md-",

        // Generic tablet size layout class
        colTabletClass: "col-sm-",

        // Generic phone size layout class
        colPhoneClass: "col-xs-",

        // Wild card column desktop selector
        colDesktopSelector: "div[class*=col-md-]",

        // Wildcard column tablet selector
        colTabletSelector: "div[class*=col-sm-]",

        // Wildcard column phone selector
        colPhoneSelector: "div[class*=col-xs-]",

        // String used to temporarily rename column classes not in use
        classRenameSuffix: "-clsstmp",

        // Default layout mode loaded after init
        layoutDefaultMode: "auto",

        // Current layout column mode
        currentClassMode: "",

        // Additional column class to add (foundation needs columns, bs3 doesn't)
        colAdditionalClass: "",

        // Buttons to prepend to each column
        colButtonsPrepend: [
              {
                 title:"Move",
                 element: "a",
                 btnClass: "gm-moveCol pull-left",
                 iconClass: "fa fa-arrows "
              },
              {
                   title:"Column Settings",
                   element: "a",
                   btnClass: "pull-right gm-colSettings",
                   iconClass: "fa fa-cog"
                },
               {
                 title:"Make Column Narrower",
                 element: "a",
                 btnClass: "gm-colDecrease pull-left",
                 iconClass: "fa fa-minus"
              },
              {
               title:"Make Column Wider",
               element: "a",
               btnClass: "gm-colIncrease pull-left",
               iconClass: "fa fa-plus"
              }
            ],

        // Buttons to append to each column
        colButtonsAppend: [
                {
                 title:"Add Nested Row",
                 element: "a",
                 btnClass: "pull-left gm-addRow",
                 iconClass: "fa fa-plus-square"
                },
                {
                 title:"Remove Column",
                 element: "a",
                 btnClass: "pull-right gm-removeCol",
                 iconClass: "fa fa-trash-o"
                }
            ],

        // CUstom col classes - add your own to make them available in the col settings
        colCustomClasses: ["example-col-class","test-class"],

        // Maximum column span value: if you've got a 24 column grid via customised bootstrap, you could set this to 24.
        colMax: 12,

        // Column resizing +- value: this is also the colMin value, as columns won't be able to go smaller than this number (otherwise you hit zero and all hell breaks loose)
        colResizeStep: 1,

  /*
     Rich Text Editors---------------
  */
        tinymce: {
            config: {
              inline: true,
              plugins: [
              "advlist autolink lists link image charmap print preview anchor",
              "searchreplace visualblocks code fullscreen",
              "insertdatetime media table contextmenu paste"
              ],
              toolbar: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
            }
        },

        // Path to CK custom comfiguration
        ckeditor: {
              customConfig: ""
        }
    };

    /**
     * Exposes gridmanager as jquery function
     * @method gridmanager
     * @param {object} options
     * @returns CallExpression
     */
    $.fn.gridmanager = function(options){
        return this.each(function(){
          var element = $(this);
          var gridmanager = new $.gridmanager(this, options);
          element.data('gridmanager', gridmanager);
        });
    };

    /**
     * General Utility Regex function used to get custom callback attributes
     * @method regex
     * @param {} elem
     * @param {} index
     * @param {} match
     * @returns CallExpression
     */
    $.expr[':'].regex = function(elem, index, match) {
      var matchParams = match[3].split(','),
        validLabels = /^(data|css):/,
        attr = {
            method: matchParams[0].match(validLabels) ?
                        matchParams[0].split(':')[0] : 'attr',
            property: matchParams.shift().replace(validLabels,'')
        },
        regexFlags = 'ig',
        regex = new RegExp(matchParams.join('').replace(/^\s+|\s+$/g,''), regexFlags);
        return regex.test(jQuery(elem)[attr.method](attr.property));
    };
})(jQuery );