JQuery i $.extend błędnie działa

0

Witam. Ostatnio piszę sobie pewien plugin pod jquery i natknąłem się na pewien problem, a mianowicie chciałem aby user miał możliwość nadpisywania opcji pluginu. Aby to zrobić używam $.extend. Niestety cała operacja zwraca mi wynik, którego się nie spodziewałem. Posłużę się screenami.

Oto bazowy obiekt:
base.JPG

Obiekt przekazywany przez użytkownika to:
options.JPG

Natomiast obiekt jaki otrzymuję to:
extended.JPG

Moje pytanie brzmi tak: dlaczego jako wynik funkcji:

var extended = $.extend(true, {}, this.options, options);

otrzymuję to co jest na trzecim obrazku? Oczekiwałem, że będzie tam opcja menu, tab i templateEngine ale w menu poza opcją position ustawioną na left będą także pozostałe opcje z pierwszego screenu czyli template oraz templateEngine... Co robię nie tak?

0

Nie miesza Ci się this.options z options?

0

Nie, this.options jest w obiekcie pluginy, a ```
options

0

Nope, masz coś pomieszane z widocznością zmiennych: https://jsbin.com/miguhukala/edit?html,js,console

0

Ogólnie piszę w TypeScript (jest dla mnie trochę bardziej przejrzystsze) i po wygenerowaniu kodu w js (ECMA5) dostaję coś takiego:

var Options = (function () {
    function Options() {
        this.templateEngine = 'bootstrap-v3.7.7';
        this.menu = new MenuOptions();
        this.tab = new TabOptions();
    }
    Options.prototype.validate = function (options) {
        if (options.menu.position == 'right' && options.tab.position == 'right') {
            throw new Error('If menu position is set to "right", tab position should be set to "left"');
        }
    };
    return Options;
}());
(function (pageBuilder) {
    var PageBuilder = (function () {
        function PageBuilder(element, options) {
            this.options = new Options();
            this.element = element;
            this.$element = $(element);
            var test = $.extend(true, {}, this.options, options);
            console.log(this.options);
            console.log(test);
        }
    }
})

Czy to przypadkiem przez to, że this.options?

0

Jak na moje, wygląda dobrze - jesteś w stanie maksymalnie uprościć kod tak, aby błąd wciąż występował?
Najlepiej wrzuć całość tak, aby dało się to od razu uruchomić.

Btw, wykorzystuj === ponad == (ścisłe porównywanie jest dobrą praktyką, ponieważ prowadzi do mniejszej liczby potencjalnych błędów).

0

http://kolodny.github.io/blog/blog/2013/12/27/my-favorite-jquery-plugin-template/

options = $.extend(true, {}, defaults, options);

To więc jest okej, błąd masz gdzieś indziej. Użyj debuggra...

True z tego co pamiętam to deep extend, możliwe że to jak jest zaimplementowany extend nie ogarnia klas wygenerowanych przez TypeScript. Spróbuj zrobić to bez klas na czystych obiektach (bez new itd. po prostu przekaż zwykły, prosty obiekt) i tak jak napisał Patryk wyrzuć wszystko i zostaw minimum konieczne, żeby błąd dalej występował (prawdopodobnie w tym momencie sam znajdziesz błąd)

0

Dzięki Patryk za sugestię - na pewno skorzystam. Co do kawałka kodu, to trochę go uprościłem tak żeby działał.

Mam małe światełko w tunelu - chyba wiem o co chodzi ale muszę dokładniej sprawdzić.

Oto kod:

var MenuOptions = (function () {
    function MenuOptions() {
        this.templateEngine = 'bootstrap-v3.7.7';
        this.position = 'right';
        this.template = '<div class="pb-menu">' +
            '<div class="menu-toggler right">' +
            '<button type="button" class="btn btn-default btn-toggle-menu">' +
            '<i class="glyphicon glyphicon-chevron-left"></i>' +
            '</button>' +
            '</div>' +
            '<div class="menu-wrapper right">' +
            '<div class="btn-group" role="group">' +
            '<button type="button" class="btn btn-default">' +
            '<i class="glyphicon glyphicon-floppy-save"></i>' +
            '</button>' +
            '<button type="button" class="btn btn-default">' +
            '<i class="glyphicon glyphicon-floppy-open"></i>' +
            '</button>' +
            '</div>' +
            '<div class="menu-content">' +
            '</div>' +
            '</div>' +
            '</div>';
        this.visible = false;
    }
    return MenuOptions;
}());
var Options = (function () {
    function Options() {
        this.templateEngine = 'bootstrap-v3.7.7';
        this.menu = new MenuOptions();
        this.tab = {
            'position': 'right',
			'testtab1': 'test'
        };
    }

    return Options;
}());
var pageBuilder;
(function (pageBuilder) {
    var PageBuilder = (function () {
        function PageBuilder(element, options) {
            this.options = new Options();
            this.element = element;
            this.$element = $(element);
			var test = $.extend(true, {}, this.options, options);
            console.log(this.options);
            console.log(test);
        }

        return PageBuilder;
    }());
    PageBuilder.NAME = 'pageBuilder';
    pageBuilder.PageBuilder = PageBuilder;
})(pageBuilder || (pageBuilder = {}));

new pageBuilder.PageBuilder(this, {
	templateEngine: 'bootstrap-v4.0.0',
	menu: {
		position: 'left'
	},
	tab: {
		position: 'left'
	}
});

Specjalnie dałem u góry jeszcze MenuOptions, żeby pokazać coś o czym wspominał Desu. A mianowicie w zmiennej Options jest coś takiego:

this.menu = new MenuOptions();
this.tab = {
	'position': 'right',
	'testtab1': 'test'
};

No i teraz jeśli robię "rekursywny" extend z pustym obiektem, this.options oraz options to dostaję dziwne wyniki..., a mianowicie:

{
	menu: {
		position: "left"
	},
	tab: {
		position: "left",
		testtab1: "test"
	}
}

Wynika z tego, że $.extend nie radzi sobie z rozszerzaniem obiektów innych niż Object (nie wiem jak to dokładnie napisać). Znalazłem jeszcze coś innego. Jest coś takiego jak Object.assign() w wyniku czego dostaję już tylko menu.position i tab.position...
W sumie teraz jestem pod ścianą, bo chciałem to napisać w ts ale nie wiem jak... Chyba po prostu te opcje będę musiał zastąpić czystym Object...

0

Czyli tak jak mówiłem, pewnie extend nie ogarnia obiektów typescriptowych które musza być inaczej zbudowane. Nie wiem czy to prawda co mówię bo ogarniam ts trzeci dzień wiec nie wiem jak działa pod spodem ale wszystko na to wskazuje.

Spróbuj napisać swoją funkcje extend zamiast rezygnować z tych swoich klas :P to nie powinno być trudne.

0

No właśnie mam taki zamiar. Nawet gdybym chciał budować to na Object, to po pewnym czasie bym się zakopał... Dzięki wielkie za pomoc :)
Btw. Bardzo fajne forum :)

0

Szybki hack - jeśli $.extend nie radzi sobie z obiektami z __proto__ innym niż Object, zawsze możesz podać tam zwykły obiekt przez:

this.options = JSON.parse(JSON.stringify(new Options()));
0

O kurde... o tym nie pomyślałem... W najbliższym czasie spróbuję przetestować :) dzięki za sugestię :)

1

Btw, to zachowanie jest - jak się okazuje: całkiem słusznie - celowe: https://bugs.jquery.com/ticket/10014

Deep extending of non-plain objects is better left to custom code, unless we want an endless stream of tickets about which properties/methods get copied, whether a created copy should be a plain object or new class instance, what to do to about prototypes, and so on ad infinitum.

1 użytkowników online, w tym zalogowanych: 0, gości: 1