KnockoutJS race condition during ajax call - javascript

Here is a weird race condition happening with knockoutjs. I'm setting two observables independantly using ajax calls. One is a list, the other is a single value. The weird thing is when I load the single value before the list, it won't bind correctly. Any suggestions?
JsFiddle: http://jsfiddle.net/JasonMore/bxfXd/110/
View
<form data-bind='submit:addItem'>
Add item: <input data-bind='value:itemToAdd, valueUpdate: "afterkeydown"' type='text' />
<button data-bind='enable: isAddButtonEnabled' type='submit'>Add</button>
</form>
<p>Your values:</p>
<select data-bind='options:allItems, value:selectedItems' height='5'> </select>
<div>
<button data-bind='click: removeSelected'>Remove</button>
<button data-bind='click: function() { allItems.sort() }, enable: allItems().length > 1'>Sort</button>
</div>
</div>
Code
var betterListModel = function() {
var self = this;
// properties
this.itemToAdd = new ko.observable("");
this.allItems = new ko.observableArray();
this.selectedItems = new ko.observable('');
// computed
this.isAddButtonEnabled = ko.computed(function() {
return self.itemToAdd().length > 0
});
//methods
this.addItem = function() {
if ((this.itemToAdd() != "") && (this.allItems.indexOf(this.itemToAdd()) < 0)) this.allItems.push(this.itemToAdd());
this.itemToAdd("");
}
this.removeSelected = function() {
this.allItems.removeAll(this.selectedItems());
this.selectedItems();
} };
var view = new betterListModel();
ko.applyBindings(view);
// load $.ajax({
url: '/echo/json/',
type: 'post',
data: {
json: $.toJSON("Ham"),
delay: 1
},
success: function(data) {
view.selectedItems(data);
} });
$.ajax({
url: '/echo/json/',
type: 'post',
data: {
json: $.toJSON(["Fries", "Eggs Benedict", "Ham", "Cheese"]),
delay: 2
},
success: function(data) {
$.each(data, function(index, value) {
view.allItems.push(value);
});
} });

Try this-->
// Whenever the states changes, reset the selectedState selection
this.allItems.subscribe(function () {
this.selectedItems(arrayOfMySelectedItems);
});

Related

javascript make select 2 use url set by outside source

I'm using select2 to pick out games from a database, however, the file I wish it to search from will change depending on what's selected from a dropdown.
How do I get it so select2 always uses the most up to date "picker_url"?
So if I select a certain option from a select box on a page, it changes the "picker_url" (an ajax file to do the search). The problem is, select2 only seems to use the original value.
Here's my current code:
var picker_url = "test1.php";
$(document).on('change', ".category_select", function(e)
{
var id = $(this).val();
if (id == 16)
{
picker_url = "test2.php";
}
});
$(".game_picker").select2({
selectOnClose: true,
width: '100%',
ajax: {
url: picker_url,
dataType: 'json',
delay: 250,
data: function (params) {
return {
q: params.term // search term
};
},
processResults: function (data) {
return {
results: $.map(data, function(obj) {
return { id: obj.id, text: obj.text };
})
};
},
cache: true,
},
minimumInputLength: 2
});
Found the answer here: https://github.com/select2/select2/issues/1679#issuecomment-280080742
var someCondition
ajax: {
url: function() {
if (someCondition) {
return '/api/1/someFile.json'
} else {
return '/api/1/someOtherFile.json'
}
}
}
I suggest to use dynamic-urls, like the code below:
$('#mySelect2').select2({
ajax: {
url: function (params) {
return '/some/url/' + params.term;
}
}
});
Inside url function you can test other variables than params, like in the following snippet:
$('#category').select2({
placeholder: "Select category...",
width: '100%',
});
$('#category').on('select2:select', function(e) {
var data = e.params.data;
console.log("category", data);
categ = e.params.data.id;
});
var categ = "1";
$('#project').select2({
placeholder: "Select item...",
width: '100%',
ajax: {
type: "GET",
url: function(params) {
console.log("ajax func", params, categ);
var url = 'https://jsonplaceholder.typicode.com/comments?postId=' + categ
return url;
},
cache: true,
processResults: function(data) {
return {
results: $.map(data, function(obj) {
return {
id: obj.id,
text: obj.name
};
})
};
},
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
<div class="group">
<select id="category">
<option value="1">cat 1</option>
<option value="2">cat 2</option>
</select>
</div>
<br>
<div class="group">
<select id="project">
<option value=""></option>
</select>
</div>
I would save off your default options and then recreate the select2 whenever you need to by extending your new URL into the default options:
var defaultOptions = {
selectOnClose: true,
width: '100%',
ajax: {
url: "test1.php",
dataType: 'json',
delay: 250,
data: function (params) {
return {
q: params.term // search term
};
},
processResults: function (data) {
return {
results: $.map(data, function(obj) {
return { id: obj.id, text: obj.text };
})
};
},
cache: true,
minimumInputLength: 2
};
//Use default to create first time
$(".game_picker").select2(defaultOptions);
//On change, recreate
$(document).on('change', ".category_select", function(e)
{
var options = defaultOptions;
if ($(this).val() == 16)
{
//Create a new options object with the url updated
options = $.extend({}, defaultOptions, { url: 'test2.php' });
}
//Create a select2 with the desired options
$(".game_picker").select2(options);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Add autocomplete input control as part of knockout view model

I have the following markup where any skills that exists as part of my view model are displayed. I have a button in my html that runs the self.create function in my view model to create a new item in the EmployeeSkillsArray IF the user decides to add a new skill. The input control below works as expected when the autocomplete portion is removed, however, my goal is to allow a user to begin typing a skill and if that skill exists allow them to select it from autocomplete. It is THIS autocomplete functionality that seems to be causing the issue within the EmployeeSkillsArray. I am able to see autocomplete working when an input control is placed OUTSIDE the array in my markup.
Is there a way to accomplish this goal of having an input control inside the foreach loop to display an item if it is in the array, otherwise allow the user to use autocomplete?
<tbody data-bind="foreach: EmployeeSkillsArray">
<tr>
<td class="col-xs-2">
<input type="hidden" data-bind="value: EmployeeSkillId, visible: false" />
<div class="input-group">
<input type="text" id="skillName" class="form-control" placeholder="Type a skill..." data-bind="value: SkillName, id: SkillsId, autoComplete: { selected: $root.selectedOption, options: $root.options }" /> <!-- corrected based on answer provided by f_martinez -->
</div>
</td>
</tr>
</tbody>
I am getting an Uncaught ReferenceError: Unable to process binding "autoComplete: function (){return { selected:selectedOption,options:options} }"
Message: selectedOption is not defined
I DO have selectionOption defined as part of my view model....
$(function () {
ko.bindingHandlers.autoComplete = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var settings = valueAccessor();
var selectedOption = settings.selected;
var options = settings.options;
var updateElementValueWithLabel = function (event, ui) {
event.preventDefault();
$(element).val(ui.item.label);
if(typeof ui.item !== "undefined") {
selectedOption(ui.item);
}
};
$(element).autocomplete({
source: options,
select: function (event, ui) {
updateElementValueWithLabel(event, ui);
},
focus: function (event, ui) {
updateElementValueWithLabel(event, ui);
},
change: function (event, ui) {
updateElementValueWithLabel(event, ui);
}
});
}
};
function ActivityViewModel() {
var self = this;
var remoteData;
$.ajax({
url: '#Url.Action("GetAllSkills", "EmployeeSkills")',
data: { },
async: false,
dataType: 'json',
type: 'GET',
contentType: "application/json; charset=utf-8",
success: function (data) {
remoteData = ($.map(data, function (item) {
return {
id: item.SkillId,
name: item.SkillName
};
}));
}
});
self.skills = remoteData;
self.selectedOption = ko.observable('');
self.options = ($.map(self.skills, function (element) {
return {
label: element.name,
value: element.id,
object: element
};
}));
var EmployeeSkill = {
EmployeeSkillId: self.EmployeeSkillId,
EmployeeId: self.EmployeeId,
SkillsId: self.SkillsId,
SkillName: self.SkillName,
SkillLevelId: self.SkillLevelId,
ApplicationUsed: self.ApplicationUsed,
selectedOption: self.selectedOption,
options: self.options
};
self.EmployeeSkill = ko.observable();
self.EmployeeSkillsArray = ko.observableArray();
$.ajax({
url: '#Url.Action("GetAllEmployeeSkills", "EmployeeSkills")',
cache: false,
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: ko.toJSON({ 'UserId' : employeeId, 'ActivityHistoryId' : activityHistoryId }),
success: function (data) {
self.EmployeeSkillsArray(data); // Put the response in ObservableArray
}
});
self.cloneRow = function () {
$.ajax({
url: '#Url.Action("AddSkill", "EmployeeSkills")',
cache: false,
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: {},
success: function (data) {
self.EmployeeSkillsArray.push(data);
}
}).fail(function (xhr, textStatus, err) {
alert(err);
});
}
}
var viewModel = new ActivityViewModel();
ko.applyBindings(viewModel);
});

How to set the id for the new created item with Knockout

The problem is that I cannot update a new created item, because on the server I receive no Id.
I am trying to learn Knockout, and I am not able to find a way to provide the Id to new created items.
I have an object with Id, and Name, using knockout I can make all Crud operations, but, after inserting a new item, if I try to change his name I am not able because that item has no Id value.
My question is: Every time when I add a new item I need to get a fresh collection of items back to the view, and rebind the view?
or, there is a way to another way to provide the Id to the new inserted items?
Here is my code:
function Person(id, name) {
var self = this;
self.Id = ko.observable(id);
self.nume = ko.observable(name);
}
function PersonVm() {
var self = this;
self.Persons = ko.observableArray([]);
self.newPerson = ko.observable(new Person())
self.isModificare = false;
self.addPerson = function () {
if (!self.isModificare) {
$.ajax("/Person/AddPerson", {
data: ko.toJSON({ Person: self.newPerson }),
type: "post", contentType: "application/json",
success: function (result) { alert(result.mesaj); }
});
} else {
$.ajax("/Person/UpdatePerson", {
data: ko.toJSON({ Person: self.newPerson }),
type: "post", contentType: "application/json",
success: function (result) { alert(result) }
});
}
self.isModificare = false;
if (!self.isModificare) self.Persons.unshift(self.newPerson());
self.newPerson(new Person());
}
self.removePerson = function () {
$.ajax("/Person/DeletePerson", {
data: ko.toJSON({ Person: self.newPerson }),
type: "post", contentType: "application/json",
success: function (result) { alert(result) }
});
self.Persons.remove(self.newPerson());
self.newPerson(new Person());
}
self.ModificaPerson = function (person) {
self.newPerson(person);
self.isModificare = true;
}
$.getJSON("/Person/GetPersons", function (allData) {
var mapPerson = $.map(allData, function (item) { return new Person(item.Id,item.Name) });
self.Persons(mapPerson);
});
}
ko.applyBindings(new PersonVm());
Edit:
This is the view:
<div class="input-group">
<input type="text" class="form-control" data-bind="value: newPerson().name">
<span class="input-group-btn">
<button class="btn btn-default" data-bind="click:addPerson">
<span class="glyphicon glyphicon-plus-sign" style="color:green"></span>
</button>
</span>
<span class="input-group-btn">
<button class="btn btn-default" data-bind="click:$root.removePerson">
<span class="glyphicon glyphicon-trash" style="color:red"></span>
</button>
</span>
</div>
<ul data-bind="foreach: Perons" class="list-group" id="content">
<li class="list-group-item" data-bind="text: name,click:$root.ModificaPerson"></li>
</ul>
Two steps to get what you want:
Ensure that your "Person/AddPerson" Web service return the ID of the created object.
Change your update method so that it sets an ID on the newPerson property:
$.ajax("/Person/AddPerson", {
data: ko.toJSON({ Person: self.newPerson }),
type: "post", contentType: "application/json",
success: function (result) {
self.newPerson().Id(result.Id);
}
});
Note that the above code supposes that your services returns a JSON object with an ID property named 'Id'.

how to bind selectlist to value using json in javascript

I'm new to web application development and i need to bind values which i retrieved from json object. i tried several ways but couldn't able to bind values to my combo box.
<script type="text/javascript">
var ViewModel = {
CheckIn : ko.observable(),
CheckOut: ko.observable(),
Lunch: ko.observable(),
Rest: ko.observable(),
WorkOnProject: ko.observable(),
Projects: ko.observableArray()
};
this.GetProjects = function () {
$.ajax({
type: "POST",
url: 'TimeRecord.aspx/ReturnComplexType',
data: {},
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (arg) {
for (var i = 0; i < arg.d.length ; i++) {
var value = arg.d[i].ProjectCode;
var option = new Option(arg.d[i].ProjectCode, arg.d[i].ProjectCode);
Select1.add(option, null);
}
},
error: function (arg) {
}
});
};
ko.applyBindings(ViewModel);
</script>
My HTML Code:
<tr>
<td class="auto-style1">Project Code </td>
<td ><select id="Select1" data-bind='options: Projects' style="width: 312px"></select>
<button data-bind='click: GetProjects'>Cancel</button>
</td>
</tr>
My Sever Side Coding :
[WebMethod]
public static object ReturnComplexType()
{
List<Project> projects = new List<Project>();
Project p = new Project();
p.ProjectID = 1;
p.ProjectCode = "ABC";
p.ProjectName = "Test";
projects.Add(p);
Project p2 = new Project();
p2.ProjectID = 2;
p2.ProjectCode = "DEF";
p2.ProjectName = "xsd";
projects.Add(p2);
return projects;
}
Your structure is way off, you're mixing object instances with function on the window object..
This is one way of solving it
ViewModel = function() {
this.CheckIn = ko.observable();
this.CheckOut = ko.observable();
this.Lunch = ko.observable();
this.Rest = ko.observable();
this.WorkOnProject = ko.observable();
this.Projects = ko.observableArray()
};
ViewModel.prototype = {
GetProjects: function () {
$.ajax({
type: "POST",
url: 'TimeRecord.aspx/ReturnComplexType',
data: {},
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
this.Projects(data);
}.bind(this),
error: function (arg) {
}
});
};
};
ko.applyBindings(new ViewModel());
What I did was to move the GetProjects function to the model object
Your select box is bound to the Projects observable but you don't set an explicit text/value
<select id="Select1" data-bind='options: Projects, optionsText: 'ProjectName', optionsValue:'ProjectID', value: SelectedProjectId"' style="width: 312px"></select>
The SelectedProjectId would be another observable in your model if you need to save the value somewhere.
The other thing you'll want to change is filling the actual observable array instead of select box directly.
<script type="text/javascript">
function ViewModel() {
var self = this;
self.CheckIn = ko.observable();
self.CheckOut = ko.observable();
self.Lunch = ko.observable();
self.Rest = ko.observable();
self.WorkOnProject = ko.observable();
self.Projects = ko.observableArray();
};
var VM = new ViewModel();
ko.applyBindings(ViewModel);
$.ajax({
type: "POST",
url: 'TimeRecord.aspx/ReturnComplexType',
data: {},
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (arg) {
for (var i = 0; i < arg.d.length ; i++) {
VM.Projects.push(d[i]);
}
},
error: function (arg) {
}
});
</script>
After you get things binding right you'll probably want to swap out the VM.Projects.push() for a speedier way.
Calling .push() when you're filling up arrays on initial load triggers a ton of notifications that can really make the page crawl.

Knockout JS calling functions on load

I am using Knockout.js to build a client-side view model. In my view model I would like to expose some functions that can be bound to elements in the page (typical MVVM model). I only want these functions to be called in response to a click event from a button, however they are been called when the view model is been constructed...
I have defined my model like this:
<script type="text/javascript">
var ViewModel = function(initialData) {
var self = this;
self.id = initialData;
self.isSubscribed = ko.observable(false);
self.name = ko.observable();
self.SubscribeToCategory = function () {
$.ajax({
url: '#Url.Action("Subscribe", "Category")',
type: 'POST',
data: {
categoryId: self.id
},
success: function () {
self.isSubscribed(true);
},
failure: function () {
self.isSubscribed(false);
}
});
alert('Subscribing...');
};
self.UnsubscribeFromCategory = function () {
$.ajax({
url: '#Url.Action("Unsubscribe", "Category")',
type: 'POST',
data: {
categoryId: self.id
},
success: function () {
self.isSubscribed(false);
},
failure: function () {
self.isSubscribed(true);
}
});
alert('Unsubscribing...');
};
self.LoadCategory = function () {
$.ajax({
url: '#Url.Action("GetCategory", "Category")',
type: 'POST',
async: true,
data: {
categoryId: self.id
},
dataType: 'json',
success: function (data) {
self.isSubscribed(data.IsSubscribed);
self.name(data.Name);
}
});
};
self.LoadCategory();
};
$(document).ready(function () {
var data = '#Model';
var viewModel = new ViewModel(data);
ko.applyBindings(viewModel);
});
When I execute the code however, the two alerts fire automatically, but I am not expecting them to. I am using ASP MVC4, and the HTML that is using the view model is below:
<p>
ID: <span data-bind="text: id"></span>
</p>
<div id="subscribe" data-bind="visible: isSubscribed == false">
<button data-bind="click: SubscribeToCategory()">Subscribe</button>
</div>
<div id="unsubscribe" data-bind="visible: isSubscribed == true">
<button data-bind="click: UnsubscribeFromCategory()">Unsubscribe</button>
</div>
<div>
Category Name: <span data-bind="text: name"></span>
Is Subscribed: <span data-bind="text: isSubscribed"></span>
</div>
I've looked through the tutorials online and some other knockout samples, as well as other places in my code where I have used knockout, but I cannot see why the two functions (SubscribeToCategory and UnsubscribeFromCategory) are firing on document load.
jsfiddle
It took me a second, but ended up being a simple fix. remove the () from your data-bind="click: SubscribeToCategory()" and make both you click handlers this data-bind="click: SubscribeToCategory" and data-bind="click: UnsubscribeFromCategory"
It appears that the brackets in the function name in the binding <button data-bind="click: SubscribeToCategory()">Subscribe</button> is the problem.
The ()'s shouldn't be there. So it should look like:
<button data-bind="click: SubscribeToCategory">Subscribe</button>

Categories