我正在尝试对jQuery对象进行敲除绑定,该对象能够知道对dom的任何更改(属性,文本,html,追加,删除等)。
ko.bindingHandlers.jqHtml =
init: (element, valueAccessor) ->
$(element).preMutate (options, el) ->
if ko.isObservable valueAccessor()
valueAccessor().valueWillMutate()
$(element).onMutate _.debounce (argsCollection) =>
@argsCollection = argsCollection
if ko.isObservable valueAccessor()
valueAccessor().valueHasMutated()
, 100
update: (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) ->
content = valueAccessor()
$(element).children().each( (i,el) -> ko.cleanNode(el) )
if _.difference($(element).children().get(), content.get()).length > 0 || _.difference(content.get(),$(element).children().get()).length > 0
$(element).children().peek().detach().unpeek()
$(element).peek().append(content).unpeek()
ko.applyBindingsToDescendants(viewModel, element)
我正在进行jsFiddle
jsfiddle明确表示的问题是,jquery不会跟踪dom中的重复项,而是会移动元素。因此,如果我有多个绑定到同一个jq元素,jQuery将只能将它放在一个数据绑定中。我想可能有一种方法可以创建一个jqObservable,它在其中包含原始的jquery元素,然后分发该元素的副本。每当onMutate通知时,它都会更新每个副本。当然,我需要一个初始阻塞程序(数据绑定到已绑定元素下面的同一元素)。我能想到的一个问题是知道哪个subsciption正在调用observable来知道要返回的jquery元素的哪个副本,因此它不会返回错误的副本,并且知道要销毁未绑定/取消订阅的元素。另外,当其中一个jquery副本被修改时,我需要更新其余的jquery副本。顺便说一句,我不在乎它是否在jQuery函数之外修改,只应该在这个插件的元素上调用jQuery函数。
任何建议都有很大帮助。
P.S。
OnMutate是我自己的自定义jQuery插件
我原本试图订阅更改事件,但除了输入之外,这不会起作用。没有顶级订阅子树修改,最接近的是每秒刷新以检查html中的差异。另一种选择是我在堆栈溢出时看到的另一个插件jollytoad's mutation events
它只适用于一些函数,但是它给了我为我需要的所有元素创建回调的想法,仅此而已,所以我创建了这个函数来自动为我想要的jQuery函数创建回调,和任何其他插件。我在下面创建的。
(($, window) ->
hasArgs = () -> arguments.length > 0
noArgs = () -> arguments.length == 0
class DomCallbacks
###*
* Defines the jQuery functions to create callbacks for, and which type of callback they contain
* @type {defaults}
* @properties {array of jQuery function names, and test to determine if the callback
* should be run based on input to the jQuery function, passed the same arguments that the jQuery
* function is}
###
defaults:
Mutate:
jqfuncs: ["addClass","append","appendTo","attr","clone","css","detach","empty","hasClass","height","html","innerHeight","innerWidth","insertAfter","insertBefore","offset","outerHeight","outerWidth","position","prepend","prependTo","prop","remove","removeAttr","removeClass","removeProp","replaceAll","replaceWith","scrollLeft","scrollTop","text","toggleClass","unwrap","val","width","wrap","wrapAll","wrapInner"]
defaultCheck: hasArgs
Look:
jqfuncs:["addClass","after",'append',"appendTo","attr","before","clone","css","detach","empty","hasClass","height","html","innerHeight","innerWidth","insertAfter","insertBefore","offset","outerHeight","outerWidth","position","prepend","prependTo","prop","remove","removeAttr","removeClass","removeProp","replaceAll","replaceWith","scrollLeft","scrollTop","text","toggleClass","unwrap","val","width","wrap","wrapAll","wrapInner"]
defaultCheck: noArgs
constructor: (options) ->
@categories = $.extend({}, @defaults, options)
originals: {}
###*
* Creates a function in jQuery's function object by replacing the name with a wrapper function
* and then calling the original function, and has the new function call each category of callback
* for the starting element and it's parents
* @param {string} name the name/key of the function being replaced
* @param {string} categories the type of callback that the function should notify
###
addCallback: (name, categories) ->
pairs = (obj) ->
pairs = []
for key of obj
if (hasOwnProperty.call(obj, key))
pairs.push([key, obj[key]])
pairs
if !@originals[name]?
@originals[name] = $.fn[name]
_this = @
categories = $.grep pairs(categories), (pair) ->
$.grep(pair[1].jqfuncs, (val) -> val.name == name || val == name).length > 0
###*
* The actual wrapper function being created
* @return {object} returns the original functions return
###
$.fn[name] = () ->
options = {args: arguments}
ittr = []
if !$.fn[name].peek
ittrArray = for category in categories
key = category[0]
val = category[1]
funcs = ($.grep(val.jqfuncs, (checking) -> checking.name == name || checking == name)).slice(0,1)[0].check
{key:key, val: val ,funcs:funcs}
for ittr in ittrArray
if !ittr.funcs?
ittr.funcs = ittr.val.defaultCheck
if !ittr.funcs? or ittr.funcs.apply(@,arguments)
@["pre"+ittr.key](null, options)
@parents()["pre"+ittr.key](null, options)
funcRet = _this.originals[name].apply(@, arguments)
options.funcRet = funcRet
if !$.fn[name].peek
for ittr in ittrArray
if !ittr.funcs?
ittr.funcs = ittr.val.defaultCheck
if !ittr.funcs? or ittr.funcs.apply(@,arguments)
@["on"+ittr.key](null, options)
@parents()["on"+ittr.key](null, options)
funcRet
$.fn[name].peek = false
###*
* restores the original function to it's property in the jQuery function object
* @param {string} name key used to find function to restore
* @return {[type]} [description]
###
removeCallback: (name) ->
if @originals[name]?
$.fn[name] = @originals[name]
delete @originals[name]
###*
* Creates a jQuery plugin capable of subscribing to and calling callbacks
* for each node
* @param {string} precedence names the function to run before or after the subscripton function
* @param {string} category the name of the type of
* @return {function} returns the function capable of setting and running callbacks for individual elements
###
createCallbackPlugin = (precedence, category) ->
###*
* A jQuery plugin which creates a sets callbacks for elements and calls them if none are passed in
* @param {function} callback the callback to be set (optional)
* @param {object} options options to be sent to the callback, set by the trigger function
* @return {jQuery} returns jQuery object for chaining
###
return (callback, options) ->
@each (i, el) ->
if callback?
if !el['domCallbacks']?
el['domCallbacks'] = {}
if !el['domCallbacks']['_'+precedence+category]?
el['domCallbacks']['_'+precedence+category] = []
el['domCallbacks']['_'+precedence+category].push callback
else
if el['domCallbacks']? && el['domCallbacks']['_'+precedence+category]?
for cb in el['domCallbacks']['_'+precedence+category]
cb(options, el)
@
@
domCallbacks = new DomCallbacks()
###*
* This creates each function inside of jQuery capable of setting and running callbacks on
* individual elements.
* @param {[type]} options [description]
* @return {[type]} [description]
###
$.extend createDomCallbacks: (options) ->
$.extend({}, domCallbacks.categories, options)
categoryFunctions = {}
for key, category of domCallbacks.categories
categoryFunctions['pre'+key] = createCallbackPlugin('pre',key)
categoryFunctions['on'+key] = createCallbackPlugin('on',key)
$.extend $.fn, categoryFunctions
for key, category of domCallbacks.categories
for val in category.jqfuncs
domCallbacks.addCallback( (if val.name then val.name else val) , domCallbacks.categories)
###*
* removes any callbacks each element in the jquery object
* @param {object} options an object conatining keys for the type of callback to be removed (e.g. options = {Mutate: {}})
* @return {[type]} returns jquery object for chaining
###
$.extend $.fn, removeDomCallbacks: (options) ->
@each (i, el) ->
if el['domCallbacks']?
if !options?
delete el['domCallbacks']
for category of options
delete el['domCallbacks']["_pre"+category]
delete el['domCallbacks']["_on"+category]
@
@
###*
* turns off all callbacks for performing actions without notifying up the dom
* @return {jQuery} for chaining
###
$.extend $.fn, peek: () ->
for key,val of domCallbacks.originals
$.fn[key].peek = true
@each (i, el) ->
@
@
###*
* turns off all callbacks for performing actions without notifying up the dom
* @return {jQuery} for chaining
###
$.extend $.fn, unpeek: () ->
for key,val of domCallbacks.originals
$.fn[key].peek = false
@each (i, el) ->
@
@
) @jQuery, @