free-jqgrid / jqGrid

jQuery grid plugin
https://github.com/free-jqgrid/jqGrid
Other
479 stars 195 forks source link

possibly wrong paths in plugins when using with node/stealjs #279

Open HansHammel opened 7 years ago

HansHammel commented 7 years ago

when using e.g. ui.multiselect plugin, steal-tools (stealjs) fails with

ERROR: Error loading "free-jqgrid@4.13.5#plugins/jqdnr" at file:C:/[...]/node_modules/free-jqgrid/plugins/jqdnr.js
Error loading "free-jqgrid@4.13.5#plugins/jqdnr" from "mqtt-admin@1.0.0#main" at file:C:/[...]/main.js
ENOENT: no such file or directory, open 'C:\[...]\node_modules\free-jqgrid\plugins\jqdnr\index.js'
ERROR:
Build failed

note the resolution

free-jqgrid@4.13.5#plugins/jqdnr

jqdnr resides in the js folder (vs. plugins)

Maybe related to the AMD wrapper (!?) and to all plugins(!?) Note: jqdnr and jqmodal are not required in the CommonJS part. Is this correct!?

PS: applies to ES6 imports, not CommonJS require style

require('free-jqgrid/plugins/ui.multiselect'); // works
import 'free-jqgrid/plugins/ui.multiselect'; // not

Greetings.

OlegKi commented 7 years ago

Please, provide more detailed information, for example the demo, about the problem. It looks like you try to load jqdnr module from wrong location free-jqgrid\plugins instead of free-jqgrid\js. I recommend to load the modules from CDN. I published all minimized and non-minimized modules (with the corresponding map-files) to both CDNs (see here): cdnjs and jsDelivr. Both CDNs supports HTTP/2 and thus the loading or modules is really effectively.

The module free-jqgrid\plugins\ui.multiselect could has another problem: missing "jquery-ui/sortable" in the dependencies at the beginning. See lie line

define(["jquery", "./jqdnr", "./jqmodal"], factory);

which could be modified to

define(["jquery", "../js/min/jqdnr", "../js/min/jqmodal", "jquery-ui/sortable"], factory);

or

define(["jquery", "../js/jqdnr", "../js/jqmodal", "jquery-ui/sortable"], factory);

(like in grid.jqueryui.js here).

If you would report that the above changes will fix your problem, then I'll commit the changes to the plugins/ui.multiselect.js.

In any way one have to define the correct mapping of jQuery UI modules (see here and here). Be carefully, that the structure of directories in jQuery UI 1.12 is a little other path (which contains widgets subfolder folder) as in jQuery UI 1.11.

The demo shows how one can use RequereJs to load the modules. Another demo uses loadCSS additionally.

UPDATED: I think that one should fix

define(["jquery", "./jqdnr", "./jqmodal"], factory);

to the following

define(["jquery", "jquery-ui/sortable"], factory);
OlegKi commented 7 years ago

I made some changes in plugins of free jqGrid, which are not yet committed to GitHub. The demo uses the preliminary code. The demo loads ui.multiselect, jquery.jqgrid.showhidecolumnmenu and jquery.createcontexmenufromnavigatorbuttons modules and it seems to work correctly.

Small remark about the usage of import 'free-jqgrid/plugins/ui.multiselect'; instead of require('free-jqgrid/plugins/ui.multiselect');. In my knowledge, there are no JavaScript engine, which has native support of import (see here. Thus you probably use Babel or another transpiler to trinspile the import to some kind of require calls. If you have a problem only with the usage of import statement, then the demo is really required to analyse and to solve the problem.

HansHammel commented 7 years ago

I can conferm that this is working for me (in the special case of the uncompressed ui.multiselect.js) define(["jquery", "../js/jqdnr", "../js/jqmodal", "jquery-ui/ui/widgets/sortable"], factory); a sample can be found in my mqtt-admin repo. use the no-bower branch. run npm run build && npm run test. then apply the patch manually and see it working. note: the folderstructure is vaild for latest jquery-ui

OlegKi commented 7 years ago

Sorry, but your last comment isn't clear enough for me. Is your last comment about my latest changes used in the demo? What you mean under define(["jquery", "../js/jqdnr", "../js/jqmodal", "jquery-ui/ui/widgets/sortable"], factory);? I wrote in UPDATED part of my first comment, that jqdnr and jqmodal are not needed at all for ui.multiselect.js and they should be removed. Instead of that one should include the reference to sortable module of jQuery UI. The code of ui.multiselect.js has no requirement to use a specific jQuery UI version and it can be used either with the version 12.x or 11.x. Both versions has different relative paths to sortable module from the root of npm package jquery-ui. Thus it's unclear for me, which define statement should has better included in the ui.multiselect.js file.

I can't install or build your repository mqtt-admin because we use different environments and your repository supposes the existence of some global packages, which I don't have. By executing npm i I get many errors and warnings, for example, about missing python (in unclear version), unsupported platform for fsevents@1.0.15 and so on.

There are a lot of different build frameworks. Moreover I, personally, use only RequireJs as AMD and I know the configuration possibilities of it. I committed the changes to simplify for your the tests in your environment. I removed unneeded "./jqdnr", "./jqmodal" from JavaScript modules included in plugins folder. I included "jquery-ui/sortable" as the dependency in ui.multiselect.js and "free-jqgrid/grid.base" as the dependency in grid.odata.js, jquery.createcontexmenufromnavigatorbuttons.js and jquery.jqgrid.showhidecolumnmenu.js. The demo demonstrates, that the latest changes allows to use plugins in RequireJs.

RequireJs allows us to define mapping between "jquery-ui/sortable" and "free-jqgrid/grid.base" (included in plugin modules) to physical paths, where the packages exist (see paths and map parts of requirejs.config used in the demo). Thus one can, for example, map sortable to "jquery-ui/ui/widgets/sortable" in case of usage jQuery UI 12.x and to map it to "jquery-ui/ui/sortable" for the usage jQuery UI 11.x without any modifications of the source of modules included in free jqGrid plugins.

It's interesting for me, whether you can use close mappings in your environments. Are my latest changes fixes the problem, which you reported or you can suggest some alternative changes?

HansHammel commented 7 years ago

Well, I tried it. No deal! steal.js (which uses babel internally) is searching the deps at the wrong place (jquery-ui >= 12.x) see:

steal.js:2655 GET http://127.0.0.1:3000/node_modules/jquery-ui/sortable/index.js 404 (Not Found)
steal.js:6654 Error loading "jquery-ui@1.12.1#sortable" at http://127.0.0.1:3000/node_modules/jquery-ui/sortable.js
Error loading "jquery-ui@1.12.1#sortable" from "mqtt-admin@1.0.0#main" at http://127.0.0.1:3000/main.js
Not Found: http://127.0.0.1:3000/node_modules/jquery-ui/sortable/index.js undefined

but apart from that, define(["jquery", "jquery-ui/ui/widgets/sortable"], factory); is working fine.

btw. in commonjs i would to something like that in Node/CommonJS section Note: You should require the dependencies in CommonJS, too.

//maybe usefull for browseryfy & co. to resolve dependencies
//untested!
var semver = require('semver');
if (semver.satisfies(require('jquery-ui').version+' || >=1.12.0'))
    require("jquery-ui/ui/widgets/sortable");
else
    require("jquery-ui/sortable");
OlegKi commented 7 years ago

I don't use steal.js myself, but I found, after some searching in the internet, that every AMD loader have to support paths and map configuration properties (see here). After more searching I found the article, which describes the usage of paths and map in StealJS. Other articles of the official StealJS documentation (here, here, here and other) describe the mapping of package paths. More articles (like this one) show how to load dependencies from CDN (my preferred way).

Thus, it seems to me, that mapping of "jquery-ui/sortable" and "free-jqgrid/grid.base" packages, defined in plugins, can be made by StealJS configuration in practically the same way like I do it in the demo for RequireJs.

HansHammel commented 7 years ago

Well, I personally take a no-build approach. I have a project with over 300 primary client-side dependencies. Managing these dependences with mappings is a horrable mess. so from my point of view the developer of a module has to make things work not leaving the task to hunderts or millions of users, enforcing each one of theme to repeat a silly task over and over. No offense btw, just another religious conviction. How about my purposal of checking the version and ajusting the path...

OlegKi commented 7 years ago

I didn't commented your suggestion about the usage of semver, because I disagree with the approach. Because you asked me directly about it, I'll describe my point of view about the subject below.

First of all, the package "free-jqgrid" should be independent from internal structure of paths of dependent packages like the "jquery-ui". It could be different distributions of the same version of the same product (for example CDN, NuGet, Maven Central and so on). The paths of different distributions could be different. The package can use related paths of his own modules, but not external modules, from which the package depends.

I think, that your suggestion is oriented on npm packages only. semver is one more npm package, which is unneeded in general for working of the ui.multiselect module. The module ui.multiselect require any jQuery in version >=1.6 and sortable module of any jQuery UI in version >=1.8. One can load jQuery UI from CDN, for example, which contains practically all versions of jQuery UI. Different URLs contains different versions of jQuery UI. I don't understand what should test the expression like semver.satisfies(require('jquery-ui').version+' || >=1.12.0') . If no jQuery UI is already loaded, then which version of jQuery UI should be preferred? What value should return require('jquery-ui').version? On the other side, if one already loaded some version, then require("jquery-ui/ui/widgets/sortable"); should do nothing (jQuery UI is already loaded). Thus one can test the content of configuration file like package.json of the outer software, but the same configuration can just map any logical module name like "jquery-ui/sortable" to any physical path. The configuration is the part of outer package and the package itself heavy depend on the packaging system of outer software and it has no relation to free jqGrid or the ui.multiselect plugin.

The syntax like "jquery-ui/sortable", which one uses, means just the combination of module ID prefix and module name. One loads the modules via loader's require method and one of goals of the loader is mapping of module prefix to the physical paths. I think that the configuration of the loader is responsible for the mapping of id prefixes, like jquery-ui, to the corresponding local paths of URLs, where sortable.js exist and from which it should be loaded. Do you found the corresponding configuration of StealJS in the links, which I send you before?

HansHammel commented 7 years ago

OK, so you have a compatibility problem in your claim (jQuery UI in version >=1.8). read https://jqueryui.com/upgrade-guide/1.12/

When you're ready to upgrade, you need to update your import paths. Before: var autocomplete = require( "jquery-ui/autocomplete" ); After: var autocomplete = require( "jquery-ui/ui/widgets/autocomplete" );

They are not talking about changing your mappings ;-) Does free-jqgrid work with browserify? BTW I personally dont use StealJS so I don't care. This was just about an updated I applied to a project called mqtt-admin. I found it verry compelling, but the auther doesn't see the advantage of gettring rid of the bower dependencies. If he sticks to jquery-ui 1.11 this may be irrelevant. The semver crap is a versionfallback (untested) and you would have to figure out how it works in your UMD "factory"...

OlegKi commented 7 years ago

Oliver, the current discussion seems too common for me. I would like o make any changes in free jqGrid code. Do you have practical suggestions of changing the code related to the discussed topic? Could you post the exact suggestions of changing the code as pull request, for example?

The current code of free jqGrid is oriented on AMD loaders only. The define part of the headers of JavaScript files should be clean now, but the part, which uses require is minimal. It includes only loading jQuery, which will be used later as $. I don't have any clean demos with CommonJS, where free jqGrid is used. One could improve free jqGrid, based on the examples. Could you provide clean demos, which can be used independent on some platform? I personally use Windows 10.

HansHammel commented 7 years ago

Here's what I thought of. Tested live in StealJS and static build with browserify. There are still issues to overcome (e.g. not backwards compatibility in node.js WITH browserify support possible) and make all those very diffrent loaders/bundlers work. As I said, I don't use them so I am no expert. But you get the idea. Hope this helps. See the comments and note the change in the UMD-wrapper (therefore I posted the whole file). There's a lot of compatibility crap and workarounds. Have fun.

/**
 * @license jQuery UI Multiselect
 *
 * Authors:
 *  Michael Aufreiter (quasipartikel.at)
 *  Yanick Rochon (yanick.rochon[at]gmail[dot]com)
 * 
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 * 
 * http://www.quasipartikel.at/multiselect/
 *
 * UPDATED by Oleg Kiriljuk (oleg.kiriljuk@ok-soft-gmbh.com) to support jQuery 1.6 and hight
 * (the usage of jQuery.attr and jQuery.removeAttr is replaced to the usage of jQuery.prop
 *  in case of working with selected options of select)
 * 
 * Depends:
 *  ui.core.js
 *  ui.sortable.js
 *
 * Optional:
 * localization (http://plugins.jquery.com/project/localisation)
 * scrollTo (http://plugins.jquery.com/project/ScrollTo)
 * 
 * Todo:
 *  Make batch actions faster
 *  Implement dynamic insertion through remote calls
 */
(function(factory) {
    "use strict";
    if (typeof define === "function" && define.amd) {
        // AMD. Register as an anonymous module.
        // depends also on free-jqgrid if its a plugin, remove if it's a model 
        define(["require", "jquery", "jquery-ui", "free-jqgrid"], function(require, jQuery) {
            window.$ = jQuery;
            window.jQuery = jQuery;
            //version check, alternatively we could use a fallback
            if (/^1\.1[2-9]\./.test($.ui.version)) {
                //jquery-ui >=1.12.x 
                //use the .js extension to avoid searching for both jquery-ui/ui/core.js and jquery-ui/ui/core/index.js (for more intelligent loaders)
                require(["jquery-ui/ui/widgets/sortable"], function() {
                        factory(jQuery);
                    }
                    //maybe you want to handle this, will lead to "Potentially unhandled rejection Error"
                    // but we let this fail intentionally to know about missing deps
                    //,function (err) {console.log(err);}
                    ,
                    function(err) {
                        if (err) {
                            //alternativly fallback here
                        }
                    });
            } else { // jquery-ui <=1.11.x
                require(["jquery-ui/sortable"], function() {
                    factory(jQuery);
                }, function(err) {
                    if (err) throw new Error("Could not find jquery-ui and/or required plugin sortable:", err);
                });
            }
        });
    } else if (typeof exports === "object") {
        // Node/CommonJS
        var semver = require('semver');
        var jQuery = require('jquery');
        window.$ = jQuery;
        window.jQuery = jQuery;
        if (semver.satisfies(require('jquery-ui/package.json').version, '>=1.12.0')) {
            require("jquery-ui");
            require("jquery-ui/ui/widgets/mouse");
            require("jquery-ui/ui/widgets/sortable");
            require("free-jqgrid");
            factory(jQuery);
        }
        //else {// this doesn't work with browerify, browerify tries to require both modules, but would  work for not if only jquery-ui would work on node.js
        //require("jquery-ui");
        //require("jquery-ui/mouse");
        //require("jquery-ui/sortable");
        //factory(jQuery);
        //}

    } else {
        // Browser globals
        if (typeof jQuery != 'undefined' &&
            typeof jQuery.ui != 'undefined' &&
            typeof jQuery.ui.mouse != 'undefined' &&
            typeof jQuery.ui.sortable !== 'undefined')
            factory(jQuery);
        else
            throw new Error("Could not find jquery-ui and/or required plugins mouse, sortable");
    }
})(function($) {
    'use strict';

    $.widget("ui.multiselect", {
        options: {
            sortable: true,
            searchable: true,
            doubleClickable: true,
            animated: 'fast',
            show: 'slideDown',
            hide: 'slideUp',
            dividerLocation: 0.6,
            availableFirst: false,
            nodeComparator: function(node1, node2) {
                var text1 = node1.text(),
                    text2 = node2.text();
                return text1 == text2 ? 0 : (text1 < text2 ? -1 : 1);
            }
        },
        _create: function() {
            this.element.hide();
            this.id = this.element.attr("id");
            this.container = $('<div class="ui-multiselect ui-helper-clearfix ui-widget"></div>').insertAfter(this.element);
            this.count = 0; // number of currently selected options
            this.selectedContainer = $('<div class="selected"></div>').appendTo(this.container);
            this.availableContainer = $('<div class="available"></div>')[this.options.availableFirst ? 'prependTo' : 'appendTo'](this.container);
            this.selectedActions = $('<div class="actions ui-widget-header ui-helper-clearfix"><span class="count">0 ' + $.ui.multiselect.locale.itemsCount + '</span><a href="#" class="remove-all">' + $.ui.multiselect.locale.removeAll + '</a></div>').appendTo(this.selectedContainer);
            this.availableActions = $('<div class="actions ui-widget-header ui-helper-clearfix"><input type="text" class="search empty ui-widget-content ui-corner-all"/><a href="#" class="add-all">' + $.ui.multiselect.locale.addAll + '</a></div>').appendTo(this.availableContainer);
            this.selectedList = $('<ul class="selected connected-list"><li class="ui-helper-hidden-accessible"></li></ul>').bind('selectstart', function() {
                return false;
            }).appendTo(this.selectedContainer);
            this.availableList = $('<ul class="available connected-list"><li class="ui-helper-hidden-accessible"></li></ul>').bind('selectstart', function() {
                return false;
            }).appendTo(this.availableContainer);

            var that = this;

            // set dimensions
            this.container.width(this.element.width() + 1);
            this.selectedContainer.width(Math.floor(this.element.width() * this.options.dividerLocation));
            this.availableContainer.width(Math.floor(this.element.width() * (1 - this.options.dividerLocation)));

            // fix list height to match <option> depending on their individual header's heights
            this.selectedList.height(Math.max(this.element.height() - this.selectedActions.height(), 1));
            this.availableList.height(Math.max(this.element.height() - this.availableActions.height(), 1));

            if (!this.options.animated) {
                this.options.show = 'show';
                this.options.hide = 'hide';
            }

            // init lists
            this._populateLists(this.element.find('option'));

            // make selection sortable
            if (this.options.sortable) {
                this.selectedList.sortable({
                    placeholder: 'ui-state-highlight',
                    axis: 'y',
                    update: function(event, ui) {
                        // apply the new sort order to the original selectbox
                        that.selectedList.find('li').each(function() {
                            if ($(this).data('optionLink')) {
                                $(this).data('optionLink').remove().appendTo(that.element);
                            }
                        });
                    },
                    receive: function(event, ui) {
                        ui.item.data('optionLink').prop('selected', true);
                        // increment count
                        that.count += 1;
                        that._updateCount();
                        // workaround, because there's no way to reference 
                        // the new element, see http://dev.jqueryui.com/ticket/4303
                        that.selectedList.children('.ui-draggable').each(function() {
                            $(this).removeClass('ui-draggable');
                            $(this).data('optionLink', ui.item.data('optionLink'));
                            $(this).data('idx', ui.item.data('idx'));
                            that._applyItemState($(this), true);
                        });

                        // workaround according to http://dev.jqueryui.com/ticket/4088
                        setTimeout(function() {
                            ui.item.remove();
                        }, 1);
                    }
                });
            }

            // set up livesearch
            if (this.options.searchable) {
                this._registerSearchEvents(this.availableContainer.find('input.search'));
            } else {
                $('.search').hide();
            }

            // batch actions
            this.container.find(".remove-all").click(function() {
                that._populateLists(that.element.find('option').prop('selected', false));
                return false;
            });

            this.container.find(".add-all").click(function() {
                var options = that.element.find('option').not(":selected");
                if (that.availableList.children('li:hidden').length > 1) {
                    that.availableList.children('li').each(function(i) {
                        if ($(this).is(":visible")) {
                            $(options[i - 1]).prop('selected', true);
                        }
                    });
                } else {
                    options.prop('selected', true);
                }
                that._populateLists(that.element.find('option'));
                return false;
            });
        },
        destroy: function() {
            this.element.show();
            this.container.remove();

            $.Widget.prototype.destroy.apply(this, arguments);
        },
        _populateLists: function(options) {
            this.selectedList.children('.ui-element').remove();
            this.availableList.children('.ui-element').remove();
            this.count = 0;

            var that = this;
            var items = $(options.map(function(i) {
                var isSelected = $(this).is(":selected"),
                    item = that._getOptionNode(this).appendTo(isSelected ? that.selectedList : that.availableList).show();

                if (isSelected) {
                    that.count += 1;
                }
                that._applyItemState(item, isSelected);
                item.data('idx', i);
                return item[0];
            }));

            // update count
            this._updateCount();
            that._filter.apply(this.availableContainer.find('input.search'), [that.availableList]);
        },
        _updateCount: function() {
            this.element.trigger('change');
            this.selectedContainer.find('span.count').text(this.count + " " + $.ui.multiselect.locale.itemsCount);
        },
        _getOptionNode: function(option) {
            option = $(option);
            var node = $('<li class="ui-state-default ui-element" title="' + (option.attr("title") || option.text()) + '"><span class="ui-icon"/>' + option.text() + '<a href="#" class="action"><span class="ui-corner-all ui-icon"/></a></li>').hide();
            node.data('optionLink', option);
            return node;
        },
        // clones an item with associated data
        // didn't find a smarter away around this
        _cloneWithData: function(clonee) {
            var clone = clonee.clone(false, false);
            clone.data('optionLink', clonee.data('optionLink'));
            clone.data('idx', clonee.data('idx'));
            return clone;
        },
        _setSelected: function(item, selected) {
            item.data('optionLink').prop('selected', selected);

            if (selected) {
                var selectedItem = this._cloneWithData(item);
                item[this.options.hide](this.options.animated, function() {
                    $(this).remove();
                });
                selectedItem.appendTo(this.selectedList).hide()[this.options.show](this.options.animated);

                this._applyItemState(selectedItem, true);
                return selectedItem;
            } else {

                // look for successor based on initial option index
                var items = this.availableList.find('li'),
                    comparator = this.options.nodeComparator;
                var succ = null,
                    i = item.data('idx'),
                    direction = comparator(item, $(items[i]));

                // TODO: test needed for dynamic list populating
                if (direction) {
                    while (i >= 0 && i < items.length) {
                        direction > 0 ? i++ : i--;
                        if (direction != comparator(item, $(items[i]))) {
                            // going up, go back one item down, otherwise leave as is
                            succ = items[direction > 0 ? i : i + 1];
                            break;
                        }
                    }
                } else {
                    succ = items[i];
                }

                var availableItem = this._cloneWithData(item);
                succ ? availableItem.insertBefore($(succ)) : availableItem.appendTo(this.availableList);
                item[this.options.hide](this.options.animated, function() {
                    $(this).remove();
                });
                availableItem.hide()[this.options.show](this.options.animated);

                this._applyItemState(availableItem, false);
                return availableItem;
            }
        },
        _applyItemState: function(item, selected) {
            if (selected) {
                if (this.options.sortable) {
                    item.children('span').addClass('ui-icon-arrowthick-2-n-s').removeClass('ui-helper-hidden').addClass('ui-icon');
                } else {
                    item.children('span').removeClass('ui-icon-arrowthick-2-n-s').addClass('ui-helper-hidden').removeClass('ui-icon');
                }
                item.find('a.action span').addClass('ui-icon-minus').removeClass('ui-icon-plus');
                this._registerRemoveEvents(item.find('a.action'));

            } else {
                item.children('span').removeClass('ui-icon-arrowthick-2-n-s').addClass('ui-helper-hidden').removeClass('ui-icon');
                item.find('a.action span').addClass('ui-icon-plus').removeClass('ui-icon-minus');
                this._registerAddEvents(item.find('a.action'));
            }

            this._registerDoubleClickEvents(item);
            this._registerHoverEvents(item);
        },
        // taken from John Resig's liveUpdate script
        _filter: function(list) {
            var input = $(this);
            var rows = list.children('li'),
                cache = rows.map(function() {

                    return $(this).text().toLowerCase();
                });

            var term = $.trim(input.val().toLowerCase()),
                scores = [];

            if (!term) {
                rows.show();
            } else {
                rows.hide();

                cache.each(function(i) {
                    if (this.indexOf(term) > -1) {
                        scores.push(i);
                    }
                });

                $.each(scores, function() {
                    $(rows[this]).show();
                });
            }
        },
        _registerDoubleClickEvents: function(elements) {
            if (!this.options.doubleClickable) {
                return;
            }
            elements.dblclick(function(ev) {
                if ($(ev.target).closest('.action').length === 0) {
                    // This may be triggered with rapid clicks on actions as well. In that
                    // case don't trigger an additional click.
                    elements.find('a.action').click();
                }
            });
        },
        _registerHoverEvents: function(elements) {
            elements.removeClass('ui-state-hover');
            elements.mouseover(function() {
                $(this).addClass('ui-state-hover');
            });
            elements.mouseout(function() {
                $(this).removeClass('ui-state-hover');
            });
        },
        _registerAddEvents: function(elements) {
            var that = this;
            elements.click(function() {
                var item = that._setSelected($(this).parent(), true);
                that.count += 1;
                that._updateCount();
                return false;
            });

            // make draggable
            if (this.options.sortable) {
                elements.each(function() {
                    $(this).parent().draggable({
                        connectToSortable: that.selectedList,
                        helper: function() {
                            var selectedItem = that._cloneWithData($(this)).width($(this).width() - 50);
                            selectedItem.width($(this).width());
                            return selectedItem;
                        },
                        appendTo: that.container,
                        containment: that.container,
                        revert: 'invalid'
                    });
                });
            }
        },
        _registerRemoveEvents: function(elements) {
            var that = this;
            elements.click(function() {
                that._setSelected($(this).parent(), false);
                that.count -= 1;
                that._updateCount();
                return false;
            });
        },
        _registerSearchEvents: function(input) {
            var that = this;

            input.focus(function() {
                    $(this).addClass('ui-state-active');
                })
                .blur(function() {
                    $(this).removeClass('ui-state-active');
                })
                .keypress(function(e) {
                    if (e.keyCode == 13) {
                        return false;
                    }
                })
                .keyup(function() {
                    that._filter.apply(this, [that.availableList]);
                });
        }
    });

    $.extend($.ui.multiselect, {
        locale: {
            addAll: 'Add all',
            removeAll: 'Remove all',
            itemsCount: 'items selected'
        }
    });
});

A even more interesting part are the limitations on css/less etc.

// used ./, bug in browseify!? browseify needs modules path!?
import './node_modules/free-jqgrid/css/ui.jqgrid.css';
import './node_modules/free-jqgrid/plugins/ui.multiselect.css';

// used ./, bug in browseify!? While you cannot use ./ for JS-files!?
// ./index.less! exclamationmark not working in browserify
//require('./index.less');
import './index.less';