2014-06-11 6 views
1

태그를 표시하려면 bootstrap-tokenfieldtypeahead의 녹아웃을 사용하고 있습니다. 이전에는 좋은 방법으로 태그를 표시하는 방법이 필요했기 때문에 맞춤 바인딩을 만들었습니다. 태그 목록이 변경되지 않고 선택한 태그 만 변경 될 때 정말 효과가있었습니다.맞춤 바인딩에 새 태그 추가

정말 간단한 예제 looks like this입니다. 보시다시피 다양한 태그 (tag1, tag2, ..., tag5)를 입력 할 수 있으며 관찰 가능이 변경됩니다. 그래서 내 맞춤 바인딩이이 경우에 작동합니다.

ko.bindingHandlers.tags = { 
    init: function(element, valueAccessor, allBindings) { 
     var initializeTags = function(listOfTags, inputID, max){ 
      var tags = new Bloodhound({ 
       local: listOfTags, 
       datumTokenizer: function(d) {return Bloodhound.tokenizers.whitespace(d.value);}, 
       queryTokenizer: Bloodhound.tokenizers.whitespace 
      }); 
      tags.initialize(); 
      inputID.tokenfield({ 
       limit : max, 
       typeahead: {source: tags.ttAdapter()} 
      }).on('tokenfield:preparetoken', function (e) { 
       var str = e.token.value, 
        flag = false, 
        i, l; 
       for(i = 0, l = listOfTags.length; i < l; i++){ 
        if (listOfTags[i]['value'] === str){ 
         flag = true; 
         break; 
        } 
       } 

       if (!flag){ 
        e.token = false; 
       } 
      }); 
     } 

     var options = allBindings().tagsOptions, 
      currentTagsList = Helper.tags1List, 
      currentTagsInverted = Helper.tags1Inverted; 

     initializeTags(currentTagsList, $(element), 4); 

     ko.utils.registerEventHandler(element, "change", function() { 
      var tags = $(element).tokenfield('getTokens'), 
       tagsID = [], 
       observable = valueAccessor(), i, l, tagID; 

      for (i = 0, l = tags.length, tagID; i < l; i++){ 
       tagID = currentTagsInverted[tags[i].value]; 

       if (typeof tagID !== 'undefined'){ 
        tagsID.push(parseInt(tagID)); 
       } 
      } 

      observable(tagsID); 
     }); 
    }, 
    update: function(element, valueAccessor, allBindings) { 
     var arr  = ko.utils.unwrapObservable(valueAccessor()), 
      options = allBindings().tagsOptions, 
      currentTags = Helper.tags1, tagsName = [], i, l, tagName; 

     if (!(arr instanceof Array)){ 
      arr = []; 
     } 

     for (i = 0, l = arr.length, tagName; i < l; i++){ 
      tagName = currentTags[arr[i]]; 
      if (typeof tagName !== 'undefined'){ 
       tagsName.push(tagName); 
      } 

     } 
     $(element).tokenfield('setTokens', tagsName); 
    } 
}; 

그러나 문제는 내가 추가 태그를 추가 할 필요가 있다는 것입니다 : 여기

그 것이다 tag6을 나는 단순히

Helper.getAllTags({ 
    "1":{"value":"tag1"}, ..., "6":{"value":"tag6"} 
}) 

을 할 경우 깜짝하지 않은 (작동하지 않습니다 나에게 그것이 왜 효과가 없는지를 안다). 이것을하는 것이 올바른 방법.

P. 내 바인딩 끔찍한이라고 생각하면

  • , 나는 당신과 동의하고 그것을 개선하는 방법들을하실 수 있습니다.

  • 바인딩 작업에 대한 명확한 설명이 필요하면 기꺼이 제공해 드리겠습니다.

  • tags1, tags1List, tags1Inverted의 아이디어는 ID 또는 이름 (나는 500 개가 있습니다)으로 적절한 태그를 빠르게 찾을 수 있어야합니다. 당신이 많은 일을 변경하려는 경우

  • 당신은

답변

2

업데이트 대답, '올바른'패턴은

나는 부트 스트랩 - tokenfield에 대한 바인딩 KnockoutJS을 만들었습니다.

https://github.com/mryellow/knockoutjs-tokenfield

첫번째로는 update s는 valueAccessor()에서 들어오는 볼 수 있습니다.

update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { 
    var observable = valueAccessor() || { }; 
    var peeked = ko.unwrap(observable.peek()); 

    ko.tokenfield[element.id].handlerEnabled = false; 

    $(element).tokenfield('setTokens',peeked); 

    ko.tokenfield[element.id].handlerEnabled = true; 
} 

여기서 모델로부터 들어오는 데이터에 대한 토큰을 만듭니다. 모든 토큰은 valueAccessor()과 같이 완전한 객체를 제공합니다. 그러나 이것은 우리 바인딩의 init쪽에있는 tokenfield:createdtoken을 트리거합니다. 따라서이 토큰을 모델에 다시 저장하지 않으려면 변수 handlerEnabled을 설정하여 이벤트 흐름을 제어하십시오.

이제 사용자와의 상호 작용을 위해, HTML value 속성, 또는 모델이 이벤트가 트리거됩니다 변경 :

ko.utils.registerEventHandler(element, 'tokenfield:createdtoken', function (e) { 
    // Detect private token created. 
    if (e.attrs[ko.tokenfield[element.id].bindings['KeyDisplay']].indexOf("_") === 0) { 
     console.log('tt-private'); 
     $(e.relatedTarget).addClass('tt-private'); 
    } 

    // Allow `update` to temporarily disable pushing back when this event fires. 
    if (ko.tokenfield[element.id].handlerEnabled == true) observable.push(e.attrs); 

}); 

참고 handlerEnabled 세계가 valueAccessor()에 다시 추가 차단.

토큰을 제거 할 때 AJAX 자동 완성에서 나온 여분의 메타 데이터가 tokenfield (patched)에서 누락되었습니다.따라서 우리는 존재하는 속성에 따라 그것을 찾아야 만합니다 :

ko.utils.registerEventHandler(element, 'tokenfield:removedtoken', function (e) { 
    var peeked = observable.peek(); 
    var item; 
    // Find item using tokenfield default values, other values are not in tokenfield meta-data. 
    ko.utils.arrayForEach(peeked, function(x) { 
     if (ko.unwrap(x.label) === e.attrs.label && ko.unwrap(x.value) === e.attrs.value) item = x; 
    }); 

    observable.remove(item); // Validation of `item` likely needed 
}); 

이렇게하면 바인더의 내부를 덮을 수 있습니다. 지금 우리는 KnockoutJS가 기대하는 바운드 모델에 모든 것을 데이터 또는 동기화 문제의 중복없이 직접 저장합니다. CSV 필드를 다시 가져오고 observableArray.fn을 사용하면 계산 된 결과가 좋고 재사용이 가능합니다.

사용법 : self.tags_csv = self.tags.computeCsv();.

ko.observableArray['fn'].computeCsv = function() { 
    console.log('observableArray.computeCsv'); 
    var self = this;   

    return ko.computed({ 
     read: function() { 
      console.log('computed.read'); 

      var csv = ''; 
      ko.utils.arrayForEach(ko.unwrap(self), function(item) { 
       console.log('item:'+JSON.stringify(item)); 
       if (csv != '') csv += ','; 
       // Our ID from AJAX response. 
       if (item.id !== undefined) { 
        csv += item.id; 
       // Tokenfield's ID form `value` attrs. 
       } else if (item.value !== undefined) { 
        csv += item.value; 
       // The label, no ID available. 
       } else { 
        csv += item.label; 
       }     
      }); 

      return csv; 
     }, 
     write: function (value) { 
      console.log('computed.write'); 

      ko.utils.arrayForEach(value.split(','), function(item) { 
       self.push({ 
        label: item, 
        value: item 
       }); 
      }); 

     } 
    }); 
}; 

이제 모델에 개체 배열과 CSV 표현이있어 서버에 보내기 전에 매핑하거나 조작 할 준비가되었습니다.

"tags": [ 
    { 
     "label": "tag1", 
     "value": "tag1" 
    }, 
    { 
     "id": "id from AJAX", 
     "field": "field from AJAX", 
     "label": "tag2", 
     "value": "tag2" 
    } 
], 
"tags_csv": "tag1,id from AJAX" 
+0

토큰이 토큰, id 등에 여분의 메타 데이터를 실제로 저장한다는 것 같습니다. – MrYellow

0

환영 다른 버전을 참조하십시오이 답변 뒤로

입니다. 모델에 addItem()/removeItem() 추가


직접 좀 더 관리 가능한 일들을 유지하는 데 도움이됩니다. 아래는 각 필드와 관련된 항목이 포함 된 모델입니다.

var tokenFieldModel = function tokenFieldModel() { 
    var self = this; 
    this.items = ko.observableArray([]); 

    this.addItem = function(attrs) { 
     console.log('addItem'); 
     self.items.push(new tokenItemModel(attrs)); 
    }; 

    this.removeItem = function(attrs) { 
     console.log('removeItem'); 
     var item; 
     if (attrs.id != null) { 
      ko.utils.arrayForEach(this.items(), function(x) { 
       if(x.id === attrs.id && ko.unwrap(x.value) == attrs.value) item = x; 
      }); 
     } else { 
      ko.utils.arrayForEach(this.items(), function(x) { 
       // TODO: Use allBindingsAccessor().tokenFieldDisplay 
       if(ko.unwrap(x.value) === attrs.value) item = x; 
      }); 
     } 
     //console.log(ko.unwrap(this.items())); 
     self.items.remove(item); 
    }; 
}; 

removeItem() 내 상황에 특히 약간의 물건을 통해 루프를 가진 지저분한 보이지만, 난 자동 완성에 대해 일치되지 않았으며 id 또는 기타 객체 키가없는 토큰을 추가 할 . 토큰을 만든 텍스트/레이블 만 있습니다. 이 다음 날 접두사하지 않는 토큰을 만들 수 있습니다

field_id = "id:111, id:222, a new tag, id:333, another new tag" 

또는

field_id = [ 
    { 
     id: 111, 
     value: 'tag1', 
     label: 'tag1' 
    }, 
    { 
     id: 222, 
     value: 'tag2', 
     label: 'tag2' 
    }, 
    { 
     value: 'a new tag' 
    }, 
] 

:

나는 다음과 같이보고 서버에이를 보낼 수 있습니다. Couchbase NoSQL을 사용하고 있으므로 데이터/문서 저장 방법에 적합합니다.

따라서 removeItem()은 배열을 검색하여 id과 일치하거나 value 만 찾는 것으로 검색해야합니다. 이 부분은 일치하는 필드를 제어하기 위해 allBindingsAccessor()의 바인딩 변수를 허용하도록 향상 될 수 있습니다.

이제 바인더의 init 안에 tokenfield에 응답 할 eventHandler를 정의 할 수 있습니다. 나는 그들의 elementId에 의해 색인 배열 tokenBaseModel.fields() 내부 페이지의 각 tokenfield이

ko.utils.registerEventHandler(element, 'tokenfield:removedtoken', function (e) { 
    console.log('tokenfield:removedtoken'); 
    console.log(e); 

    tokenBaseModel.fields[element.id].removeItem(e.attrs); 
}); 

주 (아닌 obserableArray(), 페이지의 각 tokenfield에 대해 별도의 항목 목록을 저장하는 단지 일반 배열)가 포함되어 있습니다. 자체 속성 data-bind에 정의 된

var tokenBaseModel = { 
    fields: [] 
}; 

그런 다음 바인더에 update 섹션 우리는 다른 모델로 다시 우리 tokenfield의 값을 전달할 수 있습니다.
update: function(element, valueAccessor, allBindingsAccessor, bindingContext) { 
    console.log('update'); 
    var observable = valueAccessor() || {}; 

    // Does validation on allBindingsAccessor and sets defaults. 
    var bindings = new tokenFieldUtils().processBindings(allBindingsAccessor); 

    // An `fn` util function extending both `observableArray` and `observable` to accept whichever datatype they're expecting and sort it out. 
    observable.refreshAll(ko.unwrap(tokenBaseModel.fields[element.id].items),bindings['Delimiter'],bindings['FieldKey']); 

} 

그리고 마지막으로 내 refreshAll() (실제로 valueAccessor()().refreshAll()) 함수는 valueAccessor()에 데이터를 다시 전달하는 무거운 리프팅을 수행합니다. data-bind="tokenfield: fooModel.bar"으로 결합 정의

ko.observableArray['fn'].refreshAll = function(valuesToPush, delimiter, key) { 
    var underlyingArray = this(); 
    this.valueWillMutate(); 
    this.removeAll(); 
    ko.utils.arrayPushAll(underlyingArray, valuesToPush); 
    this.valueHasMutated(); 
    return this; 
}; 

ko.observable['fn'].refreshAll() = function(valuesToPush, delimiter, key) { 
    this.valueWillMutate(); 
    var csv = ''; 
    ko.utils.arrayForEach(valuesToPush, function(item) { 
     if (csv != '') csv += delimiter; 
     if (item[key] === undefined) { 
      csv += item['value']; 
     } else { 
      csv += item[key]; 
     } 
    }); 
    this(csv); 
    this.valueHasMutated(); 
    return this; 
}; 

valueAccessor() 내 tokenfield 모델의 범위를 벗어나 외부 필드 fooModel.bar로 평가되는 것을 의미한다. (valueAccessor()은 실제로 값에 대한 링크가 아닌 가져 오기/설정하는 함수입니다).

마지막으로 valueHasMutated()을 누르면 fooModel.bar이 바인딩 된 다른 요소에서 변경 사항이 업데이트됩니다.

+1

놀라워요. 감사합니다 –

+0

우후! 점수! 나는 스스로를 해설하고 바보로 만들 수있는 사람들과 합류했습니다. ;-) 고맙습니다. – MrYellow

+0

valueAccessor 대신 items 배열을 사용하는 "업데이트"는 ID 및 기타 필드로 완성되는 새 토큰을 만들기 위해 외부 값을 받아 들일 수 없다는 것을 의미합니다. 그러나 토큰 필드가 토큰 이벤트를 생성하기 때문에 value 속성에서 토큰을 만들 수 있습니다. 양방향 솔루션에서 작업하는 경우 배열을 지원하고 반대쪽 끝에서 CSV를 처리해야하므로 모든 키가 항상 저장됩니다. – MrYellow