Search code examples
javascriptknockout.jshref

Set href using $root value within foreach loop


The link in the first td column spazzes out when trying to bind to $root.rootBaseUrl.

In the second td column, the same rootBaseUrl observable prints perfectly.

The difference is that in the first td column, I am trying to set the value within attr:.

Also, please note that there is a foreach loop happening at the tbody level. Hence the use of $root prefix.

    <tbody data-bind="foreach: siteList">        
        <tr>
            <td><h3><a data-bind="text: SiteName, attr: {href: $root.rootBaseUrl + SiteID}"></a></h3></td>                
            <td><h3><span data-bind="text: $root.rootBaseUrl"></span></h3></td>
        </tr>
    </tbody>
var rootBaseUrl = ko.observable("");
var index = window.location.toString().indexOf("RiskOrder");
var baseURL = window.location.toString().substring(0, index);               
this.rootBaseUrl(baseURL);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

Basically, I am getting the current browser URL in the JS, stripping it to the base root url, then trying to add this static URL to the href binding and concatenating with a dynamic SiteID value.

Is this possible?


Solution

  • Replace attr with text and get a glimplse of your problem:

    function Vm(){
      var self = this;
      self.SiteID = ko.observable("AX123");
    }
    
    function RootVm(){
      var self = this;
      var index = window.location.toString().indexOf("RiskOrder");
      var baseURL = window.location.toString().substring(0, index);
      
      self.rootBaseUrl = ko.observable("");
      self.SiteName = ko.observable("My Site");
      self.rootBaseUrl(baseURL);
      self.SiteList = ko.observableArray([new Vm()]);
    }
    
    ko.applyBindings(new RootVm());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    
    <table>
      <tbody data-bind="foreach: SiteList">
      <tr>
        <td>
          <h3><a data-bind="text: $root.rootBaseUrl + SiteID"></a></h3>
        </td>
        <td>
          <h3><span data-bind="text: $root.rootBaseUrl"></span></h3>
        </td>
      </tr>
      </tbody>
    </table>

    It renders function....., so a textual representation of a function. This is because rootBaseUrl is a function. If you want to use it in an expression, you have to use parentheses:

    <h3><a data-bind="text: $root.rootBaseUrl()+ SiteID()"></a></h3>
    

    If you use it as the only thing in a binding, the parentheses are optional:

    <h3><span data-bind="text: $root.rootBaseUrl()"></span></h3>
    

    So your fix would be:

    function Vm(){
      var self = this;
      self.SiteID = ko.observable("AX123");
      self.SiteName = ko.observable("My Site");
    }
    
    function RootVm(){
      var self = this;
      var index = window.location.toString().indexOf("RiskOrder");
      var baseURL = window.location.toString().substring(0, index);
      
      // On SO Snippets window.location works differently so we hack it:
      baseURL = "https://example.com/my-website/url/";
      
      self.rootBaseUrl = ko.observable("");
      self.rootBaseUrl(baseURL);
      self.SiteList = ko.observableArray([new Vm()]);
    }
    
    ko.applyBindings(new RootVm());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    
    <table>
      <tbody data-bind="foreach: SiteList">
      <tr>
        <td>
          <h3><a data-bind="text: SiteName, attr: { href: $root.rootBaseUrl() + SiteID() }"></a></h3>
        </td>
        <td>
          Second column
        </td>
      </tr>
      </tbody>
    </table>

    Or use a computed so you can (a) unit test the logic and (b) make the parentheses optional again:

    function Vm(urlBaseVm){
      var self = this;
      self.SiteID = ko.observable("AX123");
      self.SiteName = ko.observable("My Site");
      
      self.hrf = ko.computed(function() {
        return urlBaseVm.rootBaseUrl() + self.SiteID();
      });
    }
    
    function RootVm(){
      var self = this;
      var index = window.location.toString().indexOf("RiskOrder");
      var baseURL = window.location.toString().substring(0, index);
      
      // On SO Snippets window.location works differently so we hack it:
      baseURL = "https://example.com/my-website/url/";
      
      self.rootBaseUrl = ko.observable("");
      self.rootBaseUrl(baseURL);
      self.SiteList = ko.observableArray([new Vm(self)]);
    }
    
    ko.applyBindings(new RootVm());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    
    <table>
      <tbody data-bind="foreach: SiteList">
      <tr>
        <td>
          <h3><a data-bind="text: SiteName, attr: { href: hrf }"></a></h3>
        </td>
        <td>
          Second column
        </td>
      </tr>
      </tbody>
    </table>