admin管理员组

文章数量:1193758

I extended the bootstrap-typeahead in order to take an object instead of a string.
It works but I would like to know this is the right way to do the things.

Thanks.

Reference:
.html#typeahead .js

_.extend($.fn.typeahead.Constructor.prototype, {
    render: function (items) {
        var that = this;

        items = $(items).map(function (i, item) {
            i = $(that.options.item)
                .attr('data-value', item[that.options.display])
                .attr('data-id', item.id);
            i.find('a').html(that.highlighter(item));
            return i[0];
        });

        items.first().addClass('active');
        this.$menu.html(items);
        return this;
    },
    select: function () {
        var val = this.$menu.find('.active').attr('data-value'),
            id = this.$menu.find('.active').attr('data-id');
        this.$element
            .val(this.updater(val, id))
            .change();
        return this.hide()
    }
});

return function (element, options) {
    var getSource = function () {
            return {
                id: 2,
                full_name: 'first_name last_name'
            };
    };

    element.typeahead({
        minLength: 3,
        source: getSource,
        display: 'full_name',
        sorter: function (items) {

            var beginswith = [],
                caseSensitive = [],
                caseInsensitive = [],
                item,
                itemDisplayed;

            while (item = items.shift()) {
                itemDisplayed = item[this.options.display];
                if (!itemDisplayed.toLowerCase().indexOf(this.query.toLowerCase())) {
                    beginswith.push(item);
                } else if (~itemDisplayed.indexOf(this.query)) {
                    caseSensitive.push(item);
                } else {
                    caseInsensitive.push(item);
                }
            }

            return beginswith.concat(caseSensitive, caseInsensitive);
        },
        highlighter: function (item) {
            var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');

            return item[this.options.display].replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
                return '<strong>' + match + '</strong>';
            });
        },
        matcher: function (item) {
            var value = item[this.options.display];

            return {
                value: ~value.toLowerCase().indexOf(this.query.toLowerCase()),
                id: item.id
            };
        },
        updater: function (item, userId) {
            options.hiddenInputElement.val(userId);
            return item;
        }
    });
};

I extended the bootstrap-typeahead in order to take an object instead of a string.
It works but I would like to know this is the right way to do the things.

Thanks.

Reference:
http://twitter.github.com/bootstrap/javascript.html#typeahead http://twitter.github.com/bootstrap/assets/js/bootstrap-typeahead.js

_.extend($.fn.typeahead.Constructor.prototype, {
    render: function (items) {
        var that = this;

        items = $(items).map(function (i, item) {
            i = $(that.options.item)
                .attr('data-value', item[that.options.display])
                .attr('data-id', item.id);
            i.find('a').html(that.highlighter(item));
            return i[0];
        });

        items.first().addClass('active');
        this.$menu.html(items);
        return this;
    },
    select: function () {
        var val = this.$menu.find('.active').attr('data-value'),
            id = this.$menu.find('.active').attr('data-id');
        this.$element
            .val(this.updater(val, id))
            .change();
        return this.hide()
    }
});

return function (element, options) {
    var getSource = function () {
            return {
                id: 2,
                full_name: 'first_name last_name'
            };
    };

    element.typeahead({
        minLength: 3,
        source: getSource,
        display: 'full_name',
        sorter: function (items) {

            var beginswith = [],
                caseSensitive = [],
                caseInsensitive = [],
                item,
                itemDisplayed;

            while (item = items.shift()) {
                itemDisplayed = item[this.options.display];
                if (!itemDisplayed.toLowerCase().indexOf(this.query.toLowerCase())) {
                    beginswith.push(item);
                } else if (~itemDisplayed.indexOf(this.query)) {
                    caseSensitive.push(item);
                } else {
                    caseInsensitive.push(item);
                }
            }

            return beginswith.concat(caseSensitive, caseInsensitive);
        },
        highlighter: function (item) {
            var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');

            return item[this.options.display].replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
                return '<strong>' + match + '</strong>';
            });
        },
        matcher: function (item) {
            var value = item[this.options.display];

            return {
                value: ~value.toLowerCase().indexOf(this.query.toLowerCase()),
                id: item.id
            };
        },
        updater: function (item, userId) {
            options.hiddenInputElement.val(userId);
            return item;
        }
    });
};
Share Improve this question edited Aug 30, 2012 at 9:42 Lorraine Bernard asked Aug 29, 2012 at 16:49 Lorraine BernardLorraine Bernard 13.4k23 gold badges85 silver badges137 bronze badges 8
  • 1 I don't see how you are even getting that far. When I try to run your code an exception is thrown in the sorter() method for trying apply string methods on objects. Were you altering that method? Or perhaps adding functions to the objects? – merv Commented Aug 29, 2012 at 17:23
  • 1 yes, you are right because I made other changes I didn't post yet.. I will make it later. thanks – Lorraine Bernard Commented Aug 29, 2012 at 17:51
  • 1 Yeah, that would be helpful, since matcher() and sorter() both alter items before passing it to the render() method. Also, overriding sorter() is supported in the API, so there should be no need to edit that in the bootstrap-typeahead.js source. (not saying that's what you were doing - just pointing it out) – merv Commented Aug 29, 2012 at 18:00
  • 4 Looks good! My only recommendations are: 1) Consider maintaining backward compatibility on the API. As of now, you are introducing new options without defining defaults (e.g., display). You should extend defaults in order that, with new options omitted, some fallback functionality should always exist. 2) You should avoid strongly coupling your data model (source) with the plugin's methods. Specifically, I am referring to the use of item.id in the render() function. Consider generalizing render() to either copy all key-values in the model, or introduce a user-specified option. – merv Commented Aug 30, 2012 at 19:39
  • 2 @LorraineBernard I have successfully used Bootstrap's typeahead with Backbone by simply overriding sorter, matcher, source and highlighter, which is all supported in the existing API. Have you tried this? – jackwanders Commented Sep 6, 2012 at 20:35
 |  Show 3 more comments

3 Answers 3

Reset to default 18 +50

I recommend to not rewrite the prototype (which would impact all places where you could use the autocompleter, not only the following function).
This code should work:

var getSource = function () {

    var users = new Backbone.Collection([
        {
            first_name: 'primo',
            last_name: 'ultimo',
            id: 1
        },
        {
            first_name: 'altro_primo',
            last_name: 'altro_ultimo',
            id: 2
        }
    ]).models;

    return _.map(users, function (user) {
        return {
            id: user.get('id'),
            full_name: user.get('first_name') + ' ' + user.get('last_name'),
            // these functions allows Bootstrap typehead to use this item in places where it was expecting a string
            toString: function () {
                return JSON.stringify(this);
            },
            toLowerCase: function () {
                return this.full_name.toLowerCase();
            },
            indexOf: function (string) {
                return String.prototype.indexOf.apply(this.full_name, arguments);
            },
            replace: function (string) {
                return String.prototype.replace.apply(this.full_name, arguments);
            }
        };
    });
};

$('.full_name').typeahead({
            minLength: 3,
            source: getSource(),
            display: 'full_name',
            updater: function (itemString) {
                var item = JSON.parse(itemString);
                $('.id').val(item.id);
                return item.full_name;
            }
});

demo: http://jsfiddle.net/hyxMh/48/

In addition to @AntoJs answer, here is an ajax version (just 'source' part):

$('.full_name').typeahead({
    /* ... */
    source: function(query,process){
    $.ajax({
        url: 'your_url_here',
        data: {term: query, additional_data: 'some_data'},
        success: function(data){
            process($.map(data, function(user){
                return {
                    id: user.id,
                    full_name: user.first_name + ' ' + user.last_name,
                    toString: function () {
                        return JSON.stringify(this);
                    },
                    toLowerCase: function () {
                        return this.full_name.toLowerCase();
                    },
                    indexOf: function (string) {
                        return String.prototype.indexOf.apply(this.full_name, arguments);
                    },
                    replace: function (string) {
                        return String.prototype.replace.apply(this.full_name, arguments);
                    }
                }
            }));
        }
    });
    },
/* ... */
});

Btw: @AntoJs - many thanks for your answer!

@antonjs's answer wasn't working for me and I was getting the following error

TypeError: item.substr is not a function

so with jQuery v2.1.3 and bootstrap3-typeahead.js v3.1.0 the following works for me

var getSource = function () {

    var users = new Backbone.Collection([
        {
            first_name: 'primo',
            last_name: 'ultimo',
            id: 1
        },
        {
            first_name: 'altro_primo',
            last_name: 'altro_ultimo',
            id: 2
        }
    ]).models;

    return _.map(users, function (user) {
        return {
            id: user.get('id'),
            full_name: user.get('first_name') + ' ' + user.get('last_name'),
            // these functions allows Bootstrap typehead to use this item in places where it was expecting a string
            toString: function () {
                return JSON.stringify(this);
            },
            toLowerCase: function () {
                return this.full_name.toLowerCase();
            },
            indexOf: function (string) {
                return String.prototype.indexOf.apply(this.full_name, arguments);
            },
            replace: function (string) {
                return String.prototype.replace.apply(this.full_name, arguments);
            },
            substr: function(start, len){
                  return this.full_name.substr(start, len);
            }
        };
    });
};

$('.full_name').typeahead({
            minLength: 3,
            source: getSource(),
            display: 'full_name',
            updater: function (itemString) {
                var item = JSON.parse(itemString);
                $('.id').val(item.id);
                return item.full_name;
            }
});

本文标签: javascriptExtend the bootstraptypeahead in order to take an object instead of a stringStack Overflow