2017-01-24 2 views
6

Я хочу загрузить страницу сразу, а затем загрузить данные, чтобы заполнить поля select2. Используя Knockout, я не получаю ошибок, но не вижу никаких элементов в моих select2 select. Загрузка синхронно с сервера работает, но очень медленная (из-за получения app_names). У меня до сих пор:Загрузите страницу асинхронно в 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> 

вдохновение, чтобы помочь загрузилась страница на всех была найдена в wait for ajax result to bind knockout model

Я хочу, чтобы загрузить HTML, я сделаю спиннинг GIF, который говорит «загрузка», сделать AJAX, чтобы получить мой app_names, и когда app_names придет, я добавляю их в select2 и инициализирую select2.

ответ

2

здесь является рабочей скрипкой для привязки данных в JQuery ВЫБ.2 из вызова Ajax. http://jsfiddle.net/LkqTU/33425/

не уверен, если есть лучший способ сделать это, но я поместить данные в части обновления обычая связывания вместо инициализации, поскольку она загружается с помощью AJAX и не может быть их сначала.

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

В качестве альтернативы вы можете просто обернуть элемент 'select2' внутри условия' ko if: createSelect2', где 'createSelect2' ожидается как' наблюдаемый' возвращающий 'boolean '. По умолчанию это «false», и как только «обещание» вернется из вызова 'ajax', установите для него значение« true ». Это создаст элемент управления после того, как вы убедитесь, что у вас есть необходимые данные и по-прежнему сможете «инициализировать» управление, как это было сделано нормально. – gkb

+0

@gkb Я думал, что попробовал что-то подобное, обернув select2 в «.done()», вещь.idk, что я сделал неправильно, хотя, я уверен, что я просто внедрил это неправильно, поскольку я изучаю асинхронный js-материал – codyc4321

+0

, это очень красиво и, похоже, работает для любого элемента select2, поэтому я выбираю его – codyc4321

2

Это может помочь вам обернуть голову вокруг загрузки элементов в DOM после вызова AJAX.

Сначала инициализируйте свою модель просмотра (var PageModel в моем случае).

Списки выбора: наблюдаемые массивы. Инициализируйте их пустыми, сделайте свой вызов ajax.

Ajax-вызов отложен, а функция .then() имеет два параметра, xhr-успех и xhr fail.

Если ajax успешно помещает результаты в наблюдаемый массив.

Настройте некоторые условные обозначения в своем html, чтобы предотвратить ошибки KO, указав, что здесь нет данных, или свойства, которые вы пытаетесь получить, недоступны.

И наконец, с учетом того, что вы можете подписаться на наблюдаемые и что-то сделать, когда значение изменяется, в этом случае оно переходит из пустого массива в массив с данными. Когда вы знаете, что у него есть данные, инициализируйте свой материал select2, как вы считаете нужным.

Вы можете делать всевозможные аккуратные вещи, используя асинхронные вызовы. Вы можете использовать привязку кликов для запуска функции, которая загружает данные в другие наблюдаемые, чтобы получить список элементов или новое изображение (IE - второй пример).

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 похоже довольно прохладный. отличный пример. вы оба правы, idk, кто выбирает – codyc4321

Смежные вопросы