【问题标题】:Load page asynchronously in Knockoutjs在 Knockoutjs 中异步加载页面
【发布时间】:2017-11-07 08:25:47
【问题描述】:

我想立即加载一个页面,然后加载数据以填充 select2 框。使用 Knockout,我最终没有收到任何错误,但在我的 select2 select 框中看不到任何项目。从服务器同步加载可以工作,但速度很慢(因为得到app_names)。我到目前为止:

<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。

【问题讨论】:

    标签: javascript jquery knockout.js


    【解决方案1】:

    这里是一个工作小提琴,用于从 ajax 调用中对 jquery select2 进行数据绑定。 http://jsfiddle.net/LkqTU/33425/

    不确定是否有更好的方法,但我将数据放在自定义绑定的更新部分而不是 init 中,因为它是由 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);
        }
    };
    

    【讨论】:

    • 作为替代方案,您可以将select2 控件包装在ko if: createSelect2 条件中,其中createSelect2 预计是observable 返回boolean。默认为false,一旦promiseajax调用返回,设置为true。这将在您确定您拥有必要的数据并且仍然能够像往常一样init 控件之后构建控件..
    • @gkb 我以为我尝试了类似的方法,将 select2 包装在“.done()”中。我知道我做错了什么,我确定我只是在学习异步 js 的东西时实现了错误
    • 这非常漂亮,似乎适用于任何 select2 元素,所以我选择它
    【解决方案2】:

    这可能有助于您在 AJAX 调用后将项目加载到 DOM 中。

    首先初始化您的视图模型(在我的实例中为 var PageModel)。

    选择列表是 observableArrays。将它们初始化为空,进行 ajax 调用。

    ajax 调用被延迟,.then() 函数有两个参数,一个 xhr 成功和 xhr 失败。

    如果 ajax 成功,则将结果放入 observableArray。

    在你的 html 中设置一些条件,以防止 KO 抛出错误,说这里没有数据,或者你试图访问的属性不可用。

    最后,您可以订阅 observables 并在值更改时执行某些操作,在这种情况下,它会从一个空数组变为包含数据的数组。当您知道它有数据时,根据您认为合适的方式初始化您的 select2 内容。

    您可以使用对数据的异步调用来做各种简洁的事情。您可以使用点击绑定来运行将数据加载到其他可观察对象的函数,以检索项目列表或新图像(即第二个示例)。

    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>

    【讨论】:

    • brewdog 看起来很酷。很好的例子。你说的都对,不知道该选谁
    猜你喜欢
    • 1970-01-01
    • 2015-01-17
    • 1970-01-01
    • 2011-12-28
    • 1970-01-01
    • 2013-12-04
    • 1970-01-01
    • 2011-10-05
    • 2012-02-06
    相关资源
    最近更新 更多