I'm new at AngularJS. I do know some javascript, but AngularJS seems hard to learn (maybe it's just me).
My problem is as follows...
I have a list of players and I would like to make it possible for a user (coach or whoever) to add their players to the list. I have tried couple of method for these past few days, and I just can't figure it out.
Code is below, and u can check out my plunkr here:
HTML
<!DOCTYPE html>
<html >
<head>
<!-- CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link href="style.css" rel="stylesheet" />
<!-- Scripts -->
<script data-require="angular.js#1.2.17" data-semver="1.2.17"
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-app="app">
<div ng-controller="MoveCtrl" class = "container">
<div class = "row">
<div class = "col-xs-4 left-space">
<!--Forgot to add this block of code for input-->
<label>Player name: </label> <input #playerName/>
<button (click) = "add(playerName.value); playerName.value = ''">
Add
</button>
<!--Rest is the same-->
<label class="left-space-title" for="aplayers">Available Players</label>
<select class="left-space-container" size="5" multiple ng-model="available"
ng-options="player as player.name for player in availableplayers">
</select>
</div>
<div class = "col-xs-2 mid-space ">
<input id="moveright" type="button" value=">>"
ng-click="moveItem(available, availableplayers,selectedplayers)" />
<input id="moveleft" type="button" value="<<"
ng-click="moveItem(selected, selectedplayers,availableplayers)" />
</div>
<div class = "col-xs-4 right-space">
<label class="right-space-title" for="splayers">Selected Players</label>
<select class="right-space-container" size="5" multiple ng-model="selected"
ng-options="player as player.name for player in selectedplayers">
</select>
</div>
</div>
</div>
</div>
</body>
</html>
CSS
.mid-space {
margin-top: 30px;
}
.left-space__title,
.right-space__title {
width: 100%;
}
.left-space-container,
.right-space-container {
width: 100%;
min-height: 500px;
}
#moveright,
#moveleft {
width: 100%;
}
Javascript
angular.module('app', []).controller('MoveCtrl', function($scope) {
$scope.moveItem = function(items, from, to) {
items.forEach(function(item) {
var idx = from.indexOf(item);
if (idx != -1) {
from.splice(idx, 1);
to.push(item);
}
});
};
$scope.selectedplayers = [];
$scope.availableplayers = [
{
id: 1,
name: 'foo'
},
{
id: 2,
name: 'boo'
},
{
id: 3,
name: 'goo'
}
];
});
Plunkr is here if you would like to fiddle with the code directly
The plunker has been updated with your working code changes to add the new player to the available players list
add new player plunker
$scope.addItem = function(){
var numberOfplayers = $scope.availableplayers.length;
var newPlayer = {
id : numberOfplayers + 1,
name : $scope.newPlayerName
}
console.log($scope.newPlayerName);
$scope.availableplayers.push(newPlayer);
}
<label>Player name: </label> <input ng-model="newPlayerName"/>
<button ng-click = "addItem(playerName);">
Add
</button>
code changes done .
1.set the ng-model for the input field .
2.get the value of the
input fiend in controllers add player method.
3.create the new
players object with the generated id value.
4.Insert the new player
object to the availableplayers scope variable which inturn pops up
the value in the box.
Related
**screenshot of the error
Hello everyone,
I am a complete beginner in coding and have been coding along with a webinar when I received the error: Uncaught SyntaxError: Unexpected end of input. I included a screenshot of the error. This happened I think when I added the inputChange function. The instructor of the webinar did not have any errors, and my code (so far) is identical to his code during the video.
Anybody who can help me understand and solve this issue?
Here is my code:
//Variables, Arrays, and Objects, dotNotation, bracketNotation
//Dom manipulation
let items = [
{
name: 'Ironhack T',
price: 10,
image: 'https://miro.medium.com/max/5190/1*aVsUjp1zvlRb1799gDjbLA#2x.jpeg'
},
{
name: 'Ironhack Hoodie',
price: 15,
image: 'https://m.media-amazon.com/images/I/B1i3u9-Q-KS._AC_CLa%7C2140%2C2000%7CB1wqstnnTfS.png%7C0%2C0%2C2140%2C2000%2B0.0%2C0.0%2C2140.0%2C2000.0_UL1500_.png'
},
{
name: 'Ironhack Sticker',
price: 2,
image:'https://e7.pngegg.com/pngimages/887/803/png-clipart-ironhack-web-development-job-startup-company-design-blue-user-interface-design-thumbnail.png'
},
{
name: 'Ironhack Mug',
price: 8,
image: 'https://d0bb7f9bf11b5ad1a6b2-6175f06f5e3f64e15abbf67415a276ec.ssl.cf1.rackcdn.com/product-images/designlab/11-oz-traditional-ceramic-coffee-mugs-7102-white1582888132.jpg'
},
];
let list = document.querySelector('ul');
items.forEach((item, i) =>{
console.log(item.name);
list.innerHTML += `<li>
<div>Name: ${item.name}</div>
<div>Price: $${item.price}</div>
<img src="${item.image}" />
<input type='number' placeholder='quantity' onchange='inputChange(${i}, '${item.name}', '${item.price}')' />
<button>Buy item</button>
</li>`
});
function inputChange(i, name, price){
console.log('I want to buy the ',i,' item named, ',name,' that costs $',price);
};
*{
transition: all 1s;
}
body{
padding: 10px;
background-color: lightseagreen;
}
section{
display: flex;
flex-direction: row;
}
img {
width: 50px;
}
#cart{
background-color:salmon;
}
#cart, #items{
width: 50vw;
}
h1{
color:#7c32ff;
}
/*selecting a tag*/
p{
color:green;
text-decoration: line-through;
}
/*Ids have hashtags*/
#two {
background-color:rebeccapurple;
}
/*classes have dots*/
.example {
border: 5px dashed purple;
margin: 5px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!--Now my CSS is linked to my html-->
<link href="/favicon.ico" type="image/x-icon" rel="icon" />
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<section>
<div>
<h2>Items</h2>
<ul id="items">
</ul>
</div>
<div>
<h2>Cart <span id="grandTotal">$0</span></h2>
<ul id="cart">
</ul>
</div>
</section>
<!-- <div class="example">
This is a div
</div>
<div class="example" id="two">
This is a div 2
</div>
<div class="example">
This is a div 3
</div>
<i>This will be italic text</i>
<b>This will be bold text</b>
<h1>This is a certain size</h1>
<p>This is for paragraphs</p> -->
<script src="./script.js"></script>
</body>
</html>
Edit: Fixing the Single (') and Double (") Quotes in the following code is enough
Notice the double quotes wrapping the variables passed to inputChange. And notice the single quotes wrapping the complete value passed to the onchange='...' attribute
// Inside the forEach loop
list.innerHTML += `<li>
<div>Name: ${item.name}</div>
<div>Price: $${item.price}</div>
<img src="${item.image}" />
<input type='number' placeholder='quantity' onchange='inputChange("${i}", "${item.name}", "${item.price}")' />
<button>Buy item</button>
</li>`;
Edit: Alternative solution which logs the current value of input additionally
While debugging, I took another solution, which also logs the current value of the input elements (i.e. how much the customer wants to buy).
Adding only an input element for each item, with an id property for getting it later when the change event fires.
Adding event listeners with .addEventListener("change", inputChange) - inputChange will receive an event object each time the input element is changed.
inputChange is the event handler function. It extracts the input element's id, and uses it to find the data (object in items array) with the Array.prototype.find() (More on this prototype https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
// script.js
//Variables, Arrays, and Objects, dotNotation, bracketNotation
//Dom manipulation
let items = [
{
name: "Ironhack T",
price: 10,
image: "https://miro.medium.com/max/5190/1*aVsUjp1zvlRb1799gDjbLA#2x.jpeg",
},
{
name: "Ironhack Hoodie",
price: 15,
image:
"https://m.media-amazon.com/images/I/B1i3u9-Q-KS._AC_CLa%7C2140%2C2000%7CB1wqstnnTfS.png%7C0%2C0%2C2140%2C2000%2B0.0%2C0.0%2C2140.0%2C2000.0_UL1500_.png",
},
{
name: "Ironhack Sticker",
price: 2,
image:
"https://e7.pngegg.com/pngimages/887/803/png-clipart-ironhack-web-development-job-startup-company-design-blue-user-interface-design-thumbnail.png",
},
{
name: "Ironhack Mug",
price: 8,
image:
"https://d0bb7f9bf11b5ad1a6b2-6175f06f5e3f64e15abbf67415a276ec.ssl.cf1.rackcdn.com/product-images/designlab/11-oz-traditional-ceramic-coffee-mugs-7102-white1582888132.jpg",
},
];
/* Adds id property to objects inside items array */
items = items.map((item) => ({
...item,
id: item.name.replace(" ", "-").toLowerCase(),
}));
/* inputChange receives event object when the input changes */
function inputChange(event) {
// for inspection - log the event object
// console.log(event);
const { id } = event.target; // the id I gave to the input
const number = event.target.value; // the current value of the input
// find the object with the corresponding data in the items array
const data = items.find((item) => item.id === id);
console.log(
`I want to by ${number} of the item named, ${data.name} that costs $${data.price}`
);
}
let list = document.querySelector("ul");
items.forEach((item, i) => {
/* prettier-ignore */
list.innerHTML += `<li>
<div>Name: ${item.name}</div>
<div>Price: $${item.price}</div>
<img src="${item.image}" />
<input type='number' placeholder='quantity' id=${item.id} />
<button>Buy item</button>
</li>`;
});
/* Get all inputs */
const inputs = document.querySelectorAll("input");
inputs.forEach((inputEl) => inputEl.addEventListener("change", inputChange));
Additionally, one word concerning file paths in src property of script tag:
Either solution is fine in this case:
<script src="script.js"></script>
<script src="./script.js"></script>
your code ./script.js are incorrect.
this is some example of HTML File Paths:
<srcipt src="script.js"> The "script.js" file is located in the same folder as the current page
<srcipt src="js/script.js"> The "script.js" file is located in the js folder in the current folder
<srcipt src="/js/script.js"> The "script.js" file is located in the js folder at the root of the current web
<srcipt src="../script.js"> The "script.js" file is located in the folder one level up from the current folder
I have to do a web page for school that converts temperature between celsius and fahrenheit.
I tried to make it with 2 input boxes that change value based on the value of the other input box, when I write something on one of the input boxes for the first time it works, but then even though on the code the value changes, on the page it doesn't appear.
I am new to javascript and html in general and I don't know what I'm doing wrong.
This is the code:
function cambiagradi(x,y) {
if (document.getElementById(x).value == "Centigradi") {
document.getElementById(y).value = "Fahrenheit";
}
else {
document.getElementById(y).value = "Centigradi";
}
}
function Conversione(from,to,gradi) {
var x = document.getElementById(from).value;
if (document.getElementById(gradi).value == "Centigradi") {
document.getElementById(to).setAttribute("value", (x-32)*5/9);
}
else {
document.getElementById(to).setAttribute("value", (x*9/5)+32);
}
}
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body style="background-color: #008080;">
<h1 style="text-align:center">Convertitore Temperatura</h1>
<div class="container" style="display:flex; justify-content: center">
<div style=" padding: 1%; ">
<p>
<input type="text" id="box1" oninput="Conversione('box1','box2','Gradi2')">
</p>
<p style="margin-left:10%">
<label for="Gradi1">Gradi</label>
<select id="Gradi1" onchange="cambiagradi('Gradi1','Gradi2')">
<option value="Centigradi">Centigradi</option>
<option value="Fahrenheit">Fahrenheit</option>
</select>
</p>
</div>
<div style=" padding: 1%; ">=</div>
<div style=" padding: 1%; ">
<p>
<input type="text" id="box2" oninput="Conversione('box2','box1','Gradi1')">
</p>
<p style="margin-left:10%">
<label for="Gradi2">Gradi</label>
<select id="Gradi2" onchange="cambiagradi('Gradi2','Gradi1')">
<option value="Fahrenheit">Fahrenheit</option>
<option value="Centigradi">Centigradi</option>
</select>
</p>
</div>
</div>
</body>
</html>
Thanks in advance!
You should just set the value of the element and all would work as expected.
The explanation you can find here.
function Conversione(from, to, gradi) {
const x = document.getElementById(from).value;
if (document.getElementById(gradi).value == "Centigradi") {
document.getElementById(to).value = ((x - 32) * 5) / 9;
} else {
document.getElementById(to).value = (x * 9) / 5 + 32;
}
}
Some explanations within the code. Tell me if you need more. It looks more complicated but it avoids inline JavaScript which is good :)
// Define your elements as variables first as you're going to use them multiple times :
const gradi1 = document.getElementById("Gradi1");
const gradi2 = document.getElementById("Gradi2");
const box1 = document.getElementById("box1");
const box2 = document.getElementById("box2");
// Now, always use gradi1, gradi2, box1 and box2 instead of document.getElementById ....
// Then, avoid INLINE JavaScript ! (onchange="")
// We're gonna add 'event listener' to your elements
gradi1.addEventListener("change", function(){
cambiagradi(this); // 'this' means you'll know which element triggered the event when calling the function
}, false);
gradi2.addEventListener("change", function(){
cambiagradi(this);
}, false);
box1.addEventListener("input", function(){
Conversione(this)
}, false);
box2.addEventListener("input", function(){
Conversione(this)
}, false);
// You passed 'this' previously, so its back here with the name you want (ex: ancora_this)
function cambiagradi(ancora_this) {
if (ancora_this.id == "Gradi1") {
if (ancora_this.selectedIndex == 0) { // 'selectedIndex' means selected option position in the dropdown menu (begins at 0)
gradi2.selectedIndex = 1;
} else {
gradi2.selectedIndex = 0;
}
} else {
if (ancora_this.selectedIndex == 1) {
gradi1.selectedIndex = 0;
} else {
gradi1.selectedIndex = 1;
}
}
}
function Conversione(ancora_this) {
var target, conv, unit;
if (ancora_this.id == "box1") {
target = box2;
unit = gradi1;
} else {
target = box1;
unit = gradi2;
}
if (unit.selectedIndex == 0) {
conv = (Number(ancora_this.value) * 9/5) + 32;
} else {
conv = (Number(ancora_this.value) - 32)*5/9;
}
target.value = conv;
}
body {
background-color: #008080
}
h1 {
text-align: center
}
.container {
display: flex;
justify-content: center
}
.container div {
padding: 1%;
}
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Convertitore Temperatura</h1>
<div class="container">
<div>
<p>
<!-- type="number" is better because it only allows numbers -->
<input type="number" id="box1">
</p>
<p style="margin-left:10%">
<label for="Gradi1">Gradi</label>
<select id="Gradi1">
<option value="Centigradi" selected>Centigradi</option>
<option value="Fahrenheit">Fahrenheit</option>
</select>
</p>
</div>
<div>=</div>
<div>
<p>
<input type="number" id="box2">
</p>
<p style="margin-left:10%">
<label for="Gradi2">Gradi</label>
<select id="Gradi2">
<!-- I switched the order to have the same 'index' on both SELECT menus
Also, I added 'selected' to have a default selected unit at start -->
<option value="Centigradi">Centigradi</option>
<option value="Fahrenheit" selected>Fahrenheit</option>
</select>
</p>
</div>
</div>
</body>
</html>
I have this JSFiddle code :
http://jsfiddle.net/rnnb32rm/285/
<div ng-app="angularjs-starter" ng-controller="MainCtrl">
<fieldset data-ng-repeat="choice in choicesA">
<input type="text" ng-model="choice.name" name="" placeholder="Enter name">
<button class="addfields" ng-click="addNewChoice()">Add fields</button>
<button class="remove" ng-click="removeChoice()">-</button>
</fieldset>
<div id="choicesDisplay">
{{ choicesA }} <br/>
{{ choicesB }}
</div>
</div>
JS :
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {
$scope.choicesA = [{id: 'choice1'}, {id: 'choice2'}];
$scope.choicesB = [];
$scope.addNewChoice = function() {
var newItemNo = $scope.choicesA.length+1;
$scope.choicesA.push({'id':'choice'+newItemNo});
};
$scope.removeChoice = function() {
var lastItem = $scope.choicesA.length-1;
$scope.choicesA.splice(lastItem);
};
});
As you can see, I have a function addNewChoice() which adds objects to the array choicesA, and then Textboxes get added based on objects number on the choicesA array.
I need to add textboxes to the first fieldset only when I click on the Add fields button on the first fieldset, and the data that I write on those generated textboxes is binded and added to seperate objects to the choicesB array. and the same for all the other Add fields buttons (so each Add field button can only add textboxes to its own fieldset tag), which also get generated based on the number of objects in the choicesA array.
I tried everything, I just can't figure it out. I can explain more if it's a bit unclear. Thank you a ton in advance.
EDIT : Thank you all for your great help, let me explain more :
I have a Spring REST API and two Java objects (JPA entities) named Resource & Action, the object Resource contains a List of Actions, and Action contains a reference to a Resource.
When I load a page, I get an array of Resource objects that I already saved return from the database by an $http.get() method,named choicesA, the structure of array is like this :
[
{"idResource":1, "nameResource": "resName1"},
{"idResource":2, "nameResource": "resName2"}
......etc depends oh how much rows I got from the DB
]
I have another method, $http.post() which posts an array of Action objects named choicesB a seperate non nested array. The array structure is like this :
[
{"idAction":1, "nameAction":"nameAction1", "resource":
{"idResource":1, "nameResource": "resName1"},
{"idAction":2, "nameAction":"nameAction2", "resource":
{"idResource":2, "nameResource": "resName2"},
..
}
{...},
{...}
...
]
So the choicesA array contains the Resource objects that I got with the $http.get(), then I want to fill Action objects in the choicesB array and then save the array using $http.post(), every Action should contain a Resource object. If I click to add more actions in the first fieldset tag for example, means that I want to fill the first Action object in choicesB array, and assign to it the first Resource object located in the choicesA array etc..
I want to be able to decide the number of actions and fill them out, then saving them to the choicesB array. but every action is related to a specific Resource object like I described.
I hope it's clear now, I'm sorry & Thank you again.
Maybe I misunderstood your question. Maybe this will help solve your problem.
Live example on jsfiddle.
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {
$scope.choicesA = [{
id: 'choice1',
choicesB:[]
}, {
id: 'choice2',
choicesB:[]
}];
$scope.addNewChoice = function() {
var newItemNo = $scope.choicesA.length + 1;
$scope.choicesA.push({
'id': 'choice' + newItemNo,
choicesB:[]
});
};
$scope.removeChoice = function(ind) {
$scope.choicesA.splice(ind,1);
};
$scope.addNewChoiceB = function(choice) {
var newItemNo = choice.choicesB.length + 1;
choice.choicesB.push({
'id': 'choice' + newItemNo
});
};
$scope.removeChoiceB = function(choice,ind) {
choice.choicesB.splice(ind,1);
};
});
fieldset {
background: #FCFCFC;
padding: 16px;
border: 1px solid #D5D5D5;
}
.addfields {
margin: 10px 0;
}
#choicesDisplay {
padding: 10px;
background: rgb(227, 250, 227);
border: 1px solid rgb(171, 239, 171);
color: rgb(9, 56, 9);
}
.remove {
background: #C76868;
color: #FFF;
font-weight: bold;
font-size: 21px;
border: 0;
cursor: pointer;
display: inline-block;
padding: 4px 9px;
vertical-align: top;
line-height: 100%;
}
input[type="text"],
select {
padding: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="angularjs-starter" ng-controller="MainCtrl">
<button class="addfields" ng-click="addNewChoice()">Add choice</button>
<fieldset data-ng-repeat="choice in choicesA">
<input type="text" ng-model="choice.name" name="" placeholder="Enter name">
<button class="remove" ng-click="removeChoice($index)">-</button>
<button class="addfields" ng-click="addNewChoiceB(choice)">Add fields</button>
<div data-ng-repeat="choiceb in choice.choicesB">
<input type="text" ng-model="choiceb.name" name="" placeholder="Enter field">
<button class="remove" ng-click="removeChoiceB(choice,$index)">-</button>
</div>
</fieldset>
<div id="choicesDisplay">
<pre>choicesA = {{ choicesA }}</pre>
<pre data-ng-repeat="choiceb in choicesA">choicesB = {{ choiceb.choicesB }}</pre>
</div>
</div>
Updated
Live example on jsfiddle.
I believe what you are trying to do is have 2 nested arrays.
Then you would have nested ng-repeat. You keep track of which array by passing that array as argument of function
View
<fieldset data-ng-repeat="group in choices">
<div ng-repeat="choice in group">
<input type="text" ng-model="choice.name" name="" placeholder="Enter name">
<button class="addfields" ng-click="addNewChoice(group)">Add fields</button>
<button class="remove" ng-click="removeChoice(group)">-</button>
</div>
</fieldset>
JS
$scope.choices = [
// first group
[{id: 'choice1'}, { id: 'choice2'}],
//second group
[{}]
];
$scope.addNewChoice = function(group) {
var newItemNo = group.length + 1;
group.push({
'id': 'choice' + newItemNo
});
};
$scope.removeChoice = function(group) {
group.pop();
};
Note you will need to modify your ID system a bit. Normally this would come from server anyway
DEMO
<div ng-app="angularjs-starter" ng-controller="MainCtrl">
<fieldset>
<input data-ng-repeat="choice in choicesA" type="text" ng-model="choice.name" name="" placeholder="Enter name">
<button class="addfields" ng-click="addNewChoice()">Add fields</button>
<button class="remove" ng-click="removeChoice()">-</button>
</fieldset>
First portion of your requirment is solved by this that textbox is added to sepecific fieldset and 2nd requirment is unclear to me replace your htmlcode with this
I am working on a page with multiple checkboxes, and would like it to return a single div based on any combination of checks. I created a jsfiddle, but even though this is the code on my site that somewhat works, it is not working on jsfiddle:
HTML:
<div id="checkboxes">
<input type="checkbox" id="red" name="color">Red
<input type="checkbox" id="blue" name="color">Blue
<input type="checkbox" id="green" name="color">Green
</div>
<br /><br />
<div id="default" style="display:none;">Show this by default</div><br />
<div id="showred" style="display:none;">This is red</div><br />
<div id="showblue" style="display:none;">This is blue</div><br />
<div id="showgreen" style="display:none;">This is green</div><br />
<div id="showpurple" style="display:none;">This is purple</div>
JS:
$(document).ready(function() {
var r = $('#red');
var b = $('#blue');
var g = $('#green');
var p = r.add(b);
$(r).click(function(){
if ($(r).is(':not(:checked)')) {
$('#showred').show();
$('#showblue').hide();
$('#showgreen').hide();
$('#showpurple').hide();
$('#default').hide();
} else {
$('#showred').hide();
$('#showblue').hide();
$('#showgreen').hide();
$('#showpurple').hide();
$('#default').show();
}
});
$(b).click(function(){
if ($(b).is(':not(:checked)')) {
$('#showred').hide();
$('#showblue').show();
$('#showgreen').hide();
$('#showpurple').hide();
$('#default').hide();
} else {
$('#showred').hide();
$('#showblue').hide();
$('#showgreen').hide();
$('#showpurple').hide();
$('#default').show();
}
});
$(g).click(function(){
if ($(g).is(':not(:checked)')) {
$('#showred').hide();
$('#showblue').hide();
$('#showgreen').show();
$('#showpurple').hide();
$('#default').hide();
} else {
$('#showred').hide();
$('#showblue').hide();
$('#showgreen').hide();
$('#showpurple').hide();
$('#default').show();
}
});
$(p).click(function(){
if ($(r).is(':not(:checked)') && $(b).is(':not(:checked)')) {
$('#showred').hide();
$('#showblue').hide();
$('#showgreen').hide();
$('#showpurple').show();
$('#default').hide();
} else {
$('#showred').hide();
$('#showblue').hide();
$('#showgreen').hide();
$('#showpurple').hide();
$('#default').show();
}
});
});
http://jsfiddle.net/robert316/tu0o1z0s/13/
I would really appreciate any help to get this working correctly, what I would like to happen is:
User clicks "Red" -> Display red div
User clicks "Blue" -> Display blue div
User clicks "Red" and "Blue" -> Only display purple div (no red or blue)
I would also like to fix the behavior that when a user unchecks a box it reverts back to the default div display, ideally, if no boxes are selected it should show default div, and always display the div based on the combination of checkboxes.
Thank you very much for any help with this.
In case anyone finds this question, here is the final code I used to be able to show single divs from multiple checkbox selections:
Code Snippet:
$(document).ready(function () {
// select checkboxes by name
var packages = $("input[name='cc']");
// set main div id
var packageDiv = $("#listings");
// bind to change event
packages.change(function () {
// empty array
var idArr = [];
// get the checked values
var checked = $("input[name='cc']:checked");
// loop and build array
checked.each(function () {
idArr.push($(this).prop("id"));
});
// remove whitespace from multiple checkboxes array
var trimArray = idArr.join("");
toggleShowHide(trimArray, packageDiv);
});
});
function toggleShowHide(arr, elem) {
var arrLen = arr.length;
// clear last selection when unchecking boxes
$(".hide-me").hide();
// set default if array is empty
if (arrLen < 1 ){
setDefault(elem);
}
// run the show hide based on array of selection
for(i = 0; i < arrLen; i++) {
// set the name for the selected div
var temp = "#" + arr + "_div_id";
$(temp).show();
$("#default").hide();
}
// unhide
elem.show();
}
function setDefault(elem){
$("#default").show();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<!-- Checkbox -->
<input type="checkbox" id="i" name="cc" value="i_div_id" data-ref="i_div_id" />
<label>I</label>
<input type="checkbox" id="c" name="cc" value="c_div_id" data-ref="c_div_id" />
<label>C</label>
<input type="checkbox" id="p" name="cc" value="p_div_id" data-ref="p_div_id" />
<label>P</label>
<br />
<br />
<br />
<br />
<!-- Loaded/hidden content div-->
<div id="listings">
<div class="hide-me" id="default"><strong>This is default copy on page</strong></div>
<div class="hide-me" id="i_div_id" style="display:none;">You ordered: <strong>I Package</strong></div>
<div class="hide-me" id="c_div_id" style="display:none;">You ordered: <strong>C Package</strong></div>
<div class="hide-me" id="p_div_id" style="display:none;">You ordered: <strong>P Package</strong></div>
<div class="hide-me" id="ic_div_id" style="display:none;">You ordered: <strong>I-C Package</strong></div>
<div class="hide-me" id="ip_div_id" style="display:none;">You ordered: <strong>I-P Package</strong></div>
<div class="hide-me" id="cp_div_id" style="display:none;">You ordered: <strong>C-P Package</strong></div>
<div class="hide-me" id="icp_div_id" style="display:none;">You ordered: <strong>I-C-P Package</strong></div>
</div>
Rather than creating a color div for each color, why not use a single color-div and just change its css properties / classes? This would eliminate the need for extraneous show-hide / if-else logic when checkbox selections are made.
This snippet illustrates how you can "return a div based on any combination of checks" - it relies on css to handle setting the properties of a target div but you could very well handle all of this in jQuery as well.
Example:
$(document).ready(function() {
// checkboxes with name 'color', 'color-div', and reset button
var colors = $("input[name='color']");
var colorDiv = $("#color-div");
var reset = $("#reset");
// bind to 'colors' change event:
colors.change(function() {
// empty array to hold the color ids
var idArr = [];
// get the checked colors
var checked = $("input[name='color']:checked");
// loop and build array
checked.each(function() {
idArr.push($(this).prop("id"));
});
// function below
toggleColors(idArr, colorDiv);
});
// reset to defaults
$("#reset").click(function() {
// function below
setDefault(colorDiv);
// back to hidden
colorDiv.hide();
// uncheck the check boxes
$("input[name='color']:checked").removeAttr("checked");
});
});
/// function to add color css classes based on checkbox id array
function toggleColors(arr, elem) {
var arrLen = arr.length;
// set default if array is empty
if (arrLen < 1) {
setDefault(elem);
return;
};
// remove classes, add classes
elem.removeClass();
for (i = 0; i < arrLen; i++) {
elem.addClass(arr[i]);
}
// unhide
elem.show();
}
/// set the color div to "default"
function setDefault(elem) {
elem.removeClass();
elem.addClass("default");
}
/*
using css to handle color and content change!
this will prevent you from having to write complicated "if-else"
jQuery blocks.
*/
#color-div {
height: 100px;
width: 100px;
border: solid 2px #d3d3d3;
margin-bottom: 20px;
}
.default,
.default:after {
background-color: #fff;
content: "Please select a color";
}
.yellow,
.yellow:after {
background-color: yellow;
content: "I am Yellow!";
}
.red,
.red:after {
background-color: red;
content: "I am Red!";
}
.blue,
.blue:after {
background-color: blue;
content: "I am Blue!";
}
.yellow.blue,
.yellow.blue:after {
background-color: green;
content: "I am Green!";
}
.yellow.red,
.yellow.red:after {
background-color: orange;
content: "I am Orange!";
}
.blue.red,
.blue.red:after {
background-color: purple;
content: "I am Purple!";
}
.blue.red.yellow,
.blue.red.yellow:after {
background-color: brown;
content: "I am Brown :(";
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<div id="checkboxes">
<input type="checkbox" id="red" name="color" />
<label>Red</label>
<input type="checkbox" id="blue" name="color" />
<label>Blue</label>
<input type="checkbox" id="yellow" name="color" />
<label>Yellow</label>
</div>
<br />
<br />
<div id="color-div" style="display:none;"></div>
<button id="reset">Reset</button>
If css classes isn't your bag, you could essentially handle the same "toggling" through building an equivalent javascript object.
[Edit - Using a Div for Each Checkbox]
Since you have control over the properties of the checkboxes, you should use one of these properties to tie to the element being loaded by your query. You should also try to avoid using id for more than one element. For example if you have this:
<!-- Checkbox -->
<input type="checkbox" id="burgers" name="color" />
<!-- Loaded/hidden content div-->
<div id="burgerDiv" style="display:none;"></div>
I would suggest adding to your checkbox either a data-* or value attribute that ties to the id of the hidden div. I'm not sure how well the data attribute is supported in all browsers, however. Example:
<!-- Checkbox -->
<input type="checkbox" id="burgers" name="color" value="burgersDiv" data-ref="burgersDiv" />
... Then it's pretty easy to hide/show the div:
fiddle
I've played a little with knockoutjs and have produced the following example, enough to become excited by the idea of building these viewmodels in javascript so that the view can be written in much simpler, declarative manner, i.e. first you define what you want to observe, then with the data-bind attributes define what you want to happen when your viewmodel changes in certain ways.
But all of this is happening only on the client.
How could I extend this example to use knockoutjs to observe the state of objects on the server e.g. via AJAX calls?
index.htm:
<!doctype html>
<html>
<title>Knockout example</title>
<head>
<script type="text/javascript" src="js/knockout-1.1.1.debug.js"></script>
<script type="text/javascript" src="js/main.js"></script>
<link rel="stylesheet" href="css/main.css" type="text/css"/>
</head>
<body>
<!-- FIRST AREA -->
<div class="infobox">
<div data-bind="visible: noNamesFilled">
<p>This is an example with NO names filled.</p>
</div>
<div data-bind="visible: bothNamesFilled">
<p>This is an example with both names filled.</p>
</div>
<div data-bind="visible: firstNameOnlyFilled">
<p>This is an example with only the first name filled.</p>
</div>
<div data-bind="visible: lastNameOnlyFilled">
<p>This is an example with the last name filled but not the first name</p>
</div>
</div>
<!-- SECOND AREA -->
<p>First name: <input data-bind="value: firstName, valueUpdate:'afterkeydown'" /></p>
<p>Last name: <input data-bind="value: lastName, valueUpdate:'afterkeydown'" /></p>
<div data-bind="visible: bothNamesFilled">
<h2 class="normal">Hello, <span data-bind="text: fullName"></span>.</h2>
</div>
<div data-bind="visible: firstNameOnlyFilled">
<h2 class="informal">Hi there <span data-bind="text: fullName"></span>!</h2>
</div>
<div data-bind="visible: lastNameOnlyFilled">
<h2 class="formal">Hello, Mr. <span data-bind="text: fullName"></span>.</h2>
</div>
<!-- THIRD AREA -->
<div data-bind="visible: noNamesFilled">
<p><span class="bad">:-(</span> Please fill in both names.</p>
</div>
<div data-bind="visible: bothNamesFilled">
<p><span class="good">:-)</span> Good job, both names are filled!</p>
</div>
<div data-bind="visible: firstNameOnlyFilled">
<p><span class="ok">:-(</span> Please fill in the last name, too.</p>
</div>
<div data-bind="visible: lastNameOnlyFilled">
<p><span class="ko">:-(</span> Please fill in the first name as well.</p>
</div>
</body>
</html>
main.css:
* { margin: 0; padding: 0}
body { margin: 10px}
p { margin: 10px}
.infobox {
background-color: #eee;
width: 300px;
height: 100px;
padding: 10px;
}
.informal {
color: purple;
font-family: arial;
}
.normal {
color: black;
font-family: new courier;
}
.formal {
color: black;
font-size: 11pt;
font-family: times roman;
background-color: #eee;
}
.good {
width: 20px;
background-color: lightgreen;
}
.ok {
width: 20px;
background-color: yellow;
}
.bad {
width: 20px;
background-color: tomato;
}
main.js:
window.onload= function() {
var viewModel = {
firstName : ko.observable(''),
lastName : ko.observable('')
};
viewModel.fullName = ko.dependentObservable(function () {
return viewModel.firstName() + " " + viewModel.lastName();
});
viewModel.bothNamesFilled = ko.dependentObservable(function () {
return viewModel.firstName().length > 0 && viewModel.lastName().length > 0;
}, this);
viewModel.firstNameOnlyFilled = ko.dependentObservable(function () {
return viewModel.firstName().length > 0 && viewModel.lastName().length == 0;
}, this);
viewModel.lastNameOnlyFilled = ko.dependentObservable(function () {
return viewModel.firstName().length == 0 && viewModel.lastName().length > 0;
}, this);
viewModel.noNamesFilled = ko.dependentObservable(function () {
return viewModel.firstName().length == 0 && viewModel.lastName().length == 0;
}, this);
ko.applyBindings(viewModel);
}
I would use setTimeout to call a function that uses JQuery to make an $.ajax call. When it returns JSON data, set that data as your view model and finally, setTimeout again to call the function.
Here's an updated example that mainly updates main.js to work with JQuery to do the ajax call.
The HTML file includes Knockout 3 instead of 1. The HTML also includes JQuery latest to make the JQuery functionality work.
The js/server_data.js is there so you have some valid json data to start with. You can change the url in the $.ajax settings to any serverside script you have but try to set its content type to application/json. For example, PHP scripts could set the Content-type header like: header('Content-type: application/json'); before printing out the data in JSON format.
new main.html:
<!doctype html>
<html>
<title>Knockout example</title>
<head>
<script type="text/javascript" src="js/knockout-3.0.0.debug.js"></script>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript" src="js/main.js"></script>
<link rel="stylesheet" href="css/main.css" type="text/css"/>
</head>
<body>
<!-- FIRST AREA -->
<div class="infobox">
<div data-bind="visible: noNamesFilled">
<p>This is an example with NO names filled.</p>
</div>
<div data-bind="visible: bothNamesFilled">
<p>This is an example with both names filled.</p>
</div>
<div data-bind="visible: firstNameOnlyFilled">
<p>This is an example with only the first name filled.</p>
</div>
<div data-bind="visible: lastNameOnlyFilled">
<p>This is an example with the last name filled but not the first name</p>
</div>
</div>
<!-- SECOND AREA -->
<p>First name: <input data-bind="value: firstName, valueUpdate:'afterkeydown'" /></p>
<p>Last name: <input data-bind="value: lastName, valueUpdate:'afterkeydown'" /></p>
<div data-bind="visible: bothNamesFilled">
<h2 class="normal">Hello, <span data-bind="text: fullName"></span>.</h2>
</div>
<div data-bind="visible: firstNameOnlyFilled">
<h2 class="informal">Hi there <span data-bind="text: fullName"></span>!</h2>
</div>
<div data-bind="visible: lastNameOnlyFilled">
<h2 class="formal">Hello, Mr. <span data-bind="text: fullName"></span>.</h2>
</div>
<!-- THIRD AREA -->
<div data-bind="visible: noNamesFilled">
<p><span class="bad">:-(</span> Please fill in both names.</p>
</div>
<div data-bind="visible: bothNamesFilled">
<p><span class="good">:-)</span> Good job, both names are filled!</p>
</div>
<div data-bind="visible: firstNameOnlyFilled">
<p><span class="ok">:-(</span> Please fill in the last name, too.</p>
</div>
<div data-bind="visible: lastNameOnlyFilled">
<p><span class="ko">:-(</span> Please fill in the first name as well.</p>
</div>
</body>
</html>
js/main.js:
$(document).ready( function() {
var viewModel = {
firstName : ko.observable(''),
lastName : ko.observable('')
};
viewModel.fullName = ko.dependentObservable(function () {
return viewModel.firstName() + " " + viewModel.lastName();
});
viewModel.bothNamesFilled = ko.dependentObservable(function () {
return viewModel.firstName().length > 0 && viewModel.lastName().length > 0;
}, this);
viewModel.firstNameOnlyFilled = ko.dependentObservable(function () {
return viewModel.firstName().length > 0 && viewModel.lastName().length == 0;
}, this);
viewModel.lastNameOnlyFilled = ko.dependentObservable(function () {
return viewModel.firstName().length == 0 && viewModel.lastName().length > 0;
}, this);
viewModel.noNamesFilled = ko.dependentObservable(function () {
return viewModel.firstName().length == 0 && viewModel.lastName().length == 0;
}, this);
ko.applyBindings(viewModel);
// send request to the server to download the server's model information.
$.ajax(
{
'url': 'js/server_data.js',
'dataType': 'json',
'method': 'post',
'error': function(jqXHR, textStatus, errorThrown)
{
// error callback in case you play with this code and run into trouble.
alert('There was a problem handling the ajax request. The error information is: jqXHR: '
+jqXHR+", textStatus: "+textStatus+", errorThrown: "+errorThrown);
},
'success': function(data)
{
// when it is downloaded and parsed to create the "data" parameter, update the viewModel.
viewModel.firstName(data.firstName);
viewModel.lastName(data.lastName);
}
}
);
}
);
js/server_data.js representing dynamically generated data that might be from a database:
{
"firstName": "John",
"lastName": "Doe"
}
jsteve has the right general idea but don't use setTimeout if you just want to download the data when the page loads. Instead, use the JQuery's document ready callback and JQuery's ajax success callback so things run precisely when you want them to.
If you want to continually listen and react to changes to data in the server, look into long poller techniques. Long poller is more efficient and precisely timed than busy waiting that requires a frequent new request to the server.