无法处理绑定 - 绑定不是函数
Unable to process binding - bind is not a function
我正在为我的单页应用程序使用 knockoutjs,目前我遇到了一个神秘的问题。
我正在尝试显示下拉菜单,并使用挖空绑定填充它。为此,我使用了一个遍历所有元素的 foreach:
<div data-bind="foreach: favPlaces" class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a data-bind="text: name, click: $parent.openInfoWindow" class="dropdown-item">Place Name</a>
</div>
然后,在我的 viewModel 中,我有 openInfoWindow 函数(应该在单击下拉项时调用):
// View Model
var TokyoViewModel = function() {
var self = this;
// All the favorite places
this.favPlaces = ko.observableArray([]);
mFavPlaces.forEach(function(place) {
self.favPlaces.push(new FavPlace(place));
});
this.openInfoWindow = function(favPlace) {
console.log("Success!");
}
}
问题是,当我将 click: openInfoWindow 绑定添加到 dropdown-item 元素时,出现以下错误:
Uncaught TypeError: Unable to process binding "foreach: function (){return favPlaces }"
Message: Unable to process binding "click: function (){return $parent.openInfoWindow }"
Message: u(...).bind is not a function
at Object.p (knockout-3.4.1.js:17)
at knockout-3.4.1.js:89
at Object.b (knockout-3.4.1.js:9)
at init (knockout-3.4.1.js:89)
at init (knockout-3.4.1.js:103)
at knockout-3.4.1.js:72
at Object.w (knockout-3.4.1.js:39)
at knockout-3.4.1.js:72
at Object.q (knockout-3.4.1.js:11)
at m (knockout-3.4.1.js:71)
text: name binding 可以完美地独立运行。
我哪里做错了?
编辑:
这里是关于实施的更多细节。请注意,地图 div 是使用 Google 地图 API.
的地图
<body>
<div id="full-height">
<div id="map"></div>
<nav class="navbar navbar-inverse bg-inverse navbar-toggleable-md navbar-light bg-faded">
<a class="navbar-brand" href="#">
<i id="foursquare-logo" class="fa fa-foursquare" aria-hidden="true"></i>
</a>
<div id="location-dropup" class="btn-group dropup">
<button type="button" class="btn btn-secondary">Best locations</button>
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div data-bind="foreach: favPlaces" class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a data-bind="text: name, click: $parent.openInfoWindow" class="dropdown-item">Place Name</a>
</div>
</div>
</nav>
</div>
<!-- Foursquare logo -->
<script src="https://use.fontawesome.com/5228693ec0.js"></script>
<!-- Bootstrap -->
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>
<!-- KnockoutJS -->
<script src="js/lib/knockout-3.4.1.js"></script>
<script src="js/app.js"></script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDtODGjlNobKNCo4OX_voxjIkNkHCfQ3I4&callback=initMap"></script>
</body>
此处是有关所用相关 javascript 的更多详细信息。
var mFavPlaces = [
{
name: "Takeshita Street",
lat: 35.6715659,
lng: 139.7031469,
imgSrc: "img/favPlaces/takeshita.jpg"
}, {
name: "Nakamise Street",
lat: 35.7113873,
lng: 139.794207,
imgSrc: "img/favPlaces/asakusa.jpg"
}, {
name: "Yodobashi-Akiba",
lat: 35.6995227,
lng: 139.7734171,
imgSrc: "img/favPlaces/akihabara.jpg"
}, {
name: "Meiji Jingu",
lat: 35.6763976,
lng: 139.6993259,
imgSrc: "img/favPlaces/meiji.jpg"
}, {
name: "Shibuya Crossing",
lat: 35.6594087,
lng: 139.6981677,
imgSrc: "img/favPlaces/shibuya.jpg"
}
];
// Stores the Google maps markers for the favPlaces
var mMarkers = {};
var mQueryInfo = {
"near": "Tokyo",
"client_id": "AG5MATDOQ5HAXLODDIV1YALJZA4IN3LS5XEUOPWQIGHG0BHL",
"client_secret": "PPJYHED0SI5WLWC05LXGD1E3T1JDQI23EWNSTQLI2MO0WEAF",
"version": "20170220"
};
function httpGetAsync(theUrl, callback, infoWindow, placeIndex, marker) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
callback(xmlHttp.responseText, infoWindow, placeIndex, marker);
}
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
function setFavPlaceInfo(placeName, infoWindow, placeIndex, marker) {
var url = "https://api.foursquare.com/v2/venues/search?limit=1&near=" + mQueryInfo.near + "&query=" + placeName + "&v=" + mQueryInfo.version + "&client_id=" + mQueryInfo.client_id + "&client_secret=" + mQueryInfo.client_secret;
httpGetAsync(url, setInfoWindowContent, infoWindow, placeIndex, marker);
}
function setInfoWindowContent(placeDetailsText, infoWindow, placeIndex, marker) {
var placeDetails = JSON.parse(placeDetailsText);
// Extract info to display in infoWindows
var completeName = placeDetails.response.venues[0].name;
var address = placeDetails.response.venues[0].location.address;
var websiteUrl = placeDetails.response.venues[0].url;
var numberCheckins = placeDetails.response.venues[0].stats.checkinsCount;
var contentString = '<div class="infoWindow">' + '<img class="img-fluid img-thumbnail" src="' + mFavPlaces[placeIndex].imgSrc + '" style="margin-bottom:1rem;" alt="' + completeName + '" />' + '<h4>' + completeName + '</h4>' + '<b>Checkins: </b>' + numberCheckins + '<br>' + '<b>Website: </b> <a href="' + websiteUrl + '">' + websiteUrl + '</a>' + '<br>' + '<b>Address: </b> ' + address + '</div>';
infoWindow.setContent(contentString);
infoWindow.open(mMap, marker);
}
// Object representation of a favorite place
var FavPlace = function(data) {
this.name = ko.observable(data.name);
this.imgSrc = ko.observable(data.imgSrc);
}
// View Model
var TokyoViewModel = function() {
var self = this;
// All the favorite places
this.favPlaces = ko.observableArray([]);
mFavPlaces.forEach(function(place) {
self.favPlaces.push(new FavPlace(place));
});
this.openInfoWindow = function(favPlace) {
console.log("Success!");
}
}
// Initialize the map and adds markers with infoWindows
function initMap() {
var center = { lat: 35.6809814, lng: 139.7538745 };
mMap = new google.maps.Map(document.getElementById('map'), {
zoom: 12,
center: center
});
// Get details of favorite places
var placeIndex;
for (placeIndex = 0; placeIndex < mFavPlaces.length; placeIndex++) {
var marker = new google.maps.Marker({
position: { lat: mFavPlaces[placeIndex].lat, lng: mFavPlaces[placeIndex].lng },
map: mMap,
});
var infowindow = new google.maps.InfoWindow({});
// Use a closure to add listeners
google.maps.event.addListener(marker, 'click', (function(marker, placeIndex) {
return function() {
// Set infoWindow's content
setFavPlaceInfo(mFavPlaces[placeIndex].name, infowindow, placeIndex, marker);
// Set marker animation (lasts for 1 cycle == 750ms)
marker.setAnimation(google.maps.Animation.BOUNCE);
setTimeout(function() { marker.setAnimation(null); }, 750);
}
})(marker, placeIndex));
// Store the marker
mMarkers[mFavPlaces[placeIndex].name] = marker;
}
}
// Activates knockout.js
ko.applyBindings(new TokyoViewModel());
编辑 2:
使用您更新的源代码,我能够创建一个重现该问题的 jsFiddle。 fiddle
您正在加载的 jQuery 的 slim 版本似乎缺少一些 knockout 假定存在的功能。特别是在这种情况下,“.bind”函数似乎在 "foreach" 绑定内部使用。如果您将此脚本替换为应该清除的标准 jquery。
编辑 1:
Thank you for your answer. This is actually a mistake from my side, I forgot to update my code. I actually already had the $parent.openInfoWindow. I have updated the error log as well.
在这种情况下,问题不在于您发布的任何代码。这是一个工作片段,我从上面的代码中拼凑而成,但我无权访问的初始 "mFavPlaces.forEach" 除外。
// View Model
var TokyoViewModel = function() {
var self = this;
// All the favorite places
this.favPlaces = ko.observableArray([]);
//...
self.favPlaces.push(new FavPlace("name goes here?"));
this.openInfoWindow = function(favPlace) {
console.log("Success!");
}
}
var FavPlace = function(name){
//unknown view-model
var self = this;
self.name = ko.observable(name);
}
ko.applyBindings(new TokyoViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="foreach: favPlaces" class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a data-bind="text: name, click: $parent.openInfoWindow" class="dropdown-item">Place Name</a>
</div>
原答案:
此时绑定的上下文是单独的位置。名称绑定有效,因为名称在每个地方都是 属性。但是,您的点击功能似乎在父视图模型上。您可以将点击绑定更改为:data-bind="text: name, click: $parent.openInfoWindow"
它应该可以正常工作。
对于那些不使用 jquery.slim
但正在使用 require 构建解决方案的人。以下 shim 将解决您的问题:
require.config({
//rest of config
shim: {
knockout: {
deps: ["jquery"],
exports: "knockout"
}
}
//rest of config
});
控制台中显示的错误是由于加载 jQuery 和 Knockout 中的竞争条件引起的。
我正在为我的单页应用程序使用 knockoutjs,目前我遇到了一个神秘的问题。
我正在尝试显示下拉菜单,并使用挖空绑定填充它。为此,我使用了一个遍历所有元素的 foreach:
<div data-bind="foreach: favPlaces" class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a data-bind="text: name, click: $parent.openInfoWindow" class="dropdown-item">Place Name</a>
</div>
然后,在我的 viewModel 中,我有 openInfoWindow 函数(应该在单击下拉项时调用):
// View Model
var TokyoViewModel = function() {
var self = this;
// All the favorite places
this.favPlaces = ko.observableArray([]);
mFavPlaces.forEach(function(place) {
self.favPlaces.push(new FavPlace(place));
});
this.openInfoWindow = function(favPlace) {
console.log("Success!");
}
}
问题是,当我将 click: openInfoWindow 绑定添加到 dropdown-item 元素时,出现以下错误:
Uncaught TypeError: Unable to process binding "foreach: function (){return favPlaces }"
Message: Unable to process binding "click: function (){return $parent.openInfoWindow }"
Message: u(...).bind is not a function
at Object.p (knockout-3.4.1.js:17)
at knockout-3.4.1.js:89
at Object.b (knockout-3.4.1.js:9)
at init (knockout-3.4.1.js:89)
at init (knockout-3.4.1.js:103)
at knockout-3.4.1.js:72
at Object.w (knockout-3.4.1.js:39)
at knockout-3.4.1.js:72
at Object.q (knockout-3.4.1.js:11)
at m (knockout-3.4.1.js:71)
text: name binding 可以完美地独立运行。
我哪里做错了?
编辑:
这里是关于实施的更多细节。请注意,地图 div 是使用 Google 地图 API.
的地图<body>
<div id="full-height">
<div id="map"></div>
<nav class="navbar navbar-inverse bg-inverse navbar-toggleable-md navbar-light bg-faded">
<a class="navbar-brand" href="#">
<i id="foursquare-logo" class="fa fa-foursquare" aria-hidden="true"></i>
</a>
<div id="location-dropup" class="btn-group dropup">
<button type="button" class="btn btn-secondary">Best locations</button>
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div data-bind="foreach: favPlaces" class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a data-bind="text: name, click: $parent.openInfoWindow" class="dropdown-item">Place Name</a>
</div>
</div>
</nav>
</div>
<!-- Foursquare logo -->
<script src="https://use.fontawesome.com/5228693ec0.js"></script>
<!-- Bootstrap -->
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>
<!-- KnockoutJS -->
<script src="js/lib/knockout-3.4.1.js"></script>
<script src="js/app.js"></script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDtODGjlNobKNCo4OX_voxjIkNkHCfQ3I4&callback=initMap"></script>
</body>
此处是有关所用相关 javascript 的更多详细信息。
var mFavPlaces = [
{
name: "Takeshita Street",
lat: 35.6715659,
lng: 139.7031469,
imgSrc: "img/favPlaces/takeshita.jpg"
}, {
name: "Nakamise Street",
lat: 35.7113873,
lng: 139.794207,
imgSrc: "img/favPlaces/asakusa.jpg"
}, {
name: "Yodobashi-Akiba",
lat: 35.6995227,
lng: 139.7734171,
imgSrc: "img/favPlaces/akihabara.jpg"
}, {
name: "Meiji Jingu",
lat: 35.6763976,
lng: 139.6993259,
imgSrc: "img/favPlaces/meiji.jpg"
}, {
name: "Shibuya Crossing",
lat: 35.6594087,
lng: 139.6981677,
imgSrc: "img/favPlaces/shibuya.jpg"
}
];
// Stores the Google maps markers for the favPlaces
var mMarkers = {};
var mQueryInfo = {
"near": "Tokyo",
"client_id": "AG5MATDOQ5HAXLODDIV1YALJZA4IN3LS5XEUOPWQIGHG0BHL",
"client_secret": "PPJYHED0SI5WLWC05LXGD1E3T1JDQI23EWNSTQLI2MO0WEAF",
"version": "20170220"
};
function httpGetAsync(theUrl, callback, infoWindow, placeIndex, marker) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
callback(xmlHttp.responseText, infoWindow, placeIndex, marker);
}
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
function setFavPlaceInfo(placeName, infoWindow, placeIndex, marker) {
var url = "https://api.foursquare.com/v2/venues/search?limit=1&near=" + mQueryInfo.near + "&query=" + placeName + "&v=" + mQueryInfo.version + "&client_id=" + mQueryInfo.client_id + "&client_secret=" + mQueryInfo.client_secret;
httpGetAsync(url, setInfoWindowContent, infoWindow, placeIndex, marker);
}
function setInfoWindowContent(placeDetailsText, infoWindow, placeIndex, marker) {
var placeDetails = JSON.parse(placeDetailsText);
// Extract info to display in infoWindows
var completeName = placeDetails.response.venues[0].name;
var address = placeDetails.response.venues[0].location.address;
var websiteUrl = placeDetails.response.venues[0].url;
var numberCheckins = placeDetails.response.venues[0].stats.checkinsCount;
var contentString = '<div class="infoWindow">' + '<img class="img-fluid img-thumbnail" src="' + mFavPlaces[placeIndex].imgSrc + '" style="margin-bottom:1rem;" alt="' + completeName + '" />' + '<h4>' + completeName + '</h4>' + '<b>Checkins: </b>' + numberCheckins + '<br>' + '<b>Website: </b> <a href="' + websiteUrl + '">' + websiteUrl + '</a>' + '<br>' + '<b>Address: </b> ' + address + '</div>';
infoWindow.setContent(contentString);
infoWindow.open(mMap, marker);
}
// Object representation of a favorite place
var FavPlace = function(data) {
this.name = ko.observable(data.name);
this.imgSrc = ko.observable(data.imgSrc);
}
// View Model
var TokyoViewModel = function() {
var self = this;
// All the favorite places
this.favPlaces = ko.observableArray([]);
mFavPlaces.forEach(function(place) {
self.favPlaces.push(new FavPlace(place));
});
this.openInfoWindow = function(favPlace) {
console.log("Success!");
}
}
// Initialize the map and adds markers with infoWindows
function initMap() {
var center = { lat: 35.6809814, lng: 139.7538745 };
mMap = new google.maps.Map(document.getElementById('map'), {
zoom: 12,
center: center
});
// Get details of favorite places
var placeIndex;
for (placeIndex = 0; placeIndex < mFavPlaces.length; placeIndex++) {
var marker = new google.maps.Marker({
position: { lat: mFavPlaces[placeIndex].lat, lng: mFavPlaces[placeIndex].lng },
map: mMap,
});
var infowindow = new google.maps.InfoWindow({});
// Use a closure to add listeners
google.maps.event.addListener(marker, 'click', (function(marker, placeIndex) {
return function() {
// Set infoWindow's content
setFavPlaceInfo(mFavPlaces[placeIndex].name, infowindow, placeIndex, marker);
// Set marker animation (lasts for 1 cycle == 750ms)
marker.setAnimation(google.maps.Animation.BOUNCE);
setTimeout(function() { marker.setAnimation(null); }, 750);
}
})(marker, placeIndex));
// Store the marker
mMarkers[mFavPlaces[placeIndex].name] = marker;
}
}
// Activates knockout.js
ko.applyBindings(new TokyoViewModel());
编辑 2:
使用您更新的源代码,我能够创建一个重现该问题的 jsFiddle。 fiddle
您正在加载的 jQuery 的 slim 版本似乎缺少一些 knockout 假定存在的功能。特别是在这种情况下,“.bind”函数似乎在 "foreach" 绑定内部使用。如果您将此脚本替换为应该清除的标准 jquery。
编辑 1:
Thank you for your answer. This is actually a mistake from my side, I forgot to update my code. I actually already had the $parent.openInfoWindow. I have updated the error log as well.
在这种情况下,问题不在于您发布的任何代码。这是一个工作片段,我从上面的代码中拼凑而成,但我无权访问的初始 "mFavPlaces.forEach" 除外。
// View Model
var TokyoViewModel = function() {
var self = this;
// All the favorite places
this.favPlaces = ko.observableArray([]);
//...
self.favPlaces.push(new FavPlace("name goes here?"));
this.openInfoWindow = function(favPlace) {
console.log("Success!");
}
}
var FavPlace = function(name){
//unknown view-model
var self = this;
self.name = ko.observable(name);
}
ko.applyBindings(new TokyoViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="foreach: favPlaces" class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a data-bind="text: name, click: $parent.openInfoWindow" class="dropdown-item">Place Name</a>
</div>
原答案:
此时绑定的上下文是单独的位置。名称绑定有效,因为名称在每个地方都是 属性。但是,您的点击功能似乎在父视图模型上。您可以将点击绑定更改为:data-bind="text: name, click: $parent.openInfoWindow"
它应该可以正常工作。
对于那些不使用 jquery.slim
但正在使用 require 构建解决方案的人。以下 shim 将解决您的问题:
require.config({
//rest of config
shim: {
knockout: {
deps: ["jquery"],
exports: "knockout"
}
}
//rest of config
});
控制台中显示的错误是由于加载 jQuery 和 Knockout 中的竞争条件引起的。