Javascript populating list into html select repeating list twice - javascript

I am using jquery ajax to populate a html select.
The populating actually works but the problem is that for some reason the whole list is being added twice instead of just once.
Here is the code:
myfunction: function() {
$.ajax({
url: 'the-url-here',
method: 'GET',
success: function(result) {
$.each(result.cars, function(result, value) {
if (value.active === '1'){
$('#myselect').append($('<option>').text(value.name).attr('value', value.id));
}
});
}
});
}
How can I fix this so it's only populated once and not twice?

Instead of selecting the element in the loop, which causes performance issues when the list is big, select it outside the loop, then create your list of <options> and outside the loop, append to the <select>.
(function($){$(function(){
var carsDataFetchViaAjax = [
{id: 1, active: 1, name: 'Car1'},
{id: 2, active: 1, name: 'Car2'},
{id: 3, active: 0, name: 'Car3'}
]
var $select = $('#select'),
options = [];
carsDataFetchViaAjax.map(function(car, i){
if (car.active == 1){
options.push($('<option>').val(car.id).html(car.name))
}
})
$select.append(options);
})})(jQuery)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<select id="select"></select>

myfunction is being called twice somewhere, and since an append does what it is supposed to do (appends to your list), it's working as intended :)
Check if the value is already added before trying to add it:
$.each(result.cars, function(result, value) {
if (value.active === '1' && $('#myselect option[value='+ value.id +']').length == 0) {
$('#myselect').append($('<option>').text(value.name).attr('value', value.id));
}
});

Related

SyntaxError: missing ) after formal parameters when adding if/else

Can anyone tell me what i am doing wrong , i want to have different views for different users in handsontable.I am defining the table as
document.addEventListener("DOMContentLoaded", function() {
var create_table = document.getElementById('create_table');
var mydata = document.getElementById('mydata').innerHTML;//to get the hidden element
var logged_user = document.getElementById('logged_user').innerHTML;// to get remote user
var plan_creator = document.getElementById('plan_creator').innerHTML;//to get the person who has created the plan
console.log(logged_user + " " + plan_creator);
console.log(mydata);
var searchResultCount=0;
var hot,searchFiled,resultCount;
function searchResultCounter(instance, row, col, value, result) {
Handsontable.plugins.Search.DEFAULT_CALLBACK.apply(this, arguments);
if (result) {
searchResultCount++;
}
}
var buttons = {
save:document.getElementById('save'),
load:document.getElementById('load'),
file:document.getElementById('file_export')
}
var objectData = JSON.parse(mydata);//to decode data in JSON format
console.log(objectData);
hot = new Handsontable(create_table, {
data:objectData,
colHeaders: true,
rowHeaders: true,
contextMenu: true,
minRows: 30,
minCols: 13,
maxCols:18,
maxRows:100,
copyPaste:true,
dropdownMenu: true,//plugin to display menu on column header
filters:true,
columnSorting:true,//plugin to enable sorting
sortIndicator:true,//to display sorting is done
comments:true,//to add comments
colHeaders:["Testcase Name","Cell Name","Customer","Flops","Title","Status","Mfix CCR","Scenerio Brief Description","Expected Results","CCR Status","CCR No","Remarks","Testcase Path"],
if(logged_user == plan_creator) {
columns:[//when using this not able to remove column
{data:'tc_name'},
{data:'cell_name'},
{data:'customer_name'},
{data:'flops' ,type:'numeric'},
{data:'title'},
{data:'status'},
{data:'mfix_ccr'},
{data:'test_scenerio'},
{data:'expected_results'},
{data:'ccr_status'},
{data:'ccr_num'},
{data:'remarks'},
{data:'tc_path'}],
}
else{
columns:[//when using this not able to remove column
{data:'tc_name' ,readOnly:true } ,
{data:'cell_name',readOnly:true },
{data:'customer_name',readOnly:true },
{data:'flops' ,type:'numeric',readOnly:true },
{data:'title',readOnly:true },
{data:'status',readOnly:true },
{data:'mfix_ccr',readOnly:true },
{data:'test_scenerio',readOnly:true },
{data:'expected_results',readOnly:true },
{data:'ccr_status',readOnly:true },
{data:'ccr_num',readOnly:true },
{data:'remarks'},//only remarks can be added by other user
{data:'tc_path',readOnly:true }],
}
search: {
callbak:searchResultCounter
}
});
searchFiled = document.getElementById('search_filed');
resultCount=document.getElementById('resultCount');
var exportPlugin=hot.getPlugin('exportFile');
Handsontable.dom.addEvent(searchFiled, 'keyup', function(event) {
var queryResult;
console.log(this.value);
searchResultCount = 0;
queryResult = hot.search.query(this.value);
console.log(queryResult);
//resultCount.innerText = searchResultCount.toString();
hot.render();
});
buttons.file.addEventListener('click', function() {// enabling the plugin to download the file
exportPlugin.downloadFile('csv', {filename: 'MyFile',columnHeaders:true});
});
I don't get any error when i remove the if/else statement and use only one scenerio .when using above code i am getting the error, but when i remove the if/else part and just use columns attribute in a simple way , i don't get this error.But i want to have different views for the creator of plan and others.
Is there any other way to do this?
Thanks
You can't use if statements when constructing an object, but you can use the ternary ?: operator, like this:
colHeaders: ... ,
columns: logged_user == plan_creator
? /* value in case they are equal */
: /* value in case they are not equal */,
search: ...
instead of using if else like
if(logged_user == plan_creator) {
columns:[//when using this not able to remove column
{data:'tc_name'},
{data:'cell_name'},
{data:'customer_name'},
{data:'flops' ,type:'numeric'},
{data:'title'},
{data:'status'},
{data:'mfix_ccr'},
{data:'test_scenerio'},
{data:'expected_results'},
{data:'ccr_status'},
{data:'ccr_num'},
{data:'remarks'},
{data:'tc_path'}],
}
else {}
you could use the ternary Operator:
columns: (logged_user === plan_creator)
? [//when using this not able to remove column
{data:'tc_name'},
...
]
: [//when using this not able to remove column
{data:'tc_name' ,readOnly:true },
...
]

Generate tree view using json

I want to generate tree view using jQuery and JSON.
My JSON(single folder):
[{"id":"076ac97d","path":"\/test\/undefined","name":"undefined","parentDirName":"test","parentDirId":"70b77ddd-6c15"}, .... ]
When folder is in root, parentDirId key has empty value: "", if is in catalog, has a parent ID.
I want to generate ul li list tree.
Do you have an idea how to iterate in this JSON and append ul li list to html?
I have an AJAX:
$.ajax({
type: "GET",
url: ajaxUrl,
dataType: "json",
contentType: "application/json",
success: function(response){
//code
}
How to generate dir tree? Firstly append dirs to dirs with parentID.
You could use the function below. It first creates a new object structure keyed by id, which allows quick lookup of the parent of each node. At the same time it creates an LI element for each of them, together with an empty UL element. Finally all these LI and UL elements are linked together according to the parent-child relationship:
function populateUL($ul, data) {
// Register the given UL element as the root in a new data structure
var hash = {
"": { $ul: $ul }
};
// Key the objects by their id, and create individual LI elements for them,
// and an empty UL container for their potential child elements
data.forEach(function (o) {
var $ul = $("<ul>");
hash[o.id] = {
$ul: $ul,
$li: $("<li>").text(o.name).append($ul)
};
});
// Append each LI element to the correct parent UL element
data.forEach(function (o) {
hash[o.parentDirId].$ul.append(hash[o.id].$li);
});
}
// Sample response object
var response = [{
"id":"70b77ddd-6c15",
"path":"/test",
"name":"test",
"parentDirName":"",
"parentDirId":""
}, {
"id":"076ac97d",
"path":"/test/chess",
"name":"chess",
"parentDirName":"test",
"parentDirId":"70b77ddd-6c15"
}, {
"id":"076ac97e",
"path":"/test/bingo",
"name":"bingo",
"parentDirName":"test",
"parentDirId":"70b77ddd-6c15"
}, {
"id":"076ac97f",
"path":"/test/chess/major pieces",
"name":"major pieces",
"parentDirName":"chess",
"parentDirId":"076ac97d"
}, {
"id":"076ac97g",
"path":"/test/chess/major pieces/rook",
"name":"rook",
"parentDirName":"major pieces",
"parentDirId":"076ac97f"
}, {
"id":"076ac97h",
"path":"/test/chess/major pieces/queen",
"name":"queen",
"parentDirName":"major pieces",
"parentDirId":"076ac97f"
}, {
"id":"076b0000",
"path":"/test/chess/minor pieces",
"name":"minor pieces",
"parentDirName":"chess",
"parentDirId":"076ac97d"
}, {
"id":"076b0001",
"path":"/test/chess/minor pieces/knight",
"name":"knight",
"parentDirName":"minor pieces",
"parentDirId":"076b0000"
}, {
"id":"076b0002",
"path":"/test/chess/minor pieces/bishop",
"name":"bishop",
"parentDirName":"minor pieces",
"parentDirId":"076b0000"
}];
// Inject response data into document
populateUL($("#root"), response);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="root"></ul>
For this to work, the root element must be referenced by an empty string for parentDirId.
Note that the properties path and parentDirName are not used in this algorithm, since they contain information that is redundant.
I'm guessing you mean something like this
function listItem(obj) {
var html = "<ul>"
jQuery.each(obj, function(key, value) {
html += "<li>" + key + ':'
if (typeof value !== "object")
html += value
else
html += listItem(value)
html += "</li>"
})
return html + "</ul>"
}
var obj = {
"id": "076ac97d",
"rawr": {
"mew": 2
},
"path": "\/test\/",
"name ": "undefined",
"parentDirName ": "test",
"parentDirId ": "70 b77ddd "
};
document.write(listItem(obj));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Angular with multiple selects that must have unique options selected

I have an angular page that will have a dynamic number of select elements on it. Each select will have the same option collection but once an option is selected from one, that option should be removed from all of the subsequent select elements.
I found this: http://jsfiddle.net/Zv5NE/63/ which works exactly how I'd like (when an option is selected from one select, it's removed from the others and then if that same select is changed, it adds the previously selected option back to the others).
The problem is, this is using a hard coded number of select elements and also using hard coded filters for each select element...that won't work for my purposes because, as I said, my users are going to need to be able to dynamically add n number of select elements.
I've done some playing around trying to create my own filter to accommodate for this, but I'm super green to angular (angular 1 btw) and I've hit the wall.
this is a small snippet from what I've tried. Essentially I've just tried creating an array and adding selected items to that array then checking against the values in the array for the filter (I would have to add some logic for changing options obviously, but I'm really not sure this is the right direction to go):
$scope.filter = function (item) {
for (i = 0; i < $scope.names.length; i++) {
if (item == $scope.names[i]) {
return false;
}
}
return true;
};
any guidance would be greatly appreciated.
I shelved this for a while but came back to it this morning. I was able to come up with a working solution.
Here's what I wrote up. May not be the most elegant way to do it, but it works for my purposes:
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta name="viewport" content="width=device-width" />
<title>AngularTest</title>
</head>
<body ng-controller="HellowWorldCtrl">
<select ng-model="selectname0" ng-options="item as item.name for item in classes | customFilter:'selectname0':this">
<option value="">- select -</option>
</select>
<div id="selectsDiv"></div>
<br />
<input type="button" value="Add Select" ng-click="addSelect()" ng-show="cnt < classes.length -1" />
<script src="~/Scripts/angular.js"></script>
<script type="text/javascript">
var app = angular.module('app', []).controller('HellowWorldCtrl', function ($scope, $compile) {
$scope.cnt = 0;
$scope.selectsAdded = [];
$scope.selectsAdded.push('selectname0');
$scope.addSelect = function () {
$scope.cnt++;
$scope.selectsAdded.push('selectname' + $scope.cnt);
var newSelect = $compile('<div><select ng-model="selectname' + $scope.cnt + '" ng-options="item as item.name for item in classes | customFilter:\'selectname' + $scope.cnt + '\':this"><option value="">- select -</option></select></div>')($scope);
angular.element(document.getElementById('selectsDiv')).append(newSelect);
};
$scope.classes = [
{
id: 1,
name: 'Biology 101',
courseid: '12345'
},
{
id: 2,
name: 'Chemistry 101',
courseid: '12374'
},
{
id: 3,
name: 'Psychology 101',
courseid: '32165'
},
{
id: 4,
name: 'Geology 101',
courseid: '78945'
},
{
id: 5,
name: 'Math 101',
courseid: '65478'
}
];
});
app.filter('customFilter', function () {
return function (items, which, scope) {
var alreadySelectedCourses = [];
var courses = [];
for (i = 0; i < items.length; i++) { // loop over all of the items in the class array...cwc
for (j = 0; j < scope.selectsAdded.length; j++) { // loop over all of the selects added to the page...cwc
if (which == scope.selectsAdded[j]) { // check if the calling select is the same one in the loop...cwc
if (scope['selectname' + j] && scope['selectname' + j].id) { // check if the calling select has alraedy been selected...cwc
if (scope['selectname' + j].id == items[i].id) { // check if the selected value of the calling select is the same as the item in the iteration and add it to the return array if so...cwc
courses.push(items[i]);
alreadySelectedCourses.push(items[i]);
}
}
} else { // not the calling select so find out the value and don't add it to the return array...cwc
if ((scope['selectname' + j] && scope['selectname' + j].id)) { // other selects (not calling select) have values selected so add them to the alreadyselectedarray...cwc
if (scope['selectname' + j].id == items[i].id) {
alreadySelectedCourses.push(items[i]);
}
}
}
}
if (alreadySelectedCourses.indexOf(items[i]) > -1) {
continue;
} else {
courses.push(items[i]);
}
}
return courses;
}
});
</script>
</body>
</html>

How to get all data-* attributes by Prefix

I have a tag like this:
Link
When I click this link, I have a function like this
$('#ssd').click(function (event) {
var customData;
// Code to get all the custom data in format like data-info*
});
Note, the data-info* like attributes could be any number, that means you could see 1 one of them, named data-info1, or there of them, named data-info1, data-info2, data-info3.
How would I do that, I looked up the JQuery selectors, something like Attribute Starts With Selector [name^="value"] won't work because the variation here is on name...
If I console.log($('#ssd').data()); I will get an object with extra attributes that I don't need, toggle: "popover", bs.popover: Popover
Any suggestions?
This is what I did:
dataFullList = $(this).data();
$.each(dataFullList, function (index, value) {
if (index !== "toggle" && index !== "bs.popover") {
item.name = value.split(":")[0];
item.number = value.split(":")[1];
dataIWant.push(item);
}
});
So I will get a dataIWant array without stuff I don't need.
Target all elements which data-* starts with
Custom jQuery selector selector:dataStartsWith()
Here's a custom jQuery selector that will help you to:
Given the data-foo-bar prefix , target the following elements:
data-foo-bar
data-foo-bar-baz
but not:
data-foo-someting
data-something
jQuery.extend(jQuery.expr[':'], {
"dataStartsWith" : function(el, i, p, n) {
var pCamel = p[3].replace(/-([a-z])/ig, function(m,$1) { return $1.toUpperCase(); });
return Object.keys(el.dataset).some(function(i, v){
return i.indexOf(pCamel) > -1;
});
}
});
// Use like:
$('p:dataStartsWith(foo-bar)').css({color:"red"});
// To get a list of data attributes:
$('p:dataStartsWith(foo-bar)').each(function(i, el){
console.log( el.dataset );
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p data-foo-bar="a">I have data-foo-bar</p>
<p data-foo-bar-baz="b" data-extra="bbb">I have data-foo-bar-baz</p>
<p data-bar="a">I have data-bar DON'T SELECT ME</p>
<p data-something="b">I have data-something DON'T SELECT ME</p>
Custom jQuery Method $().dataStartsWith()
$.fn.dataStartsWith = function(p) {
var pCamel = p.replace(/-([a-z])/ig, function(m,$1) { return $1.toUpperCase(); });
return this.filter(function(i, el){
return Object.keys(el.dataset).some(function(v){
return v.indexOf(pCamel) > -1;
});
});
};
$('p').dataStartsWith("foo-bar").css({color:"red"});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p data-foo-bar="a">I have data-foo-bar</p>
<p data-foo-bar-baz="b" data-extra="bbb">I have data-foo-bar-baz</p>
<p data-bar="a">I have data-bar DON'T SELECT ME</p>
<p data-something="b">I have data-something DON'T SELECT ME</p>
This function will get the data-info attributes and put them into an array:
function getDataInfo($element, i, a) {
var index = i || 1, array = a || [],
info = $element.data('info' + index);
if(info === undefined) {
return array;
}
array['info' + index] = info;
return getDataInfo($element, index + 1, array);
}
$(function() {
console.log(getDataInfo($('#ssd')));
});
Here's an if condition to isolate the invalid keys while you loop the data. Used as a filter, you can choose to delete the keys you do not want - like this:
$('#ssd').click(function(e){
var data = $(this).data();
for(var key in data) {
//here is a condition to use only those data-info items
if(data.hasOwnProperty(key) && key.indexOf('info') === -1) {
console.log(key); //just to see which key it is
delete data[key]; //if you need to build a collection of only data-info keys
}
}
});
Alternatively, negate the if condition to include only those keys you want.
You can use Prefix Data. It is jQuery plugin. Return the value at the prefixed data store for the first element in the set of matched elements. Returned value can be an object based on the attribute values and attributes name structure.
Usage
Take any HTML tag with multi data-* attributes with the same prefix. In the example we focus on myprefix prefix.
<div id="example-tag"
data-myprefix='{"property1": "value1", "property2": {"property21": "value21"}, "property3": "value2"}'
data-myprefix-property2='{"property22": "value22"}'
data-myprefix-property2-property23="value23"
data-myprefix-property3="overwite-value3"
data-myprefix-property4='{"property41": "value41"}'
data-other="We do not read it"></div>
If you want to read data from data-myprefix and every data-myprefix-* attribute you can use .prefixData() with given prefix.
$('#example-tag').prefixData('myprefix');
The previous example returns the object:
{
property1: "value1",
property2: {
property21: "value21",
property22: "value22",
property23: "value23"
},
property3: "overwite-value3",
property4: {
property41: "value41"
}
}

subscribing an observableArray to a computed

I have an object that is constructed upon a table row from the database. It has all the properties that are found in that entry plus several ko.computed that are the middle layer between the entry fields and what is displayed. I need them to be able translate foreign keys for some field values.
The problem is the following: One of the properties is an ID for a string. I retrieve that ID with the computed. Now in the computed will have a value that looks like this: 'option1|option2|option3|option4'
I want the user to be able to change the options, add new ones or swap them around, but I also need to monitor what the user is doing(at least when he adds, removes or moves one property around). Hence, I have created an observable array that I will bind in a way that would allow me to monitor user's actions. Then the array will subscribe to the computed so it would update the value in the database as well.
Some of the code:
function Control(field) {
var self = this;
self.entry = field; // database entry
self.choices = ko.observableArray();
self.ctrlType = ko.computed({
read: function () {
...
},
write: function (value) {
if (value) {
...
}
},
owner: self
});
self.resolvedPropriety = ko.computed({
read: function () {
if (self.ctrlType()) {
var options = str.split('|');
self.choices(createObservablesFromArrayElements(options));
return str;
}
else {
return '';
}
},
write: function (value) {
if (value === '') {
//delete entry
}
else {
//modify entry
}
},
deferEvaluation: true,
owner: self
});
self.choices.subscribe(function (newValue) {
if (newValue.length !== 0) {
var newStr = '';
$.each(newValue, function (id, el) {
newStr += el.name() + '|';
});
newStr = newStr.substring(0, newStr.lastIndexOf("|"));
if (self.resolvedPropriety.peek() !== newStr) {
self.resolvedPropriety(newStr);
}
}
});
self.addChoice = function () {
//user added an option
self.choices.push({ name: ko.observable('new choice') });
};
self.removeChoice = function (data) {
//user removed an option
if (data) {
self.choices.remove(data);
}
};
...
}
This combination works, but not as I want to. It is a cyclic behavior and it triggers too many times. This is giving some overload on the user's actions because there are a lot of requests to the database.
What am I missing? Or is there a better way of doing it?
Quote from knockout computed observable documentation
... it doesn’t make sense to include cycles in your dependency chains.
The basic functionality I interpreted from the post:
Based on a field selection, display a list of properties/options
Have the ability to edit said property/option
Have the ability to add property/option
Have the ability to delete property/option
Have the ability to sort properties/options (its there, you have to click on the end/edge of the text field)
Have the ability to save changes
As such, I have provided a skeleton example of the functionality, except the last one, you described #JSfiddle The ability to apply the changes to the database can be addressed in several ways; None of which, unless you are willing to sacrifice the connection overhead, should include a computed or subscription on any changing data. By formatting the data (all of which I assumed could be collected in one service call) into a nice nested observable view model and passing the appropriate observables around, you can exclude the need for any ko.computed.
JS:
var viewModel = {
availableFields : ko.observableArray([
ko.observable({fieldId: 'Field1',
properties: ko.observableArray([{propertyName: "Property 1.1"}])}),
ko.observable({fieldId: 'Field2',
properties: ko.observableArray([{propertyName:"Property 2.1"},
{propertyName:"Property 2.2"}])})]),
selectedField: ko.observable(),
addProperty: function() {
var propertyCount = this.selectedField().properties().length;
this.selectedField().properties.push({propertyName: "Property " + propertyCount})
},
};
ko.applyBindings(viewModel);
$("#field-properties-list").sortable({
update: function (event, ui) {
//jquery sort doesnt affect underlying array so we have to do it manually
var children = ui.item.parent().children();
var propertiesOrderChanges = [];
for (var i = 0; i < children.length; ++i) {
var child = children[i];
var item = ko.dataFor(child);
propertiesOrderChanges.push(item)
}
viewModel.selectedField().properties(propertiesOrderChanges);
}
});
HTML:
<span>Select a field</span>
<select data-bind='foreach: availableFields, value: selectedField'>
<option data-bind='text: $data.fieldId, value: $data'></option>
</select>
<div style="padding: 10px">
<label data-bind='text: "Properties for " + selectedField().fieldId'></label>
<button data-bind='click: $root.addProperty'>Add</button>
<ul id='field-properties-list' data-bind='foreach: selectedField().properties'>
<li style = "list-style: none;">
<button data-bind="click: function() { $root.selectedField().properties.remove($data) }">Delete</button>
<input data-bind="value: $data.propertyName"></input>
</li>
</ul>
</div>

Categories

Resources