1

The following script allows jQueryUI's autocomplete to work with xeditable, and works as desired:

$(function(){
    dialog.find('a.car').xEdit('autocomplete',{name:'carName', title:'Car Name', url: '/1.0/cars/', pk: carId, autocomplete: {
        url: "/1.0/cars/",
        params: {term:null, fields:['id','name']}
    }});
});

$.fn.xEdit = function(type, options) {
    var common={
        placement: 'right',
        ajaxOptions: {type: "PUT"},
        send: 'always'
        // pk: null, title: null, params: {name: null}, url: null, //Must be passed
    }
    // Not shown, but different actions will be performed based on "type"
    options.params={name: options.name};
    delete(options.name);
    options.type='text';
    //autocomplete is not used by xeditable but by jQueryUI autocomplete, but doesn't seem to hurt anything
    this.editable($.extend({}, common, {value: null}, options))
    .on('shown', function(e, editable) {
        //console.log('on.show','this',this,'e',e,'editable',editable)
        var elem=this;
        var $input=editable.input.$input.val('');
        var $button=$input.parent().next().find('button.editable-submit').css('opacity', 0.3)
        .bind('click.prevent', function() {return false;});
        var autocomplete={
            source: function( request, response ) {
                options.autocomplete.params.term=request.term;
                $.getJSON( options.autocomplete.url, options.autocomplete.params, function(json) {
                    var data=[];
                    for (var j = 0; j < json.length; j++) {
                        data.push({id:json[j].id,label:json[j].name});
                    }
                    response(data);
                } );
            },
            minLength: 2,
            position: { my : "left top+20", at: "left bottom" },
            select: function(e, ui) {
                $input.blur();
                $button.css('opacity', 1).unbind('click.prevent');
                editable.option('params', {
                    value: ui.item.id,
                    name: options.params.name
                });
            }
        };
        if (typeof options.autocomplete.select != "undefined") autocomplete.select=options.autocomplete.select;
        $input.focus(function() {
            $button.css('opacity', 0.3).bind('click.prevent', function() {return false;});
        })
        .autocomplete(autocomplete)
        .autocomplete('widget').click(function() {return false;});
    });
    break;
};

I now wish to be able to override the select: function(e, ui) {...} (and maybe later the other methods) in xedit with a new function defined in options, and try the following:

$(function(){
    dialog.find('a.car').xEdit('autocomplete',{name:'carName', title:'Car Name', url: '/1.0/cars/', pk: carId, autocomplete: {
        url: "/1.0/cars/",
        params: {term:null, fields:['id','name']},
        select: function(e, ui) {
            //Be able to do something different
            $input.blur();
            $button.css('opacity', 1).unbind('click.prevent');
            editable.option('params', {
                value: ui.item.id,
                name: options.params.name,
                model: 123
                }
            );
        }
    }});
});

But it results in errors stating that e, ui, $input, $button, editable, options are all undefined. I think it is due to defining the new function in a different variable scope.

How can I define an object in one variable scope, and then apply another scope to it?

PS. I expect how I am doing this is pretty ugly, and if there is a prettier way, feel free to comment.

user1032531
  • 24,767
  • 68
  • 217
  • 387
  • You cannot change something's scope. – EKW Mar 10 '18 at 02:36
  • 1
    For jQuery read about [`.proxy()`](https://api.jquery.com/jQuery.proxy/) and for JavaScript [`.call()` / `.apply()` / `.bind()`](http://javascriptissexy.com/javascript-apply-call-and-bind-methods-are-essential-for-javascript-professionals/) – zer00ne Mar 10 '18 at 06:56
  • @zer00ne Ah yes, apply() is what I was thinking (I think). I don't spend enough time with JavaScript... – user1032531 Mar 10 '18 at 07:33
  • @user1032531 By understanding JavaScript, I've grown a stronger understanding of jQuery but it's no longer a crutch. Diving deep into plugins are a nightmare, good luck to you. – zer00ne Mar 10 '18 at 07:58
  • @zer00ne What I posted wasn't someone else's plugin but my own jQuery/JavaScript mixture. I probably should just use JavaScript... – user1032531 Mar 10 '18 at 13:35
  • @user1032531 You fooled me, it looks like a part of a well oiled jQ plugin to me. I learned backwards, started with enough knowledge of jQuery to F$%& things up and ended up using JavaScript to build things from the ground up. Now I realize that despite what JS elitist say, jQ will always be an aspect of web developing and JS may take a good amount of years before the layer of abstraction is on par with jQ or if jQ is retired. If you have the time, JS is a good foundation but if your'e on someone else's time (i.e. your employer's time), then plugins that someone else has made is more practical – zer00ne Mar 10 '18 at 17:08
  • @zer00ne Ha ha, just being tricky and trying to fool people! Don't know how well oiled it is, but thanks anyways. I do like jQuery sometimes, but other times, it is easier just to stay with pure JS. Don't know if it is true, but read that jQuery is on the decline and that others (maybe react) will be the new thing. – user1032531 Mar 11 '18 at 21:02

1 Answers1

1

Well, I've never used this xEdit plugin before, but your problem is JavaScript-related, so I can give my two cents.

In your first example, the select handler has access to those variables because it's lexically bound to the scope of the outer function (the handler for the shown event). When you declare a function within another, this construction is called a closure. The inner function, even if called outside of its container function, will always remember its original context. This link is unbreakable.

function a() {
  const favoriteSport = 'Football';
  const team = 'Packers';

  return function(){
    console.log(`My favorite sport and team are "${favoriteSport}" and "${team}" respectively`);
  };
}

const b = a();
const c = b;
const d = c;
const e = d;

const anObject = { e };
// One does not simply forget its origins... =)
anObject.e(); //=> My favorite sport and team are "Football" and "Packers" respectively

// This is what you're doing...
anObject.e = function(){
  console.log(`I really do love "${favoriteSport}" and the "${team}".`);
};
// BOOOM!
anObject.e(); //=> Uncaught ReferenceError: favoriteSport is not defined

When you lexically declare a function a outside of a function b, a doesn't have access to any variable declared inside b. This is what you're trying to do.

So, it's not possible to change a function's scope after it's declared.

Here's a fabulous SO question with dozens of great answers about how closures work.

Alcides Queiroz
  • 9,456
  • 3
  • 28
  • 43
  • Thanks for not totally bashing that xEdit plugin too much. And you are not alone in never using it. Just my own attempt to eliminate duplication in an application of mine. Lexically bound? I never heard that term before. Thank you for teaching me a new term. Reading up on it, it sounds like just being able to access its parent's scope, right? No restriction on just one level, right? zer00ne's comment seems to indicate that it is possible. – user1032531 Mar 10 '18 at 07:31