применить стиль к диапазону текста с помощью JavaScript в uiwebview - PullRequest
8 голосов
/ 22 мая 2010

Я отображаю простой текст в виде HTML в UIWebView на iPhone. Это в основном серия абзацев со случайной сильной или подчеркнутой фразой. Во время выполнения мне нужно применить стили к диапазонам текста.

Существует несколько похожих сценариев, один из которых выделяет результаты поиска. Если пользователь искал «что-то», я хотел бы изменить цвет фона за появлением слова, а затем восстановить исходный фон.

Можно ли применять стили к диапазонам текста, используя JavaScript? Ключевой частью этого также является возможность отмены стилей.

Кажется, есть два возможных пути для подражания. Можно было бы модифицировать html в Objective-C и передать его через javascript как новый innerHTML некоторого контейнера. Другой вариант - использовать javascript для непосредственного управления узлами DOM.

Я мог бы манипулировать html, но это звучит скучно в Objective-C, поэтому я предпочел бы манипулировать DOM, если это разумный подход. Я не очень хорошо знаком с javascript и DOM, поэтому не знаю, разумный ли это подход.

Я написал несколько процедур для перевода между текстовыми диапазонами и диапазонами узлов со смещениями. Поэтому, если я начну с текстового диапазона 100-200, который начинается в одном абзаце и заканчивается третьим, я могу получить текстовые узлы и смещения в узлах, которые представляют данный текстовый диапазон. Мне просто нужен способ разбить текстовый узел по смещению в тексте. В настоящее время я просто применяю стили к абзацам, содержащим текстовый диапазон.

Несколько заметок:

  • Прямой JavaScript, пожалуйста, никаких внешних фреймворков, таких как jquery.
  • изменения никогда не нужно записывать на диск.
  • изменения должны быть отменены или, по крайней мере, удалены.
  • применяемые стили уже существуют в файле CSS.
  • он должен работать в iPhone 3.0 и вперед.
  • все исходные файлы поставляются с приложением.
  • пожалуйста, будьте многословны.

Спасибо за любые предложения.

1 Ответ

19 голосов
/ 22 мая 2010

Я думаю, вы много просите, чтобы получить полное решение для этого, но это показалось интересным, поэтому я реализовал его. Следующее работает в последних браузерах WebKit, включая Safari на iPhone под управлением OS 3.0. Он использует нестандартный, но удобный intersectsNode метод Range, который существует в WebKit, но был удален из Firefox в 3.0, поэтому он не работает в последних версиях Firefox, но его можно сделать тривиально.

Следующие элементы будут окружать каждый выбранный текстовый узел элементом <span> с классом «someclass», а также уникальным классом, обеспечивающим легкое удаление. applyClassToSelection возвращает этот уникальный класс; передайте этот класс в removeSpansWithClass для удаления пролетов.

ОБНОВЛЕНИЕ: Исправлена ​​проблема, когда выделение целиком содержалось в одном текстовом узле

ОБНОВЛЕНИЕ 2: Сейчас протестировано и работает на iPhone с ОС 3.0.

ОБНОВЛЕНИЕ 3: Добавлена ​​функция rangeIntersectsNode для добавления поддержки Firefox 3.0 и более поздних версий. Этот код теперь должен работать в Firefox 1.0+, Safari 3.1+, Google Chrome, Opera 9.6+ и, возможно, в других (пока не проверенных). Он не работает вообще в Internet Explorer и выдаст ошибки в этом браузере. Я планирую работать над версией IE в ближайшее время.

<script type="text/javascript">
    var nextId = 0;

    var rangeIntersectsNode = (typeof window.Range != "undefined"
            && Range.prototype.intersectsNode) ?

        function(range, node) {
            return range.intersectsNode(node);
        } :

        function(range, node) {
            var nodeRange = node.ownerDocument.createRange();
            try {
                nodeRange.selectNode(node);
            } catch (e) {
                nodeRange.selectNodeContents(node);
            }

            return range.compareBoundaryPoints(Range.END_TO_START, nodeRange) == -1 &&
                range.compareBoundaryPoints(Range.START_TO_END, nodeRange) == 1;
        };

    function applyClassToSelection(cssClass) {
        var uniqueCssClass = "selection_" + (++nextId);
        var sel = window.getSelection();
        if (sel.rangeCount < 1) {
            return;
        }
        var range = sel.getRangeAt(0);
        var startNode = range.startContainer, endNode = range.endContainer;

        // Split the start and end container text nodes, if necessary
        if (endNode.nodeType == 3) {
            endNode.splitText(range.endOffset);
            range.setEnd(endNode, endNode.length);
        }

        if (startNode.nodeType == 3) {
            startNode = startNode.splitText(range.startOffset);
            range.setStart(startNode, 0);
        }

        // Create an array of all the text nodes in the selection
        // using a TreeWalker
        var containerElement = range.commonAncestorContainer;
        if (containerElement.nodeType != 1) {
            containerElement = containerElement.parentNode;
        }

        var treeWalker = document.createTreeWalker(
            containerElement,
            NodeFilter.SHOW_TEXT,
            // Note that Range.intersectsNode is non-standard but
            // implemented in WebKit
            function(node) {
                return rangeIntersectsNode(range, node) ?
                    NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
            },
            false
        );

        var selectedTextNodes = [];
        while (treeWalker.nextNode()) {
            selectedTextNodes.push(treeWalker.currentNode);
        }

        var textNode, span;

        // Place each text node within range inside a <span>
        // element with the desired class
        for (var i = 0, len = selectedTextNodes.length; i < len; ++i) {
            textNode = selectedTextNodes[i];
            span = document.createElement("span");
            span.className = cssClass + " " + uniqueCssClass;
            textNode.parentNode.insertBefore(span, textNode);
            span.appendChild(textNode);
        }

        return uniqueCssClass;
    }

    function removeSpansWithClass(cssClass) {
        var spans = document.body.getElementsByClassName(cssClass),
            span, parentNode;

        // Convert spans to an array to prevent live updating of
        // the list as we remove the spans
        spans = Array.prototype.slice.call(spans, 0);

        for (var i = 0, len = spans.length; i < len; ++i) {
            span = spans[i];
            parentNode = span.parentNode;
            parentNode.insertBefore(span.firstChild, span);
            parentNode.removeChild(span);

            // Glue any adjacent text nodes back together
            parentNode.normalize();
        }
    }

    var c;
</script>

<input type="button" onclick="c = applyClassToSelection('someclass')"
    value="Add class">
<input type="button" onclick="removeSpansWithClass(c)"
    value="Remove class">
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...