I have created this JS object from an array.
var rv = {};
$( ".part-name:visible" ).each(function( index ) {
//rv[$(this).text()] = arrayPartsName[$(this).text()];
rv[$(this).text()] = arrayPartsName[$(this).text()];
console.log(rv);
})
4GN: "4GN"
4GNTS: "4GNTS"
042645-00: "042645-00"
503711-03: "503711-03"
573699-05: "573699-05"
I have to use this object with Materialize Autocomplete and I have to edit it. The correct object must be, for example, like this
4GN: null
4GNTS: null
042645-00: null
503711-03: null
573699-05: null
How can do this?
Picking up from my comment. You can just set it to null ;) JavaScript is quite a cool language... you can pretty much set any object's properties to anything you want, null, a specific value, or even a function... see some more on the topic
But to focus on your specific question:
Change this line
rv[$(this).text()] = arrayPartsName[$(this).text()];
to
rv[$(this).text()] = null;
Something to be aware of
If you have property or key values in the JSON object with a dash in the name, you have to wrap it in quotes ", otherwise it wont be seen as valid. Although this might not be as evident, or an issue in your example as your keys are being added via the following function $(this).text().
var fruit = {
"pear": null, // something null
"talk": function() { console.log('WOOHOO!'); } // function
}
var apple = "app-le";
fruit[apple.toString()] = 'with a dash';
fruit["bana-na"] = 'with a dash';
// below is not allowed, the values will be evaluated as
// properties that dont exist, and then your js will fail
// fruit[pe-ar] = 'with a dash';
fruit.talk();
console.log(fruit);
I have the following object:
var oBadge = {
COMMENT_CREATED: {
FIRST_COMMENT_CREATED: {
code: "FIRST_COMMENT_CREATED",
src: "",
name: "Socializer",
text: "Create a comment for an idea",
condition: {
today: null,
over_all: 1
}
}
}
};
i need to check if a string i get (for example "FIRST_COMMENT_CREATED") is contained in the oBadge model. The object contains more elements - not only the comment_created element. Therefore i cannot define to check it there.
I found the following function to determine wether the element is contained within the object or not, but I also need the contained data, not only the statement whether it is contained or not.
oBadge.hasOwnProperty("FIRST_COMMENT_CREATED")
What i'm basically looking for is a way to skip the second hierachy level on my check - like:
if(oBadge.[<all>]["FIRST_COMMENT_CREATED"] !== undefined) {
// return data
}
There is no way to skip the hierarchy without looping through the object, you should use the for...in loop:
for (var prop in oBadge) {
if(oBadge[prop].hasOwnProperty("FIRST_COMMENT_CREATED")) {
// return data
}
}
Use a function and a loop (for-in):
function find(key, obj){
if(!obj.hasOwnProperty) return false; // no primitive data please!
if(obj.hasOwnProperty(key))
return obj[key];
else return false;
}
for(var key in oBadge){
if(find("FIRST_COMMENT_CREATED", key))
// code
}
I am ordering a my data and its working all correcty except some fields are empty or have no value. When ordered these empty field come up first. For example when ordering numbers we would get a huge empty list before getting the "0"-values.
I am doing it like thise:
ng-click="predicate = 'name'; reverse=!reverse"
and
ng-repeat="name in names | orderBy:predicate:reverse"
JSFiddle: http://jsfiddle.net/JZuCX/1/
Is there an easy elegant way to fix this? I want the empty fields to come last, no matter what.
How about this for sorting strings:
item in (items|orderBy:['!name', 'name'])
The advantage (apart from being more concise) is it sorts null & undefined with the blank strings.
In my case I wanted the blanks & nulls & undefineds together at the top (nulls and undefineds by default sort to the bottom), so I used:
item in (items|orderBy:['!!name', 'name'])
I'd write a filter that takes items with empty name from ordered array and places them at the end:
<li ng-repeat="item in (items|orderBy:'name'|emptyToEnd:'name')">{{item.name}}</li>
Code might look like this:
.filter("emptyToEnd", function () {
return function (array, key) {
if(!angular.isArray(array)) return;
var present = array.filter(function (item) {
return item[key];
});
var empty = array.filter(function (item) {
return !item[key]
});
return present.concat(empty);
};
});
Working example.
By the way, your fiddle doesn't contain any relevant code. Did you use the wrong link?
Update 2:
Your fiddle with my filter.
Down here! :D
This solution extends the normal functionality of the angularJs orderBy filter to take a third argument specifying whether or not to invert the normal sorting of null and undefined values. It observes the property names it is passed (not just one), and doesn't iterate over items a second as some of the other solutions do. It's used like this:
<li ng-repeat="item in (items|orderBy:'name':false:true)">{{item.name}}</li>
I found a bunch of threads, some not directly about orderBy, and compiled their techniques plus a couple bits of my own into this:
angular.module('lib')
.config(['$provide', function ($provide) {
$provide.decorator('orderByFilter', ['$delegate', '$parse', function ($delegate, $parse) {
return function () {
var predicates = arguments[1];
var invertEmpties = arguments[3];
if (angular.isDefined(invertEmpties)) {
if (!angular.isArray(predicates)) {
predicates = [predicates];
}
var newPredicates = [];
angular.forEach(predicates, function (predicate) {
if (angular.isString(predicate)) {
var trimmed = predicate;
if (trimmed.charAt(0) == '-') {
trimmed = trimmed.slice(1);
}
var keyFn = $parse(trimmed);
newPredicates.push(function (item) {
var value = keyFn(item);
return (angular.isDefined(value) && value != null) == invertEmpties;
})
}
newPredicates.push(predicate);
});
predicates = newPredicates;
}
return $delegate(arguments[0], predicates, arguments[2]);
}
}])
}]);
To use this code verbatim, be to specify 'lib' as a dependency for your app.
Credits to:
$parse
[nullSorter].concat(originalPredicates)
decorator pattern
I don't believe there's an "out of the box" solution for this. I could easily be wrong.
Here's my attempt at a solution using a function as the predicate:
ng-repeat="name in names | orderBy:predicate"
Inside your controller:
$scope.predicate = function(name) {
return name === '' ? 'zzzzzzz' : !name;
/* The 'zzzzzz' forces the empty names to the end,
I can't think of a simpler way at the moment. */
}
In addition to the solution of Klaster_1, add an extra parameter to make the filter more generic:
http://jsfiddle.net/Zukzuk/JZuCX/27/
Implementation
<tr ng-repeat="name in (names | orderBy:predicate:reverse | orderEmpty:'name':'toBottom')">
Filter
.filter('orderEmpty', function () {
return function (array, key, type) {
var present, empty, result;
if(!angular.isArray(array)) return;
present = array.filter(function (item) {
return item[key];
});
empty = array.filter(function (item) {
return !item[key]
});
switch(type) {
case 'toBottom':
result = present.concat(empty);
break;
case 'toTop':
result = empty.concat(present);
break;
// ... etc, etc ...
default:
result = array;
break;
}
return result;
};
});
Thnx Klaster_1!
Sorting, and reverse sorting, using a variable sort column, and keeping the undefined at the bottom, even below the negative values
I love the elegance of Sean's answer above! I needed to give my users the ability to choose the column to sort on, and choice of sort direction, but still require the undefined's to fall to the bottom, even if there are negative numbers.
The key insight from Sean that fixes negative numbers is !!. Use '!'+predicate if you are doing forward sorting and '!!'+predicate if you are doing reverse sorting.
The snippet below demonstrates this. By the way, I have put the variables that set the predicate (choice of propery to sort on) and reverse inside an object ("d") just so that we don't get weird scope issues. You may not need the "d."s in your environment.
Moreover you would probably want to use something better than my crappy buttons at the bottom of the page to control your sort predicate and direction. However this keeps the key parts of the code easy to read.
function mainController($scope) {
$scope.userArray = [
{ name: "Don", age: 20 },
{ name: "Bob", age: 30, height: 170 },
{ name: "Abe", age: 40, height: 160 },
{ name: "Zoe", age: 70 },
{ age: 70, height: 155 },
{ name: "Shorty",age:45,height: -200},
{ name: "TwinkleInEye", age: -1, height: 152 }
]
$scope.d = {}; // Create an object into which info can be stored and not trashed by Angular's tendency to add scopes
$scope.d.predicate = "name"; // This string is the name of the property on which to sort
$scope.d.reverse = false; // True means reverse the sort order
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="" ng-controller="mainController">
<div ng-repeat="user in (userArray | orderBy: (d.reverse ?['!!'+d.predicate,d.predicate]:['!'+d.predicate,d.predicate]) : d.reverse)">
Name {{ user.name }} : Age {{ user.age }} : Height {{ user.height }}
</div>
<br/>
<button ng-click="d.predicate='name';">Name</button>
<button ng-click="d.predicate='age';">Age</button>
<button ng-click="d.predicate='height';">Height</button> Currently: {{d.predicate}}
<br/> Leave undefined at bottom, but otherwise:
<button ng-click="d.reverse= !d.reverse;">Reverse</button> Currently: {{d.reverse}}
</body>
#Klaster_1 was really on to something but as soon as I needed a nested value the filter stopped working. Also, if I was reverse ordering I still wanted my null values to show up before 0. I added $parse to take care of the nested keys and added a reverse parameter to I knew when to put the null values at the top.
.filter("emptyToEnd", function ($parse) {
return function (array, key, reverse) {
if(!angular.isArray(array)) return;
var keyFn = $parse(key);
var present = [];
var empty = [];
angular.forEach(array, function(item){
var val = keyFn(item);
if(angular.isUndefined(val) || val === null) {
empty.push(item);
} else {
present.push(item);
}
});
if (reverse) {
return present.concat(empty);
} else {
return empty.concat(present);
}
};
});
I don't know why other answer suggest to put the null value records at the bottom, If I want to sort normally, means in ASC order all the null on top and in DESC order all the nulls go to bottom, I tried other answers here but could not helped me so change the code to convert the null to '' in my array and it works now smooth like this:
$scope.convertNullToBlank = function (array) {
for (var i = 0; i < array.length; i++) {
if (array[i].col1 === null)
array[i].col1 = '';
if (array[i].col2 === null)
array[i].col2 = '';
}
return array;
}
I created a gist with an alternative filter based on the previous solutions:
https://gist.github.com/360disrupt/1432ee1cd1685a0baf8967dc70ae14b1
The filter extends the existing angular filter:
angular.module 'tsd.orderByEmptyLast', []
.filter 'orderByEmptyLast', ($filter) ->
return (list, predicate, reverse)->
orderedList = $filter('orderBy')(list, if reverse then ['!' + predicate, '-' + predicate] else ['!' + predicate, predicate] )
return orderedList
On newer angular versions you might need to include orderByFilter instead of using $filter
angular.module 'tsd.orderByEmptyLast', ['orderByFilter']
.filter 'orderByEmptyLast', () ->
return (list, predicate, reverse)->
orderedList = orderByFilter(list, if reverse then ['!' + predicate, '-' + predicate] else ['!' + predicate, predicate] )
return orderedList
In a page I call external js file and its includes a dictionary. I want to replace element text by their attiributes.
<p data-text="dict.dataP"></p>
<p data-text="dict.data.P2"></p>
I want to fill it with external file dict
var dict = {
"dataP1": "this is my p text",
"data" : {
"p2": "my another paragraph"
},
};
I tried to use as this
$.each(function () {
if ($(this).attr("data-text").length > 0) {
$(this).html(
$(this).attr('data-text') /*we got a problem here. it returns string*/ );
}
});
codePen here:
codepen.io
JSFiddle here
jsfiddle.net
How can I do this?
edited dict. we have dictionary in dictionary.
In order to access the data attribute, use:
$(this).data('text');
Text is the part after the hyphen within your attribute declaration.
There are a few things that you should change to get this working, taking Jimbo's answer and adding a few other things:
var dict = {
"dataP": "this is my p tag text"
};
$('[data-text]').each(function () {
if ( $(this).data("text") ) {
var dictAttr = $(this).data("text");
if ( dictAttr && dict[dictAttr] ) {
$(this).html( dict[dictAttr] );
}
}
});
You should also change your mark-up to make things easier (expecially if you are only dealing with one dict object):
<p data-text="dataP"></p>
With the above changes you should get things working as you expect.
http://jsfiddle.net/b2zgw/
string-based object navigation
Rather oddly I'm actually working on a framework for string-based object navigation as we speak. However the codebase if far too complicated to just tack on the end of a stackoverflow answer. As an addition to my comment I realised there is a third option which might be the best of both worlds (depending on what you need).
:: eval
I don't recommend this method, but it can't be argued with for simplicity — the following is a snippet from the code above and should replace that part:
var dictNav = $(this).data("text"); /// e.g. say dictNav == 'dict.dataP'
var ref; eval('ref='+dictNav);
if ( ref && red.substr ) {
$(this).html( ref );
}
The problem with the above is that eval will evaluate any JavaScript - so all an attacker would have to do is find a way to add a data-text attribute to something on your page and they would be able to execute any JavaScript they liked.
:: string-based navigation
The following is a simple function that could be improved, but should give you the idea:
function navigate( obj, path ){
var cur = obj, bits = path.split('.');
for ( var i=0, l=bits.length; i<l && cur; i++ ) {
cur = cur[bits[i]];
}
return cur;
}
Using the above you can navigate your dictionary structure - be aware because you start your 'navigation path' with a particular var name (i.e. dict) you'll have to place your dictionary one level down of the object you wish to traverse; this is the reason for {dict:dict}. To support multiple dictionaries you'd just need to extend that object like so {dict:dict,dict2:dict2} — again the following is a snippet and should replace the original code:
var dictNav = $(this).data("text"); /// e.g. say dictNav == 'dict.dataP.test'
var ref = navigate({dict:dict}, dictNav);
if ( ref && red.substr ) {
$(this).html( ref );
}
:: third option - simplify your dictionary
The third option is probably the most optimal, but means that the dictionary can't be modified in realtime (or at least not easily). Basically before you run the rest of your code, push your dictionary through a parsing function that modifies it's structure:
var dict = {
"dataP": "this is a test",
"forms": {
"inputLabel": "this is a second level"
}
};
function simplifyDict( obj, target, path, subpath ){
if ( !target ) { target = {}; }
if ( !path ) { path = ''; }
for( var i in obj ) {
subpath = (path ? path + '.' : '') + i;
if ( typeof obj[i] == 'object' ) {
simplifyDict( obj[i], target, subpath );
}
else {
target[subpath] = obj[i];
}
}
return target;
}
dict = simplifyDict( dict );
The above simplify function should return something like:
{
"dataP" : "this is a test",
"forms.inputLabel" : "this is a second level"
}
Then all you need to do is use my original code right at the top of this answer. Instead of converting your strings to separate object properties and navigating the dictionary, you've switched the dictionary to be a string-based lookup... so now you can just use strings directly i.e. dict['forms.inputLabel']
You'd like to set the text from the dictionary to the element with matching data attribute, right?
Try this:
var dict = {
"dataP": "this is my p tag text",
"dataDiv": "dummy div"
};
$('p').each(function () {
var data = $(this).data('text');
$(this).html(dict[data.split('.')[1]])
});
http://jsfiddle.net/eB6E2/1/
I'm running into a maddening problem where I set a variable to point to a jQuery selector, such as: var foobar=jQuery(this); I then pass this variable to a function to be worked on. Let's simplify a little and say the function looks like this:
function SetFieldValue (selector) {
selector.val('test');
console.log ( selector );
console.log ( jQuery('#' + selector.attr('id')) );
}
In this situation if you assume that:
the selector is always a form element (and therefore val() is a valid operation)
the selector does resolve to a single dom element which has an 'id' attribute
You would then expect the two console.log statements to output the same result, right? Well I'm running into a situation where this condition only happens about 90% of the time.
In order to give more context I've created a short screencast demonstrating the problem:
SCREENCAST LINK
For reference purposes, here's the actual SetFieldValue code that is shown in the screencast:
function SetFieldValue ( domObject, value ) {
// as a safety function, check if a string representation of the domObject was passed in and convert it to a jQuery object if it was
if ( jQuery.type(domObject) === "string") {
console.log ("Value passed into SetFieldValue was a string representation so converting to jQuery object");
domObject = jQuery(domObject);
}
if ( jQuery.inArray (domObject.prop('tagName').toLowerCase(),['input' , 'select' , 'textarea']) >= 0 ) {
console.log ("setting to value attribute: " + value);
if ( domObject.hasAttr('id') ) {
domObject.val(value);
//jQuery('#' + domObject.attr('id')).val(value);
} else {
domObject.attr('value',value);
}
console.log ("Using jQuery ID it is set to: " + jQuery('#' + domObject.attr('id')).val() );
console.log ("Using jQuery selector variable it is set to: " + domObject.val() );
} else {
console.log ("setting to html attribute");
domObject.html( value );
}
return domObject;
}
Lets examine the code a bit.
First assigning back to a parameter is not a good practice adding a var at the start of your function would be a lot better, as scope can be lost.
//Suggestion change parameter to domItem
var domObject
Your missing an error handler for when the parameter is not String.
when identifying the type use
<VARNAME>.constructor.toString().match(/function (\w*)/)[1] === "<TYPE>"
It's more efficient and handles custom types.
No need for all the logic in assignment of value attribute. Any dom Object can be made to have a value attribute. also not sure why you are setting the val versus the value.
domObject.attr('value',value);
It is at this point that I can see your code could really use some documentation to help explain purpose
If you are explicitly only wanting to set value on Input fields and set value as innerhtml on non input fields then yes the logic would be needed but could be simplified to ... as the value doesn't need to be detected to overwritten.
if (jQuery.inArray (domObject.prop('tagName').toLowerCase(), ['input' , 'select' , 'textarea']) >= 0) {
domObject.attr('value',value);
} else {
domObject.html( value );
}
No Idea why you are returning the domObject out.
So a quick rewrite without the return and keeping most of the logic adding error handling results in
/*jslint sloppy: true*/
/*global jQuery*/
function SetFieldValue(domString, value) {
// as a safety function, check if a string representation of the domObjects was passed in and convert it to a jQuery object if it was
var domObjects, index;
//errorhandling
if (domString === undefined || domString === null) {
throw {error : "domString must have a value."};
}
if (domString.constructor.toString().match(/function (\w*)/)[1] !== "string") {
if (domString.constructor.toString().match(/function (\w*)/)[1].match(/HTML[a-zA-Z]*Element/) === null) {
throw {error : "domString expected to be String or domObjects"};
}
} else {
if (jQuery(domString).length === 0) {
throw {error : "domString does not resolve to a detectable domObjects."};
}
}
//errorhandling
//action
if (domString.constructor.toString().match(/function (\w*)/)[1].match(/HTML[a-zA-Z]*Element/)) {
//made as an array to normalize as jQuery returns an array allows code to be simplified
domObjects = [domString];
} else {
domObjects = jQuery(domString);
}
//given that domObjects are an array need to step through the array
for (index = domObjects.length - 1; index >= 0; index -= 1) {
if (
jQuery.inArray(
domObjects[index].tagName.toLowerCase(),
['input', 'select', 'textarea']
) >= 0
) {
if (domObjects[index].hasAttr('id')) {
domObjects[index].val(value);
} else {
domObjects[index].attr('value', value);
}
} else {
domObjects[index].html(value);
}
}
}
The above passes JSLint
I know I didn't provide enough context for people to really dig into this problem but I have in the end solved it. What was the issue? Well it was #Kobi who first asked is the DOM element's ID unique ... to which I happily reported it was. And it had been but in fact that WAS the problem. Jesus. It's always the obvious things that you then go onto overlook that get you in trouble.
Anyway, thanks for your patience and help.