I want to load a page immediately, then load data to fill in select2 boxes afterward. Using Knockout, I get no errors finally, but see no items in my select2 select
boxes. Loading synchronously from server works, but very slow (because of getting app_names
). I have so far:
<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="["cart", "catalog", "common-ui", "content", "ContentServices", "cyc", "deliverFromStore", "fbr", "fbt", "irg", "localization", "mylist-domain-service", "mylist-service", "mylist-ui", "nlpplus-service", "nlpservices", "orangegraph", "passbookService", "pricing", "promotion", "recommendations", "registry", "relatedsearch", "review_service", "sbotd-svcs", "SearchNavServices", "shipping", "SpecialBuy", "store-search", "storefinder", "typeahead2", "vectorsearch", "wayfinder"]">
<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="["cart", "catalog", "common-ui", "content", "ContentServices", "cyc", "deliverFromStore", "fbr", "fbt", "irg", "localization", "mylist-domain-service", "mylist-service", "mylist-ui", "nlpplus-service", "nlpservices", "orangegraph", "passbookService", "pricing", "promotion", "recommendations", "registry", "relatedsearch", "review_service", "sbotd-svcs", "SearchNavServices", "shipping", "SpecialBuy", "store-search", "storefinder", "typeahead2", "vectorsearch", "wayfinder"]">
<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>
The inspiration to help the page load at all was found in wait for ajax result to bind knockout model
I want to load html, I'll do the spinning gif that says "loading", make an AJAX call to get my app_names
, and when app_names
arrive I add them to select2 boxes and initialize select2.
here is a working fiddle for databinding the jquery select2 from an ajax call. http://jsfiddle.net/LkqTU/33425/
not sure if there is a better way to do it but I put the data in the update portion of the custom binding instead of the init since it is being loaded by ajax and may not be their at first.
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);
}
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With