summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Levy <alon@pobox.com>2015-03-02 15:02:04 +0200
committerAlon Levy <alon@pobox.com>2015-03-02 15:02:04 +0200
commit5d572f7a497b9a9d1bef6d20b974288dc645bcb3 (patch)
tree79115fdd696f6273e92e193f3c585739e9eb7381
parent48703a295bc76f777faea7a11ad0a9a459f6d26f (diff)
util: add text selection and functional utils for later red&blue input
-rw-r--r--src/client/textanalysis.js13
-rw-r--r--src/client/util.js143
2 files changed, 146 insertions, 10 deletions
diff --git a/src/client/textanalysis.js b/src/client/textanalysis.js
index c4f5053f..fe4b8891 100644
--- a/src/client/textanalysis.js
+++ b/src/client/textanalysis.js
@@ -2,6 +2,9 @@ define(['rz_core', 'model/core', 'model/util', 'model/diff', 'consts', 'util'],
function(rz_core, model_core, model_util, model_diff, consts, util) {
"use strict";
+// Aliases
+var reduce = util.reduce;
+
// Constants
var node_edge_separator = rz_config.node_edge_separator;
var separator_string = rz_config.separator_string;
@@ -102,16 +105,6 @@ function group(list, predicate)
return ret;
}
-function reduce(list, initial, func)
-{
- var ret = initial;
-
- for (var i = 0 ; i < list.length; ++i) {
- ret = func(ret, list[i]);
- }
- return ret;
-}
-
function list_get(list, index, def)
{
var item = list[index];
diff --git a/src/client/util.js b/src/client/util.js
index 97d6b1f0..d2114e4c 100644
--- a/src/client/util.js
+++ b/src/client/util.js
@@ -89,6 +89,145 @@ define(['jquery'], function($) {
};
}
+ /**
+ * Mirrors the selectionStart property for input[type=text] but
+ * usable on div[contentEditable=true] elements.
+ */
+ function selectionStart(element)
+ {
+ if (undefined !== element.selectionStart) {
+ return element.selectionStart;
+ }
+ var selection = window.getSelection(),
+ anchorNode = selection.anchorNode,
+ anchorOffset = textChildOffset(element, anchorNode);
+ return anchorOffset + selection.anchorOffset;
+ }
+
+ function textChildOffset(base, element)
+ {
+ function sum(a, b) { return a + b; };
+ function up_to(list, element) {
+ return list.slice(0, list.indexOf(element));
+ }
+ return reduce(up_to(text_children(base), element).map(obj_take('length')), 0, sum);
+ }
+
+ function text_children(element)
+ {
+ var text = [];
+ walk(element, function(e) {
+ if (3 === e.nodeType) {
+ text.push(e);
+ }
+ });
+ return text;
+ }
+
+ /**
+ * Return the text child containing the text at @offset and the
+ * corresponding offset within in.
+ *
+ * @param base root element
+ * @param offset text offset within base element
+ * @return {'element': text_element, 'offset': offset_in_text_element}
+ **/
+ function textChildForOffset(base, offset)
+ {
+ var sum = 0,
+ child,
+ child_offset,
+ last = base,
+ last_length = 0;
+
+ walk(base, function(element) {
+ if (3 === element.nodeType) {
+ sum += element.length;
+ if (sum > offset) {
+ child = element;
+ child_offset = offset + element.length - sum;
+ return false;
+ }
+ last = element;
+ last_length = last.length;
+ }
+ return true;
+ });
+ if (undefined !== child) {
+ return {'element': child, 'offset': child_offset};
+ }
+ return {'element': last, 'offset': last_length};
+ }
+
+ function walk(element, func)
+ {
+ var stack = [element],
+ e,
+ i;
+
+ while (stack.length > 0) {
+ e = stack.pop();
+ if (false === func(e)) {
+ return;
+ }
+ for (i = e.childNodes.length - 1; i >= 0 ; --i) {
+ stack.push(e.childNodes[i]);
+ }
+ }
+ }
+
+ function setSelection(element, start, end)
+ {
+ var selection, range,
+ start_obj, end_obj;
+
+ if (undefined !== element.selectionStart && undefined !== element.selectionEnd) {
+ element.selectionStart = start;
+ element.selectionEnd = end;
+ } else {
+ range = document.createRange();
+ start_obj = textChildForOffset(element, start);
+ end_obj = textChildForOffset(element, end);
+ range.setStart(start_obj.element, start_obj.offset);
+ range.setEnd(start_obj.element, end_obj.offset);
+ selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+ }
+
+ /**
+ * Mirros the value function on input[type=text] elements but
+ * usable on divs or any other node that can have a textContent
+ * child.
+ */
+ function value(element_raw, new_value)
+ {
+ if (undefined === new_value) {
+ if (undefined !== element_raw.value) {
+ return element_raw.value;
+ }
+ return element_raw.textContent;
+ } else {
+ if (undefined !== element_raw.value) {
+ element_raw.value = new_value;
+ } else {
+ element_raw.textContent = new_value;
+ }
+ }
+ }
+
+ // Functional programming tools
+ function reduce(list, initial, func)
+ {
+ var ret = initial;
+
+ for (var i = 0 ; i < list.length; ++i) {
+ ret = func(ret, list[i]);
+ }
+ return ret;
+ }
+
return {
array_diff: array_diff,
assert: assert,
@@ -96,8 +235,12 @@ define(['jquery'], function($) {
form_common__rest_post: form_common__rest_post,
input_validation__password: input_validation__password,
obj_take: obj_take,
+ reduce: reduce,
+ value: value,
set_from_array: set_from_array,
set_from_object: set_from_object,
set_diff: set_diff,
+ setSelection: setSelection,
+ selectionStart: selectionStart,
};
});