3

我正在使用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> 

然後,在我的視圖模型,我有的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!"); 
    } 

} 

的問題是,當我添加了點擊:

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) 

文本:的openInfoWindow,我收到以下錯誤綁定到下拉項元素名稱結合的作品完美的我自己的。

我在哪裏犯了一個錯誤?

編輯:

下面是關於實施的更多細節。請注意,地圖div是使用Google Maps 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()); 

回答

5

編輯2:

有了更新的源代碼,我能創造一個的jsfiddle再現了這個問題。 fiddle

看起來苗條你正在加載的jQuery版本缺少一些敲除假定存在的函數。特別是在這種情況下,「.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"並且它應該起作用。

+0

謝謝你的回答。這實際上是我的錯誤,我忘了更新我的代碼。我其實已經有了$ parent.openInfoWindow。我也更新了錯誤日誌。 –

+1

@Lois我沒有看到你所顯示的代碼有任何問題。你會想發佈一個更完整的例子來重現問題。 –

+1

@Lois更新了我的答案。請參閱底部的編輯2。 –

2

對於那些沒有使用jquery.slim,正在使用require構建他們的解決方案。以下墊片將解決您的問題:

require.config({ 
    //rest of config 
    shim: { 
     knockout: { 
      deps: ["jquery"], 
      exports: "knockout" 
     } 
    } 
    //rest of config 
}); 

由於加載jQuery和Knockout中的競爭條件,控制檯中顯示的錯誤發生。