knockout.js ReferenceError - Unable to parse bindings $data is not defined - javascript

I am writing Jasmine test that is using mocked database response from WebSQL database. In following code segment I am getting an error.
function createCalculatedField(calculatedValue, objectContext) {
var computedObservable = ko.computed({
read: function () {
return ko.unwrap(ko.bindingProvider.instance.parseBindingsString("text: " + calculatedValue, objectContext).text);
},
write: function (value) {
computedObservable.notifySubscribers(value);
},
owner: objectContext
});
error message I am getting is following:
ReferenceError: Unable to parse bindings. Bindings value: text: ko.unwrap(PagingStartIndex) + $context().length Message: $data is not defined
I have printed out function inputs, and reproduced the error into Chrome console in screenshot below.
upon inspection of knockout-3.0.0.custom.min.js parseBindingsString method on which it fails.
function (b,c,d,e){try{var f=this.bindingCache,h=b+(e&&e.valueAccessors||""),g;if(!(g=f[h])){var n,k="with($context){with($data||{}){return{"+a.expressionRewriting.preProcessBindings(b,e)+"}}}";n=new Function("$context","$element",k);g=f[h]=n}return g(c,d)}catch(p){throw p.message="Unable to parse bindings.\nBindings value: "+
b+"\nMessage: "+p.message,p;}}
I can see that $data is internal knockout.js parameter. This same code works just fine in production environment, so I am assuming I am not setting something somewhere, could you point me in direction on how to debug this issue, cause I am totally out of ideas at this point.

Internally, Knockout uses the following dynamic function to evaluate your expression
function($context, $element) {
with($context) {
with($data||{}) {
return {text: ko.unwrap(PagingStartIndex) + $context().length};
}
}
}
If you look at the object that is normally passed into the parseBindingsString by knockout it looks like...
{
$data: {...},
$index: ko.observable(),
$parent: {...},
$parentContext: ko.bindingContext,
$parents: [...],
$root: {...}
}
This object graph is normally created by invoking new ko.bindingContext(...) or, if you are within a custom binding, bindingContext.createChildContext(...)
Looking at your screenshot it looks like objectContext is an observableArray containing 2 elements and I'm also assuming you manually created the objectContext instance in your jasmine tests.
Therefore the object you are passing into the parseBindingString ( which comes into the dynamic function as $context ) does not have a $data field, that is the reason the exception is thrown.
The should have the object graph similar to the normal bindingContext object where the value of $data is your model
i.e.
{
$data: ko.observableArray: ( [
{
Fields:{...},
Insert: false,
SetFields:[]
}, {
Fields:{},
Insert: false,
SetFields: []
}] ),
$index: ko.observable(),
...
}
However, you will still get an error message in this instance as PagingStartIndex is missing from $data object, the same as if you got a mismatch between your binding expression and the model in your production system.

Related

Backbone.js fetch() returning object instead of child in server instance but in local instance retrieves child

I have 2 instances. My local with OS Win7, and a Server instance with OS Linux.
I am fetching the JSON data and setting it to a model using following code.
var RModel = Backbone.Model.extend({
idAttribute: 'name',
parse: function (response) {
return {
'name': response.name,
'title': response.title,
'description': response.description,
'parameters': new ParamsList(response.parameters)
};
}
});
that.model = new RModel();
that.model.url = "url/" + '?limited=false';
that.model.fetch({
cache: false
}).done(function() {
that.headerTemplate = that.headerTemplateEdit;
that.bodyTemplate = that.bodyTemplateEdit;
that.footerTemplate = footerTemplate;
that.load({});
});
In my local instance the result of following code in console.
this.model
child
_changing:false
_pending:false
_previousAttributes:Object
attributes:Object
changed:Object
cid:"c217"
id:"testUndefinedParam"
url:"/url?limited=false"
__proto__:
Backbone.Model
In server instance
this.model
i
_changing: false
_pending: false
_previousAttributes: Object
attributes: Object
changed: Object
cid: "c25920"
id: "testDateError2"
url: "/url?limited=false"
__proto__: t.Model
If anyone came across this issue please show some way retrieve JSON data properly.
My guess is that the code in your server is processed (minify/uglify) and the code in your local is not. So the processor just renamed child to i, Backbone to t etc. You should be concerned about differences in actual data rather than the constructor names the console outputs. As far as I know there is no standard and it can vary between browsers

DOM is not getting updated on updating VueJS data object

I have a VueJS data object in JS script. The JS script is linked at end of the page. So first HTML will is getting rendered. Then VueJS data object will be initialised. After initialising data object, DOM is getting updated perfectly. But after this, on updating the VueJS data object in success function of this.$http(), DOM is not getting updated. I have read documentation on reactivity and Common Beginner Gotchas. But I didn't get solution. If anyone knows the answer, it will be appreciated.
Here is my code.
index.html
<body>
....
{{ind_case.comments.length}}
<div class="comment_item" v-for="comment in ind_case.comments">
</div>
....
<script src="index.js"></script>
</body>
index.js
new Vue({
....
data: {
ind_case: {}
},
created: function () {
this.getCaseDetails();
},
methods: {
getCaseDetails: function () {
this.ind_case = {
"case_id": 16
}
this.getCaseComments(this.ind_case.case_id);
},
getCaseComments: function(case_id){
this.$http.get('/api/comments/' + case_id)
.then((response) => {
console.log(response.data); // Output: [{cmnt: "blah"}, {cmnt: "blah blah"}]
// Here i want add these comments in ind_case data object as ind_case.comments
// I have tried this
// this.$set(this.ind_case, 'comments', response.data);
// console.log(this.ind_case.comments); // Output: [Array[2], __ob__: Di]
}, (response) => {})
}
}
....
})
As seen in code, I can get comments in DOM. But I have to use .comments[0].length instead of .comments.length. And that's not the way to code.
EDIT
As suggested in below comment, I have tried this. But still same output (i.e, [Array[2], __ob__: Di]). And also one more thing. If I choose this option, I have to pre-define data object. But my requirement is run time creation/addition of data.
first thing, data should be a function and return {ind_case: {comments: []}}
i would recammend you make new object this way for ajax response
(response) => {
var data = response.data.map(o => getUwantProps(o));
this.ind_case.comments = data;
}

Angular: afterCellEdit without scope

I'm trying to implement the afterCellEdit function inside my gridOption.onREgisterApi function. I'm not using $scope in my program as is recommended to do in the guidelines.
In fact, my question is exactly the same as this one : question
Sadly it is not answered.
When I use null as one of the answer suggest I got an error.
Here is the code :
vm.gridOptions.onRegisterApi = function(gridApi){
vm.gridApi = gridApi;
vm.gridApi.edit.on.afterCellEdit(null,function(rowEntity, colDef, newValue, oldValue){
alert("afterCellEdit");
});
};
And here is my error :
typeError: Cannot read property '$on' of null
at Object.feature.on.(anonymous function) [as afterCellEdit]
Thanks !
Edit : for #SaurabhTiwari here is my $scope.gridData alternative
function onCustomerListComplete(data) {
vm.gridOptions.data = data;
}
function OnError(reason) {
$log.error(reason);
}
function activate() {
customerService.getCustomerList()
.then(onCustomerListComplete, OnError);
}
vm.gridOptions = {
enablePaginationControls: false,
useExternalSorting: true,
enableHorizontalScrollbar : uiGridConstants.scrollbars.NEVER,
enableVerticalScrollbar : uiGridConstants.scrollbars.WHEN_NEEDED,
columnDefs: [
// will be modified once the data model is set
{ field: 'id', name: '', cellTemplate: 'content/customerList/rowEditor/customerList.rowEditor.editButton.html', width: 34 },
{ name: 'customerNumber', },
{ name: 'customerName' },
{ name: 'city' },
{ name: 'creditLimit' },
{ name: 'postalCode' },
]
};
It might be late to provide some help here and you possibly have found a solution. But I would like to explain your condition using the below code.
function (scope, handler, _this) {
if ( scope !== null && typeof(scope.$on) === 'undefined' ){
gridUtil.logError('asked to listen on ' + featureName + '.on.' + eventName + ' but scope wasn\'t passed in the input parameters. It is legitimate to pass null, but you\'ve passed something else, so you probably forgot to provide scope rather than did it deliberately, not registering');
return;
}
var deregAngularOn = registerEventWithAngular(eventId, handler, self.grid, _this);
//track our listener so we can turn off and on
var listener = {handler: handler, dereg: deregAngularOn, eventId: eventId, scope: scope, _this:_this};
self.listeners.push(listener);
var removeListener = function(){
listener.dereg();
var index = self.listeners.indexOf(listener);
self.listeners.splice(index,1);
};
//destroy tracking when scope is destroyed
if (scope) {
scope.$on('$destroy', function() {
removeListener();
});
}
return removeListener;
}
This is the original code from the ui-grid Api. You can search for it in the library or from a proper scope in application you can just try doing:
$scope.gridApi.edit.on.afterCellEdit.toString()
So the point here is that this function explicitly requires a scope or a null. So You do need to pass a scope to this listener.
The point you mentioned in your question that you are not using $scope in your code is somewhat vague. There always is a scope associated if you have a controller. Angular is pretty much about scopes (atleast Angular 1 was).
What the referred guidelines says is that you should avoid heaping everything on your $scope. The guidelines also says you should use $scope only for listening and watching, which is exactly the case here

Using Select 2 with ASP.NET MVC

I am working on an ASP.NET MVC 4 app. In my app, I'm trying to replace my drop down lists with the Select 2 plugin. Currently, I'm having problems loading data from my ASP.NET MVC controller. My controller looks like this:
public class MyController : System.Web.Http.ApiController
{
[ResponseType(typeof(IEnumerable<MyItem>))]
public IHttpActionResult Get(string startsWith)
{
IEnumerable<MyItem> results = MyItem.LoadAll();
List<MyItem> temp = results.ToList<MyItem>();
var filtered = temp.Where(r => r.Name.ToLower().StartsWith(startsWith.ToLower());
return Ok(filtered);
}
}
When I set a breakpoint in this code, I notice that startsWith does not have a value The fact that the breakpoint is being hit means (I think) my url property below is set correct. However, I'm not sure why startsWith is not set. I'm calling it from Select 2 using the following JavaScript:
function formatItem(item) {
console.log(item);
}
function formatSelectedItem(item) {
console.log(item);
}
$('#mySelect').select2({
placeholder: 'Search for an item',
minimumInputLength: 3,
ajax: {
url: '/api/my',
dataType: 'jsonp',
quietMillis: 150,
data: function (term, page) {
return {
startsWith: term
};
},
results: function (data, page) {
return { results: data };
}
},
formatResult: formatItem,
formatSelection: formatSelectedItem
});
When this code runs, the only thing I see in the select 2 drop down list is Loading failed. However, I know my api is getting called. I can see in fiddler that a 200 is coming back. I can even see the JSON results, which look like this:
[
{"Id":1,"TypeId":2,"Name":"Test", "CreatedOn":"2013-07-20T15:10:31.67","CreatedBy":1},{"Id":2,"TypeId":2,"Name":"Another Item","CreatedOn":"2013-07-21T16:10:31.67","CreatedBy":1}
]
I do not understand why this isn't working.
From the documentation:
Select2 provides some shortcuts that make it easy to access local data
stored in an array...
... such an array must have "id" and "text" keys.
Your json object does not contain "id" or "text" keys :) This should work though i have not tested it:
results: function (data, page) {
return { results: data, id: 'Id', text: 'Name' }
}
There's further documentation following the link on alternative key binding... I believe thats where your problem lies.
Hopefully this helps.

How to alter the data returned by $resource in Angular.js?

I'm using an API that returns JSON data in this format:
{
paging: {
previous: null,
next: null
},
data: [
{ title: 'First Item' },
{ title: 'Second Item' },
...
]
}
I'm using Angular's $resource service to fetch this data.
My code - which is located in a controller - goes something like this:
var Entity = $resource('/api/entities');
var entities = $scope.entities = Entity.get();
And then, in the view, I can display the data like this:
<ul>
<li ng-repeat="entity in entities.data">{{entity.title}}</<li>
</ul>
It all works fine, but:
I'd rather expose only the contents of entities.data to the view, instead of the whole entities object. How can I intercept the data returned by the GET request to modify it before it populates $scope.entities?
Correlated question: since I am fetching an array of data, it would be cleaner to use Entity.query() instead of Entity.get(). But if I use Entity.query() in the code above, I get an error "TypeError: Object # has no method 'push'". This makes sense, since the API is returning an object instead of an array (hence, no 'push' method on the object). Again, if I could extract the .data attribute from the response, I'd have an array.
Following these indications by Dan Boyon, I managed to customize the default $resource service and to override the .get() or .query() methods, but I'm not sure where to go from there.
I don't think you need to modify the get or query defaults. Just use the success callback to do what you want. It should be more robust as well.
Entity.get(
{}, //params
function (data) { //success
$scope.entities = data.data;
},
function (data) { //failure
//error handling goes here
});
Html will be cleaner, too:
<ul>
<li ng-repeat="entity in entities">{{entity.title}}</<li>
</ul>
By the way, I usually declare services for my resources and then inject them into my controllers as I need them.
myServices.factory('Entity', ['$resource', function ($resource) {
return $resource('/api/entities', {}, {
});
}]);
You can use the Response Transformer (transformResponse) like this:
$resource('/api/entities', {}, {
query: {
method: 'GET',
responseType: 'json',
isArray: true,
transformResponse: function (response) {
return response.data;
}
}
});
This code modifies the "query" method behaviour, you can do the same for "get" ... !

Categories

Resources