vitalets / x-editable

In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
http://vitalets.github.io/x-editable
MIT License
6.51k stars 1.71k forks source link

custom widget with select #173

Open dberansky opened 11 years ago

dberansky commented 11 years ago

Hi,

Any pointers on how to implement a custom widget that has a select dropdown (populated from array/function/ajax) as one of its subcomponents?

Thanks D.

vitalets commented 11 years ago

hi, could you clarify what you mean under "dropdown as one of its subcomponents" ?

dberansky commented 11 years ago

Let's take the address custom component as an example. let's say I want the city field to be a dropdown rather than just a text field. Furthermore, I need that dropdown's options to be dynamically populated (via ajax).

thanks

vitalets commented 11 years ago

ok, now I get it :) you can modify address as following:

  1. replace input[type=text] with select in template
  2. populate it in render() method using source option (declare it in defaults). You should return deferred object to deal with async ajax. See for ex render in https://github.com/vitalets/x-editable/blob/master/src/inputs/list.js
dberansky commented 11 years ago

Oh, I see... I was thinking more along the lines of re-using x-editable's own dropdown widget, the one used for editing of 'select', since it already implements all the functionality I need. Is that a possibility? I've been looking at the source for how the editable select widget is implemented, but so far haven't been able to figure out how to reuse it.

vitalets commented 11 years ago

ok, may be another approach: inherit your custom widget from current select and add to template one more input (e.g. type=text). Also you need to overwrite some methods (e.g. input2value etc) to ensure correct passing value from and to widget. Let me know about your progress as it's very good usecase. thx!

jasiedu commented 11 years ago

Any updates on this issue? Do you have an example hat I could use?

dberansky commented 11 years ago

Yeh, sorry it has taken me so long to get back. I just recently managed to work on this again. The code's below. It's not exactly what I wanted (I'd prefer the select to be a true combobox capable of taking a value not currently in the suggestion list) but is good enough as an example.

/**
Contact Info editable input.

@class contactinfo
@extends abstractinput
@final
@example
<a href="#" id="contact" data-type="contactinfo" data-pk="1">awesome</a>
<script>
$(function(){
    $('#contact').editable({
        url: '/post',
        title: 'Enter contact info',
        value: {
            role: 'email',
            text: 'me@gmail.com'
        }
    });
});
</script>
**/
(function ($) {
    var ContactInfo = function (options) {
        this.init('contactinfo', options, ContactInfo.defaults);
    };

    //inherit from Abstract input
    $.fn.editableutils.inherit(ContactInfo, $.fn.editabletypes.select);

    $.extend(ContactInfo.prototype, {
        value2input: function(value) {
            console.log(value);
            if(!value) {
               return;
            }
            this.$input.filter('select').val(value.role);
            this.$input.filter('input').val(value.text);
        },

        input2value: function() {
            return {
                role: this.$input.filter('select').val(),
                text: this.$input.filter('input').val()
            }
        },

        value2html: function(value, element) {
             $(element).html(value.role+": "+value.text);
        },

        str2value: function(str) {
            if( !str || !(typeof(str) === 'string'))
               return str;

            var splitPos = str.indexOf(':');
            if(splitPos < 0)
               return str;
            var type = str.substr(0,splitPos).trim();
            var text = str.substr(splitPos+1).trim();

            return {role:type, text:text}
        }
    });

    ContactInfo.defaults = $.extend({}, $.fn.editabletypes.select.defaults, {
        tpl: '<select class="input-medium"></select>'+
             '<input type="text" name="contact_value" style="margin-left: 1em;" class="input-medium" size="12" placeholder="contact">',

        inputclass: ''
    });

    $.fn.editabletypes.contactinfo = ContactInfo;

}(window.jQuery));
chichilatte commented 11 years ago

Wow dbransky, it works! Some minor corrections:

        value: {
            role: email,
            text: me@gmail.com
        }

should be:

        value: {
            role: 'email',
            text: 'me@gmail.com'
        },

And I needed to add a source for the dropdown:

<a href="#" id="contact" data-type="contactinfo" data-pk="1"
   data-source="[{value: 'email', text: 'email'},{value: 'postcode', text: 'Postcode'}]"
>awesome</a>

One other thing for anyone interested: the data arrives server-side as (php in this example)...

Array
(
    [role] => email
    [text] => me@gmail.com
)
dberansky commented 11 years ago

thanks, fixed the missing quotes in comment doc

nuvolaTeam commented 9 years ago

Hi all, I have a similar issue but i am not able to figure this out. I have a text field and a select box and i want to load select options from an ajax call. Here my code:

(function ($) {
    "use strict";

    var ModelData = function (options) {
        this.source = options.source;
        this.init('modelData', options, ModelData.defaults);
    };

    //inherit from Abstract input
    $.fn.editableutils.inherit(ModelData, $.fn.editabletypes.select);

    $.extend(ModelData.prototype, {
        /**
         Renders input from tpl

         @method render() 
         **/
        render: function () {
            this.$input = this.$tpl.find('textarea');
            this.$select = this.$tpl.find('select');
            this.$select.empty();

            var fillItems = function ($el, data) {
                if ($.isArray(data)) {
                    for (var i = 0; i < data.length; i++) {

                        $el.append($('<option>', {
                            value: data[i].value
                        }).text(data[i].text));

                    }
                }

                return $el;
            };

            var deferred = $.Deferred();

            this.error = null;
            this.onSourceReady(function () {
                console.log(this.source());
            fillItems(this.$select, this.source());
                deferred.resolve();
            }, function () {
                this.error = this.options.sourceError;
                deferred.resolve();
            });

            return deferred.promise();

        },
        /**
         Default method to show value in element. Can be overwritten by display option.

         @method value2html(value, element) 
         **/
        value2html: function (value, element) {
            if (!value) {
                $(element).empty();
                return;
            }
            var relationshipText = value.relationship;
            $.each(this.source, function (i, v) {
                if (v.value === relationshipText) {
                    relationshipText = v.text;
                }
            });

            value.noteView = value.note ? value.note : 'n/a';

            var html = '<div class="col-md-12"><span class="col-dataset">RELATIONSHIP: </span>' +
                    '<span class="editable-post-select" data-type="select" data-pk="1" data-value="' + value.relationship + '" style="font-size: 12px;">' +
                    relationshipText +
                    '</span>' +
                    '</div>' +
                    '<div class="col-md-12"><span class="col-dataset">NOTE: </span>' +
                    '<span class="editable-post-textarea" data-type="textarea" data-pk="2" data-value="' + value.note + '"style="font-size: 12px;">' +
                    value.noteView +
                    '</span>' +
                    '</div>';
            $(element).html(html);
        },
        /**
         Gets value from element's html

         @method html2value(html) 
         **/
        html2value: function (html) {

            return null;
        },
        /**
         Converts value to string. 
         It is used in internal comparing (not for sending to server).

         @method value2str(value)  
         **/
        value2str: function (value) {
            var str = '';
            if (value) {
                for (var k in value) {
                    str = str + k + ':' + value[k] + ';';
                }
            }
            return str;
        },
        /*
         Converts string to value. Used for reading value from 'data-value' attribute.

         @method str2value(str)  
         */
        str2value: function (str) {
            /*
             this is mainly for parsing value defined in data-value attribute. 
             If you will always set value by javascript, no need to overwrite it
             */
            return str;
        },
        /**
         Sets value of input.

         @method value2input(value) 
         @param {mixed} value
         **/
        value2input: function (value) {
            if (!value) {
                return;
            }
            this.$select.val(value.relationship);
            this.$input.filter('[name="note"]').val(value.note);
        },
        /**
         Returns value of input.

         @method input2value() 
         **/
        input2value: function () {
            return {
                relationship: this.$select.val(),
                note: this.$input.filter('[name="note"]').val()
            };
        },
        /**
         Activates input: sets focus on the first field.

         @method activate() 
         **/
        activate: function () {
            this.$select.focus();
        }

    });

    ModelData.defaults = $.extend({}, $.fn.editabletypes.select.defaults, {
        tpl: '<div class="form-group">' +
                '<label for="relationship" class="col-sm-2 control-label col-dataset">RELATIONSHIP:</label>' +
                '<div class="col-sm-4"><select name="relationship" class="input-sm form-control"></select></div>' +
                '  </div> ' +
                '<div class="form-group">' +
                '<label for="note" class="col-sm-2 control-label col-dataset">NOTE:</label>' +
                '<div class="col-sm-9"><textarea rows="1" name="note" class="input-sm form-control"></textarea>' +
                ' </div>' +
                ' </div> ',
        inputclass: '',
        source: []
    });

    $.fn.editabletypes.modelData = ModelData;

}(window.jQuery));

$(function () {

        $.fn.editable.defaults.mode = 'inline';

        $('.editable-post-select').off('click');
        $('.editable-post-textarea').off('click');

        $('.edit-post-button').each(function () {

            $(this).parent().prev().children('.modelData').editable({
                toggle: 'manual',
                url: $(this).attr('href'),
                pk: 1,
                name: 'modelData',
                value: {
                    relationship: $(this).siblings('input[name="referenceRelationship"]').val(),
                    note: $(this).siblings('input[name="referenceNote"]').val()
                },
                source: function () {
    var tmp = null;
    $.ajax({
        url: "dataset/relation",
        async: true,
        success: function (data) {
            tmp = data;
        }
    });
    return tmp;
},
                highlight: false,
                validate: function (value) {
                    if (value.relationship === '')
                        return 'Relation with model is required.';
                },
                success: function (response, newValue) {
                    if (response.valid === true) {
                        message = 'The dataset has been successfully updated!';
                        type = 'success';
                    } else {
                        message = 'There has been an error during saving. Please contact the MIDAS support.';
                        type = 'danger';
                    }

                    $.bootstrapGrowl(message, {
                        element: 'body',
                        type: type,
                        offset: {from: 'bottom', amount: 50},
                        align: 'left',
                        width: 350,
                        delay: 4500,
                        allow_dismiss: true
                    });
                    $('#can-reload').attr('value', true);
                }
            });

            $(this).click(function (e) {
                e.preventDefault();
                e.stopPropagation();

                $(this).parent().prev().children('.modelData').editable('toggle');
                $('form').removeClass('form-inline').addClass('form-horizontal');
            });
        });
    });
devendra1102 commented 8 years ago

Hello, I have to display some tooltip for each dropdown item. How to do this?