WhatsInAName
WhatsInAName

Reputation: 724

Asp.net mvc4, knockout js, knockout ViewModel loads for all views?

I am new to asp.net mvc4 and knockoutjs and need help understanding how the views work.

  1. I have a _Layout.vbhtml in Shared folder and it's the "master" page for all pages in the project.

  2. I have AccountController, HomeController and GrowerController

  3. I have Grower folder for the GrowerController in the Views folder. Index is the default view as usual.

  4. In Views/Grower/Index, I have a knoockout ViewModel that retrieves data from the server.

  5. Now, when I go to other views like Home/Index, I see in Firebug's console that, it's making those calls to get data from the server even though I am not on the view where I created the knockout ViewModel.

I am very confused. Does it happen because I use _layout.vbhtml for all pages? What am I doing wrong?

EDIT: *_Layout.vbhtml*

    <!DOCTYPE html>
    <html lang="en">
      <head>
       <meta charset="utf-8" />
       <title>@ViewData("Title")</title>
       <meta name="viewport" content="width=device-width" />
       <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
       <link href="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.css" rel="stylesheet" type="text/css" />

        @* Javascrips files *@
        <script src="@Url.Content("~/Scripts/knockout-2.1.0.js")" type="text/javascript"></script>
        <script src="@Url.Content("http://code.jquery.com/jquery-1.7.1.min.js")" type="text/javascript"></script>
        <script src="@Url.Content("http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.js")" type="text/javascript"></script>
        <script src="@Url.Content("~/Scripts/custom.js")" type="text/javascript"></script>


        <!-- Custom stylesheet overriding styles -->
        @If Request.QueryString("pr") = "dow" or ViewData("pr") = "dow" Then
         @<link rel="stylesheet" href="@Url.Content("~/Content/CustomDow.css")" />
        Else
         @<link rel="stylesheet" href="@Url.Content("~/Content/Custom.css")" />
        End If
     </head>
     <body>
       <div data-role="page" data-theme="b">
        <div data-role="header">
            @If IsSectionDefined("Header") Then
                @RenderSection("Header")
            Else
                @<h1>@ViewData("Title")</h1>
                @Html.Partial("_LoginPartial")
            End If
        </div>
        <div data-role="content">
            @RenderBody()
        </div>
    </div>

    @RenderSection("scripts", required:=False)
</body>

Grower/Index.vbhtml

    @Code
      ViewData("Title") = "Select a Grower/Branch"
    End Code

    @section scripts
      <script type="text/javascript">

      function SuperViewModel() {

        //====== GrowerInfo =======
        var self = this;
        self.GrowerName = ko.observable();
        self.GrowerCompany = ko.observable();
        self.GrowerAddress = ko.observable();
        self.ShowGrowerCompany = ko.observable();
        self.GrowerID = ko.observable();

        self.updateGrowerInfo = function () {
          $.getJSON("GetGrower", function (allData) {
          self.GrowerName(allData.Name);
          self.GrowerCompany(allData.CompanyName);
          self.GrowerAddress(allData.Address);
          self.ShowGrowerCompany(allData.ShowCompany);
          self.GrowerID(allData.ID);
         });
        };

        //Load initial state from server and populate viewmodel
        self.updateGrowerInfo();
        //========= End GrowerInfo ==========

         if ($("#hfFlag").val() == "1") {
          //========= BranchInfo ==========
          self.BranchName = ko.observable();
          self.Company = ko.observable();
          self.Address = ko.observable();
          self.ID = ko.observable();

          //Load initial state from server and populate viewmodel
          self.updateBranchInfo = function () {
           $.getJSON("GetBranch", function (allData) {
            self.BranchName(allData.Name);
            self.Company(allData.CompanyName);
            self.Address(allData.Address);
            self.ID(allData.ID);
          });
        };

        self.updateBranchInfo();
        //=========== End BranchInfo ==============
      }


      //=============== GrowerList ===============
      var MyGrower = function (data) {
       this.growerId = ko.observable(data.GrowerId);
       this.growerName = ko.observable(data.GrowerName);
     };

      self.growers = ko.observableArray([]);

      self.updateGrowers = function () {
        //refresh listview
        $("#ulGrowerList").listview();
        $("#ulGrowerList").listview("refresh");

      $.getJSON("GetGrowers", function (allData) {
        var mappedGrowers = $.map(allData, function (item) { return new MyGrower(item) });
        self.growers(mappedGrowers);


        });
      };

      self.setSelectedClassToGrowerList = function (item, event) {

        $(ulGrowerList).closest('ul').find('a').removeClass('highlight');
        $(ulGrowerList).closest('ul').find('.selected').remove();

        $(event.target).toggleClass("highlight");
        if ($(event.target).hasClass("highlight")) {
          $(event.target).append("<span class='selected'>Selected</span>");

          replaceByValue('GrowerID', event.target.id);
          postjsonToServerNow("grower");

          //update GrowerInfo 
          $.getJSON("GetGrower", function (allData) {
            self.GrowerName(allData.Name);
            self.GrowerCompany(allData.CompanyName);
            self.GrowerAddress(allData.Address);
            self.ShowGrowerCompany(allData.ShowCompany);
            self.GrowerID(allData.ID);
          });

        } else {
          $(event.target).find(".selected").remove();
        }
      };



      self.setSelectedClassToBranchList = function (item, event) {

        $(ulBranchList).closest('ul').find('a').removeClass('highlight');
        $(ulBranchList).closest('ul').find('.selected').remove();

        $(event.target).toggleClass("highlight");

        if ($(event.target).hasClass("highlight")) {
          $(event.target).append("<span class='selected'>Selected</span>");

          replaceByValue('BranchID', event.target.id);
          postjsonToServerNow("branch");
        } else {
          $(event.target).find(".selected").remove();
        }

      };



      //Load initial state from server and populate viewmodel
      self.updateGrowers();

      //============ End GrowerList =============
    }

    //============= End ViewModel Section ====================//


      $(document).bind('pageinit', function () {
        //enable ko
        ko.applyBindings(new SuperViewModel());


        $("#divBranchList").hide();

        //show hide lists
        $("#btnGrower").click(function () {
        $("#divGrowerList").show();
        $("#divBranchList").hide();
       });

       $("#btnBranch").click(function () {
         $("#divBranchList").show();
         $("#divGrowerList").hide();
       });

      });
    </script>
    End Section




    <table class="maintable" id="maintable">
      <tr>
        <td class="left">
          <div id="GrowerInfo">
            <strong>Grower</strong><br />
            <a data-role="button" data-theme="e" id="btnGrower" data-bind="click: updateGrowers">
              <h3>
               <span data-bind="text: GrowerName"></span>
              </h3>
              <span data-bind="text:GrowerCompany, visible: ShowGrowerCompany" class="block"></span><span data-bind="text: GrowerAddress">
              </span>
              <br />
              <span data-bind="text: GrowerID"></span>
            </a>
          </div>
          @If ViewData("IsDealer") Then
            @<div id="BranchInfo">
              <strong>Branch</strong> <a data-role="button" data-theme="e" id="btnBranch">
                <h3>
                  <span data-bind="text: BranchName"></span>
                </h3>
                <span data-bind="text: Company"></span>
                <br />
                <span data-bind="text: Address"></span>
                <br />
                <span data-bind="text: ID"></span></a>
            </div>
          End If
        </td>
        <td class="splitline">
        </td>
        <td class="right">
          <div class="content-right">
            <div id="divGrowerList" style="overflow: auto; height: 450px; padding: 10px;">

            <p>Total growers: <span data-bind="text: growers().length">&nbsp;</span></p>

               <ul data-inset="true" data-filter="true" data-bind="foreach: growers" data-role="listview" id="ulGrowerList">
                  <li><a data-bind="click: $parent.setSelectedClassToGrowerList, attr: {id: growerId}"><span data-bind="text: growerName, attr: {id: growerId}, click: $parent.setSelectedClassToGrowerList" /></a></li>
              </ul>

              <textarea name="growers" rows="10" data-bind="value: ko.toJSON(growers)"></textarea>

            </div>
            <div id="divBranchList">
              @If ViewData("IsDealer") Then
                @Html.Action("MyBranchList2")
              End If
            </div>
            @If ViewData("IsDealer") Then
              @<input type="hidden" id="hfFlag" value="1" />
            Else
              @<input type="hidden" id="hfFlag" value="0" />
            End If
          </div>
        </td>
      </tr>
    </table>

Upvotes: 1

Views: 1817

Answers (2)

Sethi
Sethi

Reputation: 1398

Without seeing your _Layout.vbhtml I cannot say for certain, but since you have the javascript running on every page it could be possible you have your script tag on your _Layout.vbhtml

So, move this into your Index.vbhtml, or to place it in the <head> tag, use a @RenderSection("Scripts") in your Layout.vbhtml and in your view use:

@Section "Scripts"

@<script>
    // write JS here or reference a file using the src attribute
</script>

End Section

This places everything between @Section "SectionName" and End Section in your Layout.vbhtml in place of @RenderSection("Scripts")

Edit:

From your comments to one.beat.consumer I think I see your problem. But understand I am still guessing because I haven't seen your code - a BIG issue, seeing as you failed to mention you were using jQuery Mobile.

There are major differences between a regular MVC4 web application and a jQuery Mobile application.

One is: with jQM you are always on the same page -- you load pages from the server, via ajax, which are then stripped down to just <div data-role="page">...</div>. This explains why if you put scripts for specific pages in their <head> section it will be ignored by jQM. The only <head> section not ignored is that of the original page loaded which isn't always your home page.

Therefore, to load a script for a specific page you cannot put it in the <head> element using MVC Sections. You will need to reference the script within your <div data-role="page">. Although if you are caching your pages this will only fire once, and you may need to bind to pageshow if you want it to fire again (to refresh the viewmodel, maybe).

Lastly,

You said you were applying your viewmodel in you pageinit handler. This is called whenever a page is fetched from the server and injected into the DOM.

If you want to keep your js out of the view, you can reference a site-wide js file (custom.js) in your _Layout.vbhtml <head> and use:

$(document).on('pageinit', '#PageId', function(event) {
    /* do viewmodel stuff here */
});

This will run whenever a <div data-role="page"> with id="PageId" is fetched and injected into the DOM. You can use any selector here, so .require-vm can be used to fire when a page with class="require-vm" is injected.

Upvotes: 0

one.beat.consumer
one.beat.consumer

Reputation: 9504

You have defined your Knockout ViewModel in your _layout page.

This is fine if you intend to use this view model on all pages, but it sounds like you don't want to. Move it into the specific action view to isolate it.

in your comment above to Sethi, you mentioned the javascripts don't work when you do that... that is likely because of your placement of the script tags in both the layout and views. I would bet you are trying to build the view model before knockout.js is loaded.

Good practice:

In your layout, place your script tags at the bottom of the page, just before the </body> close tag... the last tag before it closes should be your RenderSection() call as you have it setup.

Now anywhere in your views you can define this script section and ensure that it will occur after your framework scripts - ex. jQuery, Knockout, etc.

Also, keep in mind with jQuery it is almost always preferable to use $.ready() to ensure that your scripts run only after the DOM is loaded.

Upvotes: 0

Related Questions