I'm having a bit of trouble seeing what's wrong with my code. More likely with the Knockout.js part... It's giving me the following error:
Message: Unable to process binding "attr: function (){return {href:website()} }"
HTML
<div class="demo-card-square mdl-card mdl-shadow--2dp" data-bind="foreach: favoriteSpot">
<div class="mdl-card__title mdl-card--expand">
<h2 class="mdl-card__title-text">Update</h2>
</div>
<div class="mdl-card__supporting-text" data-bind="text:name"></div>
<div class="mdl-card__supporting-text" data-bind="text:location"></div>
<a data-bind="attr: {href: website()}">Website</a>
</div>
JS
var favoriteSpotsList = [{
venueName: "name",
venueLocation: "address",
website: "url",
image: "<img src='img'",
}];
var favoriteSpot = function(data) {
this.name = ko.observable(data.venueName);
this.address = ko.observable(data.venueLocation);
this.website = ko.observable(data.website);
this.image = ko.observable(data.img);
};
var AppViewModel = function() {
var self = this;
/* Create array of hotspot locations. */
this.hotSpotList = ko.observableArray([]);
favoriteSpotsList.forEach(function(spot) {
self.hotSpotList.push(new favoriteSpot(spot));
});
};
ko.applyBindings(new AppViewModel());
As #saj and #haim770 mentioned in comment, there is no favoriteSpot property on the view-model. So, the data bind should loop the hotSpotList to get the website property in it. Like below.,
data-bind="foreach: hotSpotList"
There is an easy way to identify these kind of issues, specifically while performing bindings in view
You just need add a button with click binding, The Button should be placed before the exception line.
<button data-bind="click: function () { console.log($context); }"> Context Log </button>
The above code will log the entire context in the browser console(F12). As usual you will get the exception. And this code will not resolve the issue. But this will be very helpful to identify the issue.
The above code will log the entire context of the current operation. Which holds object, property with the value.
Below are common scenarios where as you can exactly find your binding object has exceptions.
1. Properties are present/missing due to the scope level problems?
2. Whether it has case sensitive problem?
3. Your object comes under where? Is it a parent, child / Alone?
4. Human error which makes exception while binding.
There are few other ways to find the object/data in view:
1. Logs the root:
<button data-bind="click: function () { console.log($root); }"> Root Log </button>
2. Logs the Current scope data:
<button data-bind="click: function () { console.log($data); }"> Current Data Log </button>
3. Logs the parent data: (specifically helpful when we do looping)
<button data-bind="click: function () { console.log($parent); }"> Parent Log </button>
4. Logs the list of parent data: (specifically helpful when we do looping with different types of parents)
<button data-bind="click: function () { console.log($parents); }"> Parents Log </button>
5. Logs the list of parent data: (specifically helpful when we do looping and access different types of parents)
<button data-bind="click: function () { console.log(objectName.propertyName); }">Property Log </button>
For Example in your case you can do like below:
<!-- Added this button before the exception -->
<button data-bind="click: function () { console.log(favoriteSpot); }">Log </button>
<div class="demo-card-square mdl-card mdl-shadow--2dp" data-bind="foreach: favoriteSpot">
<div class="mdl-card__title mdl-card--expand">
<h2 class="mdl-card__title-text">Update</h2>
</div>
<div class="mdl-card__supporting-text" data-bind="text:name">
</div>
<div class="mdl-card__supporting-text" data-bind="text:location">
</div>
<a data-bind="attr: {href: website()}">Website</a>
</div>
When you click the button, obviously the message will be logged as undefined in console.
Hope this helps.,
Related
I have searched and I have tried different selectors but I can't figure this out. I am following a tutorial, but I am not getting a result.
The click event doesn't seem to be binding to the dynamically generated div section '.person-brief'? There is no click event associated with it. I tried .live() also, but that seems to have been deprecated.
Any idea what I am doing wrong?
person.js model
var gotoDetails = function (selectedPerson) {
if (selectedPerson && selectedPerson.id()) {
var url = '#/persondetail/' + selectedPerson.id();
router.navigateTo(url);
}
};
var viewAttached = function (view) {
bindEventToList(view, '.person-brief', gotoDetails);
};
var bindEventToList = function (rootSelector, selector, callback, eventName) {
var eName = eventName || 'click';
$(rootSelector).on(eName, selector, function () {
var ser = ko.dataFor(this);
callback(ser);
return false;
});
};
var vm = {
people: people,
title: 'people demo',
viewAttached: viewAttached
};
return vm;
person.html view
<section id="person-view" class="view">
<header>
<a class="btn btn-info btn-force-refresh pull-right"
data-bind="click: refresh" href="#"><i class="icon-refresh"></i>Refresh</a>
<h3 class="page-title" data-bind="text: title"></h3>
<div class="article-counter">
<address data-bind="text: people().length"></address>
<address>found what</address>
</div>
</header>
<section class="view-list" data-bind="foreach: people">
<article class="article-left-content">
<div class="person-brief" title="Go to person details">
<small data-bind="text: firstname" class="right"></small>
<small data-bind="text: lastname"></small>
</div>
</article>
</section>
</section>
With KnockoutJS you should use the click binding (or alternatively the event binding), not use jQuery to manually manipulate the DOM.
Something like this becomes your code:
var vm = {
people: people,
title: 'people demo',
viewAttached: viewAttached
};
vm.myHandler = function (person) {
goToDetails(person);
return false;
};
And since myHandler is so simple you might as well inline the goToDetails code, which has access to vm from its closure.
You bind in the view like this:
<div class="person-brief" data-bind="click: $root.myHandler">
...
</div>
A general tip: do a tutorial on either jQuery, or KnockoutJS. If you take the latter, try to use as little is possible jQuery (which is usually quite possible), most notably don't use jQuery to manipulate the DOM (except in custom binding handlers and after-render functions).
I have a SPA set up with some complex code. First, i am making a call to a service to get some data and binding it to a scope object. Code example below:
$scope.tables.outgoingCommunication = {
columns: OutgoingCommunicationModel.columns,
rows: []
};
$scope.getOutgoingDocs = () => {
myService.GetPrintHistory($scope.item.ItemId, $scope.item.ItemDesc, $scope.oDocAge).success((response) => {
$scope.tables.outgoingCommunication.rows = response.Response.body.Value;
});
};
My HTML has this code to bind to the outgoingCommunication table:
<section ng-if="tables.outgoingCommunication.rows.length" table-directive="outgoingCommunication" rows="tables.outgoingCommunication.rows" columns="tables.outgoingCommunication.columns" sort-field="'DatePosted'" descending="true" parent-method="toggleAge()" table-template="Templates/app/items/Outgoing-Communication.html"></section>
The template has this code in it:
<div id="outgoing-communication" class="table-responsive">
<div class="table container-fluid overflow-scroll" >
<!--Header Row-->
<div class="thead row tr hidden-xs">
<div ng-repeat="column in columns" class="{{column.columnSize}} th" ng-class="{'overflow-ellipsis': column.allowTruncate == true}" ng-click="column.sortable && Sort(column.field)">
{{column.title}} <i ng-if="column.sortable == true" ng-class="{'icon icon-caret-down inactive': sortField != column.field, 'icon icon-caret-down': descending && sortField == column.field, 'icon icon-caret-up': !descending && sortField == column.field}"></i>
</div>
</div>
<!--Body Rows-->
<div ng-repeat="row in rows | orderBy:sortField:descending" class="row tr">
<div>
<!--Switch Case-->
<div ng-switch="column.field" ng-repeat="column in columns" class="{{column.columnSize}} td" ng-class="{'overflow-ellipsis': column.allowTruncate == true}">
<b class="visible-xs-inline-block">{{column.title}} </b>
<!--When Actions-->
<div ng-switch-when="Actions">
View <i class="icon icon-lg icon-file-o"></i>
</div>
<!--When DatePosted-->
<div ng-switch-when="DatePosted">
{{row[column.field] | date: 'MM/dd/yyyy'}}
</div>
<!--When Default-->
<div ng-switch-default>{{row[column.field]}}</div>
</div>
</div>
</div>
</div>
<a ng-if="$parent.oDocAge == 1" href="" ng-click="parentMethod()">View More</a>
<a ng-if="$parent.oDocAge == 3" href="" ng-click="parentMethod()">View Less</a>
</div>
The data I am getting back from the call to GetPrintHistory has a JSON format as follows:
{
FormNumber: "1060",
FormDescription: "Invoice",
PrintProcessId: 6440187,
DatePosted: "2014-12-20T00:00:00",
PrintXMLId: 5286992,
ItemImageNum: 26
}
There is a series of these items in the collection.
My model is defined as:
define(["require", "exports"], function (require, exports) {
var OutgoingCommunicationModel;
(function (OutgoingCommunicationModel) {
OutgoingCommunicationModel.columns = function () {
return [
{
field: "DatePosted",
title: "Date Posted",
columnSize: "col-sm-3",
allowTruncate: true,
sortable: true
},
{
field: "FormDescription",
title: "Description",
columnSize: "col-sm-3",
allowTruncate: false,
sortable: true
},
{
field: "Actions",
columnSize: "col-sm-3",
allowTruncate: false,
sortable: false
}
];
};
})(OutgoingCommunicationModel|| (OutgoingCommunicationModel= {}));
return OutgoingCommunicationModel;
});
My current code is displaying the FormDescription column and DatePosted field with no problem. The problem is the Actions column. I need to have the anchor call a service method defined below:
this.GetOutgoingDocument = function (itemId, itemImageNumber, printProcessId, printXmlId) {
return _this.$http({
method: "GET",
url: "api/item/GetOutgoingDocument",
params: { itemId: itemId, itemImageNumber: itemImageNumber, printProcessId: printProcessId, printXmlId: printXmlId }
});
};
I cannot for the life of me figure out how to set up the call in the ng-repeat with the ng-switch-when embedding to call the service method. If I need to, I can try and build a codepen or something to get a sample set up.
Have you tried ng-init?
<div ng-switch-when="Actions">
View <i class="icon icon-lg icon-file-o"></i>
</div>
This could work if you want the service called when switching, if you want the service called when clicking a button you could use ng-click = "GetOutgoingDocument()"
This is assuming you have GetOutgoingDocument on the $scope.
So there were a few ways I needed to face this.
The table directive I was using to open the template and display the data had an isolated scope. To get around this, I added a scope variable externalCall: "&" and set up my section tag that called this directive as follows: external-call="selectRow(itemId,itemImageNum, printProcessId, printXMLId)". Finally, I used this in my anchor code: <a ng-click="externalCall({itemId: row.ItemId, itemImageNum: row.ItemImageNum, printProcessId: row.PrintProcessId, printXMLId: row.PrintXMLId})">View <i class="icon icon-lg icon-file-o"></i> </a>
As you can see in the code above, I discovered the row variable held all the data i needed from my row that was returned.
In the long run, I ended up having to take a slightly different route. Since I am returning a PDF from the Web API call, I instead moved over to ng-href to call the API call directly.
<a target="_blank" ng-href="api/policy/GetOutgoingDocument?itemId={{row.ItemId}}&itemImageNum={{row.ItemImageNum}}&printProcessId={{row.PrintProcessId}}&printXMLId={{row.PrintXMLId}}">View <i class="icon icon-lg icon-file-o"></i> </a>
I will need to do something about the Web API call though. If I get an error, I am seeing an Ajax document showing a 400 error on it. I want to handle it differently than that. May have to ask a different question.
In the Ember app I'm building, I've got an ArrayController managing a list of items with several columns of data for each record object in the array with a sort button in each column header in the view. I have set up the list to sort on a given column per Balint Erdi's recommended method here. You will see this sorting in my code below.
The sorting works fine. However, the problem arises when I remove an item from the array. Currently, when I attempt to remove an item from the array, the correct item is apparently removed from the array and is properly deleted from the store and the delete is saved to my backend. However, after the item removal, my view is not correct. In some cases, the wrong item is shown as removed, in other cases, no item is shown as removed. Yet IF I press sort again, the view is updated correctly.
So, the index of the array is obviously getting off some how, but I'm not sure how and all of my attempts to apply the tricks of others are not working!
Here is my route object:
App.UsersFilesRoute = Ember.Route.extend({
model: function () {
return this.modelFor('users').get('files');
}
});
Here is my ArrayController:
App.UsersFilesController = Ember.ArrayController.extend({
sortProperties: ['name'],
sortedFiles: Ember.computed.sort('model', 'sortProperties'),
actions: {
addFile: function(file) {
var newFile = this.store.createRecord('file', {
name: file.name.trim(),
fileSize: file.size,
loaded: false
});
this.pushObject(newFile);
},
sortBy: function (sortProperties) {
this.set('sortProperties', [sortProperties]);
},
removeFile: function (fileToRemove) {
var _this = this;
var file = this.store.find('file', fileToRemove.get('id'));
file.then( function (file) {
_this.removeObject(file);
file.deleteRecord();
file.save();
});
},
saveFile: function (file) {
....
}
}
});
And here is my template code:
<div class="hidden-xs row user-file-header-row">
<div class="col-sm-5 col-md-5 user-file-header">
File Name
<button type="button" class="btn-xs btn-default files-sort-btn" {{ action 'sortBy' 'name'}}></button>
</div>
<div class="col-sm-1 col-md-1 user-file-header">
Size
<button type="button" class="btn-xs btn-default files-sort-btn" {{ action 'sortBy' 'fileSize'}}></button>
</div>
</div>
{{#each file in sortedFiles}}
<div class="row user-file user-file-break">
<div class="col-xs-11 col-sm-5 col-md-5 user-file-name">
<a {{ bind-attr href="file.path" }} >{{ file.name }} </a>
</div>
<div class="col-xs-9 col-sm-1 col-md-1">
{{ format-file-size file.fileSize }}
</div>
<div class="col-xs-9 col-sm-1 col-md-1">
<button type="button" class="btn-xs btn-default files-list-btn" {{ action 'removeFile' file }}></button>
</div>
</div>
{{/each}}
NOTE: There is some similarity between my question and this other StackOverflow question: After using jQuery UI to sort an Ember.js item, using Ember Data's model.deleteRecord() doesn't work, however, I've attempted to apply that answer my own problem with no success. Furthermore, I have no jQuery going on here in my sorting.
OK. I have found an answer, or rather an answer has found me.
My problem was that in the code above I was removing the itemfrom the ArrayController and then calling .delete() and .save(). This sequences of calls was sending conflicting signals to Ember on how to update my view. Apparently, the .removeObject() was actually removing the item from the array, but then the subsequent .delete()/.save() was setting the model behind the view to a state just before deletion (not sure about that but that's what I saw happening).
So anyways, .destroyRecord() returns a promise, so I moved the .removeObject() within the .then() for the promise, and that resolves the issue.
So, the following code in the removeFile action resolved the issue:
removeFile: function () {
var self = this;
var fileToRemove = this.get('fileToRemove');
var file = this.store.find('file', fileToRemove.get('id'));
file.then (function (file) {
file.destroyRecord().then(function(){
self.get('model').removeObject(file);
});
});
}
Note that you don't have to do the this.store.find() first, you could simply do the following:
removeFile: function () {
var self = this;
var fileToRemove = this.get('fileToRemove');
fileToRemove .destroyRecord().then(function(){
self.get('model').removeObject(file);
});
}
However, I chose to be conservative and double-check the store. That seems safer to me.
I have the following event in my client file:
Template.categories.events({
...
'keyup #add-category': function (e,t){
if (e.which === 13)
{
var catVal = String(e.target.value || "");
if (catVal)
{
lists.insert({Category:catVal,owner:this.userId});
Session.set('adding_category', false);
}
}
},
...
});
And this is the relevant template part:
<template name="categories">
<div id="categories" class="btn-group">
{{#if new_cat}}
<div class="category">
<input type="text" id="add-category" value="" />
</div>
{{else}}
<div class="category btn btn-inverse" id="btnNewCat">+</div>
{{/if}}
{{#each lists}}
<div class="category btn {{list_status}}" id="{{_id}}">
{{Category}}
</div>
{{/each}}
</div>
</template>
So when a new Category is inserted, the owner should be set.. But it doesn't.
Here's the entry in MongoDB:
> db.lists.find()
{ "Category" : "test-admin", "_id" : "EsybjC3SLnNzCBx2t" }
Any idea what I'm doing wrong? (actually I'm following the "Getting Started with Meteor" book lending library example
EDIT it seems that:
console.log(this.userId);
undefined
Swap this line:
lists.insert({Category:catVal,owner:this.userId});
to this one:
lists.insert({Category:catVal,owner:Meteor.userId()});
this is probably not what you expect it to be inside that event. You should debug to confirm that that is the case. You are probably just getting undefined for this.userId. I would recommend assigning this to a variable (call it "self" or "that") outside of this event handler function but inside the scope where this will be what you actually want it to be. You can then reference that variable inside the event handler.
It should look like this:
function thatRegistersEvents() {
var self = this;
// ...
registerSomeEvent(function () {
return self.someThisProperty;
});
}
This is the correct behavior. If you read the official Meteor documentation for references to this.UserId, it is only available in Meteor.publish() and Meteor.methods(). this.UserId is not available in Meteor.template(), which you have done in the code sample above, so one must use Meteor.userId() in templates.
Note: This is not a question about ObservableArrays.
Let's say I have the following viewmodel:
var viewmodel = {
arrayOfBooleans: [
ko.observable(false),
ko.observable(false),
ko.observable(false)
]
}
And a view like so:
<div data-bind="foreach: arrayOfBooleans">
<button data-bind="click: ????">Set to true</button>
</div>
What can I do inside the foreach to get the <button> to set the observable to true when clicked? Using data-bind="click: someFunction", the first argument someFunction gets is the unwrapped values of observables in the array (not the observables themselves), and seemingly no way to get back at the observables or to pass custom arguments.
Hope it will give some idea .
var viewmodel = {
var self = this;
self.arrayOfBooleans = ko.observableArray([]);
self.arrayOfBooleans.push(new _newBoolean());
self.arrayOfBooleans.push(new _newBoolean());
self.arrayOfBooleans.push(new _newBoolean());
function _newBoolean() {
self.one = ko.observable(false);
}
self.setToTrue = function(index){
self.arrayOfBooleans()[index].one(true);
};
}
If you want it as button
<div data-bind="foreach: arrayOfBooleans">
<button data-bind="click: $root.setToTrue($parent.arrayOfBooleans.indexOf($data))">
Set to true
</button>
<input type=hidden data-bind="value:one" />
</div>
If you want it as radio button than it is much more simple
<div data-bind="foreach: arrayOfBooleans">
<span>Set To True</span><input type=radio data-bind="value:one" />
</div>
If you like this..Please Click uplink
*or*
If it solves your problem .. Mark this as answer