실시간 공동 작업 스프레드시트 응용 프로그램 구축 방법(Google Sheets Clone)
55240 단어 pubnubjavascript
이것이 바로 우리가 오늘 이 강좌에서 구축할 내용이다.우리는 협업 스프레드시트에 특히 관심을 기울일 것이지만, 이러한 개념과 디자인 모델은 증분 업데이트를 할 수 있는 협업 문서 - 텍스트 문서, 스프레드시트 등에 적용될 수 있다.
시작하기 전에 목표를 설정하자.
기술 창고
주막
PubNub: 변경 사항을 전달하고 다른 협업자와 동기화하는 실시간 API 및 인프라입니다.
Handsontable: 우리가 선택한 전자 표 모듈.
튜토리얼 때문에
Parcel: 빠른 원형 제작에 사용되는 제로 설정 웹 패키지입니다.
Faker: 허위 데이터 생성(이 예에서 샘플 정보는 칸을 채웁니다.
개발 도구 체인
세 가지 절차를 시작할 수 있다.
package.json:
초기화$ mkdir sync-sheet
$ cd sync-sheet
$ npm init -y
설치 도구 체인 종속 항목:$ npm i -D parcel-bundler
주의: 지금부터 의존 항목을 수동으로 설치할 필요가 없습니다.우리가 그것을 실행할 때, Parcel은 그것을 처리하지만, 나는 가방과 절차를 알아차릴 것이다.브라우저의 일관성을 확보하기 위해 최소
babel
및 browserlist
설정을 사용합니다.우리는 브라우저의 마지막 두 버전을 지원하기를 희망합니다.
browserlist
을 package.json.
에 추가"browserslist": [
"last 2 version"
]
루트에 .babelrc
추가: {
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry"
}
] ],
"plugins": [
"@babel/plugin-transform-runtime"
] }
개발 의존항 경보: @babel/core
및 @babel/plugin-transform-runtime
.입구점
우리는 입구점이 필요하다. 우리는 그것을 통해 소포에 도착할 수 있다.소스 폴더를 만듭니다.
$ mkdir src
소스 폴더에 색인 파일 추가하기$ touch src/index.html
파일 Src/IndexHtml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Sheet Sync using pubnub</title>
</head>
<body>
<!-- our spreadsheet goes here -->
<script src="./main.js"></script>
</body>
</html>
script
태그가 main.js
에 연결되어 있기 때문에 우리도 그것을 만듭니다.$ touch src/main.js
index.html
에 대한 서비스 제공, 개발 시작:$ npx parcel src/index.html
지도의
PubNub 추가 구성 요소
PubNub를 사용하려면 우선 sign up for a PubNub account.이 필요합니다. 걱정하지 마세요. 저희는 당신의 모든 원형과 개발에 아낌없는 무료 층을 제공합니다.
등록 후 PubNub Admin Dashboard:에서 다음 로딩 항목 활성화
보기
우리의 관점은 다음과 같다.
<body>
<div id="online-users"></div>
<button id="clear-data"> Clear </button>
<div id="sheet"></div>
<button id="seed-data"> Seed sample row </button>
<script src="./main.js"></script>
</body>
스프레드시트
다음은
handsontable
을 초기화하고 자신을 위한 전자 표를 준비하겠습니다.$ touch src/spreadsheet.js
파일 Src/스프레드시트.회사 명
import Handsontable from 'handsontable';
import 'handsontable/dist/handsontable.full.min.css';
const hotSettings = {
columns: [
{
data: 'available',
type: 'checkbox',
}, {
data: 'id',
type: 'numeric',
},
{
data: 'email',
type: 'text',
}, {
data: 'fullName',
type: 'text',
},
],
stretchH: 'all',
autoWrapRow: true,
rowHeaders: true,
colHeaders: [
'',
'Id',
'Email',
'Name',
],
columnSorting: {
indicator: true,
},
autoColumnSize: {
samplingRatio: 23,
},
contextMenu: true,
licenseKey: 'non-commercial-and-evaluation',
customBorders: true,
colWidths: ['7%', '16%', '38%', '39%'],
className: 'sheet',
};
export default (data, elementId) => new Handsontable(
document.querySelector(elementId), {
data,
... hotSettings
});
의존 경고: handsontable
src/spreadsheet.js
을 우리의 src/main.js
으로 가져옵니다.전자 표를 저장하는 서버가 있다면, 여기에서 sheetData
을 가져와 채워야 합니다.import Sheet from '~/spreadsheet'
const sheetData = [];
const sheet = Sheet('sheet', sheetData);
이제 샘플 줄의 씨앗을 위한 탐지기를 단추에 추가합니다.import faker from 'faker';
document.getElementById('seed-data').addEventListener('click', () => {
sheet.populateFromArray(data.length, 0, [[
faker.random.boolean(),
faker.random.number(),
faker.internet.email(),
faker.name.findName(),
]]); });
의존 경고:faker사용자 세션
사용자의 세션은 일반적으로 탭에서 지속되기 때문에 탭 간에 변경 사항을 동기화하기 위해 다른 유형의 세션이 필요합니다.우리는 편집 세션이라고 부른다.
귀하는 자신의 신분 검증 논리가 있을 수 있지만, 이 강좌에서는 사용자 세션을 간단하게 유지하고 모의합니다.
$ touch src/user.js
파일 src/user.회사 명
import faker from 'faker';
const userFromLocalStorage = window.localStorage.user &&
JSON.parse(window.localStorage.user);
const user = userFromLocalStorage || {
id: faker.random.uuid(),
color: faker.internet.color(160, 160, 160),
name: faker.name.findName(),
};
// editing session
user.sessionUUID = faker.random.uuid();
window.localStorage.user = JSON.stringify(user);
export default user;
의존 경고:faker이렇게 하면 우리의 사용자 세션을 처리할 수 있지만, 보시다시피, 우리는 localStorage에서 편집 세션을 선택하지 않습니다. 왜냐하면 우리는 모든 옵션 카드에 유일한 세션이 필요하기 때문입니다.
PubNub 커넥터
편집 세션을 사용하여 PubNub 인스턴스를 초기화합니다.
$ mkdir src/connectors
$ touch src/connectors/pubnub.js
파일 Src/Pubnub.회사 명
import PubNub from 'pubnub';
import user from '../user';
const pubnub = new PubNub({
publishKey: process.env.PUBNUB_PUBLISH_KEY,
subscribeKey: process.env.PUBNUB_SUBSCRIBE_KEY,
uuid: user.sessionUUID,
});
export default pubnub;
종속성 경고: PubNub연결
우리는 두 가지 유형의 연결이 필요하다.
파일 Src/Hooks.회사 명
const hooks = {
record: {},
replay: {},
};
레코드 연결
우리는
handsontable
에서 제공한 다음과 같은 연결을 사용할 것이다AfterChange은 모든 칸이 변경된 후에 터치합니다.
AfterCreateRow은 줄을 추가한 후 터치합니다.
AfterRemoveRow은 줄을 삭제한 후 터치합니다.
정렬 후 AfterColumnSort을 터치합니다.
이것도 우리가 전송한 변경 사항을 다시 재생한 후에 촉발될 수 있기 때문에 무한순환에 들어가지 않도록 하는 방법이 필요하다.다행히도, 이전 세 개의 트리거에 대해, 우리는 그것을 다시 PubNub에 발표하지 않도록 변경된 출처를 볼 수 있다.열 정렬에 대해 우리는 대체 해결 방안이 필요하다.
레코드 연결은
PubNub
으로 증가분을 발표하기 위해 PubNub 객체와 워크시트의 이름(이것은 우리의 채널 이름)이 필요합니다. 따라서 레코드 함수는 PubNub
과 sheetName
을 매개 변수로 받아들이고 handsontable
의 연결 리셋을 되돌려줍니다.바꾸다
hooks.record.afterChange = (pubnub, sheetName) => function recordAfterChange(
changes,
source,
){
if (source === 'sync' || !changes) {
return; }
// Publish all deltas to pubnub in sequence.
changes.reduce(async (prev, [row, prop, oldValue, newValue]) => {
await prev;
return pubnub.publish({
message: {
operation: 'afterChange',
delta: {
row, prop, oldValue, newValue,
},
},
channel: sheetName,
});
}, true); };
행 만들기
hooks.record.afterCreateRow = (pubnub, sheetName) => function afterCreateRow(
index,
amount,
source, ){
if (source === 'sync') {
return;
}
pubnub.publish({
message: { operation: 'afterCreateRow', delta: { index, amount } },
channel: sheetName,
}); };
행 삭제
hooks.record.afterRemoveRow = (pubnub, sheetName) => function afterRemoveRow(
index,
amount,
source, ){
if (source === 'sync' || source === 'ObserveChanges.change') {
return;
}
pubnub.publish({
message: { operation: 'afterRemoveRow', delta: { index, amount } },
channel: sheetName,
});
};
열 정렬
앞의 갈고리와 달리sort는
source
을 제공하지 않습니다.반대로 우리는 변수 lastSortFromSync
을 유지하여 마지막으로 PubNub에 동기화된 정렬 설정을 저장할 것입니다.현재 정렬 구성에 변경 사항이 없으면 다른 증가분을 PubNub에 게시하는 것을 건너뜁니다.let lastSortFromSync;
hooks.record.afterColumnSort = (pubnub, sheetName) => function afterColumnSort(
[currentSortConfig],
[destinationSortConfig],
){
if (lastSortFromSync === destinationSortConfig) {
return; }
if (
lastSortFromSync && destinationSortConfig
&& lastSortFromSync.column === destinationSortConfig.column
&& lastSortFromSync.sortOrder === destinationSortConfig.sortOrder
){ return;
}
pubnub.publish({
message: {
operation: 'afterColumnSort',
delta: { currentSortConfig, destinationSortConfig },
},
channel: sheetName,
});
};
만약 내가 이것들을 나의 서버에 동기화해야 한다면 어떻게 해야 할지 알고 싶을지도 모른다.두 가지 방법이 있습니다.권장 사항: PubNub에 게시하고 PubNub Functions을 사용하여 서버에 API 호출을 수행합니다.
재방송 연결
다른 사람이 녹음한 어떤 내용도 반드시 재방송해야 한다.그래서 우리는 통상적인 용의자가 있다.
setDataAtCell을 사용하여
afterChange
을 재방송합니다.alter을 사용하여
afterCreateRow
을 재방송합니다.afterRemoveRow
도 alter으로 재방송됩니다.clearSort을 사용하여 이전의 정렬과 sort 설정 순서를 재설정하여
afterColumnSort
을 재방송합니다.바꾸다
hooks.replay.afterChange = function replayAfterChange(hot, {
row, prop, newValue,
}) {
hot.setDataAtCell(row, hot.propToCol(prop), newValue, 'sync');
};
행 만들기
hooks.replay.afterCreateRow = function replayAfterCreateRow(hot, {
index, amount,
}) {
hot.alter('insert_row', index, amount, 'sync');
};
행 삭제
hooks.replay.afterRemoveRow = function replayAfterRemoveRow(hot, {
index, amount,
}) {
hot.alter('remove_row', index, amount, 'sync');
};
열 정렬
hooks.replay.afterColumnSort = function replayAfterColumnSort(hot, {
destinationSortConfig,
}) {
if (!destinationSortConfig) {
hot.getPlugin('columnSorting').clearSort();
return; }
hot.getPlugin('columnSorting').sort(destinationSortConfig);
};
export default hooks;
상태 연결
사용자 활동을 추적합니다. 다시 말하면 사용자가 어떤 칸을 처리하고 있거나 사용자가 어떤 칸을 강조 표시하고 있는지 추적합니다.이것은 스프레드시트의 히스토리나 감사 추적의 일부분이 아니므로 PubNub 상태로 설정합니다.
선택 및 선택 취소 후 연결을 추가합니다.
src/main.js
에 추가합니다.hot.addHook('afterSelectionEnd', (row, col, row2, col2) => {
pubnub.setState(
{
state: {
selection: {
row, col, row2, col2,
},
user, },
channels: [sheetName],
},
); });
hot.addHook('afterDeselect', () => {
pubnub.setState(
{
state: {
selection: null,
user, },
channels: [sheetName],
},
); });
우리는 setBorders을 사용하여 다른 사람들이 어떤 세포를 연구하고 있는지 보여줄 것이다.const customBordersPlugin = hot.getPlugin('customBorders');
function setBorders({
row, col, row2, col2,
}, color) {
customBordersPlugin.setBorders([[row, col, row, col2]], {
top: { width: 2, color },
});
customBordersPlugin.setBorders([[row2, col, row2, col2]], {
bottom: { width: 2, color },
});
customBordersPlugin.setBorders([[row, col, row2, col]], {
left: { width: 2, color },
});
customBordersPlugin.setBorders([[row, col2, row2, col2]], {
right: { width: 2, color },
}); }
존재 및 상태
다음 함수는 PubNub에서 상태를 가져옵니다.상태 현재 스프레드시트를 열고 있는 모든 개인 사용자와 해당 상태를 읽어들입니다.그리고 사용자의 상태를 사용하여
#online-users
을 채우고 setBorders
을 호출하여 사용자의 상태를 사용하여 칸을 강조 표시합니다. function fetchPresense() {
pubnub.hereNow(
{
channels: [sheetName],
includeUUIDs: true,
includeState: true,
},
(status, { channels: { test_sheet: { occupants } } }) => {
customBordersPlugin.clearBorders();
const sessions = new Set();
const html = occupants.reduce((acc, { state = {} }) => {
if (!state.user || (state.user.uuid === user.uuid) ||
sessions.has(state.user.uuid)) {
return acc; }
sessions.add(state.user.uuid);
if (state.selection) {
setBorders(state.selection, state.user.color);
}
return `${acc}
<span> ${state.user.name} is online </span> <br/>`;
}, '');
document.getElementById('online-users').innerHTML = html;
},
); }
테이블 재설정
테이블 정리에는 다음 두 단계가 포함됩니다.
document.getElementById('clear-data').addEventListener('click', () => {
data.length = 0;
pubnub.deleteMessages({
channel: sheetName,
}, renderHistory);
pubnub.publish({
channel: sheetName,
message: { operation: 'afterClearHistory' },
});
});
PubNub 탐지기
두 개의 탐지기를 추가해야 합니다.
한 사람이 이 메시지를 들을 수 있다.수신된 작업이
afterClearHistory
이면 데이터를 지웁니다.그렇지 않으면, 우리는 상응하는 재방송 연결을 호출한다.두 번째는presence입니다. 앞에서 설명한
fetchPresense
함수를 호출합니다.pubnub.addListener({
message({ publisher, message: { operation, delta }, timetoken }) {
if (publisher !== pubnub.getUUID()) {
if (operation === 'afterClearHistory') {
data.length = 0;
hot.render();
} else {
hooks.replay[operation](hot, delta);
}
} },
presence({ uuid }) {
if (uuid === pubnub.getUUID()) {
return; }
fetchPresense();
},
});
감사 추적 재생
웹 브라우저에 국한되기 때문에 새로운 파트너가 최신 스프레드시트 상태를 실행하고 사용할 수 있는 방법이 필요합니다.
PubNub의 채널 히스토리는 스프레드시트의 감사 로그로 사용됩니다.그리고 나서 우리는 이 로그들을 재방송하여 사용자에게 최신 상태를 제공할 수 있다.
Src/Main。회사 명
pubnub.history({
channel: [sheetName],
}, (status, { messages }) => {
messages.forEach((message) => {
hooks.replay[message.entry.operation](hot, message.entry.delta);
});
});
가입 및 초기화 연결
우리는 역사가 재연될 때까지 기다리고 싶다.로그를 다시 재생한 후, 우리는 새로운 소식을 얻기 위해 채널을 구독합니다.또한 사용자의 초기 상태를 설정합니다.마지막으로, 우리는 모든 기록 연결을 초기화했다.
우리는 이 모든 것을 역사 리셋에 추가할 것이다.
Src/Main에 첨부합니다.회사 명
pubnub.history({
channel: [sheetName],
}, (status, { messages }) => {
messages.forEach((message) => {
hooks.replay[message.entry.operation](hot, message.entry.delta);
});
pubnub.subscribe({
channels: [sheetName],
withPresence: true,
});
pubnub.setState(
{
state: {
selection: null,
user,
},
channels: [sheetName],
},
);
Object.keys(hooks.record).forEach((hook) => {
hot.addHook(hook, hooks.record[hook](pubnub, sheetName));
});
fetchPresense();
});
결과
그렇습니다!우리 모두 준비가 다 되었으니 이제 시작합시다.
$ npx parcel src/index.html
브라우저에서 http://localhost:1234을 엽니다.다른 탭에서 다시 엽니다.그리고 익명 브라우저를 열고 다시 한 번 하세요.브라우저를 하나 더 추가하는 게 어때요?되도록 많이 해라.이제 그 중 하나를 변경하고 탭, 창, 브라우저에 전파됩니다.또는 live collaborative spreadsheets demo app here을 보십시오.몇 개의 웹 브라우저에서 그것을 열면 실시간 동기화 효과를 볼 수 있습니다!
마찬가지로, 여기에는 full GitHub repo이 참고할 수 있습니다.
Reference
이 문제에 관하여(실시간 공동 작업 스프레드시트 응용 프로그램 구축 방법(Google Sheets Clone)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/pubnub/how-to-build-a-realtime-collaborative-spreadsheets-app-ie-a-google-sheets-clone-2dp텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)