Comparing and interlacing variables for editing purposes with AngularJS - javascript

I have a list of names (staff in stafflist) from which I select some names and add them as an object to an array (paxlist). This operation is repeated, so several objects with different names are added into the array.
What I am attempting to do is to be able to edit each one of this objects to add or remove names.
For UX reasons, when I first select names from stafflist, they turn blue, and they reset to white when the object is added.
Basically, the effect/functionality I'm looking for is:
The object is added -> The main list resets
The edit button from one of the objects is clicked
The list of names of the object is compared with the main list, and the relevant names are highlighted (in blue) as existing/already selected names.
The user selects or deselects names.
The edition is completed, the resulting object saved and the main list reset.
I have a Plunkr depicting the addition functionality, but I don't see clear how could I compare and make the 2 variables (stafflist and pax in recordlist) work together as to edit the result.
I'm not specially looking for somebody to do it and solve this for me, but more to understand the logic behind a possible solution, as so far I can't think of anything...
Any comments will be highly appreciated!

I created a new Plunk with what I think you were trying to accomplish. Basically I just added a new state (editMode) which captured the pax being edited.
var editMode;
$scope.editRecord = function(record) {
editMode = record.pax;
$scope.stafflist.forEach(function (s) {
s.chosen = false;
});
record.pax.forEach(function(p) {
$scope.stafflist.forEach(function (s) {
if(p.name === s.name) {
s.chosen = true;
}
});
});
};
I then used this new state to figure out whether I was creating a new record or editing an existing one.
$scope.pushStaff = function (staff) {
staff.chosen = true;
var arr = editMode || $scope.paxlist;
arr.push(staff);
};
$scope.unpushStaff = function (staff) {
staff.chosen = false;
var arr = editMode || $scope.paxlist;
var index=arr.indexOf(staff);
arr.splice(index,1);
};
I'm sure there are cleaner approaches, but this is one way to do it.

Related

Posting model back to controller containing complex collection

I've posted a question similar to this, but I don't believe it clearly stated my question correctly. So here goes a better explanation!
I'm working on a C# MVC 5 application. I have a page that dynamically lists the model's collection property A. Each item in this collection uses a bit of bootstrap to show/hide it's own collection property B. So just to clarify that: This model has a collection of objects each of which has a collection of objects associated with it. These objects each have a checkbox associated with them bound to an isChecked boolean too that can be used to select/deselect items. Checking a box of an item from collection A, automatically selects all checkboxes of its collection B. So, as you can perhaps see, the sole purpose of having a checkbox on collection A items, is to act as a select all for its associated collection B items.
I'm trying to pass the entire model back to the controller on POST and then do stuff with it, e.g. grab all items that have their , but am running into a few problems.
E.g.
#foreach (var category in Model.Categories)
{
var headerId = category.Name;
var childCounter = 0;
#*some divs and headers here*#
#if (category.Items.Any())
{
#*lets give it the ability to check all of its associated items*#
#Html.CheckBoxFor(d => category.IsChecked, new {id = #headerId, onclick = String.Format("CheckUncheckChildren({0}, {1})", #headerId, category.MenuItems.Count)})
}
else
{
#Html.CheckBoxFor(d => category.IsChecked)
}
#*some other divs and headings*#
#if (category.MenuItems.Any())
{
#foreach (var item in dealItemCategory.MenuItems)
{
var childId = childCounter++ + "_" + headerId;
var serverId = item.Id;
#*some other divs and headings*#
#Html.CheckBoxFor(d => item.IsChecked, new {id = #childId})
}
}
}
Lower down on the page I have:
for (var i = 0; i < Model.Categories.Count(); i++)
{
var category = Model.Categories.ElementAt(i);
var headerId = category.Name;
#Html.HiddenFor(d => category, new {id = #headerId})
for (var j = 0; j < Model.Categories.ElementAt(i).Items.Count(); j++)
{
var item = Model.Categories.ElementAt(i).Items.ElementAt(j);
#Html.HiddenFor(d => item, new {id = j + "_" + #headerId})
}
}
In my mind, I'm retaining these properties on the page for this complex collection... (not that my understanding is 100%) I've forced ids for these hidden fields so that they correlate with the ids of the Checkboxes. Maybe not the best idea, but it was just the last thing I tried to get this to work...
Now I have two options here:
1) Believe in the above to mean that whenever a checkbox is selected/deselected, the object in the collection it is associated with, will get its isChecked boolean changed. Then, POST back the entire model and hope I'll be able to cycle through all checked items and do awesome stuff.
or
2) Create a string property in the model to hold a JSON representation of this complex collection of objects. When the page is POSTED, update this JSON string to the state of the complex collection that has been hopefully updated to have some of its items checked/unchecked. Then, POST back the entire model and server-side I would deserialize this single object into a logically equivalent complex collection and work from there.
I.e.
My model change would be adding a property:
public string JsonCategories{ get; set; }
And then in the view I'd have to initiate a post back:
#Html.HiddenFor(d => d.JsonCategories);
function accept() {
var categories= #Html.Raw(Json.Encode(Model.Categories));
var jsonCategories = JSON.stringify(categories);
var a = $('#JsonCategories').val();
$('#JsonCategories').val(jsonCategories );
$("form").submit();
};
Attempting solution A, I either get a null for the Categories property, or if I add #Html.HiddenFor(d => d.Categories); I get an empty object, count of 0.
Attempting solution B, I do get a lovely JSON representation for the complex Categories back in server land, but I see checkboxes I checked aren't changing the isChecked bools leading me to believe that, while the JSON object is able to be set to the Categories object on POST client-side, everything done by the user isn't being kept so ultimately the Categories collection of the model hasn't changed since it was passed to the View initially.
Sorry if this all seems complicated, it is and for a junior like me, I thought it best I ask around as I've never posted back lists and stuff to the controller before.
I'd be happy to provide more information if it will help!
Warmest regards,
If you change your foreach loops to for loops you can make use of the loop index in the lambda expression. This should make the engine output the correct syntax for posting the values. For example:
#for (int i = 0; i < Model.Categories.Count(); i++)
{
...
#Html.CheckBoxFor(d => d.Categories[i].IsChecked)
...
}
This should correctly bind the values to the POST request. The resulting html will be similar to the following snippet (0 being the first item, 1 would be the second etc.):
<input name="Categories[0].IsChecked" ... ></input>
Alternatively you can create editor templates for your child collections, which will result in you writing out this snippet to replace where your for loop is currently.
#Html.EditorFor(m => m.Categories)
More on how to do this here.
This should help you go with option 1.

Alternatives to comparing object property using if...else

I hope I make my question clear.
I have a few objects which I have created with a property called address. For example, Obj1 has address 0x0000, Obj2 has address 0x0004 and so on.
There is a list of random input addresses which I need to process. Once the input address is one of the object address (if addr=0x0000||addr=0x0004....), then a function will be automatically loaded.
Currently, I am comparing it by a list of Object addresses, which I think is a waste of iteration. Is there any way, I can access it by index? For example, once I enter an input address of 0x0004, a function will be run directly.
edit : Thanks for all your answers :) I have a clearer idea now
You got 2 choices:
Use switch to define all functions:
switch (input){
case '0x0000': function(input){}; break;
case ...
}
Use a predefined map:
var functionMappings = {
'0x0000': function_1,
'0x0001': function_2,
};
if(functionMappings[input]) {
functionMappings[input](input);
}
I would prefer the second example, because it can be created dynamically in the runtime.
Update:
You can create your functionMappings dynamicaly like this:
var functionMappings = {// create first entries, or defualt functions
'0x0001': function_1,
'0x0002': function_2
};
if(condition_x) { // add new or overwrite existing functions
functionMappings[0x0005] = function_5;
}
Because this is an map, you can even overwrite default function definitions, if you need.
Iterate once and put all addresses as keys to an object:
var addresses = {};
for(var i=0;i<objList.length;i++){
addresses[objList[i].addr] = {};
addresses[objList[i].addr].fn = correspondingFunction
// you can even attach the corresponding object to it
// addresses[objList[i].addr].obj = addresses[objList[i].addr];
}
I use a loop for the example but you can do it any way that suits you
Then you have a list of your objects keyed by their address, and you can fetch them by it:
function getAddrFunction(addr){
if(addresses[addr] !== undefined){
return addresses[addr].fn;
}
return null;
}

Knockout JS: Value passed to Writeable Computed returns [object Object]?

Background
While working on an ongoing learning project, I noticed that I require a writeable computed function to solve a problem I'm facing. Here's the skinny: I'm trying to 1) drop a user-specified number of assignments scores 2) of a user specified type from a gradebook.
The user inputs the value above, then click a button, dropLowestScores. The code then adds the lowest scores to an array called 'lowest', which exists on each student object.
Then, observable means on each student are updated based on new values, dropping the lowest ones.
Problem
My problem comes to life when I try to drop. I'm not confident that my writeable computed is structured correctly, but I'm not sure what's wrong either. I noticed that that properties 'n' and workType are correctly given in my read function, but in my write, they are not as desired. In read, for example, workType returns default value, homework, but in my write function, it returns [object Object].
Responses that can clarify what my problem is and give me a strategy for correctly scripting a writeable computed would be appreciated.
Relevant JS and HTML snippets follow.
JSBin: Full Project
JS
this.dropLowestScores = ko.computed({
// read the parameters necessary to write values to property 'lowest'
read: function() {
// user sets value of 'n' in the page via ko 'options'
var n = _this.n().n;
// user selects 'workType' from list of ko options
var workType = _this.workType().workType;
console.log("workType:" + workType);
return n,workType;
},
// now use current parameter values to set new values to 'lowest' arrays
write: function(n,workType) {
//this.n = n;
//this.workType = workType;
// 'lowest' exists as a property for each student,
// ergo, I loop through each student
ko.utils.arrayForEach(_this.students(), function(student){
var i = _this.students.indexOf(student);
console.log("_this.assignments: " + _this.assignments()[i].workType().workType);
console.log("this.workType: " + this.workType);
console.log(_this.assignments()[i].workType().workType == this.workType);
// if the current assignment is the same as the user-specified assignment,
//add the score for that assignment to array 'tmp'; then set lowest = tmp
if(_this.assignments()[i].workType().workType == this.workType){
var tmp = student.scores().sort(_this.comparator).slice(0,this.n);
console.log(tmp.length);
student.lowest(tmp);
}
});
}
});
HTML
<button data-bind="click: dropLowestScores">Drop Lowest Scores</button>
Currently, I just have the above function bound to a butten. Ideally, this is how I'd leave it. Other properties referenced, such as n, workType, and mean are input in a table.
I feel silly now, but it turns out I was on the right track; indeed, the solution was trivially simple. In the read function, I just needed to define properties in the following way:this.variable = ... as opposed to var variable = ....
this.dropLowestScores = ko.computed({
// read the parameters necessary to write values to property 'lowest'
read: function() {
// user sets value of 'n' in the page via ko 'options'
this.n = _this.n().n;
// user selects 'workType' from list of ko options
this.workType = _this.workType().workType;
console.log("workType:" + workType);
return n,workType;
},
...

Duplicate an array in Knockout.js

I'm trying to make a little damage calculator for the game Diablo 3 (I know, I know).
Basically the idea is that it has a "before" and "after" array of values that represent items for your character. The "after" array should duplicate the "before" array when that's updated. However, changes to the "after" array should not update the "before" array.
Each array then displays a DPS (more of this is better) total, and it shows you the difference between the two. The idea is then it makes for easy comparison of two items when using the in-game auction house.
I have the first bit set up - the "before" array is working great. However I'm at a loss as to how to create the "after" array, and I'm wondering if I've made this a different magnitude of complexity. Should I be using two view models, replicating it in jQuery, or using the mapping plugin? I can't quite find anything that's exactly what I'm after, the UI requirements especially seem a bit of a sticking point
Fiddle of where I'm up to: http://jsfiddle.net/kimadactyl/GuMuY/8/
Here's a solution that should get you started. I refactored your HeroItem to take a config object and an optional linked Hero.
I am assuming for the moment the array is fixed length. I create the after array from the items array by mapping it to a new HeroItem, using jquery extend to do a deep copy.
When a link is passed in the HeroItem will subscribe to changes on it's observables and update one-way only as specified.
function HeroItem(config, link) {
var self = this, prop;
self.item = config.item;
self.int = ko.observable(config.int);
self.ias = ko.observable(config.ias);
self.critdmg = ko.observable(config.critdmg);
self.critpc = ko.observable(config.critpc);
self.min = ko.observable(config.min);
self.max = ko.observable(config.max);
if (link) {
for (prop in link) {
if (link.hasOwnProperty(prop) && ko.isObservable(link[prop])) {
console.log("subscribing " + prop);
link[prop].subscribe((function(p) {
return function (newValue) {
console.log("updating " + p+ " to " + newValue);
self[p](newValue);
}
})(prop));
}
}
}
}
self.after = ko.observableArray(ko.utils.arrayMap(self.items(), function(i) {
return new HeroItem($.extend({}, ko.toJS(i)), i);
}));
http://jsfiddle.net/madcapnmckay/2MNFn/1/
No custom bindings needed, it just uses the subscription capabilities all KO observables have. If you want to extend this to cope with dynamic length arrays simple subscribe to the items array and cleanup the after array accordingly.
Hope this helps.

Knockout: Dynamically controlling number of objects in an array?

So, I'm grabbing a number from a model, and trying to use that number (the length of an observable array) to dynamically control the number of objects in a "sub-model." In this case, when I add or remove "meta headings" in the parent model, the child model will automatically have the right number of corresponding "meta fields."
So this grabs the length of the array I'm basing things on:
self.metaCount = ko.computed(function() {
return parent.metaHeadings().length;
});
This is the array I want to dynamically push objects into:
self.metaColumns = ko.observableArray([]);
And this is how I'm trying to dynamically push items into the array:
self.columnUpdate = ko.computed(function() {
for (i=0; i<self.metaCount(); i++) {
self.metaColumns.push({heading: ko.observable()});
}
});
Now, I'm doing all of this in the model. The reason is that I have several instances of models and sub-models, and it makes more sense to have each one handle its own updating when a change takes place.
Am I going about this all wrong?
I would say it depends on your requirements. Firstly does your current code work correctly? If not what are the problems?
Is the metaColumns array required to to editable independently? If the answer is no then why maintain them as a separate property when you could simply do:
self.metaColumns = ko.computed(function() {
var result = [];
for (i=0; i<self.metaCount(); i++) {
self.metaColumns.push({heading: ko.observable()});
}
return result;
});
I notice that you currently aren't clearing the metaColumns array when the columnUpdate is recomputed so it will keep adding to the array.
Hope this helps.

Categories

Resources