[PATCH/foreman 1/1] fixes #1182 - added inline editing

Signed-off-by: Ohad Levy <ohadlevy@gmail.com>

··· --- app/helpers/application_helper.rb | 49 ++-- app/views/layouts/application.html.erb | 4 +- public/javascripts/application.js | 39 +++ public/javascripts/jquery.jeditable.js | 556 ++++++++++++++++++++++++++++++++ 4 files changed, 624 insertions(+), 24 deletions(-) create mode 100644 public/javascripts/jquery.jeditable.js

diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index de731d6…90a7e7a 100644
— a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -68,29 +68,6 @@ module ApplicationHelper
link_to_function(“Uncheck all”, “checkAll(’##{form_name}’, false)”)
end

  • def searchtab title, search, options
  • opts = {:action => params[:action], :tab_name => title, :search => search}
  • selected_class = options[:selected] ? “selectedtab” : “”
  • content_tag(:li) do
  •  link_to opts, :class => selected_class do
    
  •    h(title) + (options[:no_close_button] ? "": (link_to "x", opts.merge(:remove_me => true), :class => "#{selected_class} close"))
    
  •  end
    
  • end
  • end
  • def toggle_searchbar
  • update_page do |page|
  •  page['search'].toggle
    
  •  page['tabs'].toggle
    
  • end
  • end
  • def fact_name_select
  • param = params[:search]["#{@via}fact_name_id_eq"] if params[:search]
  • return param.to_i unless param.empty?
  • return @fact_name_id if @fact_name_id
  • end
  • Return true if user is authorized for controller/action, otherwise false

    +controller+ : String or symbol for the controller

    +action+ : String or symbol for the action

@@ -166,4 +143,30 @@ module ApplicationHelper
auto_complete_field_jquery(method, “#{path}/auto_complete_#{method}”, completion_options)
end

  • def edit_textfield(object, property, options={})
  • edit_inline(object, property, options.merge({:type => “edit_textfield”}))
  • end
  • def edit_textarea(object, property, options={})
  • edit_inline(object, property, options.merge({:type => “edit_textarea”}))
  • end
  • protected
  • def edit_inline(object, property, options={})
  • name = “#{type}[#{property}]”
  • helper = options[:helper]
  • value = helper.nil? ? object.send(property) : self.send(helper, object)
  • klass = options[:type]
  • update_url = options[:update_url] || url_for(object)
  • opts = { :title => “Click to edit”, “data-url” => update_url, :class => klass,
  •  :name => name, "data-field" => property, :value => value}
    
  • content_tag_for :span, object, opts do
  •  h(value)
    
  • end
  • end

end
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index ca88fbe…61079df 100644
— a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -7,7 +7,9 @@

 <title> <%= h(yield(:title) || "Foreman") %></title>
 <%= stylesheet_link_tag 'reset', 'style', 'typography', 'grid', 'jquery-ui', 'jquery.jnotify' %>
  • <%= javascript_include_tag :defaults, ‘menu’, ‘highcharts’, ‘jquery.jnotify’ %>
  • <%= javascript_include_tag :defaults, ‘menu’, ‘highcharts’, ‘jquery.jnotify’, ‘jquery.jeditable’ %>
  • <%= javascript_tag “var AUTH_TOKEN = #{form_authenticity_token.inspect};” if protect_against_forgery? %>
  • <%= yield(:head) %>

diff --git a/public/javascripts/application.js b/public/javascripts/application.js
index 8057ae0…04fcb6f 100644
— a/public/javascripts/application.js
+++ b/public/javascripts/application.js
@@ -157,3 +157,42 @@ function pie_chart(div, title, data) {
}]
});
};
+
+$(document).ready(function() {

  • var common_settings = {
  • method : ‘PUT’,
  • submit : ‘Save’,
  • indicator : “”,
  • tooltip : ‘Click to edit…’,
  • placeholder : ‘Click to edit…’,
  • submitdata : {authenticity_token: AUTH_TOKEN, format : “json”},
  • onsuccess : function(data) {
  •  var parsed = $.parseJSON(data);
    
  •  $(this).html(parsed[$(this).attr('name').split("[")[0]][$(this).attr('data-field')]);
    
  • },
  • onerror : function(settings, original, xhr) {
  •  original.reset();
    
  •  var error = $.parseJSON(xhr.responseText)["errors"]
    
  •  $.jnotify(error, { type: "error", sticky: true });
    
  • }
  • };
  • $(’.edit_textfield’).each(function() {
  • var settings = {
  •  type : 'text',
    
  •  name : $(this).attr('name'),
    
  • };
  • $(this).editable($(this).attr(‘data-url’), $.extend(common_settings, settings));
  • });
  • $(’.edit_textarea’).each(function() {
  • var settings = {
  •  type : 'textarea',
    
  •  name : $(this).attr('name'),
    
  •  rows : 8,
    
  •  cols : 36
    
  • };
  • $(this).editable($(this).attr(‘data-url’), $.extend(common_settings, settings));
  • });

+});
\ No newline at end of file
diff --git a/public/javascripts/jquery.jeditable.js b/public/javascripts/jquery.jeditable.js
new file mode 100644
index 0000000…ae07c8e
— /dev/null
+++ b/public/javascripts/jquery.jeditable.js
@@ -0,0 +1,556 @@
+/*

+/**

    • Version 1.7.1
    • ** means there is basic unit tests for this parameter.
    • @name Jeditable
    • @type jQuery
    • @param String target (POST) URL or function to send edited content to **
    • @param Hash options additional options
    • @param String options[method] method to use to send edited content (POST or PUT) **
    • @param Function options[callback] Function to run after submitting edited content **
    • @param String options[name] POST parameter name of edited content
    • @param String options[id] POST parameter name of edited div id
    • @param Hash options[submitdata] Extra parameters to send when submitting edited content.
    • @param String options[type] text, textarea or select (or any 3rd party input type) **
    • @param Integer options[rows] number of rows if using textarea **
    • @param Integer options[cols] number of columns if using textarea **
    • @param Mixed options[height] ‘auto’, ‘none’ or height in pixels **
    • @param Mixed options[width] ‘auto’, ‘none’ or width in pixels **
    • @param String options[loadurl] URL to fetch input content before editing **
    • @param String options[loadtype] Request type for load url. Should be GET or POST.
    • @param String options[loadtext] Text to display while loading external content.
    • @param Mixed options[loaddata] Extra parameters to pass when fetching content before editing.
    • @param Mixed options[data] Or content given as paramameter. String or function.**
    • @param String options[indicator] indicator html to show when saving
    • @param String options[tooltip] optional tooltip text via title attribute **
    • @param String options[event] jQuery event such as ‘click’ of ‘dblclick’ **
    • @param String options[submit] submit button value, empty means no button **
    • @param String options[cancel] cancel button value, empty means no button **
    • @param String options[cssclass] CSS class to apply to input form. ‘inherit’ to copy from parent. **
    • @param String options[style] Style to apply to input form ‘inherit’ to copy from parent. **
    • @param String options[select] true or false, when true text is highlighted ??
    • @param String options[placeholder] Placeholder text or html to insert when element is empty. **
    • @param String options[onblur] ‘cancel’, ‘submit’, ‘ignore’ or function ??
    • @param Function options[onsubmit] function(settings, original) { … } called before submit
    • @param Function options[onreset] function(settings, original) { … } called before reset
    • @param Function options[onsuccess] function(result, status, xhr) { … } called on success
    • @param Function options[onerror] function(settings, original, xhr) { … } called on error
    • @param Hash options[ajaxoptions] jQuery Ajax options. See docs.jquery.com.
  • */

+(function($) {
+

  • $.fn.editable = function(target, options) {
  •    if ('disable' == target) {
    
  •        $(this).data('disabled.editable', true);
    
  •        return;
    
  •    }
    
  •    if ('enable' == target) {
    
  •        $(this).data('disabled.editable', false);
    
  •        return;
    
  •    }
    
  •    if ('destroy' == target) {
    
  •        $(this)
    
  •            .unbind($(this).data('event.editable'))
    
  •            .removeData('disabled.editable')
    
  •            .removeData('event.editable');
    
  •        return;
    
  •    }
    
  •    var settings = $.extend({}, $.fn.editable.defaults, {target:target}, options);
    
  •    /* setup some functions */
    
  •    var plugin   = $.editable.types[settings.type].plugin || function() { };
    
  •    var submit   = $.editable.types[settings.type].submit || function() { };
    
  •    var buttons  = $.editable.types[settings.type].buttons 
    
  •                || $.editable.types['defaults'].buttons;
    
  •    var content  = $.editable.types[settings.type].content 
    
  •                || $.editable.types['defaults'].content;
    
  •    var element  = $.editable.types[settings.type].element 
    
  •                || $.editable.types['defaults'].element;
    
  •    var reset    = $.editable.types[settings.type].reset 
    
  •                || $.editable.types['defaults'].reset;
    
  •    var callback = settings.callback || function() { };
    
  •    var onedit   = settings.onedit   || function() { }; 
    
  •    var onsubmit = settings.onsubmit || function() { };
    
  •    var onreset  = settings.onreset  || function() { };
    
  •    var onsuccess = settings.onsuccess || function() { };
    
  •    var onerror  = settings.onerror  || reset;
    
  •    var oneditcomplete   = settings.oneditcomplete   || function() { };
    
  •    var onresetcomplete  = settings.onresetcomplete  || function() { };
    
  •    /* show tooltip */
    
  •    if (settings.tooltip) {
    
  •        $(this).attr('title', settings.tooltip);
    
  •    }
    
  •    settings.autowidth  = 'auto' == settings.width;
    
  •    settings.autoheight = 'auto' == settings.height;
    
  •    return this.each(function() {
    
  •        /* save this to self because this changes when scope changes */
    
  •        var self = this;  
    
  •        /* inlined block elements lose their width and height after first edit */
    
  •        /* save them for later use as workaround */
    
  •        var savedwidth  = $(self).width();
    
  •        var savedheight = $(self).height();
    
  •        /* save so it can be later used by $.editable('destroy') */
    
  •        $(this).data('event.editable', settings.event);
    
  •        /* if element is empty add something clickable (if requested) */
    
  •        if (!$.trim($(this).html())) {
    
  •            $(this).html(settings.placeholder);
    
  •        }
    
  •        $(this).bind(settings.event, function(e) {
    
  •            /* abort if disabled for this element */
    
  •            if (true === $(this).data('disabled.editable')) {
    
  •                return;
    
  •            }
    
  •            /* prevent throwing an exeption if edit field is clicked again */
    
  •            if (self.editing) {
    
  •                return;
    
  •            }
    
  •            /* abort if onedit hook returns false */
    
  •            if (false === onedit.apply(this, [settings, self])) {
    
  •               return;
    
  •            }
    
  •            /* prevent default action and bubbling */
    
  •            e.preventDefault();
    
  •            e.stopPropagation();
    
  •            /* remove tooltip */
    
  •            if (settings.tooltip) {
    
  •                $(self).removeAttr('title');
    
  •            }
    
  •            /* figure out how wide and tall we are, saved width and height */
    
  •            /* are workaround for http://dev.jquery.com/ticket/2190 */
    
  •            if (0 == $(self).width()) {
    
  •                //$(self).css('visibility', 'hidden');
    
  •                settings.width  = savedwidth;
    
  •                settings.height = savedheight;
    
  •            } else {
    
  •                if (settings.width != 'none') {
    
  •                    settings.width = 
    
  •                        settings.autowidth ? $(self).width()  : settings.width;
    
  •                }
    
  •                if (settings.height != 'none') {
    
  •                    settings.height = 
    
  •                        settings.autoheight ? $(self).height() : settings.height;
    
  •                }
    
  •            }
    
  •            //$(this).css('visibility', '');
    
  •            /* remove placeholder text, replace is here because of IE */
    
  •            if ($(this).html().toLowerCase().replace(/(;|")/g, '') == 
    
  •                settings.placeholder.toLowerCase().replace(/(;|")/g, '')) {
    
  •                    $(this).html('');
    
  •            }
    
  •            self.editing    = true;
    
  •            self.revert     = $(self).html();
    
  •            $(self).html('');
    
  •            /* create the form object */
    
  •            var form = $('<form />');
    
  •            /* apply css or style or both */
    
  •            if (settings.cssclass) {
    
  •                if ('inherit' == settings.cssclass) {
    
  •                    form.attr('class', $(self).attr('class'));
    
  •                } else {
    
  •                    form.attr('class', settings.cssclass);
    
  •                }
    
  •            }
    
  •            if (settings.style) {
    
  •                if ('inherit' == settings.style) {
    
  •                    form.attr('style', $(self).attr('style'));
    
  •                    /* IE needs the second line or display wont be inherited */
    
  •                    form.css('display', $(self).css('display'));                
    
  •                } else {
    
  •                    form.attr('style', settings.style);
    
  •                }
    
  •            }
    
  •            /* add main input element to form and store it in input */
    
  •            var input = element.apply(form, [settings, self]);
    
  •            /* set input content via POST, GET, given data or existing value */
    
  •            var input_content;
    
  •            if (settings.loadurl) {
    
  •                var t = setTimeout(function() {
    
  •                    input.disabled = true;
    
  •                    content.apply(form, [settings.loadtext, settings, self]);
    
  •                }, 100);
    
  •                var loaddata = {};
    
  •                loaddata[settings.id] = self.id;
    
  •                if ($.isFunction(settings.loaddata)) {
    
  •                    $.extend(loaddata, settings.loaddata.apply(self, [self.revert, settings]));
    
  •                } else {
    
  •                    $.extend(loaddata, settings.loaddata);
    
  •                }
    
  •                $.ajax({
    
  •                   type : settings.loadtype,
    
  •                   url  : settings.loadurl,
    
  •                   data : loaddata,
    
  •                   async : false,
    
  •                   success: function(result) {
    
  •                      window.clearTimeout(t);
    
  •                      input_content = result;
    
  •                      input.disabled = false;
    
  •                   }
    
  •                });
    
  •            } else if (settings.data) {
    
  •                input_content = settings.data;
    
  •                if ($.isFunction(settings.data)) {
    
  •                    input_content = settings.data.apply(self, [self.revert, settings]);
    
  •                }
    
  •            } else {
    
  •                input_content = self.revert; 
    
  •            }
    
  •            content.apply(form, [input_content, settings, self]);
    
  •            input.attr('name', settings.name);
    
  •            /* add buttons to the form */
    
  •            buttons.apply(form, [settings, self]);
    
  •            /* add created form to self */
    
  •            $(self).append(form);
    
  •            /* attach 3rd party plugin if requested */
    
  •            plugin.apply(form, [settings, self]);
    
  •            /* focus to first visible form element */
    
  •            $(':input:visible:enabled:first', form).focus();
    
  •            /* highlight input contents when requested */
    
  •            if (settings.select) {
    
  •                input.select();
    
  •            }
    
  •            /* discard changes if pressing esc */
    
  •            input.keydown(function(e) {
    
  •                if (e.keyCode == 27) {
    
  •                    e.preventDefault();
    
  •                    //self.reset();
    
  •                    reset.apply(form, [settings, self]);
    
  •                }
    
  •            });
    
  •            /* discard, submit or nothing with changes when clicking outside */
    
  •            /* do nothing is usable when navigating with tab */
    
  •            var t;
    
  •            if ('cancel' == settings.onblur) {
    
  •                input.blur(function(e) {
    
  •                    /* prevent canceling if submit was clicked */
    
  •                    t = setTimeout(function() {
    
  •                        reset.apply(form, [settings, self]);
    
  •                    }, 500);
    
  •                });
    
  •            } else if ('submit' == settings.onblur) {
    
  •                input.blur(function(e) {
    
  •                    /* prevent double submit if submit was clicked */
    
  •                    t = setTimeout(function() {
    
  •                        form.submit();
    
  •                    }, 200);
    
  •                });
    
  •            } else if ($.isFunction(settings.onblur)) {