Як очистити / видалити помітні прив’язки в Knockout.js?


113

Я будую функціональність на веб-сторінці, яку користувач може виконувати кілька разів. За допомогою дії користувача об’єкт / модель створюється та застосовується до HTML за допомогою ko.applyBindings ().

Обмежений даними HTML створюється за допомогою шаблонів jQuery.

Все йде нормально.

Коли я повторюю цей крок, створюючи другий об'єкт / модель і викликаю ko.applyBindings (), у мене виникають дві проблеми:

  1. Розмітка показує попередній об'єкт / модель, а також новий об'єкт / модель.
  2. Відбувається помилка javascript, що стосується одного з властивостей об'єкта / моделі, хоча він все ще відображається в розмітці.

Щоб вирішити цю проблему, після першого проходу я закликаю jQuery's .empty (), щоб видалити шаблонний HTML, який містить усі атрибути прив'язки даних, так що він більше не знаходиться в DOM. Коли користувач запускає процес другого проходу, пов'язаний з даними HTML знову додається до DOM.

Але, як я вже говорив, коли HTML знову додається до DOM і знову прив'язується до нового об'єкта / моделі, він все ще включає дані першого об'єкта / моделі, і я все одно отримую помилку JS, яка не виникає під час першого проходу.

Здається висновок, що нокаут тримається за ці зв'язані властивості, навіть якщо розмітка видалена з DOM.

Тож те, що я шукаю, - це засіб видалити ці пов'язані властивості з нокауту; кажучи нокаутом, що моделі більше не спостерігається. Чи є спосіб це зробити?

EDIT

Основний процес полягає в тому, що користувач завантажує файл; сервер потім відповідає об'єктом JSON, пов'язаний з даними HTML додається до DOM, тоді об'єктна модель JSON пов'язана з цим HTML за допомогою

mn.AccountCreationModel = new AccountViewModel(jsonData.Account);
ko.applyBindings(mn.AccountCreationModel);

Після того, як користувач здійснив кілька виборів на моделі, той самий об'єкт буде розміщений назад на сервері, пов'язаний з даними HTML видаляється з цього пункту DOM, і я отримую наступний JS

mn.AccountCreationModel = null;

Коли користувач бажає зробити це ще раз, всі ці дії повторюються.

Я боюся, що код занадто "задіяний", щоб зробити демо jsFiddle.


Викликати ko.applyBindings кілька разів, особливо на тому ж елементі, що містить dom, не рекомендується. Може бути інший спосіб досягти того, що ви хочете. Однак вам потрібно буде надати більше коду. Будь ласка, включіть jsfiddle, якщо можливо.
— madcapnmckay

Чому б не відкрити initфункцію, в якій ви передаєте дані для застосування?
— KyorCode

Відповіді:


169

Ви спробували викликати метод чистого вузла нокауту на елементі DOM для розпорядження об'єктами, пов'язаними з пам'яттю?

var element = $('#elementId')[0]; 
ko.cleanNode(element);

Потім знову застосувати прив'язки нокауту лише до цього елемента з вашими новими моделями перегляду оновить прив'язку вашого перегляду.


33
Це працює - дякую. Я не можу знайти жодної документації щодо цього методу.
— awj

Я теж шукав документацію на цю функцію нокаутів. Наскільки я можу зрозуміти з вихідного коду, він викликає deleteпевні клавіші на самих елементах dom, де, мабуть, зберігається вся магія вибивання. Якщо у когось є джерело документації, я був би дуже зобов'язаний.
— Патрік М

2
Ви не знайдете його. Я шукав документи високого та низького рівнів для функцій ко-утиліти, але жодного не існує. Ця публікація в блозі - це найближче, що ви знайдете, але воно охоплює лише членів ko.utils: knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
— Nick Daniels

1
Ви також захочете видалити події вручну, як показано в моїй відповіді нижче.
— Майкл Беркомпас

1
@KodeKreachor Я вже розмістив робочий приклад нижче. Моя думка полягала в тому, що, можливо, буде краще зберегти зв’язування недоторканим, видаючи дані замість ViewModel. Таким чином, вам ніколи не доведеться мати справу з повторним зв'язуванням. Це здається більш чистим, ніж використання незадокументованих методів, щоб вручну від’єднати безпосередньо від DOM. Крім того, CleanNode є проблематичним, оскільки він не випускає жодного з обробників подій (див. Відповідь тут для більш детальної інформації: stackoverflow.com/questions/15063794/… )
— Zac,

31

Для проекту, над яким я працюю, я написав просту ko.unapplyBindingsфункцію, яка приймає вузол jQuery та видалити булеву форму. Він спочатку розв’язує всі події jQuery, оскільки ko.cleanNodeметод не піклується про це. Я перевірив на предмет витоку пам’яті, і, здається, він працює чудово.

ko.unapplyBindings = function ($node, remove) {
    // unbind events
    $node.find("*").each(function () {
        $(this).unbind();
    });

    // Remove KO subscriptions and references
    if (remove) {
        ko.removeNode($node[0]);
    } else {
        ko.cleanNode($node[0]);
    }
};

Лише один застереження, я не перевіряв повторне прив’язування до чогось, що тільки що ko.cleanNode()зателефонував, а не весь HTML замінений.
— Майкл Беркомпас

4
Чи не вирішило б також ваше рішення скасувати всі інші прив'язки подій? чи є можливість видалити лише обробників подій ko?
— lordvlad

не змінюючи ядро ​​ко, яке є
— lordvlad

1
Щоправда, однак це неможливо, наскільки я можу сказати, без кореневих змін. Дивіться цю проблему, яку я підняв тут: github.com/SteveSanderson/knockout/isissue/724
— Michael Berkompas

Хіба не ідея про те, що ви дуже рідко самі повинні доторкатися до дому? Ця відповідь проходить через dom, і, безумовно, буде далеко не корисною в моєму випадку використання.
— Blowsie

12

Ви можете спробувати скористатися прив'язкою, яку пропонує нокаут: http://knockoutjs.com/documentation/with-binding.html Ідея полягає у використанні застосувати прив'язки один раз, і коли ваші дані змінюються, просто оновлюйте свою модель.

Скажімо, у вас є модель перегляду вищого рівня storeViewModel, ваш кошик представлений cartViewModel та список елементів у кошику, - скажімо, cartItemsViewModel.

Ви б прив’язали модель верхнього рівня - storeViewModel до всієї сторінки. Потім ви можете відокремити частини вашої сторінки, які відповідають за кошик або товари.

Припустимо, що cartItemsViewModel має таку структуру:

var actualCartItemsModel = { CartItems: [
  { ItemName: "FirstItem", Price: 12 }, 
  { ItemName: "SecondItem", Price: 10 }
] }

CartItemsViewModel може бути спочатку порожнім.

Кроки виглядатимуть так:

  1. Визначте прив’язки в html. Відокремте прив'язку cartItemsViewModel.

      
        <div data-bind="with: cartItemsViewModel">
          <div data-bind="foreach: CartItems">
            <span data-bind="text: ItemName"></span>
            <span data-bind="text: Price"></span>
          </div>
        </div>
      
    
  2. Модель магазину надходить з вашого сервера (або створюється будь-яким іншим способом).

    var storeViewModel = ko.mapping.fromJS(modelFromServer)

  3. Визначте порожні моделі на своїй моделі перегляду верхнього рівня. Тоді структура цієї моделі може бути оновлена ​​фактичними даними.

      
        storeViewModel.cartItemsViewModel = ko.observable();
        storeViewModel.cartViewModel = ko.observable();
     
    
  4. Прив’яжіть модель виду верхнього рівня.

    ko.applyBindings(storeViewModel);

  5. Коли об'єкт cartItemsViewModel доступний, тоді призначте його раніше визначеному заповнювачу.

    storeViewModel.cartItemsViewModel(actualCartItemsModel);

Якщо ви хочете очистити елементи кошика: storeViewModel.cartItemsViewModel(null);

Нокаут подбає про html - тобто він з’явиться, коли модель не буде порожньою, а вміст div (той із "з прив'язкою") зникне.


9

Мені потрібно зателефонувати ko.applyЗв'язуючи кожен раз, коли натискати кнопку пошуку, і відфільтровані дані повертаються з сервера, і в цьому випадку слід працювати за мною, не використовуючи ko.cleanNode.

Я зазнав, що якщо ми замінимо foreach на шаблон, то він повинен спрацьовувати у випадку колекцій / observableArray.

Цей сценарій вам може бути корисним.

<ul data-bind="template: { name: 'template', foreach: Events }"></ul>

<script id="template" type="text/html">
    <li><span data-bind="text: Name"></span></li>
</script>

1
Я витратив щонайменше 4 години на те, щоб вирішити подібну проблему, і для мене працює тільки рішення Ааміра.
— Антонін Єлінек

@AntoninJelinek інша річ, яку я відчув у своєму сценарії, - повністю видалити html, і динамічно додати його знову, щоб абсолютно вилучити все. наприклад, у мене є контейнер коду Knockout div <div id = "knockoutContainerDiv"> </div> на $ .Ajax Результат успіху, я виконую щоразу, коли метод сервера викликає $ ("# knockoutContainerDiv"). kids.remove (); / / видалити вміст його виклику методу для додавання динамічного html з нокаутом коду $ ("# knockoutContainerDiv"). append ("діти з кодом прив'язки нокауту") та викликом ApplyBinding знову
— aamir sajjad

1
Гей, Амміре, у мене був такий самий сценарій, як і у вас. Все, що ви згадали, працювало чудово, за винятком того, що мені довелося використовувати ko.cleanNode (елемент); до кожного повторного зв’язування.
— Радослав Мінчев

@ РадославМінчев, ти думаєш, я можу допомогти тобі далі, якщо так, то як, я з радістю поділюся своїми думками та досвідом щодо конкретного питання.
— aamir sajjad

Дякую. @aamirsajjad, я просто хотів зазначити, що для мене працювало - викликати функцію cleanNode (), щоб вона працювала.
— Радослав Мінчев

6

Замість того, щоб використовувати внутрішні функції KO і не мати справу з видаленням обробника подій JQuery з обробкою, набагато кращою ідеєю є використання withабо templateприв'язка. Коли ви це зробите, ko заново створює цю частину DOM, і вона автоматично очищається. Це також рекомендований спосіб, дивіться тут: https://stackoverflow.com/a/15069509/207661 .


4

Я думаю, що може бути краще зберігати прив’язку весь час і просто оновлювати пов’язані з цим дані. Я зіткнувся з цим питанням і виявив, що саме дзвінок із використанням .resetAll()методу в масиві, в якому я зберігав свої дані, був найбільш ефективним способом зробити це.

В основному ви можете почати з глобальної вар, яка містить дані, що мають бути надані через ViewModel:

var myLiveData = ko.observableArray();

Мені знадобився певний час, щоб зрозуміти, що я не можу просто створити myLiveDataнормальний масив - ko.oberservableArrayчастина була важливою.

Тоді ви можете піти вперед і робити все, що завгодно myLiveData. Наприклад, $.getJSONзателефонуйте:

$.getJSON("http://foo.bar/data.json?callback=?", function(data) {
    myLiveData.removeAll();
    /* parse the JSON data however you want, get it into myLiveData, as below */
    myLiveData.push(data[0].foo);
    myLiveData.push(data[4].bar);
});

Після цього ви можете продовжувати та застосовувати прив’язки, використовуючи свій ViewModel, як завжди:

function MyViewModel() {
    var self = this;
    self.myData = myLiveData;
};
ko.applyBindings(new MyViewModel());

Тоді в HTML просто використовуйте myDataяк зазвичай.

Таким чином, ви можете просто спілкуватися з myLiveData з будь-якої функції. Наприклад, якщо ви хочете оновлювати кожні кілька секунд, просто загорніть цю $.getJSONлінію у функцію та зателефонуйте setIntervalїй. Ніколи не потрібно буде видаляти прив’язку, доки ви пам’ятаєте, щоб утримувати myLiveData.removeAll();рядок.

Якщо ваші дані дійсно величезні, користувач навіть не зможе помітити час між скиданням масиву та додаванням найактуальніших даних назад.


З часу, коли я поставив питання, це я зараз роблю. Просто деякі з цих методів нокаутування або недокументовані, або вам справді потрібно озирнутися (вже знаючи ім'я функції), щоб знайти, що це робить.
— awj

Мені довелося також дуже важко знайти це (коли кількість годин копання документів перевищує отриману кількість рядків коду ... ух). Радий, що ти це працює.
— Зак

2

Нещодавно ko.cleanNode(element);у мене ko.removeNode(element);виникла проблема з пам'яттю, і вона не зробить це для мене . Javascript + Knockout.js течі пам'яті - Як переконатися, що об’єкт знищується?


У нокауті 3.1 ko.removeNode насправді викликає ko.cleanNode. Я не знаю, що це було так і для більш ранніх версій.
— кіпраїней

1

Ви думали про це:

try {
    ko.applyBindings(PersonListViewModel);
}
catch (err) {
    console.log(err.message);
}

Я придумав це, тому що в нокауті я знайшов цей код

    var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
    if (!sourceBindings) {
        if (alreadyBound) {
            throw Error("You cannot apply bindings multiple times to the same element.");
        }
        ko.utils.domData.set(node, boundElementDomDataKey, true);
    }

Тож для мене це насправді не проблема, яка її вже пов'язана, це те, що помилка не була спіймана і не вирішена ...


0

Я виявив, що якщо модель перегляду містить багато прив’язок ділів, найкращим способом очищення ko.applyBindings(new someModelView);є використання: ko.cleanNode($("body")[0]);Це дозволяє викликати нове ko.applyBindings(new someModelView2);динамічно, без турботи про те, що попередня модель перегляду все ще прив’язана.


4
Я хотів би додати декілька пунктів: (1) це очистить ВСІ прив'язки з вашої веб-сторінки, які можуть відповідати вашій заявці, але я думаю, що існує багато додатків, де прив'язки додаються до декількох частин сторінки для окремих причини. Можливо, багатьом користувачам не буде корисно очищати ВСІ прив’язки в одній змішаній команді. (2) швидший, більш ефективний, власний метод отримання JavaScript - $("body")[0]це document.body.
— awj
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.