2013-10-14 3 views
13

Я написал специальный обработчик привязки, который переключает, является ли элемент contentEditable. Я также хочу, чтобы любые привязки html обновлялись при редактировании содержимого элемента, поэтому он прослушивает входные события и обновляет привязки html, если они доступны.knockout contentEditable binding

ko.bindingHandlers.contentEditable = { 
    update: function (element, valueAccessor, allBindingsAccessor) { 
     var value = ko.unwrap(valueAccessor()); 
     element.contentEditable = value; 

     var $element = $(element); 

     if (value) { 
      var allBindings = allBindingsAccessor(); 
      var htmlBinding = allBindings.html; 

      if (ko.isWriteableObservable(htmlBinding)) { 
       $element.on("input", function (event) { 
        htmlBinding(element.innerHTML); 
       }); 
      } 
     } else { 
      $element.off("input"); 
     } 
    } 
}; 

Однако здесь проблема:

  • пользователь вводит что-то в элемент
  • входного срабатывает событие
  • HTML, связывание обновляется
  • innerHTML элемента обновляется
  • Позиция курсора в элементе возвращается к началу

jsfiddle говорит тысячу слов ... http://jsfiddle.net/93eEr/1/

Я немного озадачен, как именно, чтобы справиться с этим.

+0

Вам нужно обернуть значение html в вычисленное значение, которое только обновляет привязку html, если вы не редактируете. Также он должен уведомлять о мутации, когда вы прекращаете редактирование – Anders

+1

Быстрая и грязная информация должна обновляться только при размывании http://jsfiddle.net/93eEr/2/ – Anders

+0

Более приятной версией было бы написать новый обработчик привязки из-за отсутствия лучшего name, назовем его 'htmlLazy', который обновляет элементы DOM, к которым он привязан, только когда они не являются в настоящее время' contentEditable'. – Tomalak

ответ

17

ko.bindingHandlers.htmlLazy = { 
 
    update: function (element, valueAccessor) { 
 
     var value = ko.unwrap(valueAccessor()); 
 
     
 
     if (!element.isContentEditable) { 
 
      element.innerHTML = value; 
 
     } 
 
    } 
 
}; 
 
ko.bindingHandlers.contentEditable = { 
 
    init: function (element, valueAccessor, allBindingsAccessor) { 
 
     var value = ko.unwrap(valueAccessor()), 
 
      htmlLazy = allBindingsAccessor().htmlLazy; 
 
     
 
     $(element).on("input", function() { 
 
      if (this.isContentEditable && ko.isWriteableObservable(htmlLazy)) { 
 
       htmlLazy(this.innerHTML); 
 
      } 
 
     }); 
 
    }, 
 
    update: function (element, valueAccessor) { 
 
     var value = ko.unwrap(valueAccessor()); 
 
     
 
     element.contentEditable = value; 
 
     
 
     if (!element.isContentEditable) { 
 
      $(element).trigger("input"); 
 
     } 
 
    } 
 
}; 
 

 
var viewModel = { 
 
    editable: ko.observable(false), 
 
    content: ko.observable("<i>This</i> is the initial content!") 
 
}; 
 

 
ko.applyBindings(viewModel);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script> 
 

 
<label>Editable: <input type="checkbox" data-bind="checked: editable"/></label> 
 
<hr> 
 
<div data-bind="contentEditable: editable, htmlLazy: content"></div> 
 
<hr> 
 
<pre data-bind="text: content"></pre>

сделать трюк с минимальными изменениями. См. http://jsfiddle.net/93eEr/3/

Вы можете назвать обработчик привязки htmlEditable, может быть, это лучше, чем называть его «ленивым». Вам решать.

Обратите внимание, что событие «ввода» не обязательно должно быть развязано каждый раз. Он не будет срабатывать, когда элемент не будет удовлетворительным.

+0

Отлично, спасибо! Причина, по которой я отключал входное событие, заключалась в том, что я хотел привязываться к обновлению, а не к init. В моей ситуации на странице будут сотни этих редактируемых разделов, но только один или два, вероятно, будут редактироваться на любом просмотре страницы. Я хотел избежать накладных расходов, связанных со всеми событиями во время инициализации. – enoshixi

+0

Я вижу.Вы можете использовать делегирование событий в этом случае - просто привяжите обработчик события 'input' к контейнеру и используйте' ko.contextFor (this) 'для получения контекста привязки (также [см.] (Http://knockoutjs.com/documentation /unobtrusive-event-handling.html), но обратите внимание, что документация относится к теперь устаревшим функциям '.live' и' .delegate', которые были заменены на '.on()'). – Tomalak

+2

+1 Это действительно полезно. NB, если вы измените привязку в обработчике 'contentEditable' к следующему, оно также работает и в IE:' $ (element) .on («ввод размытия вставной пасты copy cut cut mouseup», function() {'[и т. Д.] – JcFx

3

@ Ответ Томалака является совершенным, поэтому мой подъем.

Но для тех, кто прибывает сюда, как и я:

  • ищет один-и-сделано пользовательские привязки
  • , что предполагает, что содержание всегда редактируемые
  • нормально с value: -esque обновления (например: на размытость)

Я предлагаю следующее:

ko.bindingHandlers.contentEditable = { 
    init: function(element, valueAccessor) { 
     var value = valueAccessor(); 

     function onBlur(){ 
      if (ko.isWriteableObservable(value)) { 
       value(this.innerHTML); 
      } 
     };     

     element.innerHTML = value(); //set initial value 
     element.contentEditable = true; //mark contentEditable true 
     element.addEventListener('blur', onBlur); //add blur listener 
    } 
};