I am making an application. I have a form where the user can type in a product. I get the price with an angularjs service. I inject it in. RateInfoService.
So if I type in "Milk" it will say in the DOM {{rate}} -> $2
But if I fill in chips, it will change offcourse to {{rate}} -> $1.50
But what I want to fill in Milk and after that Chips.
So I want it to show in the html page
Milk->$2 & Chips->$1.50.
So it will show both items, the user can keep typing in products and submitting it. (ng-click). I might be able to do that in an array but note that I have a refresh rate. Not sure if that's possible to do with an ng-repeat or even if that is possible.
.controller("MainCtrl", function($scope, $http, $timeout, $ionicPlatform, $cordovaLocalNotification, $ionicPopup, RateInfoService, AlarmService, MonitorService) {
$scope.refreshRate = 5000;
$scope.refreshData = function() {
RateInfoService.getMarket($scope.alarmingMarket).success(function(data) {
$scope.rate = data.query.results.rate.Rate;
$scope.updateTime = data.query.results.rate.Time;
})
}
<label class="item item-input">
<span class="input-label">Product name</span>
<input type="text" min="3" ng-model="hard" name="text" autofocus required>
</label>
Also some extra info
$scope.submit = function() {
$scope.alarmingMarket = $scope.hard
console.log($scope.monitors);
};
$scope.alarmingMarket = $scope.hard
I used this so it would only get the market price after the user has filled in & pressed submit.
RateInfoService to get the price: http://pastebin.com/gHfhzMjR
I just found out it's possible to dump multiple objects in the url.
https://query.private%20in%20("Milk","Chips")&format=json&diagnostics=true&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=
How can I do this the best way in angular? i'm new in angularjs (3 weeks)
this is the Json formatted view (Explains the $scope.rate = data.query.results.rate.Rate;)
"results": {
"rate": [
{
"id": "MILK",
"Name": "MILK",
"Rate": "1.1054",
"Date": "10/26/2015",
"Time": "9:37pm",
"Ask": "1.1056",
"Bid": "1.1052"
},
{
"id": "CHIPS",
"Name": "CHIPS",
"Rate": "1.5349",
"Date": "10/26/2015",
"Time": "9:37pm",
"Ask": "1.5352",
"Bid": "1.5346"
I hope this was enough info.
*** Not sure why the styling goes wrong. Sorry for the messy page. Stack overflow gives a good preview of this post, but something goes wrong when I publish it. A lot of the code isn't greyed out
My solution
$scope.alarmingMarket = [];
$scope.submit = function() {
// $scope.alarmingMarket = $scope.hard
$scope.alarmingMarket.push($scope.hard);
console.log($scope.alarmingMarket);
};
This to push everything in the $scope.alarmingMarket array.
A service to get the rates
module.service('RateInfoService', function($http) {
return {
getMarket: function(alarmingMarket) {
var market = alarmingMarket
return $http.get('https://query.private%20in%20(%22'+ market + '%22)&format=json&diagnostics=true&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=')
}
}
})
market is the array.
and to parse the Json
$scope.refreshData = function() {
RateInfoService.getMarket($scope.alarmingMarket).success(function(data) {
if($scope.alarmingMarket.length <= 1){ //if alarmingMarket[] has only 1 market
$scope.rate = [];
$scope.rate.marketid = data.query.results.rate.id;
$scope.rate.rate = data.query.results.rate.Rate;
$scope.rate.updateTime = data.query.results.rate.Time;
console.log($scope.rate);
}else{
angular.forEach(data.query.results.rate, function (value, key) {
$scope.rate = [];
$scope.rate.marketid = data.query.results.rate[key].id;
$scope.rate.rate = data.query.results.rate[key].Rate;
$scope.rate.updateTime = data.query.results.rate[key].Time;
console.log($scope.rate);
});
}
})
Sorry don't have enough rep to post a comment. Please give me a plus if this helps so I can get more rep.
From what you posted it seems like you are trying to repeat over items selected by the user and then getting each new rate per item (not sure if $scope.alarmingMarket is an array of items or not).
If you wanted to get all the latest rates for items selected by the user, you have to pass the entire array of items up and get back an array and let the ng-repeat do the work by assigning the value to the $scope.alarmingMarket (if that was your item list).
Related
I am trying to make a REST call to get the total count of the cities based on the text entered in a textbox.
I am making the same REST call the second time to get 25 records for the same search text.
For example if I entered the text Paris, the 1st rest call will take the maxPage size to 1000 and get me all the records.
And the second time, the maxPage is set to 25 to get the first 25 records.
The idea is to show the total number of records matching the search text and also show the 1st 25 records.
(for ex: 25/400)
The problem that I am facing is by the time I am done with entering the text, the 1st rest call is taking only a part of the text and the 2nd call is taking the whole text.
The count returned for the text is not matching the 2nd call's response.
For example: the 1st call is ending up with the text 'Par' and getting me 250 results where as the 2nd call is taking 'Paris'. I am ending up with 25/250 which is not correct.
May be this is because of some timing issue since the searchCities is called on ng-change. I cannot introduce a button on the screen so it has to be ng-change only.
Please help me resolve this.
Please have a look at the below code.
$scope.getCityCount = function(phrase){
var searchPhrase = phrase;
var dataObj = { term: searchPhrase, maxPage: 1000, pagefrom: 0 };
var srchdata = JSON.stringify( dataObj );
Cities.query( {}, srchdata, function(cityData) { //1st REST call
if (cityData.status == "success") {
$scope.cityCount= cityData.data.length; //total records
$scope.loadMoreCities(searchPhrase); //2nd call:to get 25 records
}
});
}
$scope.loadMoreCities = function(phrase){
var searchParam = { term: phrase, maxPage: 25, pagefrom: 0 };
Cities.query( {}, searchParam, function(dataObj) {
if (dataObj.status == "success") {
var citiesFound = dataObj.data;
}
});
}
$scope.searchCities = function( phrase ) {
if(phrase.length > 1){
$scope.getCityCount(phrase);
}
}
EDIT:
Though I am searching for Paris, I am getting final result for Pa. Please see the logs::
getSearchCount#1: {"searchTerm":"Paris","maxPage":10000,"pagefrom":0}is=====400
getSearchCount#2: {"searchTerm":"Paris","maxPage":25,"pagefrom":0}is===== 25
getSearchCount#1: {"searchTerm":"Pari","maxPage":10000,"pagefrom":0}is===== 105
getSearchCount#2: {"searchTerm":"Pari","maxPage":25,"pagefrom":0}is===== 25
getSearchCount#1: {"searchTerm":"Pa","maxPage":10000,"pagefrom":0}is===== 722
getSearchCount#2: {"searchTerm":"Pa","maxPage":25,"pagefrom":0}is===== 25
On the screen I get 25/722
HTML:
<input type="text" ng-model="phrase" ng-change="searchCities(phrase)">
This is a pretty bad idea. You should do server side paging instead. Have the API return the pager object along with the search results. For example, here is an API GET request:
/api/accounts?searchText=someText&pageSize=25&page=1
Here is the response:
{
"pager": {
"pageCount": 1,
"totalItemCount": 342,
"pageNumber": 1,
"pageSize": 25,
"hasPreviousPage": false,
"hasNextPage": true,
"isFirstPage": true,
"isLastPage": false,
"firstItemOnPage": 1,
"lastItemOnPage": 25
},
"results": [
{
"id": 15343,
"name": "Account Name",
},
// more accounts returned here...
{
"id": 2314,
"name": "Account Name 2",
}
],
"searchText": "someText"
}
When the client wants to load more, the request would change to this:
/api/accounts?searchText=someText&pageSize=25&page=2
..where you see we're trying to fetch the 2nd page.
How you implement server side paging is another question. Here is a rudimentary server side paging example to give you an idea.
Update:
If you absolutely can't change the api to deliver paging information, try using a debounce delay. You can read about it in the documentation but it will essentially trigger the ng-change update when the timer expires instead of on the keypress. Here is how you would implement it.
<input type="text" ng-model="phrase" ng-model-options='{ debounce: 1000 }' ng-change="searchCities(phrase)">
For example if i had the json dataset here of all languages of books:
$scope.data = [{
"title": "Alice in wonderland",
"author": "Lewis Carroll",
"lang": ["en"]
}, {
"title": "Journey to the West",
"author": "Wu Cheng'en",
"lang": ["ch"]
}]
And I simply wanted to display exclusively english books, would I be able to do this purely using a filter in ng-repeat?
E.g.
<div ng-repeat="d in data | filter:d.lang='en'" style="margin-bottom: 2%">
{{d.title}}
</div>
I do not want to do it via any sort of form control (radio button etc). Would this be possible?
-EDIT- Thanks #GrizzlyMcBear for leading me down the right path! I got it to work with a slightly different filter function (which I'll paste below)
app.filter('MyFilter', function() {
var out = [];
angular.forEach(input, function(i) {
if (i.lang[0] === 'en') {
out.push(i);
}
})
return out;
}
});
and in the HTML
<div ng-repeat="d in data | MyFilter" style="margin-bottom: 2%">
{{d.title}}
</div>
Try like this
<div ng-repeat="d in data | filter: { lang : 'en'} " style="margin-bottom: 2%">
DEMO
You should use angular's filter,
I would also suggest that you use a function in the filter:
<div ng-repeat="item in collection | filter:filteringFunction" style="margin-bottom: 2%">
{{d.title}}
</div>
This way gives you more freeeeeedom (you're more than welcome to shout it Mel Gibson style ;-) )
in filtering your data by introducing more complex filtering logic.
var filteredLang = "en";
function filterByBookLanguage(collectionItem) {
var result = false;
if (collectionItem.lang[0] === filteredLang) {
result = true;
}
return result;
}
$scope.filteringFunction = filterByBookLanguage;
Now If you wish, you can also change the comperator function - filterByBookLanguage
(my terminology).
Say that your boss suddenly wants you to change the filtering logic from filtering books
into filtering by the author's name. Now all you have to do is add this condition:
if (bossWantsToChangeFilter) {
$scope.filteringFunction = filterByAuthorName;
} else {
$scope.filteringFunction = filterByBookLanguage;
}
All you have to remember is to write the comperator function with the current filtered item
as an argument and update the compared value of the language/author name
in the location you've found convenient ($scope, local variable, service etc.).
I did a lot of searching and tried eleventy-billion different Google search combinations, but all I can find on this issue is how to set a default option in a select box.
I have a page where an admin can select a user from a list of users, and then Angular JS grabs the user_id of that user (using ng-change), sends it to the DB via POST, and then the goal is to change the value of the other inputs to the values from the DB. I have the values, but running into a hitch when using that value to get my state select box to change to the user's state.
The JS in question:
$scope.getUserInfo = function(user_id) {
this.user_id = user_id;
$http.post("lib/scripts/managing_user.php", {func: 'retrieve_user_info', user_id: this.user_id}).
success(function(data) {
console.log(data);
$scope.is_active = data['0']['active'];
//Interacts with the ng-checked directive. It takes a bool, and data returns 0 or 1 in active.
$scope.username = data['0']['username'];
//Assigns the data value to the ng-model directive with the value username
//Have to treat data as a 2D array because that is how it is returned
$scope.email = data['0']['email'];
$scope.fName = data['0']['first_name'];
$scope.lName = data['0']['last_name'];
$scope.schoolList = data['0']['school_id']; (<-Does not work)
}).
I accomplished the same thing using jQuery before. Here is the code if it helps you see what I want to do.
if ($("#school").children("option:selected"))
$("#school").children("option:selected").attr("selected", "false");
$("#school #" + this['school_id'] + "").attr("selected", "true");
Here is the Select Box that I want changed.
<div class="row-fluid">
<span class="adduser_heading">School:</span>
<select id="school" class="adduser_input" ng-model="user.schoolList" ng-options="name.school_name for (key, name) in schoolList" ng-disabled="is_disabled" name="school" style="width: 246px;"></select>
</div>
I get the list of schools from the DB, and that populates that list. After selecting a user, I want this select box to change to that user's school. The ultimate goal is for the admin to be able to change the selected school and submit it, changing the school in the DB.
Hope I described the problem adequately. Basically: I want to select an option in a select box from the JS using Angular JS.
Edit: As per the advice of oware, I created a function that gets just the school name from the object array and returns it to $scope.user.schoolList. Unfortunately, that did not work.
$scope.user.schoolList = $scope.findInSchoolList(data['0']['school_id']);
$scope.findInSchoolList = function(school_id) {
var school_id = school_id;
var school;
school = $scope.schoolList[school_id];
school = school['school_name'];
return school;
};
And here is the format of what is returned from the DB with regards to school. I don't really want to post "data" since that has the information of an actual person. Basically, the information with regards to school is what is below.
school_id: "106"
school_name: "Central Campus High School"
Your ng-model is set to user.schoolList, while you're assigning the default value to $scope.schoolList.
It should be $scope.user.schoolList instead.
If you want to use the find function, you still need to return the right object, not just the name; and you need to fix your function. So something like this should work:
$scope.findInSchoolList = function(school_id) {
for(var i = 0; i < $scope.schoolList.length; i++) {
if ($scope.schoolList[i].school_id == school_id) {
return $scope.schoolList[i];
}
}
};
Here's a working example:
angular.module('app', [])
.controller('Ctrl', function($scope) {
var findInSchoolList = function(school_id) {
for (var i = 0; i < $scope.schoolList.length; i++) {
if ($scope.schoolList[i].school_id == school_id) {
return $scope.schoolList[i];
}
}
};
$scope.schoolList = [{
school_id: "1",
school_name: "Central Campus High School"
}, {
school_id: "106",
school_name: "Another High School"
}, {
school_id: "231",
school_name: "Yet Another High School"
}, {
school_id: "23",
school_name: "The Correct High School"
}, {
school_id: "2",
school_name: "Last High School"
}]
$scope.user = {
schoolList: findInSchoolList(23)
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="row-fluid" ng-app="app" ng-controller="Ctrl">
<span class="adduser_heading">School:</span>
<select id="school" class="adduser_input" ng-model="user.schoolList" ng-options="name.school_name for (key, name) in schoolList" ng-disabled="is_disabled" name="school" style="width: 246px;"></select>
</div>
you have to select the item from the array that populated the list, for example, if you have:
$scope.school_list = [{id:1, name:'harvard'}, {id:2, name:'other'}]
and you want to select with:
$scope.user.schoolList = {id:1, name:'harvard'}
it won't work, you have to make a fucntion that finds the element in the array and then assign it to the $scope.user.schoolList variable (that is bound to the ng-model of your list)
you have to do something like this:
$scope.user.schoolList = findInSchoolList({id:1, name:'harvard'})
and it will select the item from the select list
hope it helps
Problem Overview
Let's say I have a shipment of candy. The shipment has a number of boxes, and each box has a number of unique candy types. Every box has a unique id, different from every other box; the same is true for candy types. Furthermore, a candy has additional traits, like color, flavor and quantity.
Example Code
Take the following HTML example:
<div class="shipment">
<div class="box" data-boxid="a">
<div class="candy" data-candyid="1" data-color="orange" data-flavor="orange" data-qty="7">
<!-- unimportant content -->
</div>
<div class="candy" data-candyid="2" data-color="red" data-flavor="strawberry" data-qty="4">
<!-- unimportant content -->
</div>
</div>
<div class="box" data-boxid="b">
<div class="candy" data-candyid="3" data-color="green" data-flavor="lime">
<!-- unimportant content -->
</div>
</div>
</div>
Previous Attempts
I've seen similar examples of table parsing with jQuery's .map() function, and I've also seen mention of .each(), but I've been unable to generate any working code.
Desired Output
I want to generate (with jQuery) a JSON object similar to the following:
{
"shipment": {
"a": {
"1": {
"color": "orange",
"flavor": "orange",
"qty": "7"
},
"2": {
"color": "red",
"flavor": "strawberry",
"qty": "4"
}
},
"b": {
"3": {
"color": "green",
"flavor": "lime"
}
}
}
}
Additional Notes
My app already uses jQuery extensively, so it seems like a logical tool for the job. However, if plain 'ol JavaScript is a more appropriate choice, feel free to say so.
The HTML is always going to be well-formed and always going to follow a the format specified. However, in some cases, information may be incomplete. Note that the third candy had no quantity specified, so quantity was simply ignored while building the object.
This generates what you asked for:
var json = {};
$('.shipment').each(function(i,a) {
json.shipment = {};
$(a).find('.box').each(function(j,b) {
var boxid = $(b).data('boxid');
json.shipment[boxid] = {};
$(b).find('.candy').each(function(k,c) {
var $c = $(c),
candyid = $c.data('candyid'),
color = $c.data('color'),
flavor = $c.data('flavor'),
qty = $c.data('qty');
json.shipment[boxid][candyid] = {};
if (color) json.shipment[boxid][candyid].color = color;
if (flavor) json.shipment[boxid][candyid].flavor = flavor;
if (qty) json.shipment[boxid][candyid].qty = qty;
});
});
});
http://jsfiddle.net/mblase75/D22mD/
As you can see, at each level it uses .each() to loop through the elements matching a certain class and then .find().each() to loop through the desired children. In between, .data() is used to grab the desired data- attributes and the json object is built level by level.
Is this close to what you are looking for? – http://jsfiddle.net/4RPHL/
I have used data() and JSON.stringify to create the json.
Be sure to check your console where I have logged the output.
$(".candy").each(function() {
console.log(JSON.stringify($(this).data()))
});
Nice problem! Thanks to Chris' post I was able to get this to work.
var shipments = [],
shipment = {},
boxes = {},
candy = {};
$(".shipment").each(function() {
var shipment = {},
boxes = {};
$(this).children().each(function(){
var boxdata = $(this).data();
candy = {};
$(this).children().each(function(){
var candydata = $(this).data();
candy[candydata["candyid"]] = {
color: candydata["color"],
flavor: candydata["flavor"],
qty: candydata["qty"]
};
boxes[boxdata["boxid"]] = candy;
});
//console.log(JSON.stringify(boxes)); // works
});
shipment = {shipment: boxes};
shipments.push(shipment); // for multiples
});
console.log(JSON.stringify(shipments[0]));
console.log(shipments.length); // 1 for the example html above
The select lists are not rendering with the correct option selected. I've tried this a number of different ways including a computed selected observable (this.selected = ko.computed(return parseInt(selected(), 10) == this.id; )) and find in array functions.
In production, the dataArea elements would be populated with server side data. Using the divs with "data-" attributes keeps server side and client side scripting separate (I find this helps the designers).
A record would be displayed in non edit mode first with the option to edit by clicking the edit button. In edit mode, the initial values for the record appear in input controls. You would have the option to say, choose another customer and the having the form load new associated projects. Loading a new customer would reset the project list as expected.
So while loading a new customer would work well, its the transition to editing the current values that is causing an issue. The selected project needs to appear in the drop down list. If a new customer is chosen, the list populates with new options and no defaults are required.
http://jsfiddle.net/mathewvance/ZQLRx/
* original sample (please ignore) http://jsfiddle.net/mathewvance/wAGzh/ *
Thanks.
<p>
issue: When the select options are read, the inital value gets reset to the first object in the options. How do I keep the original value selected when transitioning to edit mode?
</p>
<div>
<h2>Edit Quote '1001'</h2>
<div class="editor-row" data-bind="with: selectedCustomer">
<label>Customer</label>
<div data-bind="visible: !$root.isEditMode()">
<span data-bind="text: CompanyName"></span>
</div>
<div data-bind="visible: $root.isEditMode()">
<input type="radio" name="customerGroup" value="1" data-bind="value: id"> Company Name 1
<input type="radio" name="customerGroup" value="2" data-bind="value: id"> Company Name 2
</div>
</div>
<div class="editor-row">
<label>Project</label>
<div data-bind="visible: !isEditMode()">
<span data-bind="text: selectedProject.Name"></span>
</div>
<div data-bind="visible: isEditMode()">
<select data-bind="options: selectedCustomer().projects, optionsText: 'Name', value: selectedProject"></select>
</div>
</div>
<div>
<button data-bind="click: function() { turnOnEditMode() }">Edit</button>
<button data-bind="click: function() { turnOffEditMode() }">Cancel</button>
</div>
</div>
<hr/>
<div data-bind="text: ko.toJSON($root)"></div>
function ajaxCallGetProjectsByCustomer(customerId) {
var database = '[{"CustomerId": 1, "Name":"Company Name 1", "Projects": [ { "ProjectId": "11", "Name": "project 11" }, { "ProjectId": "12", "Name": "project 12" }, { "ProjectId": "13", "Name": "project 13" }] }, {"CustomerId": 2, "Name": "Company Name 2", "Projects": [ { "ProjectId": "21", "Name": "project 21" }, { "ProjectId": "22", "Name": "project 22" }, { "ProjectId": "23", "Name": "project 23" }] }]';
var json = ko.utils.parseJson(database);
//console.log('parseJson(database) - ' + json);
//ko.utils.arrayForEach(json, function(item) {
// console.log('CustomerId: ' + item.CustomerId);
//});
return ko.utils.arrayFirst(json, function(item){
return item.CustomerId == customerId;
});
}
var Customer = function(id, name, projects) {
var self = this;
this.id = ko.observable(id);
this.CompanyName = ko.observable(name);
this.projects = ko.observableArray(ko.utils.arrayMap(projects, function(item) {
return new Project(item.ProjectId, item.Name);
}));
};
Customer.load = function(id) {
var data = ajaxCallGetProjectsByCustomer(id);
var customer = new Customer(
data.CustomerId,
data.Name,
data.Projects);
};
var Project= function(id, name) {
this.id = id;
this.Name = ko.observable(name);
};
var QuoteViewModel = function () {
var self = this;
$customerData = $('#customerData'); // data from html elements
$projectData = $('#projectData');
// intial values to display from html data
var customer = new Customer (
$customerData .attr('data-id'),
$customerData .attr('data-companyName'),
[{"ProjectId": $projectData .attr('data-id'), "Name": $projectData .attr('data-name')}]
)
this.selectedCustomer = ko.observable(customer);
this.selectedProject = ko.observable($projectData.attr('data-id'));
this.isEditMode = ko.observable(false);
this.selectedCustomer.subscribe(function(){
// todo: load customer projects from database api when editing
});
this.turnOnEditMode = function() {
var customerId = self.selectedCustomer().id();
console.log('customerId: ' + customerId);
Customer.load(customerId);
self.isEditMode(true);
};
this.turnOffEditMode = function() {
self.isEditMode(false);
};
};
var viewModel = new QuoteViewModel();
ko.applyBindings(viewModel);
One the initial value you load
this.dongle = ko.observable($dongleData.attr('data-id'));
This would be the string value "3". Where as the dongle html select element is actually saving/expecting to retrieve the object { "Id": "3", "Name": "dongle 3" }.
Here is a working version that gets the correct initial values and allows editing.
http://jsfiddle.net/madcapnmckay/28FVr/5/
If you need to save the a specific value and not the whole dongle/widget object, you can use the optionsValue attribute to store just the id. Here is it working in the same way.
http://jsfiddle.net/madcapnmckay/VnjyT/4/
EDIT
Ok I have a working version for you. I'll try to summarize everything I changed and why.
http://jsfiddle.net/madcapnmckay/jXr8W/
To get the customer info to work
The Customer name was not stored in the ajaxCallGetProjectsByCustomer json so when you loaded a customer there was no way to determine the new name from the data received. I added a Name property to each customer in the json with name "Company Name 1" etc.
To get the projects collection to work
The problem here was as stated originally with the dongles. You initialize the selectedProject observable with $projectData.attr('data-id') which equates to string value of 13. This is incorrect as the select list is configured in such a way that it actually saves/expects to receive the project object itself. Changing this id assignment to an object assignment made the initial value of project work correctly.
var project = ko.utils.arrayFirst(customer.projects(), function(project){
return project.id == Number($projectData.attr('data-id'));
});
this.selectedProject = ko.observable(project);
FYI there was a minor error in the html, the selectedProject.Name needed to be selectedProject().Name. No big deal.
I'm sure you could have figured out those pretty easily. The next bit is where the real issue is. You reload the Customer every time the edit button is clicked. This seems strange and you may want to reconsider that approach.
However what happens is you load a customer object from the server by id. Assign it to the selectedCustomer observable, this actually works fine. But then because the drop down is bound to selectedCustomer().projects and viewModel.selectedProject it expects that selectedProject is a member of selectedCustomer().projects. In the case of objects the equality operator is assessing whether the references match and in your case they do not because the original selectedProject was destroyed with its associated customer when you overwrote the selectedCustomer value. The fact that the ids are the same is irrelevant.
I have put in place a hack to solve this.
var oldProjectId = viewModel.selectedProject().id;
viewModel.selectedCustomer(customer);
var sameProjectDifferentInstance = ko.utils.arrayFirst(customer.projects(), function(project){
return project.id == oldProjectId;
});
viewModel.selectedProject(sameProjectDifferentInstance || customer.projects()[0]);
This saves the old projectId before assigning the new customer, looks up a project object in the new customer object and assigns it or defaults to the first if not found.
I would recommend rethinking when you load objects and how you handle their lifecycle. If you hold the current objects it memory with a full list of projects included you don't need to reload them to edit, simply edit and then send the update back to the server.
You may find it easier to hold json from the server in js variables instead of html dom elements. e.g.
<script>var projectInitialData = '#Model.ProjectInitialData.toJSON()';</script>
Hope this helps.