AngularJS - ngRepeat by $index weirdness - javascript

I am trying to implement an array (possible_parts) of objects and when an object is clicked add it to another array (warranty_parts). The trick is that the warranty_parts list can contain the same object more than once. I am using "track by $index" and everything works fine.
Then i need to inject another object (reason) to the selected part from warranty_part which I do with:
<select ng-model="selected_reason" ng-options="war.name for war in warranty_reasons"
ng-change="load_warranty_subreasons($index, part, selected_reason)">
And the function:
$scope.load_warranty_subreasons = function(index, part, reason){
part.selected_reason = reason;
}
But when I do this, all the parts in warranty_parts get the reason selected for the last part which means that I somehow append the reason to the all the parts. I tried to append it via $index:
$scope.warranty_parts[$index].selected_reason = reason;
But I end up with the same result. I also tried $apply, I tried to generate random string and insert it into the part object so the objects are different but I keep getting the same result- the reason is appended to all the parts and not the only one i want.
Anyone can help me with this? I also tried:
ng-repeat="part in warranty_parts track by $index(part)"
But then I get an error message: number in not a function.
Thanks!

Related

How can jade attributes be set for Dynamically click generated variables in Bootstrap Modal?

I have returned a valid JSON through mongoose to my Jade file, the JSON named things looks like this,
[{
_id: ObjectId("7788h356i0909v7863b75999"),
important: "Critical123",
property:[{name: "Test456"},{name: "Test789"},{name: "Test101112"}]
},
{
_id: ObjectId("7788h356i0909v7863b75908"),
important: "Critical",
property:[{name: "TestNew"},{name:"TestNewlyOpened"}]
}
]
I have a certain jade file spitting out details of a page I wanted like below
Basically when you click on glyphicon-plus-sign, I am opening a modal window
if thing
each something in thing
tr.odd.gradeX(id="firstRow" rowspan="2")
td.imp(type="button") #{something.important}
a.glyphicon-plus-sign(id="#{something._id}" data-toggle="modal" data-target=".bs-example-modal-lg")
My Modal is declared in the samefile below as
.modal.fade.bs-example-modal-lg(id="modalBoxSomething" tabindex='-1', role='dialog', aria-labelledby='myLargeModalLabel', aria-hidden='true', style='display: none;')
.modal-dialog.modal-lg
.modal-content
.modal-header#headerModal
h4#myLargeModalLabel.modal-title
button.close(id="modalCloseButton" type='button', data-dismiss='modal', aria-label='Close')
span(aria-hidden='true') ×
.modal-body
.somethingDetails.col-md-6.col-lg-6
if property
each nameProperty in property
p#propertyName #{nameProperty.name}
This doesn't work. Can I loop over nested sub items in an array for a specific item click event?
I want to repeat the values of property array for each something in this modal window.
Or Should I write a Javascript to do this?
for(var k=0; k<things.length; k++){
for(var m=0; k<things[k].property.length; m++){
$('#propertyName').append('<p>'+JSON.stringify(things[k].property[m].name+'</p>');
}
}
What is the best practice here? The Javascript solution works, But it would be great if what I am trying to achieve in Jade works sublimely.
Can I loop over nested sub items in an array for a specific item click event?
Of course you can. Jade templates are rendered using JavaScript and you can also use raw JavaScript in your templates by using - in the beginning of the line. If your JavaScript code does what you want you can move it's logic into your template file.
What is the best practice here?
Using Jade.
It's not clear what property is in your template file. If you expect Jade to know that it's a property of an array of objects this is not the case. You need to iterate through the array and then iterate through property array of each object element, i.e. 2 nested loops.
Assuming you have used things identifier for the array:
each thing, index in things
h2.sample= thing.important
each item in thing.property
p.propertyName= item.name
Note that IDs must be unique otherwise you have generated an invalid markup.

jQuery Id Selection and Dynamic Arrays

I have some code I'm struggling with. The good news is the code working as intended for a single instance; after some thought I've decided to feature multiple of these image selectors on a page. This works but the ugly approach of duplicating the code doesn't scale well (e.g. what if you want 50 of these on there?) The snag I've hit is how I can refer to a specific array. Is an array even an ideal solution for this?
The Objective
I have a series of images that a user may select from, up to X amount. The selected image ids are stored in an array and the image is added to a "selected images pool". This occurs by using an onClick for the slider, I obtain the Id from the element attributes. This is where I'm getting stuck.
var dataArray = $(this).closest("[id^=carousel]").data('array');
var slideCounter = $(this).closest("[id^=carousel]").data('counter');
slideCounter = dataArray.length;
The slideCounter returns the length of the string, not the array elements. How can I tell this code to refer to a particular array? See the fiddle for a better idea of the markup and code: jsFiddle
I have no doubt that there is a better approach. I'm relatively new to front end work, I'd appreciate any insights, I've burnt some brain cells on this, thanks!
From looking at your HTML, it looks like when you do this:
var dataArray = $(this).closest("[id^=carousel]").data('array');
what you're trying to do is to read the name of an array with .data() and then somehow turn that name (which is a string) into the array that's in your variable. My guess is that there's probably a better way to structure your code rather than putting javascript variable names in your HTML. I'd probably put a key name in the HTML and then store the arrays in an object where you can access them by that key name at any time.
Without trying to refactor your code, here's an idea for what you were trying to accomplish:
If selectedSlidesIdArray1 is a global variable, then you can do this:
var dataArray = window[$(this).closest("[id^=carousel]").data('array')];
Using the [stringVariable] notation on an object, lets you access a property by a literal string or a variable that contains a string. Since all global variables are also properties on the window object, you can do it this way for global variables.
If selectedSlidesIdArray1 is not a global variable, then you should probably put it in an object and then you can do this:
var dataArray = yourObj[$(this).closest("[id^=carousel]").data('array')];
Instead of trying to translate an arbitrary string into a JavaScript variable of the same name, why not just use another array? You can have nested arrays, which is to say an array of arrays.
Thus, instead of selectedSlidesIdArray1, selectedSlidesIdArray2, etc., you would have one selectedSlidesIdArray with sub-arrays, which you could then pull the index for using a data attribute.

Editing a delimited string using multiple input fields in AngularJS

I am trying to do the following quite unsuccessfully so far.
I have an string that is semicolon separated. Say a list of emails, so
'email1#example.com;email2#example.com;email3#example.com'
What I am trying to accomplish is split this string (using split(';')) into an array of strings or array of objects (to aid binding). Each of the items I would like to bind to different input elements. After editing I want to read the concatenated value again to send to my backend.
Problem is that when editing one of the split inputs, the original item value is not update (which makes sense as I am guessing the individual items are copies of parts of the original), but I am wondering if there is a way to do something like that.
Note that I want this to go both ways, so watching the individual inputs and updating the original one manually, would just fire an infinite loop of updates.
I have tried a few different ways, including creating an items property get/set using Object.defineProperty to read and right to the string (set was never fired).
take a look at this plnker
You can construct a temporary array on each field update in order to do the string replacement of the old segment with the new value. In order to tackle the lost focus problem you will have to use the ngReapeat's track by $index. The internal array will not be recreated unless you add the separator to your original string.
Here is the complete solution on Plunker
Your main issue is your ng-model attribute on your repeated input element. I would start with making use of ng-repeat's $index variable to properly bind in ng-model. In your original Plunker 'name' is NOT a scope property you can bind to, so this should be changed to ng-model="names[$index]"
Here is a Plunker to reflect this. I made quite a few changes for clarity and to have a working example.
NOTE: You will find that when editing fields directly bound to a repeater, every change will fire a $digest and your repeated <input> elements will refresh. So the next issue to solve is regaining focus to the element you are editing after this happens. There are many solutions to this, however, this should be answered in a different question.
Although binding to a string primitive is discouraged, you could try ng-list.
<form name="graddiv" ng-controller="Ctrl">
List: <input name="namesInput" ng-list ng-model="vm.names"/>
<ul>
<input ng-repeat="name in vm.names track by $index" ng-model="name" ng-change="updateMe($index, name)"/>
</ul>
You'll need both track by $index and an ng-change handler because of the primitive string binding.
function Ctrl($scope) {
$scope.vm = {}; // objref so we can retain names ref binding
$scope.vm.names = ['Christian', 'Jason Miller', 'Judy Dobry', 'Bijal Shah', 'Duyun Chen', 'Marvin Plettner', 'Sio Cheang', 'Patrick McMahon', 'Chuen Wing Chan'];
$scope.updateMe = function($index, value){
// ng quirk - unfortunately we need to create a new array instance to get the formatters to run
// see http://stackoverflow.com/questions/15590140/ng-list-input-not-updating-when-adding-items-to-array
$scope.vm.names[$index] = value; // unfortunately, this will regenerate the input
$scope.vm.names = angular.copy($scope.vm.names); // create a new array instance to run the ng-list formatters
};
}
Here's your updated plunkr

querySelectorAll to find matching data-attribute

My app uses a Parse backend to keep a running list of all the concerts in my area that my friends and I are interested in.
On the main page I use a parse query display a module for each show stored in the database. As each module is created, I use this code to add a data attribute to the show's outermost div, corresponding to the show's object ID in parse:
var showId = object.id;
$("div.show_module:last").data("showId", showId);
I'm successfully able to retrieve the showId of a specific show when the user clicks on the show's module:
$("#showsList").delegate(".showModuleBody", "click", function() {
var storeObjectId = $(this).closest("div.show_module").data("showId");
});
That all works great, proving that assigning the data-attribute is working.
Where I'm running into trouble is trying to find an element with a specific data attribute or a specific value for that attribute on a given page. The end goal is to get the y-offset of that div so I can scroll the page to the appropriate spot. I assumed I could use the following code to find the element, but it isn't working -
// find all elements with class .show_module
var allShows = document.querySelectorAll('.show_module');
// find all elements with showId data attribute
var showsWithShowId = document.querySelectorAll('[data-showId]');
// find all elements with a specific showId data attribute
var showToFind = document.querySelectorAll("[data-showId='2']");
The first of those 3 works, proving that all the elements I'm interested in are loaded into the page by the time I'm calling this function, but the 2nd and 3rd queries return nothing.
Any idea what I'm doing wrong here? Is it something with syntax? Is querySelectorAll just incompatible with how I'm setting the data attribute?
I tried to include only what I figured are the salient bits of code, but if more is necessary please let me know.
Try This
$('*[data-customerID="22"]');
For more info, look here:
Selecting element by data attribute
jQuery's .data method does not create a HTML attribute, but associates a value in its internal data store with the element.
If you want to set a data attribute with jQuery, then you need to use:
$("div.show_module:last").attr("data-showId", showId);
To get the value, you can use .data('showId') or .attr('data-showId').
(note that HTML attributes are case-insensitive, so you can also write "data-showid" instead.)

Knockout Mapping Plugin - Capture Array Value

I have a JSON array coming from a REST API. I am using the Knockout mapping plugin to process the array and load the JSON into preset form values (if a user has added values to the form previously - I have data there to test the Knockout arrays). The form essentially adds or deletes div blocks with inputs so users can add/delete "work" experiences.
My trouble is with trying to decipher how the plugin maps the arrays. I am trying to locate a specific value (the id) of a row in the array so I can add it as a variable to tell the API to delete that specific row. I can get Knockout to explicitly output the row value in the html, but I can't figure out how to capture it otherwise. In the template "foreach" I have a button that references a "remove:" and that's where I'm stuck in trying to capture the value from the array.
For Example in the HTML:
This outputs the two rows of the "work" object no problem:
<span data-bind="text: ko.mapping.toJSON(workModel.work())"></span>
[{"id":"1","schoolID":"2","place":"","position":"Science Teacher","description":"I worked at ASD for 1 year as a Science teacher.","start":"2011","end":"2012","profileID":"91"},{"id":"2","schoolID":"1","place":"American School of Taiwan","position":"Science Guy","description":"I was just another science guy","start":"2008","end":"2011","profileID":"91"}]
This outputs the id of the first row and item in the array:
<span data-bind="text: ko.mapping.toJSON(workModel.work()[0].id)"></span>
"1"
But in the javascript, if you click on the remove button generated by the foreach template...
gone = function(work) {
alert(ko.mapping.toJSON(workModel.work(this).id));
}
Gives me this error in Firebug, and then the UI reloads and drops out the template block I just clicked on.
Unable to parse bindings. Message: TypeError: workModel.work()[0] is undefined; Bindings value: text: ko.mapping.toJSON(workModel.work()[0].id)
Even though, if I replace the above alert with the explicit statement:
gone = function(work) {
alert(ko.mapping.toJSON(workModel.work()[0].id));
}
I get the correct value of "1" again. I know it has to do with the "this" aspect of the code, but I'm not sure what the mapping plugin is doing so that I can capture the specific value from the array...make sense? Any help would be greatly appreciated.
I'm going out on a limb here, but I do think it's the this-problem yes. Scoping in Javascript can be a hassle sometimes. Try doing something like this in the scope containing the gone-function:
var self = this;
gone = function(work) {
alert(ko.mapping.toJSON(workModel.work(self).id));
}
Disclaimer: I'm not able to test this myself right now, but give it a try :)
I finally got it. It came from combining different post on Stack Overflow and also from the Knockout forums. I'm sure other folks have more elegant solutions than this, but it works for me.
In the foreach loop on the "Delete" (or whatever button you want to use to capture the value) button I included the following on the data-bind:
Remove
Then in the javascript I have:
var self = this;
var row_id;
self.remove = function(index){
var row_id = index;
alert(row_id);
}
The alert returns the row ID of the loaded JSON as I wanted. The $data.id() could be changed/used to return any mapped element from the loaded JSON. The row_id is then a global that can be accessed elsewhere as well.

Categories

Resources