Я хочу иметь коллекцию вроде:
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, либо входные данные, представляющие объекты, массив будет соответствующим образом обновлен.
Например, div будет выглядеть примерно так без материала angularJS:
<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, а также с новыми вводами, которые нужно вставить, или текстом, который нужно ввести, соответствующим образом обновляя массив.
Я знаю, что, возможно, мне придется использовать наблюдателей мутаций, но я не знаю, как это сделать в этом сложном примере. Я надеялся, что AngularJs получит более автоматизированную интеграцию с наблюдателями мутаций: /
Мой примитивный подход заключался в следующем: я сделал директиву для всей коллекции, директиву для innerText и директиву для объектов. Связывание входных данных с именами объектов, конечно, работает, но не тогда, когда внутренняя DOM contenteditable изменена. Кроме того, наличие {{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: на основе обновленного ответа ниже показано, как реальная двусторонняя привязка работает без предложенных $ watch с использованием предварительной и последующей ссылки компиляции:
// 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>