gagoar/SrBuj

View on GitHub
lib/assets/javascripts/SrBuj.js

Summary

Maintainability
C
1 day
Test Coverage
(function ($, undefined) {
    /**
     * Better Unobtrusive  javascript request for Jquery
     * https://github.com/gagoar/SrBuj
     *
     * Requires jQuery 1.7.0 or later.
     *
     * Released under the MIT license
     *
     * JSHINT
     * jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, jquery:true, indent:4, maxerr:50
     * global jQuery
     */

    'use strict';

    $.fn.present = function () {
        return this.length === 1 && this;
    };
    var SrBuj;
    $.SrBuj = SrBuj = {
        version: '0.10.0',
        selector: '[data-remote][data-target]',
        notifyHeaders: {
          message  : 'X-SRBUJ-MSG',
          type     : 'X-SRBUJ-TYPE',
          side     : 'X-SRBUJ-SIDE',
          position : 'X-SRBUJ-POS',
          time     : 'X-SRBUJ-TIME',
          remove   : 'X-SRBUJ-REMOVE',
          raw      : 'X-SRBUJ-RAW'
        },
        defaults: {
            '$el': undefined,
            target: undefined,
            onError: undefined,
            wrapper: undefined,
            method: 'GET',
            modal: false,
            change: true,
            custom: false,
            replace: false,
            callback: false,
            remove: false,
            url: undefined,
            push: false,
            jqueryselector: false
        },
        changeDom: function (method, $target, data) {
            switch (method) {
            case 'POST':
                $target.append(data);
                break;
            case 'PUT':
            case 'PATCH':
                $target.replaceWith(data);
                break;
            case 'DELETE':
                $target.remove();
                break;
            default:
                $target.html(data);
            }
        },
        kindOfCallback: function (callback) {
            if (typeof callback === 'function') {
                return callback;
            } else {
                if (window[callback]) {
                    return window[callback];
                } else {
                    return new Function(callback);
                }
            }
        },
        getUrl: function($el){
          var url = $el.attr('href') || $el.attr('action');
          if((/^[\/#](\w)/).test(url)){
            return url
          }
        },
        needToChangeUrl: function(url){
          return !((window.document.location.origin + url) == window.document.location.href)
        },
        browserSupportsPushState: function(){
          return window.history && window.history.pushState && window.history.replaceState
        },
        getVerb: function ($el) {
            var respond_as = $el.data('respond-as'),
                dataVerb = $el.data('method'),
                replace = $el.data('replace'),
                proto = $el.attr('method');
            if (respond_as) {
                return respond_as.toUpperCase();
            } else
            if (dataVerb) {
              return dataVerb.toUpperCase();
            } else {
                if (proto) {
                  return replace ? 'PUT' : proto.toUpperCase();
                }
            }
        },
        getOptions: function (el, user_options) {
            user_options = user_options || {};
            var options = {},
                $el = $(user_options.el || el).present(),
                el_options = {
                    '$el': $el,
                    'target': $el.data('target'),
                    'method': $.SrBuj.getVerb($el),
                    'onError': $el.data('error'),
                    'callback': $el.data('callback'),
                    'change': !($el.data('nochange') || false),
                    'jqueryselector': $el.is('[data-jqueryselector]'),
                    'modal': $el.is('[data-modal]'),
                    'wrapper': $el.data('error'),
                    'custom': $el.is('[data-custom]'),
                    'replace': $el.data('replace'),
                    'url': $.SrBuj.getUrl($el),
                    'push': $el.is('[data-push]'),
                    'remove': $el.is('[data-srbuj]')
                };
            user_options = user_options || {};
            for ( var attr in this.defaults ) {
                if ( this.defaults.hasOwnProperty(attr) ) {
                    options[attr] = user_options[attr] || el_options[attr] || this.defaults[attr];
                }
            }
            return options;
        },
        success: function (e, data, status, xhr, user_options) {
          if ( xhr ){
            var notify = {};
            for ( var attr in $.SrBuj.notifyHeaders )
              if ( $.SrBuj.notifyHeaders.hasOwnProperty(attr) ) {
                notify[attr] = xhr.getResponseHeader($.SrBuj.notifyHeaders[attr]);
              }
              if (notify.message){ $.SrBuj.Util.notify(notify) }
              if (notify.remove){ $.SrBuj.Util.removeNotify() }
              if ( (/javascript/).test( xhr.getResponseHeader('Content-Type') )){
                return true;
              }
          }
          var options = $.SrBuj.getOptions(e.target, user_options),
          $target = options.jqueryselector ? $(options.target).present() : $(document.getElementById(options.target)),
          $wrapper = options.jqueryselector ? $(options.wrapper) : $(document.getElementById(options.wrapper));
          if ($target.present()) {
            e.stopPropagation();
            if (!options.custom) {
              if (options.change) {
                $.SrBuj.changeDom(options.method, $target, data);
              }
              if (options.modal) {
                if (options.wrapper) {
                  $wrapper.modal('toggle');
                } else {
                  $target.modal('toggle');
                }
              }
            }
            if(options.url && options.push && $.SrBuj.needToChangeUrl(options.url) && $.SrBuj.browserSupportsPushState() ){
              window.history.pushState({ SrBuj: true }, '', options.url)
            }
            if (options.callback) {
              var callback = $.SrBuj.kindOfCallback(options.callback);
              if (callback) {
                callback.apply(this, [e, data, status]);
              }
            }
            if (options.remove){
              this.remove();
            }
          }},
        fail: function (e, data, status, error_code, user_options) {
          e.stopPropagation();
          var $el = $(e.target).present(),
              error = $el.data('error'),
              jqueryselector = $el.data('jqueryselector'),
              $error = jqueryselector ? $(error) : $(document.getElementById(error));
          if (error) {
            $.SrBuj.changeDom('ERROR', $error, data.responseText);
            var notify = {};
            for (var attr in $.SrBuj.notifyHeaders)
              if ($.SrBuj.notifyHeaders.hasOwnProperty(attr)) {
                notify[attr] = data.getResponseHeader($.SrBuj.notifyHeaders[attr]);
              }
              if (notify.message){ $.SrBuj.Util.notify(notify) }
          }else {
            throw 'cant find data-error on element ' + e.target + ' maybe content_type missing on response?';
          }
        },
        bind: function (selector) {
            selector = selector || this.selector;
            $(document).on('ajax:success', selector, this.success);
            $(document).on('ajax:error', selector, this.fail);
        },
        Util: {
          notify: function (options){
          /* This function will show a growling element, with the message and attached class that was given.
           * will endure only a few seconds and its going to be removed from DOM afterwards.
           * use: $.SrBuj.Util.notify({message: 'This is Madness', type: 'info'}) this will produce
           * <s id=_growlingMsg class=info>This is Madness</s>
           */
           var options = options || {};
             if ($.SrBuj.gMsg_interval){ window.clearInterval($.SrBuj.gMsg_interval) }
             var gMsg = document.getElementById('_growlingMsg')|| document.createElement('s');
             gMsg.id = '_growlingMsg';
             if ( options.type ){
               gMsg.className = (/^(info|warning|error)$/).test( options.type ) ?
                ['alert', '-' , options.type ].join('') : options.type;
             }else{
               gMsg.className = 'alert-info';
             }
             gMsg.innerHTML = options.raw ? options.message
                                          : $.SrBuj.Util.decodeUTF8(options.message);
             $('body').append(gMsg);
             gMsg.className += [' alert', options.side || 'right', options.position ||  'bottom', 'srbuj-notify' ].join(' ').toLowerCase();
             if (options.time != -1) {
               options.time = Number(options.time) > 0 ? options.time : 2000;
               $.SrBuj.gMsg_interval = window.setInterval( $.SrBuj.Util.removeNotify, options.time );
             }
          },
          removeNotify: function(){
            $('#_growlingMsg').removeClass('srbuj-notify');
          },
          randomCode: function(size) {
            var size = !isNaN(size) && size < 12 ? size : 12
            return Math.random().toString(36).substring(4).substring(0, size)
          },
          decodeUTF8: function(message) {
            return decodeURIComponent(escape(message))
          },
          link: function (user_options){
            /* This function will create a link with options, trigger it and remove the link afterwards
             * user_options must be a hash (Obj) with key: value without the data word
             * if the hash contain more keys, these will be integrated too as data-key form. Example:
             * { target: 'modal', error: 'modal', modal: true, href: '/admin/request'}
             * this will become:
             * <a href: '/admin/request' data-remote data-srbuj data-target= 'modal' data-modal data-error= 'modal' ></a>
             * note: we add an extra attribute, in order to debug ([data-srbuj]) and recover the link that has been made from the dom that
             * if the attribute [data-srbuj] its present in any element that is handled by SrBuj response it will be removed form dom after complete the process
             * the white spaces will be removed from keys.
            */
            var user_options = user_options || {};
            $.extend(user_options, {remote: true, srbuj: true});
            if(user_options.target && user_options.href){
              var _srbujLink = document.createElement('a'),
                  randomKey = $.SrBuj.Util.randomCode(6);

              _srbujLink.id = ['_srbujLink', randomKey].join('_');
              if (user_options.force_refresh) {
                var params     = { key: randomKey }
                  , urlParams  = $.param(params)
                  , splitedUrl = user_options.href.split(/\?.+/)
                  , joinChar   = (splitedUrl.length > 1) ? '&'
                                 : splitedUrl[0].match(/\?$/) ? ''
                                   : '?';

                user_options.href +=  joinChar + urlParams;
              }
              _srbujLink.href = user_options.href;
                  delete user_options.href;
              for (var attr in user_options) {
                if (user_options.hasOwnProperty(attr)) {
                  var key = ['data', attr.replace(/\s/g, '')].join('-'),
                      value = user_options[attr];
                  if( value === true ){ value = '' }
                  _srbujLink.setAttribute(key, value);
                }
              }
              $('body').append(_srbujLink);
              $(_srbujLink).trigger('click');
            }else{
              throw 'not enough options given. Maybe target or href not present?'
            }
          }
        }
    };

    $(function () {
        $.SrBuj.bind();
    });

})(jQuery);