nanonerd nanonerd - 16 days ago 8
Javascript Question

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?

Answer

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>