2017-01-24 24 views
6

Chcę załadować stronę natychmiast, a następnie załadować dane, aby wypełnić później pola select2. Używając Knockout, nie otrzymuję żadnych błędów, ale nie widzę żadnych elementów w moich polach select2 select. Ładowanie synchroniczne z serwera działa, ale bardzo wolno (z powodu uzyskania app_names). Mam tak daleko:Ładowanie strony asynchronicznie w Knockoutjs

<head> 

    <meta charset="utf-8"> 
    <meta http-equiv="X-UA-Compatible" content="IE=edge"> 
    <meta name="viewport" content="width=device-width, initial-scale=1"> 
    <title>Admin suite</title> 

    <!-- Load javascript libraries --> 
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> 
    <script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/js/bootstrap.min.js"></script> 
    <script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.min.js"></script> 

    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.css" rel="stylesheet"> 

    <!-- best interactive input box --> 
    <link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.min.css" rel="stylesheet" /> 

    <script type="text/javascript" src="//cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script> 

    <script type="text/javascript" src="//cdn.jsdelivr.net/bootstrap.daterangepicker/2/daterangepicker.js"></script> 
    <link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/bootstrap.daterangepicker/2/daterangepicker.css" /> 

    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script> 
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js"></script> 

    <link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" rel="stylesheet"> 
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"></script> 
    <!-- semantic --> 
    <!-- <link href="https://cdnjs.com/libraries/semantic-ui" rel="stylesheet"/> --> 

    <style> 
     .center { 
      float: none; 
      margin-left: auto; 
      margin-right: auto; 
     } 
     #centered { 
      width: 50%; 
      margin: 0 auto; 
      margin-top: 100 
     } 
     #middleman-datepicker { 
      cursor: pointer; 
     } 
     .column { float: left; padding: 5px 10px; } 
     .row { overflow: hidden; } 
     label { 
      display: -webkit-box; 
      display: -webkit-flex; 
      display: -ms-flexbox; 
      display: flex; 
      -webkit-box-align: center; 
      -webkit-align-items: center; 
      -ms-flex-align: center; 
      align-items: center; 
     } 
     input[type=radio], 
     input[type=checkbox] { 
      -webkit-box-flex: 0; 
      -webkit-flex: none; 
      -ms-flex: none; 
      flex: none; 
      margin-right: 10px; 
     } 
     .btn-primary, 
     .btn-primary:active, 
     .btn-primary:visited, 
     .btn-primary:focus { 
      background-color: #f49e42; 
      border-color: #8064A2; 
     } 
     .btn-primary:focus { 
      background-color: #f49542; 
     } 
     .btn-primary:hover { 
      background-color: #f48c42; 
     } 
    </style> 

    <meta id="my-data" 
     data-app-names="[&#34;cart&#34;, &#34;catalog&#34;, &#34;common-ui&#34;, &#34;content&#34;, &#34;ContentServices&#34;, &#34;cyc&#34;, &#34;deliverFromStore&#34;, &#34;fbr&#34;, &#34;fbt&#34;, &#34;irg&#34;, &#34;localization&#34;, &#34;mylist-domain-service&#34;, &#34;mylist-service&#34;, &#34;mylist-ui&#34;, &#34;nlpplus-service&#34;, &#34;nlpservices&#34;, &#34;orangegraph&#34;, &#34;passbookService&#34;, &#34;pricing&#34;, &#34;promotion&#34;, &#34;recommendations&#34;, &#34;registry&#34;, &#34;relatedsearch&#34;, &#34;review_service&#34;, &#34;sbotd-svcs&#34;, &#34;SearchNavServices&#34;, &#34;shipping&#34;, &#34;SpecialBuy&#34;, &#34;store-search&#34;, &#34;storefinder&#34;, &#34;typeahead2&#34;, &#34;vectorsearch&#34;, &#34;wayfinder&#34;]"> 

    <style> 
     .deactivate-services-box, 
     .delete-services-box { 
      width: 400px; 

     } 

     .clear-button { 
      margin-left: 10px; 
     } 

    </style> 

</head> 

<body> 
    <nav class="navbar navbar-default navbar-fixed-top"> 
     <div class="container"> 
      <div class="navbar-header"> 
       <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> 
        <span class="sr-only">Toggle Navigation</span> 
        <span class="icon-bar"></span> 
        <span class="icon-bar"></span> 
        <span class="icon-bar"></span> 
       </button> 
       <a class="navbar-brand" href="/">SLO admin suite</a> 
      </div> 

      <a data-toggle="dropdown" class="dropdown-toggle" href="#"> 
      <div id="navbar" class="navbar-collapse collapse"> 
       <ul class="nav navbar-nav navbar-right"> 
         <li class="dropdown"> 
          Navigate 
          <span class="caret"></span> 
         <ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> 
          <li class="dropdown-header"><a href="/">Middleman backfill</a></li> 
          <li class="dropdown-header"><a href="/healthchecks">Healthcheck statuses</a></li> 
          <li class="dropdown-header"><a href="/delete-services">Delete/deactivate services</a></li> 
         </ul></li> 

       </ul> 
      </div></a> 
     </div> 
    </nav> 

    <script type="text/javascript"> 
    </script> 

    <div style="padding-top: 90px; float: left;" class="container"> 
     <div > 
      <div class=""> 

<!-- https://select2.github.io/examples.html --> 


    <meta id="my-data" 
     data-app-names="[&#34;cart&#34;, &#34;catalog&#34;, &#34;common-ui&#34;, &#34;content&#34;, &#34;ContentServices&#34;, &#34;cyc&#34;, &#34;deliverFromStore&#34;, &#34;fbr&#34;, &#34;fbt&#34;, &#34;irg&#34;, &#34;localization&#34;, &#34;mylist-domain-service&#34;, &#34;mylist-service&#34;, &#34;mylist-ui&#34;, &#34;nlpplus-service&#34;, &#34;nlpservices&#34;, &#34;orangegraph&#34;, &#34;passbookService&#34;, &#34;pricing&#34;, &#34;promotion&#34;, &#34;recommendations&#34;, &#34;registry&#34;, &#34;relatedsearch&#34;, &#34;review_service&#34;, &#34;sbotd-svcs&#34;, &#34;SearchNavServices&#34;, &#34;shipping&#34;, &#34;SpecialBuy&#34;, &#34;store-search&#34;, &#34;storefinder&#34;, &#34;typeahead2&#34;, &#34;vectorsearch&#34;, &#34;wayfinder&#34;]"> 

    <style> 
     .deactivate-services-box, 
     .delete-services-box { 
      width: 400px; 

     } 

     .clear-button { 
      margin-left: 10px; 
     } 

    </style> 


    <body> 
     <div id="centered"> 
      <span data-bind="visible: currently_running_ajax"> 
       <h4>Pretending to run deactivation/deletion for 3 secs...</h4> 
       <p><img src="/static/img/loader.gif"/></p> 

      </span> 

      <div id="ajax-return-error-message" style="position:fixed; top:10%; right:45%; color: red; z-index: 999; display: none;"></div> 

      <h2>Deactivate services</h2> 
      <select class="deactivate-services-box" multiple="multiple" data-bind="foreach: app_names"> 
       <option data-bind="value: $data, text: $data"></option> 
      </select> 
      <button id="deactivate-clear-all-button" class="clear-button">Clear all</button> 

      <h2>Permanently delete services</h2> 
      <select class="delete-services-box" multiple="multiple" data-bind="foreach: app_names"> 
       <option data-bind="value: $data, text: $data"></option> 
      </select> 
      <button id="delete-clear-all-button" class="clear-button">Clear all</button> 

      <br><br> 
      <p id="empty-set-error-message" style="color: red; display: none;">Please make a selection</p> 
      <button id="submit-button" data-bind="click: submit_deactivation_and_or_deletion" class="btn-primary btn-lg" style="margin-left: 20px; ">Submit</button> 
      <button id="submit-button" data-bind="click: submit_fails_demo" class="btn-info btn-lg" style="margin-left: 20px; ">Submit will fail</button> 

     </div> 

     <script type="text/javascript"> 
      var app_names = []; 
      console.log("app names 1"); 
      // knockout 
      function DeleteServicesViewModel(){ 
       var self = this; 

       self.app_names = app_names; 
       console.log("app names 2"); 
       self.currently_running_ajax = ko.observable(false); 

       // var djangoData = $('#my-data').data(); 
       // self.app_names = djangoData.appNames; 

       self.find_any_duplicates = function(list_one, list_two){ 
        var duplicates = []; 
        for (i = 0; i < list_one.length; i++){ 
         var item = list_one[i]; 
         if (_.contains(list_two, item)){ 
          duplicates.push(item); 
         } 
        } 
        return duplicates; 
       } 

       self.display_error_message = function(error){ 
        setTimeout(
         function() { 
          $("#ajax-return-error-message").text(error) 
          $("#ajax-return-error-message").slideDown(500, function(){ 
           setTimeout(function(){ 
            $("#ajax-return-error-message").slideUp(500); 
           }, 2600); 
          }); 
         }, 300 
        ); 
       } 

       self.submit_deactivation_and_or_deletion = function(){ 
        var deactivate_values = $deactivate_services_box.val(); 
        var deletion_values = $delete_services_box.val(); 
        // alert(deactivate_values); 

        if (deactivate_values.length == 0 && deletion_values.length == 0){ 
         $("#empty-set-error-message").slideDown(500, function(){ 
          setTimeout(function(){ 
           $("#empty-set-error-message").slideUp(500); 
          }, 1700); 
         }); 

         return; 
        } 

        var duplicates = self.find_any_duplicates(deactivate_values, deletion_values); 
        if (duplicates.length){ 
         alert("We cannot both delete and deactivate the same item. You have the following duplicates:\n\n%dups%\n\nPlease remove duplicates".replace("%dups%", duplicates)); 
         return; 
        } 
        console.log('duplicates: ', duplicates); 

        self.currently_running_ajax(true); 
        $.ajax({ 
         url: "/run-deactivation-and-deletion", 
         method: "POST", 
         headers: { 
          "Content-Type": "application/json" 
         }, 
         data: ko.toJSON(
          { deactivate_list: deactivate_values, deletion_list: deletion_values } 
         ), 
         success: function(data) { 
          console.log("worked"); 
          $deactivate_services_box.val(null).trigger("change"); 
          $delete_services_box.val(null).trigger("change"); 
         }, 
         error: function(xhr, textStatus, error) { 
          console.log("failed"); 
          console.log(error); 
          self.currently_running_ajax(false); 
          self.display_error_message(error); 

         }, 
         complete: function(){ 
          self.currently_running_ajax(false); 
         } 
        }); 
       } 

       // TODO: delete after demo 
       self.submit_fails_demo = function(){ 
        var deactivate_values = $deactivate_services_box.val(); 
        var deletion_values = $delete_services_box.val(); 
        // alert(deactivate_values); 

        if (deactivate_values.length == 0 && deletion_values.length == 0){ 
         $("#empty-set-error-message").slideDown(500, function(){ 
          setTimeout(function(){ 
           $("#empty-set-error-message").slideUp(500); 
          }, 1700); 
         }); 

         return; 
        } 

        var duplicates = self.find_any_duplicates(deactivate_values, deletion_values); 
        if (duplicates.length){ 
         alert("We cannot both delete and deactivate the same item. You have the following duplicates:\n\n%dups%\n\nPlease remove duplicates".replace("%dups%", duplicates)); 
         return; 
        } 
        console.log('duplicates: ', duplicates); 

        self.currently_running_ajax(true); 
        $.ajax({ 
         url: "/run-deactivation-and-deletion-fails-demo", 
         method: "POST", 
         headers: { 
          "Content-Type": "application/json" 
         }, 
         data: ko.toJSON(
          { deactivate_list: deactivate_values, deletion_list: deletion_values } 
         ), 
         // designed to fail for demo 
         error: function(xhr, textStatus, error) { 
          console.log("failed"); 
          console.log(error); 
          self.currently_running_ajax(false); 
          self.display_error_message(error); 
         }, 
         complete: function(){ 
          self.currently_running_ajax(false); 
         } 
        }); 
       } 
      } 

      $.getJSON("/app-names", function(data){ 
       var app_names_json_string = data.app_names; 
       var app_names = JSON.parse(data.app_names); 
       console.log("app names 3"); 

       ko.applyBindings(new DeleteServicesViewModel(app_names)); 

       var $deactivate_services_box = $(".deactivate-services-box"); 
       var $delete_services_box = $(".delete-services-box"); 

       $deactivate_services_box.select2(); 
       $delete_services_box.select2(); 

       $("#deactivate-clear-all-button").on("click", function() { $deactivate_services_box.val(null).trigger("change"); }); 
       $("#delete-clear-all-button").on("click", function() { $delete_services_box.val(null).trigger("change"); }); 
      }); 

      // ko.applyBindings(new DeleteServicesViewModel()); 

     </script> 
    </body> 


</div><br> 

     </div> 
    </div> 
</body> 
</html> 

inspiracji, które pomogą strona załadowała się w ogóle został znaleziony w wait for ajax result to bind knockout model

Chcę załadować html, zrobię gif przędzenia z napisem „loading”, zrób Wywołanie AJAX, aby uzyskać mój app_names, a kiedy app_names przybyć I dodać je do select2 pola i zainicjować select2.

Odpowiedz

2

o to skrzypce pracy dla Databinding jQuery select2 z wywołania AJAX. http://jsfiddle.net/LkqTU/33425/

nie wiem, czy jest lepszy sposób, ale umieszczam dane w części aktualizacji niestandardowego powiązania zamiast init, ponieważ jest on ładowany przez ajax i może nie być ich na początku.

ko.bindingHandlers.select2 = { 
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { 
     ko.bindingHandlers.value.init(element,valueAccessor, allBindings); 
      $(element).select2({ 
      }) 
    }, 
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) { 
     var data = allBindings.get('select2Data'); 
     var dataUnwrapped = ko.toJS(data); 
      $(element).select2({ 
       data: dataUnwrapped 
      }) 
     var value = valueAccessor(); 
     ko.bindingHandlers.value.update(element,valueAccessor); 
    } 
}; 
+1

Jako alternatywa, można po prostu owinąć kontrolkę 'select2' wewnątrz warunku' ko if: createSelect2', gdzie 'createSelect2' ma być' obserwowalnym' zwracającym 'boolean '. Domyślnie jest to "false", a gdy 'promise' wróci z wywołania' ajax', ustaw to na 'true'. To zbuduje kontrolę po tym, jak jesteś pewien, że masz niezbędne dane i nadal możesz "zainicjować" sterowanie tak, jak zwykle. – gkb

+0

@gkb Myślałem, że próbowałem czegoś podobnego, owijając select2 w ".done()" rzecz.idk co zrobiłem źle, jestem pewien, że po prostu zaimplementowałem go źle, ponieważ uczę się asynchronicznego js rzeczy – codyc4321

+0

to jest bardzo piękne i wydaje się działać dla każdego elementu select2, więc wybieram go – codyc4321

2

Może to pomóc w opanowaniu ładowania elementów do DOM po wywołaniu AJAX.

Najpierw należy najpierw zainicjalizować model widoku (var PageModel in my instance).

Listy wyboru to obserwowalneArrays. Zainicjuj je puste, wykonaj połączenie ajaxowe.

Wywołanie ajax jest odłożone, a funkcja .then() ma dwie paramy, sukces xhr i xhr kończą się niepowodzeniem.

Jeśli ajax się powiedzie, umieść wyniki w obserwowalnymArtyku.

Skonfiguruj niektóre elementy warunkowe w swoim html, aby zapobiec wysyłaniu przez KO błędów informujących, że nie ma tu danych lub właściwości, do których próbujesz uzyskać dostęp, są niedostępne.

W końcu odłóż od tego, że możesz zasubskrybować obserwowalne obiekty i zrobić coś, gdy wartość się zmieni, w tym przypadku przechodzi z pustej tablicy do tablicy z danymi. Kiedy wiesz, że ma dane, zainicjuj pliki select2, które uznasz za stosowne.

Możesz wykonywać różne rzeczy za pomocą asynchronicznych wywołań danych. Można użyć wiązania kliknięcia, aby uruchomić funkcję ładującą dane do innych obserwowalnych w celu pobrania listy elementów lub nowego obrazu (np. Drugi przykład).

var PageModel = function(r) { 
 
    var self = this; 
 

 
    this.Select1 = ko.observableArray([]); 
 
    self.Select1.subscribe(function (val) { 
 
    if (val) { 
 
     // Run function to initialize Select2 box 
 
     // $('#some-select2-thingy').select2 stuff or whatever 
 
     console.log('This select has data now'); 
 
    } 
 
    }); 
 
    this.Loading1 = ko.observable(false); 
 
    this.Errors = ko.observableArray([]); 
 
    ajaxCall('https://api.punkapi.com/v2/beers', 'GET', self.Select1, self.Loading1, self.Errors); 
 

 
    this.Image = ko.observable(); 
 
    this.Loading2 = ko.observable(false); 
 
    this.LoadImage = function() { 
 
    ajaxCall('https://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=american+psycho', 'GET', self.Image, self.Loading2, self.Errors) 
 
    } 
 
    this.ClearImage = function() { 
 
    self.Image(null); 
 
    } 
 
}; 
 

 
window.model = new PageModel(); 
 
ko.applyBindings(model); 
 

 
function ajaxCall(url, method, selectObj, loadingObj, errorObj) { 
 
    return $.when($.ajax({ 
 
    url: url, 
 
    method: method, 
 
    beforeSend: function() { 
 
     loadingObj(true); 
 
    } 
 
    })).then(function(response) { 
 
    selectObj(response); 
 
    loadingObj(false); 
 
    }, function(error) { 
 
    errorObj.push(error); 
 
    }); 
 
};
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/> 
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script> 
 

 
<div class="container-fluid" style="margin-top: 30px;"> 
 
    <div class="row" style="margin-bottom: 30px;"> 
 
    <div class="col-sm-6 col-sm-push-3"> 
 
     <p class="alert alert-info" data-bind="visible: Loading1()">Loading...</p> 
 
     <!-- ko if: Select1().length && !Loading1() --> 
 
     <select id="some-select2-thingy" class="form-control" data-bind="options: Select1, optionsText: 'name', optionsValue: 'id', optionsCaption: '- Select a Beer -'"></select> 
 
     <!-- /ko --> 
 
    </div> 
 
    </div> 
 
    <div class="row"> 
 
    <div class="col-sm-6 col-sm-push-3"> 
 
     <div class="text-center" style="margin-bottom: 10px;"> 
 
     <button class="btn btn-info" data-bind="click: LoadImage, text: Image() ? 'Get A Different Image' : 'Get An Image'">Get An Image</button> 
 
     <button class="btn btn-danger" data-bind="click: ClearImage, visible: Image()">Clear Image</button> 
 
     </div> 
 
     <p class="alert alert-info" data-bind="visible: Loading2()">Loading...</p> 
 
     <!-- ko if: Image() && !Loading2() --> 
 
     <!-- ko with: Image --> 
 
     <div class="text-center"> 
 
     <img data-bind="attr: {'src': data.fixed_height_downsampled_url}"> 
 
     </div> 
 
     <!-- /ko --> 
 
     <!-- /ko --> 
 
    </div> 
 
    </div> 
 
</div>

+0

brewdog wydaje się całkiem fajny. świetny przykład. obaj macie rację, idk kto wybrać – codyc4321