UNPKG

3.96 kBJavaScriptView Raw
1var textDiff = require('./textDiff');
2
3exports.add = addDocumentListeners;
4exports.inputSupportsSelection = inputSupportsSelection;
5
6// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#do-not-apply
7// TODO: Date types support
8function inputSupportsSelection(input) {
9 var type = input.type;
10 return (
11 type === 'text' ||
12 type === 'textarea' ||
13 type === 'search' ||
14 type === 'url' ||
15 type === 'tel' ||
16 type === 'password'
17 );
18}
19function inputIsNumberValue(input) {
20 var type = input.type;
21 return (type === 'number' || (type === 'range' && !input.multiple));
22}
23var inputValue = function(input) {
24 return inputIsNumberValue(input) ? input.valueAsNumber : input.value;
25};
26
27function addDocumentListeners(doc) {
28 doc.addEventListener('input', documentInput, true);
29 doc.addEventListener('change', documentChange, true);
30
31 // Listen to more events for versions of IE with buggy input event implementations
32 if (parseFloat(window.navigator.appVersion.split('MSIE ')[1]) <= 9) {
33 // We're listening on selectionchange because there's no other event emitted when
34 // the user clicks 'delete' from a context menu when right clicking on selected text.
35 // So although this event fires overly aggressively, it's the only real way
36 // to ensure that we can detect all changes to the input value in IE <= 9
37 doc.addEventListener('selectionchange', function(e){
38 if (document.activeElement) {
39 documentInput({target: document.activeElement}); // selectionchange evts don't have the e.target we need
40 }
41 }, true);
42 }
43
44 // For some reason valueAsNumber returns NaN for number inputs in IE
45 // until a new IE version that handles this is released, parse input.value as a fallback
46 var input = document.createElement('input');
47 input.type = 'number';
48 input.value = '7';
49 if (input.valueAsNumber !== input.valueAsNumber) {
50 var oldInputValue = inputValue;
51 inputValue = function(input) {
52 if (input.type === 'number') {
53 return inputIsNumberValue(input) ? parseFloat(input.value) : input.value;
54 } else {
55 return oldInputValue.apply(this, arguments);
56 }
57 };
58 }
59}
60
61function documentInput(e) {
62 var target = e.target;
63
64 if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
65 setInputValue(e, target);
66 }
67}
68
69function documentChange(e) {
70 var target = e.target;
71
72 if (target.tagName === 'INPUT') {
73 setBoundProperty(target, 'checked');
74 setInputValue(e, target);
75
76 } else if (target.tagName === 'SELECT') {
77 setOptionBindings(target);
78
79 } else if (target.tagName === 'TEXTAREA') {
80 setInputValue(e, target);
81 }
82}
83
84function setBoundProperty(node, property) {
85 var binding = node.$bindAttributes && node.$bindAttributes[property];
86 if (!binding || binding.isUnbound()) return;
87
88 var value = node[property];
89 binding.template.expression.set(binding.context, value);
90}
91
92function setInputValue(e, target) {
93 var binding = target.$bindAttributes && target.$bindAttributes.value;
94 if (!binding || binding.isUnbound()) return;
95
96 if (inputSupportsSelection(target)) {
97 var pass = {$event: e};
98 textDiffBinding(binding, target.value, pass);
99 } else {
100 var value = inputValue(target);
101 binding.template.expression.set(binding.context, value);
102 }
103}
104
105function textDiffBinding(binding, value, pass) {
106 var expression = binding.template.expression;
107 var segments = expression.pathSegments(binding.context);
108 if (segments) {
109 var model = binding.context.controller.model.pass(pass);
110 textDiff.onTextInput(model, segments, value);
111 } else if (expression.set) {
112 expression.set(binding.context, value);
113 }
114}
115
116function setOptionBindings(parent) {
117 for (var node = parent.firstChild; node; node = node.nextSibling) {
118 if (node.tagName === 'OPTION') {
119 setBoundProperty(node, 'selected');
120 } else if (node.hasChildNodes()) {
121 setOptionBindings(node);
122 }
123 }
124}