Як очистити / видалити помітні прив’язки в 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.