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);
};
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 computed property in my data this.coinPairingOptions that needs to render its radio buttons based on some of the other fields in this schema. I have reduced the amount of code in the file to save space.
data: function () {
return {
schema: {
{model: "symbolPair", type: "radios", label: "Pair with", values:
this.coinPairingOptions, required: true}
},
computed: {
coinPairingOptions() {
console.log("computing coinPairingOptions")
let coin = this.model.symbol.toUpperCase();
let options = [];
if (this.model.exchange === 'Coinbase') {
options = this.getCoinbasePairs
} else if (this.model.exchange === 'Binance') {
options = this.getBinancePairs
} else {
}
console.log(options.get(coin));
return options.get(coin);
},
}
In the dev tools I can see the computed property changing to the correct values however it is not changing in the data. Apparently, this is appropriate behavior, but what is a way around this? I have tried putting {{this.coinPairingOptions}} in the html and it errors because it's a computed property with not value initially.
Any help would be appreciated!
You can't use computed property in data, because data evaluates before the computed properties did.
You can use a watcher to achieve the intended result. Have a look at the documentation, you can add the argument immediate to trigger the callback immediately with the current value of the expression.
Computed properties are already accessible in the template by using {{}}. You don't need to put a this in front of the computed.
I'm trying to determine the best pattern to solve the following:
I want to show a list of people in a specific department
I made the department index a regular, reactive Vue property
I made the list of people in the department a computed property
Now:
My backend (a Mac app) can dispatch a "Person at index changed" event and I must reload the name of a single person.
But: the person property is an item in a computed property (i.e. "people", see code below).
How can I update the name of a person in the list list of people, which in turn is a computed property (although it is "computed" from the departmentIndex + backend call)?
I assume that I have a mistake in my original setup. Maybe the people list should not be a computed property in the first place?
Here is the code:
function pretendUpdateEventFromBackend() {
var event = new CustomEvent('PersonUpdated', {detail:{index:1, name:'Jimmy'}});
document.dispatchEvent(event);
}
var backend = {
// The actual backend is a Mac app with an embedded WebView...
listPeopleInDepartment(departmentIndex) {
return [
{name:'John'},
{name:'James'},
{name:'Jane'}
];
}
}
var app = new Vue({
el: '#app',
data: {
message:'',
departmentIndex:0,
},
computed: {
people() {
return backend.listPeopleInDepartment(this.departmentIndex);
}
},
created() {
const me = this;
document.addEventListener('PersonUpdated', function(e){
me.message += 'Updated ';
var personIndex = e.detail.index;
var newName = e.detail.name;
// How can I update person in the list of computed people here?
// Or how can I force a reload of the people list?
me.message += 'Person at: ' + personIndex + ' new name: ' + newName + "\n";
});
},
});
Html:
<button onclick="pretendUpdateEventFromBackend()">Trigger Update Event from Backend</button>
<div id="app">
<div class="person" v-for="person in people">
Name: {{ person.name }}
</div>
<pre>{{message}}</pre>
</div>
EDIT:
Try it on jsbin: http://jsbin.com/guwezoyena/1/edit?html,js,output
I just thought I should give you some further insight on your setup:
If listPeopleInDepartment is an ajax call in your actual code, this might be a bad pattern. See, Vue will identify every property (including other computed properties) that is used within a computed property and make it sure to re-compute it whenever any of them changes. In your case, this would mean whenever departmentIndex changes it'll recompute people.
Here's why it might be problematic, since you must return the result of the ajax request, it can't be asynchronous so it'll be a blocking call that would render the page unresponsive while it runs. Also, let's say you were viewing department 1, then you change to 2, if you go back to 1 it'll have to reload the people in department 1 because it's not stored anywhere.
You should probably implement some caching strategy and have ajax loads only when that data isn't already available, also in a non blocking fashion. You could achieve this with an array that stores the arrays of peopleInDepartment indexed by department id, and a watcher for departmentIndex that would do something like this:
function (newValue){
if (!this.peopleCache[newValue]){
var that = this;
//Load it via ajax
loadPeopleInDepartment(newValue, function(result){
that.peopleCache[newValue] = result;
});
}
}
I have an object containing an array, which gets incremented after some amount of logic has completed.
Vue.js doesn't seem to be capturing this increment and displaying it to the view.
HTML:
<div id="demo">
<p>{{points}}</p>
</div>
JS:
function Board()
{
this.team1 = {pointsMade:[0]};
}
var newBoard = new Board();
newBoard.team1.pointsMade[0]++
new Vue({
el: '#demo',
data: {
points: newBoard.team1.pointsMade
}
})
setTimeout(newBoard.team1.pointsMade[0]++,1000)
I have a JSFiddle that outlines the problem.
You can see that after setTimeout(newBoard.team1.pointsMade[0]++,1000) runs, the value should be '2', but is only displayed at '1'. What am I missing here?
When you define
data: {
points: newBoard.team1.pointsMade[0]
}
the points variable is just assigned current value of newBoard.team1.pointsMade[0], which is 1 at this moment. There is no magic here. JS primitives works by value, not by references.
So, after updating the newBoard.team1.pointsMade[0] variable, the point variable is not updating of course.
To make this work, use object instead of primitive value. Objects work by reference in JS.
From Properties and Methods example:
var data = { a: 1 }
var vm = new Vue({
data: data
})
vm.a === data.a // -> true
// setting the property also affects original data
vm.a = 2
data.a // -> 2
// ... and vice-versa
data.a = 3
vm.a // -> 3
edit
There is another caveat here:
Due to limitations in JavaScript, Vue cannot detect when you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue. So, use Vue.set(newBoard.team1.pointsMade, 0, newBoard.team1.pointsMade[0] + 1);.
I updated your fiddle.
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