I'm trying to teach myself Knockout.js by making a simple list of games one can add and remove from. At the moment I'm stuck on removing a specific item from an observable array. I have an array of games, and I have it foreach bound to a div that lists out the title, genre, platform, etc. for each game. I also have a Remove button for each game, but they don't work. I have it set up exactly like it is in Knockout's documentation here:
https://knockoutjs.com/documentation/click-binding.html
I also found someone else with the exact same problem here:
Remove items from Knockout observable array
However, the splice solution listed did not work for me. The function did fire this time, but instead of removing the correct item from the array, it removed the last item in the array, regardless of which Remove button was clicked. I'm at a loss as to why the code from Knockout's documentation doesn't work, and why the splice solution isn't working correctly. Here is my code. Pardon all the hard coded values. I'm just trying to get the basics of it working at the moment.
#{
ViewBag.Title = "My Game List";
}
<head>
<script type="text/javascript">
$(function () {
var game1 = ko.observable({
title: 'Bioshock',
genre: 'Shooter',
platform: 'XBox 360',
releaseDate: '8/21/2007',
developer: 'Irrational Games',
publisher: '2K Games',
imageURL: 'https://upload.wikimedia.org/wikipedia/en/thumb/6/6d/BioShock_cover.jpg/220px-BioShock_cover.jpg'
});
var game2 = ko.observable({
title: 'The Legend of Zelda: Ocarina of Time',
genre: 'RPG',
platform: 'N64',
releaseDate: '11/21/1998',
developer: 'Nintendo',
publisher: 'Nintendo',
imageURL: 'https://cdn-images-1.medium.com/max/1600/1*n2iccNMASW983gg-ZmMdTw.jpeg'
});
var game3 = ko.observable({
title: 'Devil May Cry',
genre: 'Hack-n-Slash',
platform: 'PS2',
releaseDate: '8/23/2001',
developer: 'Capcom',
publisher: 'Capcom',
imageURL: 'https://upload.wikimedia.org/wikipedia/en/thumb/1/1e/DMC1FrontCover.jpg/220px-DMC1FrontCover.jpg'
});
var game4 = ko.observable({
title: 'Comix Zone',
genre: 'Beat-em-Up',
platform: 'Sega Genesis',
releaseDate: '8/2/1995',
developer: 'Sega',
publisher: 'Sega',
imageURL: 'https://upload.wikimedia.org/wikipedia/en/thumb/0/0e/Comix_Zone_Coverart.png/220px-Comix_Zone_Coverart.png'
});
var game5 = ko.observable({
title: 'To the Moon',
genre: 'Visual Novel',
platform: 'PC',
releaseDate: '9/1/2011',
developer: 'Freebird Games',
publisher: 'Freebird Games',
imageURL: 'https://steamcdn-a.akamaihd.net/steam/apps/206440/capsule_616x353.jpg?t=1519836062'
});
function gamesViewModel() {
var self = this;
self.gamesList = ko.observableArray([game1, game2, game3, game4, game5]);
self.gameToAdd = ko.observable({
title: 'Mass Effect',
genre: 'RPG',
platform: 'PC',
releaseDate: '11/20/2007',
developer: 'BioWare',
publisher: 'EA',
imageURL: 'https://steamcdn-a.akamaihd.net/steam/apps/17460/header.jpg?t=1447351599'
});
self.addGame = function () {
self.gamesList.push(self.gameToAdd);
};
self.removeGame = function (gameToRemove) {
self.gamesList.remove(gameToRemove);
//var gameIndex = self.gamesList.indexOf(gameToRemove);
//self.gamesList.splice(gameIndex, 1);
};
}
ko.applyBindings(new gamesViewModel);
});
</script>
</head>
<div class="jumbotron">
<h1>TOP 5 GAMES</h1>
</div>
<div class="row">
<h4>Games</h4>
<div class="card-columns" data-bind="foreach: gamesList">
<div class="card">
<a data-bind="attr: {href: imageURL}" target="_blank">
<img class="card-img-top" data-bind="attr: {src: imageURL}" />
</a>
<div class="card-body">
<h5 class="card-title" data-bind="text: title"></h5>
<div class="card-text">
<div>
<span>Genre: </span>
<span data-bind="text: genre" />
</div>
<div>
<span>Platform: </span>
<span data-bind="text: platform" />
</div>
<div>
<span>Release Date: </span>
<span data-bind="text: releaseDate" />
</div>
<div>
<span>Developer: </span>
<span data-bind="text: developer" />
</div>
<div>
<span>Publisher: </span>
<span data-bind="text: publisher" />
</div>
</div>
<button class="btn btn-danger" data-bind="click: $parent.removeGame">-Remove</button>
</div>
</div>
</div>
<button data-bind="click: addGame">+Add</button>
</div>
Using "()" in knockoutjs is very tricky. Your code is perfect but here is the problem. game objects (game1, game2, ... ) are declared as observable (i would keep them as normal variable) and you are pushing observable reference in gamesList not actual values. Thats why remove method is not able to identify it.
Either declare game objects without observable or assign them with "()" in list.
$(function () {
var game1 = ko.observable({
title: 'Bioshock',
genre: 'Shooter',
platform: 'XBox 360',
releaseDate: '8/21/2007',
developer: 'Irrational Games',
publisher: '2K Games',
imageURL: 'https://upload.wikimedia.org/wikipedia/en/thumb/6/6d/BioShock_cover.jpg/220px-BioShock_cover.jpg'
});
var game2 = ko.observable({
title: 'The Legend of Zelda: Ocarina of Time',
genre: 'RPG',
platform: 'N64',
releaseDate: '11/21/1998',
developer: 'Nintendo',
publisher: 'Nintendo',
imageURL: 'https://cdn-images-1.medium.com/max/1600/1*n2iccNMASW983gg-ZmMdTw.jpeg'
});
var game3 = ko.observable({
title: 'Devil May Cry',
genre: 'Hack-n-Slash',
platform: 'PS2',
releaseDate: '8/23/2001',
developer: 'Capcom',
publisher: 'Capcom',
imageURL: 'https://upload.wikimedia.org/wikipedia/en/thumb/1/1e/DMC1FrontCover.jpg/220px-DMC1FrontCover.jpg'
});
var game4 = ko.observable({
title: 'Comix Zone',
genre: 'Beat-em-Up',
platform: 'Sega Genesis',
releaseDate: '8/2/1995',
developer: 'Sega',
publisher: 'Sega',
imageURL: 'https://upload.wikimedia.org/wikipedia/en/thumb/0/0e/Comix_Zone_Coverart.png/220px-Comix_Zone_Coverart.png'
});
var game5 = ko.observable({
title: 'To the Moon',
genre: 'Visual Novel',
platform: 'PC',
releaseDate: '9/1/2011',
developer: 'Freebird Games',
publisher: 'Freebird Games',
imageURL: 'https://steamcdn-a.akamaihd.net/steam/apps/206440/capsule_616x353.jpg?t=1519836062'
});
function gamesViewModel() {
var self = this;
self.gamesList = ko.observableArray([game1(), game2(), game3(), game4(), game5()]);
self.gameToAdd = ko.observable({
title: 'Mass Effect',
genre: 'RPG',
platform: 'PC',
releaseDate: '11/20/2007',
developer: 'BioWare',
publisher: 'EA',
imageURL: 'https://steamcdn-a.akamaihd.net/steam/apps/17460/header.jpg?t=1447351599'
});
self.addGame = function () {
self.gamesList.push(self.gameToAdd);
};
self.removeGame = function (gameToRemove) {
self.gamesList.remove(gameToRemove);
//var gameIndex = self.gamesList.indexOf(gameToRemove);
//self.gamesList.splice(gameIndex, 1);
};
}
ko.applyBindings(new gamesViewModel);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="jumbotron">
<h1>TOP 5 GAMES</h1>
</div>
<div class="row">
<h4>Games</h4>
<div class="card-columns" data-bind="foreach: gamesList">
<div class="card">
<a data-bind="attr: {href: imageURL}" target="_blank">
<img class="card-img-top" data-bind="attr: {src: imageURL}" />
</a>
<div class="card-body">
<h5 class="card-title" data-bind="text: title"></h5>
<div class="card-text">
<div>
<span>Genre: </span>
<span data-bind="text: genre" />
</div>
<div>
<span>Platform: </span>
<span data-bind="text: platform" />
</div>
<div>
<span>Release Date: </span>
<span data-bind="text: releaseDate" />
</div>
<div>
<span>Developer: </span>
<span data-bind="text: developer" />
</div>
<div>
<span>Publisher: </span>
<span data-bind="text: publisher" />
</div>
</div>
<button class="btn btn-danger" data-bind="click: $parent.removeGame">-Remove</button>
</div>
</div>
</div>
<button data-bind="click: addGame">+Add</button>
</div>
Related
What is the mistake in my code? cust.name and cust.city are not displaying.
Why are no list items created?
<html>
<body>
<div data-ng-controller="SimpleController">
Name :
<br />
<input type="text" data-ng-model="name" placeholder="Enter Your Name " /> {{ name }}
<br />
<ul>
<li data-ng-repeat="cust in customers | filter:name | orderBy:'city' ">
{{ cust.name | uppercase }} - {{ cust.city | lowercase }}
</li>
</ul>
</div>
<script src="angular.min.js"></script>
<script>
function SimpleController($scope) {
$scope.customers = [
{
name: 'Rishabh Shrivas',
city: 'New Delhi'
},
{
name: 'Rishabh Bharadawaj',
city: 'Noida'
},
{
name: 'Rishabh Sen',
city: 'Gurgaon'
}
];
}
</script>
</body>
</html>
You need an ng-app section in the HTML, even an empty one is sufficient. Also, you need to create the controller differently:
var myApp = angular.module('myApp',[]);
myApp.controller('SimpleController', ['$scope', function($scope) {
$scope.customers = [
{
name: 'Rishabh Shrivas',
city: 'New Delhi'
},
{
name: 'Rishabh Bharadawaj',
city: 'Noida'
},
{
name: 'Rishabh Sen',
city: 'Gurgaon'
}
];
}]);
On my home page I added a button named 'city'. On clicking it opens a popup listing the city names. On clicking the city 'paris' the data with city name must be displayed.
homepage
<ion-header-bar class="bar bar-subheader">
<button class="button button-flat button-positive"ng-click="citypopup();" style="padding-right: 63px;
padding-left: 18px;">
<l >City </l>
<l class="icon ion-chevron-down"></l>
</button>
</ion-header-bar>
<ion-content>
<div class='card ' ng-repeat="item in list | filter:test.searchCity ">
<div class="list " >
<div class='item' style="padding-top:0px;" > {{item.id}}
<l class="item-icon-right" style="padding-left:30%"> {{item.date}} </l>
</div>
<div class='item' style="padding-top:0px;" >{{item.status }}
<l class="item-icon-right" style="text-align:right;">{{item.QCstatus}}</l>
<i class="icon ion-chevron-right"></i>
</div>
<b class='item '> {{item.Factory}} </b>
<l class='item '>{{item.city }}</l>
</div>
popup page
<ion-list>
<ul class="list" ng-model="test.searchCity" ng-repeat="ci in cityList | orderBy:'city' " >
<li class="item" ng-click="test.searchCity">{{ci.city}} </li>
</ul>
</ion-list>
javascript
$scope.test = {
searchCity: null,
};
$scope.cityList = [
{
"city": "Chennai"
}, {
"city": "Banglore"
}, {
"city": "Delhi"
}
];
$scope.list = [
{
id: "#001",
date:"DEC 04 2016",
status: "Successful",
QCstatus: "Approve",
Factory: "ARUN ICE CREAM",
city: "paris"
},
{
id: "#002",
date: "JAN 11 2016",
status: "Successful",
QCstatus: "Approve",
Factory: "Floxtronics",
city: "Delhi"
},
{
id: "#003",
date: "Feb 14 2016",
status: "Bug fixing",
QCstatus: "Approve",
Factory: "Aachi",
city: "Chennai"
}
];
$scope.$watch('test.searchCity', function () {
$scope.test.searchCity = null;
console.log('test.searchCity')
});
$scope.citypopup = function() {
var myPopup = $ionicPopup.show({
scope: $scope,
templateUrl: 'templates/cityTemplate.html',
buttons: [
{ text: 'Cancel',
type: 'button-positive' },
]
});
}
What I need is when I click 'city' button, my city popup appears and when I click on a city nothing happens! Could someone help me?
No need to create another popup page, you can open popup content in citypopup() function
$scope.citypopup = function() {
$scope.popup = $ionicPopup.show({
template:"<ion-list><ul class='list' ng-model='test.searchCity' ng-repeat='ci in cityList | orderBy:city ' ><li class='item' ng-click='searchCity(ci.city)'>{{ci.city}} </li> </ul></ion-list>" ,
title: 'City',
scope: $scope
});
}
$scope.searchCity = function(city){
$scope.test.searchCity = city;
$scope.popup.close();
}
You never assign test.searchCity.
In you popup page code you use ng-click="test.searchCity", where you probably should use ng-click="test.searchCity = ci.city".
Unrelated to your problem (but a matter of correct use of patterns): No need for ngModel in a ul element (it's only for form elements), and ng-repeat makes more sense when used on lis rather than uls.
In conclusion:
<ion-list>
<ul class="list">
<li class="item" ng-repeat="ci in cityList | orderBy:'city' " ng-click="test.searchCity = ci.city">{{ci.city}} </li>
</ul>
</ion-list>
I am trying to generate a HTML form with angularjs ngRepeat. I load a JSON file that contains the fields of desired form and save this object in my $scope. I have a ngRepeat element that generates form inputs based on the JSON object.
The problem is when I print $scope.<myFormName> it just have one field name "{{header.name}}" as I expect some fields based on my JSON model.
Actually I see the form generated in HTML properly but the same thing does not happen in angular scope.
angular.module("myApp",[])
.controller("myCtrl",["$scope",function($scope){
// In this funtion I Set fields of form and make my model empty
$scope.getHeaders = function () {
console.info('getHeaders');
$scope.headersForCreation = [
{
name: 'givenName',
type: 'string',
caption: "nameAndFamily"
},
{
name: 'userName',
type: 'string',
caption: "userName"
},
{
name: 'password',
type: 'password',
caption: "password"
},
{
name: 'passwordConfirm',
type: 'password',
caption: "passwordConfirm"
},
{
name: 'mail',
type: 'string',
caption: "email"
},
{
name: 'mobile',
type: 'string',
caption: "mobile",
regex: '0[1-9][0-9]{9}'
}
];
$scope.userInputsInCreation = {};
angular.forEach($scope.headersForCreation, function (headerItem, key) {
$scope.userInputsInCreation[headerItem.name] = '';
});
}; // end of get header
$scope.getHeaders();
console.log($scope.createForm)
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl" id="add-container-access" ng->
<form class="form-horizontal" name="createForm" init="getHeaders()">
<div class="form-group" ng-repeat="header in headersForCreation" >
<label for="id-{{header.name}}" class="col-sm-4 control-label">
{{header.caption}}: </label>
<div class="col-sm-8"
ng-class="{'has-error': createForm.{{header.name}}.$error.$invalid }">
<input type="header.type" class="form-control"
name="{{header.name}}" ng-required="{{header.notNull}}"
id="id-{{header.name}}"
ng-model="userInputsInCreation[header.name]" >
</div>
</div>
<div class="form-group">
<div class="col-sm-2">
<button class="btn btn-default" >
ADD
<i class="glyphicon glyphicon-plus-sign"></i>
</button>
</div>
</div>
</form>
</div>
Because your controller initialized before AngularJS form directive.
angular.module("myApp",[])
.controller("myCtrl",["$scope","$timeout",function($scope, $timeout){
// In this funtion I Set fields of form and make my model empty
$scope.getHeaders = function () {
console.info('getHeaders');
$scope.headersForCreation = [
{
name: 'givenName',
type: 'string',
caption: "nameAndFamily"
},
{
name: 'userName',
type: 'string',
caption: "userName"
},
{
name: 'password',
type: 'password',
caption: "password"
},
{
name: 'passwordConfirm',
type: 'password',
caption: "passwordConfirm"
},
{
name: 'mail',
type: 'string',
caption: "email"
},
{
name: 'mobile',
type: 'string',
caption: "mobile",
regex: '0[1-9][0-9]{9}'
}
];
$scope.userInputsInCreation = {};
angular.forEach($scope.headersForCreation, function (headerItem, key) {
$scope.userInputsInCreation[headerItem.name] = '';
});
}; // end of get header
$scope.getHeaders();
$timeout(function(){ console.log($scope.createForm); });
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl" id="add-container-access" ng->
<form class="form-horizontal" name="createForm" init="getHeaders()">
<div class="form-group" ng-repeat="header in headersForCreation" >
<label for="id-{{header.name}}" class="col-sm-4 control-label">
{{header.caption}}: </label>
<div class="col-sm-8"
ng-class="{'has-error': createForm.{{header.name}}.$error.$invalid }">
<input type="header.type" class="form-control"
name="{{header.name}}" ng-required="{{header.notNull}}"
id="id-{{header.name}}"
ng-model="userInputsInCreation[header.name]" >
</div>
</div>
<div class="form-group">
<div class="col-sm-2">
<button class="btn btn-default" >
ADD
<i class="glyphicon glyphicon-plus-sign"></i>
</button>
</div>
</div>
</form>
</div>
This is a bit of a educational post.
I am trying to understand why my plunker here does not work. I simply want to connect facotry to controller, and then use ng-repeat to import data.
I have tried several syntax but cannot write proper implementation.
Tahnks for you help.
controller:
var wmapp = angular.module('wmapp' []);
.controller('restaurantlistController', function ($scope, $rootScope, restaurantsFactory) {
"use strict";
$scope.restaurantList = restaurantsFactory.getRestaurants(); //call to restaurantfactory
})
factory :
angular.module('wmapp.factory_restaurants', [])
.factory('restaurantsFactory', function () {
"use strict";
var factory = {
Restaurants : [
{Name: 'A La Biche Au Bois', venueType: 'Restaurant ', subCuisine: 'Italian', subsubCuisine: 'Classic/traditional', address: '45 Avenue Ledru-Rollin', cp: '75012', city: 'Paris', country: 'France', countryCode: 'FR', itemid: 'PARIREST002', phoneNumber: '+331 43 43 34 38', website: '', lat: 48.8482040, long: 2.3706140, icon: 'img/restaurant_pointer_WMcustom_40x49_v3.png'},
{Name: 'Allard ', venueType: 'Restaurant ', subCuisine: 'French', subsubCuisine: 'Classic/traditional', address: '41, rue Saint-André des Arts', cp: '75006', city: 'Paris', country: 'France', countryCode: 'FR', itemid: 'PARIREST004', phoneNumber: '+33158002342', website: 'http://www.restaurant-allard.fr/en', lat: 48.8532490, long: 2.3409810, icon: 'img/restaurant_pointer_WMcustom_40x49_v3.png'},
{Name: 'Ambassade d\'Auvergne', venueType: 'Restaurant ', subCuisine: 'French', subsubCuisine: 'Auvergne region', address: '22 Rue du Grenier Saint-Lazare', cp: '75003', city: 'Paris', country: 'France', countryCode: 'FR', itemid: 'PARIREST005', phoneNumber: '+331 42 72 31 22', website: 'http://www.ambassade-auvergne.com/', lat: 48.8631310, long: 2.3534140, icon: 'img/restaurant_pointer_WMcustom_40x49_v3.png'}
// more restaurans
],
getRestaurants : function () {
return factory.Restaurants;
},
};
return factory;
});
html:
<div data-ng-controller="restaurantlistController" ng-repeat="restaurant in restaurantList " href="#">
<article class="item_frame">
<div class="marker_left_container">
<img class="venue_rest_marker" ng-src="{{restaurant.icon}}" />
<span class="venu_type_text">{{restaurant.venueType}}</span>
<span class="distance_from_user_rest">0.7 km</span>
<span class="distance_from_user_rest2">from current location</span>
</div>
<div class="restaurant_details_container">
<h1 class="restaurant_name_inlist">{{restaurant.Name}}</h1>
<span class="restaurant_detail_inlist2">{{restaurant.subCuisine}} <br />
{{restaurant.subsubCuisine}}</span>
<span class="restaurant_address">{{restaurant.address}}, <br />
</span>
<span class="restaurant_address">{{restaurant.cp}}, {{restaurant.city}} <br />
<br />
</span>
<span class="restaurant_others">{{restaurant.phoneNumber}} <br />
</span>
<span class="restaurant_others">{{restaurant.website}} <br />
<br />
</span>
</div>
</article>
<!--main article frame 1 -->
</div>
there more then 4 errors mainly semicolon , dots , module not include errors
check this link it is working now plunker
I tried the following code:
var app = angular.module('app', ['ngRoute', 'dx'])
app.controller('IndexCtrl', function($scope){
var contacts = [
{ name: "Barbara J. Coggins", phone: "512-964-2757", email: "BarbaraJCoggins#rhyta.com", category: "Family" },
{ name: "Carol M. Das", phone: "360-684-1334", email: "CarolMDas#jourrapide.com", category: "Friends" },
{ name: "Janet R. Skinner", phone: "520-573-7903", email: "JanetRSkinner#jourrapide.com", category: "Work" }
];
$scope.slideOutOptions = {
dataSource: contacts,
itemTemplate: 'item',
menuItemTemlate: 'menuItem'
}
})
<!-- HTML -->
<div class="app-index" ng-controller="IndexCtrl">
<div dx-slideout="slideOutOptions">
<div data-options="dxTemplate: { name: 'item' }">
<h1 data-bind="text: category"></h1>
<p><b>Name:</b> <span data-bind="text: name"></span></p>
<p><b>Phone:</b> <span data-bind="text: phone"></span></p>
<p><b>e-mail:</b> <span data-bind="text: email"></span></p>
</div>
<div data-options="dxTemplate: { name: 'menuItem' }">
<b data-bind="text: name"></b>
</div>
</div>
</div>
AngularJS there is not enough documentation on the DevExpress site. there are only examples using Knockout. Checkout PhoneJS DXSlideOut Documentation
The problem is in the HTML templates. You should use Angular syntax there.
<div dx-slideout="slideOutOptions">
<div data-options="dxTemplate: { name: 'item' }">
<h1>{{category}}</h1>
<p><b>Name:</b> <span>{{name}}</span></p>
<p><b>Phone:</b> <span>{{phone}}</span></p>
<p><b>e-mail:</b> <span>{{email}}</span></p>
</div>
<div data-options="dxTemplate: { name: 'menuItem' }">
<b>{{name}}</b>
</div>
</div>
Checkout docs about Angular approach.