Так что это работает немного запутанно, и документы не объясняют, что/почему значения индекса являются тем, чем они являются.
Значения индекса действительно полезны только для обновления локальных списков (локальные списки представляют собой массив объектов, которые соответствуют/ссылаются на объекты Realtime в ваших CollaborativeLists), поэтому индексы настраиваются таким образом, что вы можете использовать их напрямую для изменения вашего локального списка шаг за шагом. В реальном времени api не пытается отправить минимальное количество событий, и если вы перемещаете один и тот же элемент несколько раз, индексы, отображаемые в событиях, не обязательно отражают окончательное местоположение элемента в списке.
Также полезно быстрый бит от «Обработка событий» Руководство Realtime API для разработчиков:
Все события на изменения, которые происходят в операции соединения являются испускается после того, все изменения будут применены к модели ,
С этим в виду, быстрый пример, чтобы проиллюстрировать то, что на самом деле происходит и почему это происходит так:
beginCompoundOperation()
move(0, 3)
move(2, 0)
endCompoundOperation()
И с результатами переезда:
beginCompoundOperation()
[A B C D]
move(0, 3)
[B C A D]
move(2, 0)
[A B C D]
endCompoundOperation()
В свою очередь, , следующие события:
VALUE_ADDED : Index: 3, MovedFromIndex: 0
VALUE_REMOVED : Index: 0, MovedToIndex: 3
VALUE_ADDED : Index: 0, MovedFromIndex: 2
VALUE_REMOVED : Index, 3, MovedToIndex: 0
(Note that the MovedFromIndx/MovedToIndex refer to the state of the list BEFORE the action in the event)
Это позволит вашему локальному список, чтобы сделать следующие изменения:
[A B C D]
VALUE_ADDED(3, 0)
[A B C A D]
VALUE_REMOVED(0, 3)
[B C A D]
VALUE_ADDED(0, 2)
[A B C A D]
VALUE_REMOVED(3, 0)
[A B C D]
Таким образом, в вашем примере, во время добавленного события, индекс назначения принимает во внимание, что вы двигаетесь пункт дальше в список, не удаляя его которое произойдет в соответствующем событии VALUES_REMOVED.
Надеюсь, что это полезна!
И, конечно, какой-то код, чтобы проверить вещи, если вы уже не имеете что-то вроде этого:
move_test.HTML
<!DOCTYPE html>
<html>
<head>
<title>Move Test</title>
<script type="text/javascript" src="//apis.google.com/js/api.js"></script>
<script src="/js/misc/move_test.js" type="application/javascript" ></script>
</head>
<body onload="onPageLoad()">
<button onclick="runScenario()">Run Scenario</button>
</body>
move_test.js
var clientId = "<YOUR CLIENT ID HERE>";
var REALTIME_MIMETYPE = 'application/vnd.google-apps.drive-sdk';
// Everything interesting is at the bottom in the onDocLoaded function.
function createRealtimeFile(title, description, callback)
{
console.log('Creating Drive Document');
gapi.client.drive.files.insert({
'resource':
{
'mimeType': REALTIME_MIMETYPE,
'title': title,
'description': description
}
}).execute(function (docInfo)
{
callback(docInfo, /*newDoc*/true);
});
}
function openRealtimeFile(title, callback)
{
gapi.client.load('drive', 'v2', function()
{
gapi.client.drive.files.list(
{
'q': 'title='+"'"+title+"' and 'me' in owners and trashed=false"
}).execute(function (results)
{
if (!results.items || results.items.length === 0)
{
createRealtimeFile(title, /*DocDescription*/"", callback);
}
else
{
callback(results.items[0], /*newDoc*/false);
}
});
});
}
function onPageLoad()
{
var GScope =
{
Drive: 'https://www.googleapis.com/auth/drive.file'
};
gapi.load('auth:client,drive-realtime,drive-share', function()
{
var handleAuthResult = function(authResult)
{
console.log('Requesting Drive Document');
openRealtimeFile("TESTDOC__", function (docInfo, newDoc)
{
if (docInfo && docInfo.id)
{
gapi.drive.realtime.load(docInfo.id, onDocLoaded, onDocInitialized, onDocLoadError);
}
else
{
console.log('Unable to find realtime doc');
debugger;
}
});
};
gapi.auth.authorize(
{
client_id: clientId,
scope: [ GScope.Drive ],
immediate: false
}, handleAuthResult);
});
}
function onDocLoadError(e)
{
console.log('Doc Load Error: ', e);
findAndLoadDoc();
}
function onDocInitialized(model)
{
console.log('Drive Document Initialized');
var root = model.createMap();
model.getRoot().set('docRoot', root);
}
function itemListen(item, listenerFn)
{
if (listenerFn)
{
item.addEventListener(gapi.drive.realtime.EventType.OBJECT_CHANGED, listenerFn);
}
}
function createItem(model, name, listenerFn)
{
var map = model.createMap();
var items = model.createList();
map.set('items', items);
map.set('name', name);
if (listenerFn)
{
map.addEventListener(gapi.drive.realtime.EventType.OBJECT_CHANGED, listenerFn);
}
return map;
}
function addItem(parent, child)
{
parent.get('items').push(child);
}
function removeItem(parent, index)
{
parent.get('items').remove(index);
}
function moveItem(oldParent, oldIndex, newParent, newIndex)
{
if (oldParent !== newParent)
{
oldParent.get('items').moveToList(oldIndex, newParent.get('items'), newIndex);
}
else
{
oldParent.get('items').move(oldIndex, newIndex);
}
}
var scenarioRunning = false
function rootListen(event)
{
if (!scenarioRunning)
{
return;
}
for (var i = 0; i < event.events.length; ++i)
{
var ev = event.events[i];
switch (event.events[i].type)
{
case gapi.drive.realtime.EventType.VALUES_ADDED:
console.log('ADDED: ', ev);
console.log(' Index: ', ev.index, ev.movedFromIndex);
console.log(' List: ', ev.target.get(ev.index).get('name'));
console.log(' Value: ', ev.values[0].get('name'));
printList(root);
break;
case gapi.drive.realtime.EventType.VALUES_REMOVED:
console.log('REMOVED: ', ev);
console.log(' Index: ', ev.index, ev.movedToIndex);
console.log(' List: ', ev.target.get(ev.index).get('name'));
console.log(' Value: ', ev.values[0].get('name'));
printList(root);
break;
default:
debugger;
break;
}
}
}
function printList(item)
{
var list = item.get('items');
var str = '[ ';
for (var i = 0; i < list.length; ++i)
{
var entry = list.get(i);
str += entry.get('name') + ' ';
}
str += ']';
console.log(str);
}
var docModel;
var docRoot;
var root;
function onDocLoaded(doc)
{
docModel = doc.getModel();
docRoot = docModel.getRoot();
window.__docRoot = docRoot;
if (docRoot.has('root'))
{
docRoot.delete('root');
}
docRoot.set('root', createItem(docModel, 'root', rootListen));
console.log('Document Loaded - Scenario Ready');
root = docRoot.get('root');
docModel.beginCompoundOperation();
// root
// |
// [A, B, C, D]
var childA = createItem(docModel, 'childA');
addItem(root, childA);
var childB = createItem(docModel, 'childB');
addItem(root, childB);
var childC = createItem(docModel, 'childC');
addItem(root, childC);
var childD = createItem(docModel, 'childD');
addItem(root, childD);
docModel.endCompoundOperation();
window.runScenario = function()
{
scenarioRunning = true;
printList(root);
console.log('---- Scenario Start ----');
docModel.beginCompoundOperation();
moveItem(root, 0, root, 3);
moveItem(root, 2, root, 0);
docModel.endCompoundOperation();
console.log('---- Scenario Complete ----');
printList(root);
}
}
Спасибо, я не заметил, что событие было до удалить, которая решает эту проблему. Есть ли причина, по которой вы не подавляете никаких событий, которые в любом случае являются noops? Кроме того, кажется более естественным каким-либо образом иметь событие удаления перед событием добавления. Является ли событие добавления первым, чтобы значения индекса не изменялись, или существует другая причина? –
Я просто догадываюсь, но кажется вероятным, что причина не удалять nop-события - это последовательность и простота. Не нужно быть логикой, чтобы решить, являются ли события нулевыми или если они имеют эффект. В конце концов, возможно, что конечный результат будет равно нулю (перемещение (0, 2), перемещение (2, 0) и т. Д.), Поэтому ответственность подталкивается к потребителю. Что касается добавления vs remove в качестве первого события - добавление на первом месте позволяет вам легче различать событие «move» и «remove». Если событие удаления было первым, вы не знали бы, удалился ли он из списка или переместился в новое место ». –