0

I am trying to format a field in the HTML of a site with Angular server side code. I want to write a javascript/jquery script on the HTML page to format the field from a decimal to a fraction. I have javascript code to perform the conversion. What I can't get to work is selecting each text element on the page. Each element I want to change has the ingredient-amount class. However, when I select elements using that class, I don't get any results. Can someone help me?

First some background because it will explain why I don't just make a change in the controller. My company has received an Angular JS application hosted on Azure. The controllers are all Angular JS. We have the server side source code, but it is compiled into a dll and that dll is then put on Azure. Currently we don't have permission from the client to publish changes to the dll. So we're stuck with editing the HTML page (which is on Azure, but outside the dll). I need to find all of the decimal fields on the page and convert them to fractions. As I said, I have a script to do the conversion, but I don't know how to access each element.

The fields are created like so:

                    <table class="component-ingredients-container">
                    <tr ng-repeat-start="component in currentRecipe.Components"
                        ng-show="component.Name">
                        <td>&nbsp;</td>
                        <td class="component-name-cell">
                            <span class="component-name">{{component.Name}}</span>
                        </td>
                    </tr>
                    <tr ng-repeat-end ng-repeat="ingredient in component.Ingredients">
                        <td>
                            <span class="ingredient-amount">{{ingredient.Amount}}</span>
                            <span class="ingredient-unit">{{ingredient.Unit}}</span>
                            <span>{{ingredient.Lookup.Name}}</span>
                            <div class="ingredient-preparation" ng-show="ingredient.Preparation">
                                <span>{{ingredient.Preparation }}</span>
                            </div>
                        </td>
                    </tr>
                </table>

The result is something like this:

Sauce

2.5 cups tomatoes

.5 tablespoon spices

.125 ounces something else

I want to grab those numbers (2.5, .5, and .125) so I can pass them to my conversion script.

My first attempt was to use jquery, but I got an error saying $ was not defined (I tried jquery in place of $ with similar results). I also tried adding script tags with links to the jquery code without success.

        $(document).ready(function () {
        try
        {
            $('.ingredient-amount').each(function (ndx) {
                var org = $(this).text();
                console.log("Object:" + org);
            });
        }
        catch (ex)
        {
            console.log("Error:" + ex);
        }
    });

My conclusion was that Angular or some other code was causing a conflict.

I tried to write the query in javascript, which seemed to only find one instance of the class .ingredient-amount

    function r(f){/in/.test(document.readyState)?setTimeout(r,9,f):f()}
r(function () {
    try {
        var divs = document.querySelectorAll('.ingredient-amount');

        [].forEach.call(divs, function (div) {
            try {
                console.log("Inside");
                console.log(divs.text());
            }
            catch (ex) {
                console.log(ex);
            }
        });
    }
    catch (ex) {
        console.log(ex);
    }
});

I tried something different next. I added an ID to the span tags and tried finding that specific ID. However, it couldn't be found. I got a null as the result.

<span class="ingredient-amount" id="amount-{{$index}}">{{ingredient.Amount}}</span>

    function r(f) { /in/.test(document.readyState) ? setTimeout(r, 9, f) : f() }
r(function () {
    console.log("Ready");
    var div = document.getElementById('amount-0');
    console.log("Element: " + div);
    console.log("Got it!");
});

My conclusion is that I'm not selecting those angular fields correctly. How can I do that?

Update:

I was able to add a button and get that working. However, I'm not able to get any kind of on ready function working. I've tried (1.) jquery document ready; jquery doesn't work, so that's a bust, (2.) The shortest ready, bind ready, and several others from here: $(document).ready equivalent without jQuery (3.) Just including the code outside a function as the last part of the script tag.

How can I get this code to work when the page opens instead of having to click a button?

<button id='btnConvertToFractions' onclick="buttonConvert()">Convert to Fractions</button>


function buttonConvert()
{
    try
    {
        console.log("Convert started");
        var divs = document.querySelectorAll('.ingredient-amount');

        [].forEach.call(divs, function (div) {
            console.log("Before: " + div.textContent);
            div.textContent = Fraction(div.textContent);
            console.log("After: " + div.textContent);
        });
        console.log("Convert finished");
    }
    catch (ex)
    {
        console.log("Error: " + ex);
    }
};
Community
  • 1
  • 1
boilers222
  • 1,728
  • 7
  • 29
  • 55
  • When you used jquery, did you load the jquery library before your script? – Cruiser Mar 07 '16 at 21:07
  • How are you able to get your script included if you can't access the other script? Also are you saying that you can edit the html? `document.ready` is virtually useless to you – charlietfl Mar 07 '16 at 21:15
  • Cruiser: I loaded the jquery directly above the javascript. Charlietfl: I can edit the HTML page, but not the controller (.js). – boilers222 Mar 07 '16 at 22:10

3 Answers3

0

You can use :

var amounts = document.getElementsByClassName("ingredient-amount");`

An example here: jsFiddle

profesor79
  • 8,236
  • 3
  • 27
  • 49
ust3000
  • 141
  • 8
  • Thanks ust3000. I tried this and it doesn't work. I get a null returned. That was when I tried it using the javascript equivalent of document ready: function r(f) { /in/.test(document.readyState) ? setTimeout(r, 9, f) : f() } r(function () {. Now if I tried it on a button click (see my updated post) it works. How can I get it to work when the page loads and not a button click? – boilers222 Mar 08 '16 at 14:23
  • In: function r(f) { /in/.test(document.readyState) you have to change console.log(divs.text()); to console.log(div.innerHTML); . I have updated the code [https://jsfiddle.net/hmos8nm2/1/] and it works. – ust3000 Mar 08 '16 at 16:46
  • This didn't work either. The problem is that var divs = document.querySelectorAll('.ingredient-amount') isn't finding any elements with the class ingredient-amount. – boilers222 Mar 09 '16 at 13:15
0

Here is the solution I came up with. I found an angular document ready function, but at first it didn't seem to work. Then I found a suggestion online to add a timeout. Combining these worked. The timeout seems to be giving the page the time to load all of the angular data elements (the ng-repeat div and its contents).

    angular.element(document).ready(
    setTimeout(
          function () {
              buttonConvert();
          }
        , 2000));
boilers222
  • 1,728
  • 7
  • 29
  • 55
  • The timeout is giving the AngularJS framework time to bootstrap. It's not the ideal solution, as the page only loads once. If you have multiple views in your single-page app, this could be a problem. – James Tikalsky Mar 10 '16 at 03:23
0

The "Angular way" to do this is to write a custom filter.

For example:

angular.module('YourModule', []).
  filter('fraction', function() {
    return function(input) {
      var out = "",
          wholeNumber = parseInt(input, 10),
          decimals = input - wholeNumber,
          fraction = '';

      if (decimals === .5) {
        fraction = '1/2';
      }

      return out + wholeNumber + ' ' + fraction;
    }
  });

You can then use the filter directly in the AngularJS template:

<span class="ingredient-amount">{{ingredient.Amount|fraction}}</span>

A little explanation... Each element in the View Template HTML is parsed when it's about to be added to the DOM. As AngularJS parses each element, it looks for custom elements and attributes, and also for expressions:

{{ingredient.Amount}}

The actual value of ingredient.Amount is inserted into the element before it's added to the DOM.

All we've done is add a filter that formats the value of ingredient.Amount.

James Tikalsky
  • 3,078
  • 1
  • 19
  • 12