What is the best way to view all jQuery data key-value pairs across every element (in jQuery 2.x)?
A selection-oriented approach ( e.g. $('*').data() ) obviously does not work, because the return value is tied to a single element.
I know that I can iterate over every element, checking each for data:
var allData = [];
$('html *').each(function() {
if($.hasData(this)) {
allData.push({ el: this, data: $(this).data() })
}
})
JSFiddle
This does produce the expected output, but iterating over each possible data key feels like a backwards approach to this problem.
Is there some way to find all element data directly?
N.B. I'm interested for debugging, not production code.
You could select every element within the body with $("body *") and apply jQuery's .filter() to it. Working example:
var $elementsContainingData $("body *").filter(function() {
if($.hasData(this)) return this;
});
console.log($elementsContainingData);
Edit
As #spokey mentioned before, there's an internal variable named "cache" within the jQuery object: $.cache.
This variable consists of a bunch of objects which contain keys like "data" or "events":
5: Object
data: Object
events: Object
handle: function (a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)}
__proto__: Object
You can iterate through that object and filter for the data:
var filteredCache = $.each($.cache,function() {
if(typeof this["data"] === "object") return this;
});
Here's an working example plus a function to merge that stuff into a single and more handy object consisting only of dataKey => dataValue pairings: Fiddle
Edit
As mentioned in comments this solution does not work in jQuery version 2.x since $.cache is deprecated.
My last suggestion is creating a hook for jQuerys data function in order to extend an own object$.dataCache = {}; each time data() is called.
Extending, replacing or adding jQuerys functions is done by accessing $.fn.functionName:
$.fn.data = function(fn,hook) {
return function() {
hook.apply(this,arguments);
return fn.apply(this,arguments);
}
}($.fn.data,function(key,value) {
var objReturn = {};
objReturn[key] = value;
$.extend($.dataCache,objReturn);
});
This also works great in jQuery version 2: Fiddle
Related
Is there something that I'm missing that would allow item to log as an object with a parameter, but when I try to access that parameter, it's undefined?
What I've tried so far:
console.log(item) => { title: "foo", content: "bar" } , that's fine
console.log(typeof item) => object
console.log(item.title) => "undefined"
I'll include some of the context just in case it's relevant to the problem.
var TextController = function(myCollection) {
this.myCollection = myCollection
}
TextController.prototype.list = function(req, res, next) {
this.myCollection.find({}).exec(function(err, doc) {
var set = new Set([])
doc.forEach(function(item) {
console.log(item) // Here item shows the parameter
console.log(item.title) // "undefined"
set.add(item.title)
})
res.json(set.get());
})
}
Based on suggestion I dropped debugger before this line to check what item actually is via the node repl debugger. This is what I found : http://hastebin.com/qatireweni.sm
From this I tried console.log(item._doc.title) and it works just fine.. So, this seems more like a mongoose question now than anything.
There are questions similar to this, but they seem to be related to 'this' accessing of objects or they're trying to get the object outside the scope of the function. In this case, I don't think I'm doing either of those, but inform me if I'm wrong. Thanks
Solution
You can call the toObject method in order to access the fields. For example:
var itemObject = item.toObject();
console.log(itemObject.title); // "foo"
Why
As you point out that the real fields are stored in the _doc field of the document.
But why console.log(item) => { title: "foo", content: "bar" }?
From the source code of mongoose(document.js), we can find that the toString method of Document call the toObject method. So console.log will show fields 'correctly'. The source code is shown below:
var inspect = require('util').inspect;
...
/**
* Helper for console.log
*
* #api public
*/
Document.prototype.inspect = function(options) {
var isPOJO = options &&
utils.getFunctionName(options.constructor) === 'Object';
var opts;
if (isPOJO) {
opts = options;
} else if (this.schema.options.toObject) {
opts = clone(this.schema.options.toObject);
} else {
opts = {};
}
opts.minimize = false;
opts.retainKeyOrder = true;
return this.toObject(opts);
};
/**
* Helper for console.log
*
* #api public
* #method toString
*/
Document.prototype.toString = function() {
return inspect(this.inspect());
};
Make sure that you have defined title in your schema:
var MyCollectionSchema = new mongoose.Schema({
_id: String,
title: String
});
Try performing a for in loop over item and see if you can access values.
for (var k in item) {
console.log(item[k]);
}
If it works, it would mean your keys have some non-printable characters or something like this.
From what you said in the comments, it looks like somehow item is an instance of a String primitive wrapper.
E.g.
var s = new String('test');
typeof s; //object
s instanceof String; //true
To verify this theory, try this:
eval('(' + item + ')').title;
It could also be that item is an object that has a toString method that displays what you see.
EDIT: To identify these issues quickly, you can use console.dir instead of console.log, since it display an interactive list of the object properties. You can also but a breakpoint and add a watch.
Use findOne() instead of find().
The find() method returns an array of values, even if you have only one possible result, you'll need to use item[0] to get it.
The findOne method returns one object or none, then you'll be able to access its properties with no issues.
Old question, but since I had a problem with this too, I'll answer it.
This probably happened because you're using find() instead of findOne(). So in the end, you're calling a method for an array of documents instead of a document, resulting in finding an array and not a single document. Using findOne() will let you get access the object normally.
A better way to tackle an issue like this is using doc.toObject() like this
doc.toObject({ getters: true })
other options include:
getters: apply all getters (path and virtual getters)
virtuals: apply virtual getters (can override getters option)
minimize: remove empty objects (defaults to true)
transform: a transform function to apply to the resulting document before returning
depopulate: depopulate any populated paths, replacing them with their original refs (defaults to false)
versionKey: whether to include the version key (defaults to true)
so for example you can say
Model.findOne().exec((err, doc) => {
if (!err) {
doc.toObject({ getters: true })
console.log('doc _id:', doc._id) // or title
}
})
and now it will work
You don't have whitespace or funny characters in ' title', do you? They can be defined if you've quoted identifiers into the object/map definition. For example:
var problem = {
' title': 'Foo',
'content': 'Bar'
};
That might cause console.log(item) to display similar to what you're expecting, but cause your undefined problem when you access the title property without it's preceding space.
I think using 'find' method returns an array of Documents.I tried this and I was able to print the title
for (var i = 0; i < doc.length; i++) {
console.log("iteration " + i);
console.log('ID:' + docs[i]._id);
console.log(docs[i].title);
}
If you only want to get the info without all mongoose benefits, save i.e., you can use .lean() in your query. It will get your info quicker and you'll can use it as an object directly.
https://mongoosejs.com/docs/api.html#query_Query-lean
As says in docs, this is the best to read-only scenarios.
Are you initializing your object?
function MyObject()
{
this.Title = "";
this.Content = "";
}
var myo1 = new MyObject();
If you do not initialize or have not set a title. You will get undefined.
When you make tue query, use .lean() E.g
const order = await Order.findId("84578437").lean()
find returns an array of object , so to access element use indexing, like
doc[0].title
I have a set of id values in 4 arrays. Each array will be assigned a text value for an h1 and a p that I haven't put in yet. Right now I'm just trying to get it to alert if one of the images in array graphicDesign is clicked. I tried using $.inArray
DEMO
var graphicDesign = [$('#design'), $('#DD'), $('#SElogo')];
var webDesign = [$('#bootstrap'), $('#farm'), $('#pong'), $('#SE'), $('#dung')];
var programming = [$('#SE'), $('#dung'), $('#sacar')];
var other = [$('#firm')];
function categories() {
if ($.inArray(this, graphicDesign) > -1) {
alert('hello');
}
}
You should not store DOM objects in an array and try to match them with $.inArray.
Using ids or another attribute would be a better solution.
For example :
https://jsfiddle.net/1f9xd3t0/
var graphicDesign = ['design', 'DD', 'SElogo'];
function categories(id) {
if ($.inArray(id, graphicDesign) > -1) {
alert('hello');
}
}
categories('design');
You need to pass the event object to categories().
$('.portPic').click(function(e) {
// ...
categories(e);
});
function categories(e) {
console.log(e.target);
if ($.inArray(e.target, graphicDesign) > -1) {
alert('hello');
}
}
UPDATE
And maybe use id's rather than jQuery objects in your arrays.
var graphicDesign = ['design', 'DD', 'SElogo'];
Then use e.target.id in categories().
You can use typeof , here is an example.
// Objects
typeof {a:1} === 'object';
// use Array.isArray or Object.prototype.toString.call
// to differentiate regular objects from arrays
typeof [1, 2, 4] === 'object';
Array.indexOf() is a native function that does the same thing.
graphicDesign.indexOf(this) > -1 would be the equivalent of what you wrote.
In your usage, this is going to refer to the global object, unless you elsewhere assign this function to an object and call it as a method... But then you're trying to tell if the object you're calling it on is inside the graphicDesign array?
Here's an example of a usage that would fire the alert:
var graphicDesign = [ {} ]
graphicDesign[0].categories = function() {
if (graphicDesign.indexOf(this) > -1) {
alert('the object this method was called on is inside the graphicDesign array')
}
}
graphicDesign[0].categories()
It's unclear exactly what you're trying to accomplish, however (you mention a click detection, but there's no click handler here, etc.)... I hope this helps?
This block of $.inArray is working, but you put them in wrong place, it always returned -1, so you cannot get the alert('hello'). Please fix the overall logic.
if ($.inArray(this, graphicDesign) > -1) {
alert('hello'); }
Anyone know a quick and efficient way to grab all the data attributes from a single element? I realize that jQuerys .data() will do just that, however it will not give me data attributes set using .attr() UNLESS I first select the data attribute using .data(); Also, you can't select elements by data attributes that were added using .data(), which seems silly.
html
<div data-foo="bar"></div>
javascript
$("div").data();
//returns {foo:bar} good :)
$("div").attr("data-hello","world");
$("div").data()
//returns {foo:bar} no good :(
$("div[data-hello]");
//returns the div, all good :)
$("div").data("find","me");
$("div[data-find]");
//returns nothing, very bad
Hopefully this explains
You can use the dataset property in modern browsers(IE11+ only), but you can enhance the solution to use .attributes to support older browsers
var $in = $('input'),
input = $in[0], //here input is a dom element reference
dataMap = input.dataset;
//if dataset is not supported
if (typeof dataMap == 'undefined') {
dataMap = {};
$.each(input.attributes, function (key, attr) {
var match = attr.name.match(/^data-(.+)/);
if (match) {
dataMap[match[0]] = attr.value;
}
})
}
$.each(dataMap, function (key, value) {
console.log(key, value)
})
Demo: Fiddle
Different versions of Internet Explorer support different features that are relevant to this issue. In version 11, support for dataset was added, which returns a DOMStringMap of data-attribute names (minus the "data-" portion), and their respective values.
In versions 9 and 10, we can leverage Array.prototype.slice to convert the well-supported attributes collection into an array that we can then reduce to an object, similar to DOMStringMap.
We can combine both of these approaches into a single function that accepts an element as its argument, and returns an object like this { name: "pat", "age": 23 } for all data- attributes:
function getDataAttributes ( el ) {
return el.dataset || [].slice.call( el.attributes ).reduce(function ( o, a ) {
return /^data-/.test( a.name ) && ( o[ a.name.substr( 5 ) ] = a.value ), o;
}, {} );
}
If you require support for Internet Explorer 8, or below, you can still use the above approaches, and simply polyfill Array.prototype.reduce.
I've been writing an app with the kogrid, recently I changed my datasource from an array of objects to an array of knockout objects. However, to my surprise when I update the observable properties within my objects the grid is not updated.
Here is my data array:
self.gridData = ko.observableArray([
{ name: ko.observable("joe"), age: ko.observable(5) }
]);
when I update the age property nothing happens on the grid:
self.gridData()[0].age(6);
does anyone have a good answer for why this is?
Update
I've answered the question below, but does anyone know why the kogrid would be caching the unwrapped values?
I looked into the kogrid source and found this line in src/classes/row.js
self.getProperty = function (path) {
return self.propertyCache[path] || (self.propertyCache[path] = window.kg.utils.evalProperty(self.entity, path));
};
it looks like the property cache is caching the unwrapped value of the property we're accessing in the default cell template:
<div data-bind="attr: { 'class': 'kgCellText colt' + $index()}, html: $data.getProperty($parent)"></div>
(Note: $data in the template is the column, which has a getProperty wrapper for row.getProperty)
I simply removed the line to cache property values like this:
self.getProperty = function (path) {
return window.kg.utils.evalProperty(self.entity, path);
};
I basically want the equivalent to binding to 'add' and 'remove' events in Backbone's Collections. I see basically no way of doing this in AngularJS, and the current workaround we've settled for is $watch()ing the array's length and manually diffing/recalculating the whole thing. Is this really what the cool kids do?
Edit: Specifically, watching the array's length means I don't easily know which element has been changed, I need to manually "diff".
I think using $watch is a good solution, but $watchCollection can be better for you. $watchCollection doesn't perform deep comparison and just watchs for array modification like insert, delete or sort (not item update).
For exemple, if you want to keep an attribut order synchronize with the array order :
$scope.sortableItems = [
{order: 1, text: 'foo'},
{order: 2, text: 'bar'},
{order: 3, text: 'baz'}
];
$scope.$watchCollection('sortableItems', function(newCol, oldCol, scope) {
for (var index in newCol) {
var item = newCol[index];
item.order = parseInt(index) + 1;
}
});
But for your problem, I do not know if there is a better solution than manually browse the array to identify the change.
The way to watch an array in Angular is $watch(array, function(){} ,true)
I would create child scopes and watch them individually.
here is an example:
$scope.myCollection = [];
var addChild = function()
{
var Child = $scope.$new();
Child.name = 'Your Name here';
Child.$watch('name', function(newValue) {
// .... do something when the attribute 'name' is changed ...
});
Child.$on('$destroy', function() {
//... do something when this child gets destroyed
});
$scope.myCollection.push(Child); // add the child to collection array
};
// Pass the item to this method as parameter,
// do it within an ngRepeat of the collection in your views
$scope.deleteButtonClicked = function(item)
{
var index = $scope.myCollection.indexOf(item); //gets the item index
delete $scope.myCollection[index]; // removes the item on the array
item.$destroy(); // destroys the original items
}
Please tell more about your usecase. One of the solutions of tracking element persistance is using ngRepeat directive with custom directive that listening element's $destroy event:
<div ng-repeat="item in items" on-delete="doSomething(item)">
angular.module("app").directive("onDelete", function() {
return {
link: function (scope, element, attrs) {
element.on("$destroy", function () {
scope.$eval(attrs.onDelete);
});
}
}
});
Perhaps the solution is to create the collection class ( like backbone does ) and you can hook into events pretty easily as well.
The solution I have done here isnt really comprehensive, but should give you a general guidance on how this could be done perhaps.
http://beta.plnkr.co/edit/dGJFDhf9p5KJqeUfcTys?p=preview