1

I am building a JS framework to simulate AngularJS models, only for educational purposes.

The thing is: I assign a callback to run when the models(inputs) are updated, but after they run by the first time, they "disappear".

Before trying to do this using jQuery, I was trying with querySelectorAll and got stuck on the same problem.

Here it is: http://jsfiddle.net/YmY2w/

HTML

<div class="container" jd-app="test">
    <input type="text" jd-model="name" value="foo" /><br />
    <input type="text" jd-model="email" value="foo@foo" />
    <hr />
    <p>Name: {{ name }}</p>
    <p>Email: {{ email }}</p>
</div>

JAVASCRIPT

(function($, window, document, undefined) {
    'use strict';

    // registering models
    (function() {
        var $app = $('[jd-app]');
        var $models = $('[jd-model]', $app);

        $models.each(function(index) {
            UpdateModel.apply(this);
        });

        function UpdateModel() {
            var model = { name: $(this).attr('jd-model'), value: this.value }
            var re    = new RegExp('{{\\s+('+ model.name +')\\s+}}', 'gm');

            $('*', $app).each(function(index) {
                var $this = $(this);
                $this.text( $this.text().replace(re, model.value) );
            });

            $(this).on('keyup', UpdateModel);
        }
    })();

})(jQuery, window, document);

What am I doing wrong? Is there a better way to accomplish this?

Danillo Souza
  • 81
  • 1
  • 8

1 Answers1

0

The problem you're having is that when your script first sees {{ name }} it converts it to the value of the model "name". But then in your html, you have the text "foo" instead of {{ name }} (the thing you're trying to search/replace), so you aren't able to update the displayed text.

What you have to do is keep track of the individual DOM nodes (as attached to particular models) which contain the text that initially has the "{{ }}" in them with the proper model name.

I came up with a kind of ugly prototype here:

http://jsfiddle.net/YmY2w/11/

I'm not sure how flexible it is in terms of identifying the proper templated locations, but it serves as a demo for the kind of implementation I'm talking about. Also - not very efficient I would guess. (But then neither is angularjs really...)

(function($, window, document, undefined) {
    'use strict';

    // From http://stackoverflow.com/questions/298750/how-do-i-select-text-nodes-with-jquery
    function getTextNodesIn(node, includeWhitespaceNodes) {
        var textNodes = [], nonWhitespaceMatcher = /\S/;

        function getTextNodes(node) {
            if (node.nodeType == 3) {
                if (includeWhitespaceNodes || nonWhitespaceMatcher.test(node.nodeValue)) {
                    textNodes.push(node);
                }
            } else {
                for (var i = 0, len = node.childNodes.length; i < len; ++i) {
                    getTextNodes(node.childNodes[i]);
                }
            }
        }

        getTextNodes(node);
        return textNodes;
    }

    // registering models
    (function() {
        var $app = $('[jd-app]');
        var $models = $('[jd-model]', $app);
        var models = [];

        $models.each(function(index) {
            registerModel.apply(this);
        });

        function registerModel() {
          var model = { name: $(this).attr('jd-model'), value: this.value, domElements: [] };            
          var re    = new RegExp('{{\\s+('+ model.name +')\\s+}}', 'gm');
          $app.contents().each(function(index) {
          if ($(this).text().match(re)) {
            var textNodes = getTextNodesIn(this, false);
            console.log(textNodes);
            $.each(textNodes, function(index, elem) {
              if (elem.nodeValue.match(re)) {
                var text = elem.nodeValue;
                var myArray = re.exec(text);
                var match = myArray[0];

                var firstIndex = text.indexOf(match);

                var newElem = elem.splitText(firstIndex);

                newElem.splitText(match.length);

                model.domElements.push(newElem);
             }
           });
         }
      });

      models.push(model);

      $(this).on('keyup', updateModel);
      $(this).trigger('keyup');
    }


        function updateModel() {
            var $input = $(this);
            $.each(models, function(index, model) {
                if (model.name === $input.attr('jd-model')) {
                    var newVal = $input.val();
                    $.each(model.domElements, function(index, elem) {
                        elem.nodeValue = newVal;
                    });
                }
            });
        }

    })();

})(jQuery, window, document);
urban_raccoons
  • 3,499
  • 1
  • 22
  • 33