我想要一个像这样的集合:
var array = [{innerText: "I was with ", index:0},{obj: "Mary", id: 1, index:1}, {innerText: " and ", index:2},{obj: "John", id: 2, index:3}];
和一个内容可编辑的 div,它会将所有内容都放在上面,但绑定到数组,这样当我更改 innerText 或表示对象的输入时,数组将相应地更新。
例如,没有 angularJS 的 div 看起来像这样:
<div contenteditable="true">
I was with <input type="text" value="Mary" data-index="1"/> and <input type="text" value="John" data-index="3"/>
</div>
这应该与 div 中的退格键以及要插入的新输入或要键入的文本一起使用,从而相应地更新数组。
我知道可能我必须使用 Mutation Observers,但我不知道如何在这个复杂的示例中使用。我希望 AngularJs 能够与突变观察者进行更自动化的集成:/
我的原始方法是:我为整个集合制作了一个指令,为 innerText 制作了一个指令,为对象制作了一个指令。输入与对象名称的绑定当然有效,但在 contenteditable 的内部 DOM 发生突变时无效。同样将 {{innerText}} 作为 innerText 的模板并在 contenteditable 中使用它并不能保证有人会实际输入它,因此绑定将起作用(而不是在它之前或之后)
编辑:如果它使具有相同内容的集合变得更容易,那么可编辑仍然非常有用
var array = [{obj: "Mary", id: 1, index:1}, {obj: "John", id: 2, index:3}, {innerText: "I was with @ and @"];
Edit2:重新打开问题。以前接受的答案方法非常好,但今天我意识到它不是真正的双向绑定。它实际上是单向绑定。从视图到模型。如果使用提供的代码的更新版本(来自先前接受的答案)来获得类似的模型,则将奖励赏金
modelValue": [
{
"innerText": "abc",
"index": 0
},
{
"obj": "abc",
"index": 1
},
{
"innerText": "abc",
"index": 2
}
]
这将使视图:
"viewValue": "\n abc\n <input type=\"text\">\n abc\n "
该解决方案必须为服务提供代码,该服务将在按下新按钮时返回一个像上面那样的静态模型,并且控制器中的一个函数将把 modelValue 放在作用域中,并且模型将转换为上面的 viewValue .
Edit3:根据下面的更新答案,这里是真正的 2-way binding 如何在没有建议的 $watch 的情况下使用 compile pre-link 和 post-link 工作:
// Code goes here
var myApp = angular.module('myApp', []);
myApp.controller('test', ['$scope',
function($scope) {
$scope.addInput = function() {
//Put in a directive if using for real
var input = document.createElement('input');
input.type = "text";
$(input).attr("data-label","obj");
$(input).attr("data-name","");
$(input).attr("data-id","randomId");
document.querySelector("div[contenteditable]").appendChild(input);
input.focus();
}
}
]);
myApp.directive('contenteditable', ['$compile', function($compile) {
return {
require: 'ngModel',
controller: [
'$scope',
function($scope) {
// Load initial value.
$scope.getViewValue = function() {
var tempDiv = document.createElement("div");
angular.forEach($scope.model.modelValue,
function(obj, index) {
if (obj.innerText) {
var newTextNode = document.createTextNode(" "+obj.innerText+" ");
tempDiv.appendChild(newTextNode);
} else if (obj.name) {
var newInput = document.createElement('input');
newInput.setAttribute('data-id',obj.id);
newInput.setAttribute('data-label', obj.label);
newInput.setAttribute('autosize', 'autosize');
newInput.setAttribute('data-name', obj.name);
newInput.setAttribute('value', obj.nickname);
newInput.setAttribute('type','text');
$(newInput).addClass('element-'+obj.label);
tempDiv.appendChild(newInput);
}
}
);
return tempDiv.innerHTML;
};
$scope.model = { "viewValue": "", "modelValue": [{"nickname":"Abc","index":0,"id":"2","label":"obj","name":"Abc"},{"innerText":"does something with","index":1},{"nickname":"bcd","index":3,"id":"0","label":"obj","name":"bcd"}] };
$scope.model.viewValue = $scope.getViewValue();
}],
compile: function(elm, attrs){
return {
pre: function(scope, elm, attrs, ctrl, transcludeFn){
elm.html(scope.model.viewValue);
ctrl.$setViewValue(elm.html());
console.log(elm);
angular.forEach(elm[0].childNodes, function (node, index) {
if (node.nodeName === "INPUT") {
$compile(node)(scope);
}
});
//click all of them to make them autosize
$('div.editable input').click();
},
post: function(scope, elm, attrs, ctrl) {
//prevent enter from being pressed
elm.bind('keydown',function(evt){
if (evt.keyCode == 13) {
evt.preventDefault();
return false;
}
});
//click all of them to make them autosize
$('div.editable input').click();
//Change listeners
elm.bind('blur keyup paste input click', function() {
var new$viewValue = {
viewValue: elm.html(),
modelValue: []
}
var index = 0;
angular.forEach(elm[0].childNodes, function(value, index) {
if (value.nodeName === "INPUT") {
if (value.value) {
var obj = {
nickname: value.value,
index: index,
id: $(value).attr("data-id"),
label: $(value).attr("data-label"),
name: $(value).attr("data-name")
};
new$viewValue.modelValue.push(obj);
//if type is entity
} else {
value.parentNode.removeChild(value);
}
} else if (value.nodeName === "#text") {
var last = null;
if(new$viewValue.modelValue.length > 0){
var last = new$viewValue.modelValue[new$viewValue.modelValue.length-1];
}
//if last was innerText (update it)
if (last!=null && last.innerText){
last.innerText += value.textContent.trim()
}
//else push it
else {
new$viewValue.modelValue.push({
innerText: value.textContent.trim(),
index: index
});
}
}
index++;
});
ctrl.$setViewValue(new$viewValue);
console.log(JSON.stringify(scope.model.modelValue));
});
}
}
},
};
}]);
div > div > div {
background-color: grey;
min-width: 100px;
min-height: 10px;
}
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="test">
<button ng-click="addInput()">Add Input</button>
<div contenteditable="true" ng-model="model">
</div>
See Console</div>
</div>