Skip to content

Commit 0837ba0

Browse files
committed
initial implementation
1 parent 3dceb69 commit 0837ba0

9 files changed

+244
-2
lines changed

.eslintrc.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = {
2+
extends: 'xo',
3+
env: {
4+
'browser': true
5+
},
6+
rules: {
7+
'indent': ['error', 2, {SwitchCase: 1}],
8+
'space-before-function-paren': ['error', 'never'],
9+
'curly': ['error', 'multi-line', 'consistent']
10+
}
11+
};

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
*.swp
3+
node_modules
4+
coverage

.travis.yml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
language: node_js
2+
node_js:
3+
- 6
4+
- 4
5+
- 0.10
6+
script: "npm run test-cover"
7+
# Send coverage data to Coveralls
8+
after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
# json0-string-binding
2-
Two-way bind json0 string operations to an HTML text input or textarea
1+
# sharedb-string-binding
2+
Two-way bind json0 string operations in a ShareDB doc to an HTML text input or textarea

index.js

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
var TextDiffBinding = require('text-diff-binding');
2+
3+
module.exports = StringBinding;
4+
5+
function StringBinding(element, doc, path) {
6+
TextDiffBinding.call(this, element);
7+
this.doc = doc;
8+
this.path = path || [];
9+
this._opListener = null;
10+
this._inputListener = null;
11+
}
12+
StringBinding.prototype = Object.create(TextDiffBinding.prototype);
13+
StringBinding.prototype.constructor = StringBinding;
14+
15+
StringBinding.prototype.setup = function() {
16+
this.update();
17+
this.attachDoc();
18+
this.attachElement();
19+
};
20+
21+
StringBinding.prototype.destroy = function() {
22+
this.detachElement();
23+
this.detachDoc();
24+
};
25+
26+
StringBinding.prototype.attachElement = function() {
27+
var binding = this;
28+
this._inputListener = function() {
29+
binding.onInput();
30+
};
31+
this.element.addEventListener('input', this._inputListener, false);
32+
};
33+
34+
StringBinding.prototype.detachElement = function() {
35+
this.element.removeEventListener('input', this._inputListener, false);
36+
};
37+
38+
StringBinding.prototype.attachDoc = function() {
39+
var binding = this;
40+
this._opListener = function(op, source) {
41+
binding._onOp(op, source);
42+
};
43+
this.doc.on('op', this._opListener);
44+
};
45+
46+
StringBinding.prototype.detachDoc = function() {
47+
this.doc.removeListener('op', this._opListener);
48+
};
49+
50+
StringBinding.prototype._onOp = function(op, source) {
51+
if (source === this) return;
52+
if (op.length === 0) return;
53+
if (op.length > 1) {
54+
throw new Error('Op with multiple components emitted');
55+
}
56+
var component = op[0];
57+
if (isSubpath(this.path, component.p)) {
58+
this._parseInsertOp(component);
59+
this._parseRemoveOp(component);
60+
} else if (isSubpath(component.p, this.path)) {
61+
this._parseParentOp();
62+
}
63+
};
64+
65+
StringBinding.prototype._parseInsertOp = function(component) {
66+
if (!component.si) return;
67+
var index = component.p[component.p.length - 1];
68+
var length = component.si.length;
69+
this.onInsert(index, length);
70+
};
71+
72+
StringBinding.prototype._parseRemoveOp = function(component) {
73+
if (!component.sd) return;
74+
var index = component.p[component.p.length - 1];
75+
var length = component.sd.length;
76+
this.onRemove(index, length);
77+
};
78+
79+
StringBinding.prototype._parseParentOp = function() {
80+
this.update();
81+
};
82+
83+
StringBinding.prototype._get = function() {
84+
var value = this.doc.data;
85+
for (var i = 0; i < this.path.length; i++) {
86+
var segment = this.path[i];
87+
value = value[segment];
88+
}
89+
return value;
90+
};
91+
92+
StringBinding.prototype._insert = function(index, text) {
93+
var path = this.path.concat(index);
94+
var op = {p: path, si: text};
95+
this.doc.submitOp(op, {source: this});
96+
};
97+
98+
StringBinding.prototype._remove = function(index, text) {
99+
var path = this.path.concat(index);
100+
var op = {p: path, sd: text};
101+
this.doc.submitOp(op, {source: this});
102+
};
103+
104+
function isSubpath(path, testPath) {
105+
for (var i = 0; i < path.length; i++) {
106+
if (testPath[i] !== path[i]) return false;
107+
}
108+
return true;
109+
}

package.json

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "sharedb-string-binding",
3+
"version": "1.0.0",
4+
"description": "Two-way bind json0 string operations in a ShareDB doc to an HTML text input or textarea",
5+
"repository": {
6+
"type": "git",
7+
"url": "git://github.com/share/sharedb-string-binding.git"
8+
},
9+
"scripts": {
10+
"lint": "eslint --ignore-path .gitignore .",
11+
"test": "node_modules/.bin/mocha && npm run lint",
12+
"test-cover": "node_modules/istanbul/lib/cli.js cover node_modules/mocha/bin/_mocha && npm run lint"
13+
},
14+
"devDependencies": {
15+
"coveralls": "^2.11.9",
16+
"eslint": "^2.13.0",
17+
"eslint-config-xo": "^0.14.1",
18+
"expect.js": "^0.3.1",
19+
"istanbul": "^0.4.3",
20+
"mocha": "^2.5.3",
21+
"sharedb": "^1.0.0-beta"
22+
},
23+
"directories": {
24+
"test": "test"
25+
},
26+
"author": "Nate Smith",
27+
"license": "MIT",
28+
"dependencies": {
29+
"text-diff-binding": "^1.0.0"
30+
}
31+
}

test/.eslintrc.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
3+
module.exports = {
4+
env: {
5+
'mocha': true
6+
},
7+
rules: {
8+
'max-len': 'off',
9+
'max-nested-callbacks': 'off',
10+
'require-jsdoc': 'off'
11+
}
12+
};

test/index.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
var expect = require('expect.js');
2+
var StringBinding = require('../index.js');
3+
var Backend = require('sharedb');
4+
5+
global.document = {};
6+
7+
describe('StringBinding', function() {
8+
beforeEach(function() {
9+
// Mock an HTML text input or textarea
10+
var element = this.element = {
11+
value: '',
12+
selectionStart: 0,
13+
selectionEnd: 0,
14+
selectionDirection: 'none'
15+
};
16+
element.setSelectionRange = function(selectionStart, selectionEnd, selectionDirection) {
17+
element.selectionStart = selectionStart;
18+
element.selectionEnd = selectionEnd;
19+
element.selectionDirection = selectionDirection || 'none';
20+
};
21+
document.activeElement = element;
22+
23+
this.backend = new Backend();
24+
this.connection = this.backend.connect();
25+
this.doc = this.connection.get('dogs', 'dog1');
26+
this.doc.create({name: ''});
27+
28+
this.binding = new StringBinding(this.element, this.doc, ['name']);
29+
});
30+
31+
it('supports updating the element when the doc value is the same', function() {
32+
expect(this.element.value).equal('');
33+
this.binding.update();
34+
expect(this.element.value).equal('');
35+
});
36+
37+
it('supports updating the element from the current doc value', function() {
38+
this.doc.submitOp({p: ['name'], oi: 'Fido'});
39+
this.binding.update();
40+
expect(this.element.value).equal('Fido');
41+
});
42+
43+
it('inserts text on input change', function() {
44+
this.element.value = 'Fido';
45+
expect(this.doc.data.name).equal('');
46+
this.binding.onInput();
47+
expect(this.doc.data.name).equal('Fido');
48+
});
49+
50+
it('removes text on input change', function() {
51+
this.doc.submitOp({p: ['name'], oi: 'Fido'});
52+
expect(this.doc.data.name).equal('Fido');
53+
this.binding.onInput();
54+
expect(this.doc.data.name).equal('');
55+
});
56+
57+
it('replaces text on input change', function() {
58+
this.element.value = 'Fido';
59+
this.doc.submitOp({p: ['name'], oi: 'Spot'});
60+
this.binding.onInput();
61+
expect(this.doc.data.name).equal('Fido');
62+
});
63+
});

test/mocha.opts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
--reporter spec
2+
--timeout 1200
3+
--check-leaks
4+
--recursive

0 commit comments

Comments
 (0)