I'm using vue, and I found a bizarre behaviour while working on one of my projects.
When I update an array in javascript the items are put inside the old html elements (I suppose) so if these old elements have some particular attributes the new items are going to get them as well.
I'll put this example code (visually it sucks but that's not the point).
<head>
<title>Test</title>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue#3" defer></script>
<script src="script.js" defer></script>
<style>
div[time-isselected="true"] {
background: rgb(0, 255, 0);
}
</style>
</head>
<body>
<div class="day container">
<div class="selection" v-for="day in daysList">
<input type="radio" class="radio-day" name="radio"
:id="returnTheInput(day)" :value="returnTheInput(day)" #click="setSelectedDay(day)">
<label :for="returnTheInput(day)">{{day}}</label>
</div>
</div>
<div class="hour-container">
<div v-for="hour in hoursList" class="hour" :id="returnTheInput(hour)" #click="setSelectedHour(hour)">
{{hour}}
</div>
</div>
</body>
Here's the script:
let daysList = ["mon 15","tue 16"];
let hoursList = [];
let selectedDay = undefined;
const valuesForTest = {
[daysList[0]]: ["10:00", "11:00"],
[daysList[1]]: ["15:00", "16:00"]
}
const { createApp } = Vue;
const vm = Vue.createApp({
data(){
return {
daysList: daysList,
hoursList: hoursList
};
},
methods: {
returnTheInput(input){
return input;
},
setSelectedDay(day){
selectedDay = day;
vm.hoursList.splice(0, hoursList.length); //Vue is reactive to splice
for(let i = 0; i < valuesForTest[selectedDay].length; i++){
vm.hoursList.push(valuesForTest[selectedDay][i]);
}
},
setSelectedHour(hour){
document.getElementById(hour).setAttribute("time-isselected", "true");
}
}
}).mount("body");
To see my point:
select a day
select an hour (click on it)
select the other day
By doing this the hour will still be selected, even though it will be from the new ones.
That's not what I had expected nor what I'd want. I thought the new items would be assigned to completely new html elements.
How do I avoid this? I could change the internal logic of my script, but I was wondering if there was another way. Ideally I'd want Vue to create new html elements for the new items (since I guess it's recycling the old ones).
There are at least 2 solutions for this.
The first is to assign an unique key to each child with the :key attribute:
let daysList = ["mon 15","tue 16"];
let hoursList = [];
let selectedDay = undefined;
const valuesForTest = {
[daysList[0]]: ["10:00", "11:00"],
[daysList[1]]: ["15:00", "16:00"]
}
const { createApp } = Vue;
const vm = Vue.createApp({
data(){
return {
daysList: daysList,
hoursList: hoursList
};
},
methods: {
returnTheInput(input){
return input;
},
setSelectedDay(day){
selectedDay = day;
vm.hoursList.splice(0, hoursList.length); //Vue is reactive to splice
for(let i = 0; i < valuesForTest[selectedDay].length; i++){
vm.hoursList.push(valuesForTest[selectedDay][i]);
}
},
setSelectedHour(hour){
document.getElementById(hour).setAttribute("time-isselected", "true");
}
}
}).mount("body");
<head>
<title>Test</title>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue#3.2.37/dist/vue.global.js"></script>
<style>
div[time-isselected="true"] {
background: rgb(0, 255, 0);
}
</style>
</head>
<body>
<div class="day container">
<div class="selection" v-for="day in daysList">
<input type="radio" class="radio-day" name="radio"
:id="returnTheInput(day)" :value="returnTheInput(day)" #click="setSelectedDay(day)">
<label :for="returnTheInput(day)">{{day}}</label>
</div>
</div>
<div class="hour-container">
<div v-for="hour in hoursList" :key="hour" class="hour" :id="returnTheInput(hour)" #click="setSelectedHour(hour)">
{{hour}}
</div>
</div>
</body>
The second is to reset child elements then re-render them asynchronously with the nextTick utility:
let daysList = ["mon 15","tue 16"];
let hoursList = [];
let selectedDay = undefined;
const valuesForTest = {
[daysList[0]]: ["10:00", "11:00"],
[daysList[1]]: ["15:00", "16:00"]
}
const { createApp } = Vue;
const vm = Vue.createApp({
data(){
return {
daysList: daysList,
hoursList: hoursList
};
},
methods: {
returnTheInput(input){
return input;
},
setSelectedDay(day){
vm.hoursList = [];
selectedDay = day;
Vue.nextTick(() => {
vm.hoursList.splice(0, hoursList.length); //Vue is reactive to splice
for(let i = 0; i < valuesForTest[selectedDay].length; i++){
vm.hoursList.push(valuesForTest[selectedDay][i]);
}
});
},
setSelectedHour(hour){
document.getElementById(hour).setAttribute("time-isselected", "true");
}
}
}).mount("body");
<head>
<title>Test</title>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue#3.2.37/dist/vue.global.js"></script>
<style>
div[time-isselected="true"] {
background: rgb(0, 255, 0);
}
</style>
</head>
<body>
<div class="day container">
<div class="selection" v-for="day in daysList">
<input type="radio" class="radio-day" name="radio"
:id="returnTheInput(day)" :value="returnTheInput(day)" #click="setSelectedDay(day)">
<label :for="returnTheInput(day)">{{day}}</label>
</div>
</div>
<div class="hour-container">
<div v-for="hour in hoursList" class="hour" :id="returnTheInput(hour)" #click="setSelectedHour(hour)">
{{hour}}
</div>
</div>
</body>
Related
I have aproblem with tagify. When I remove a tag (using tag cross), visually the tag is removed from the text area but then it is passed as a parameter via ajax. For example, if I have the following text:
Vito hello
and Vito is a tag, if I delete it, visually only hello remains but still if I send the text taking the text area via jquery still it considers me the text Vito hello.
What is the problem?
EDIT:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>BigMouth v1.0</title>
<script src="../jquery.min.js"></script>
<link rel="stylesheet" href="../jquery-ui.min.css">
<script src="../jquery-ui.min.js"></script>
<script src="../tagify"></script>
<script src="../tagify.polyfills.min.js"></script>
<link href="../tagify.css" rel="stylesheet" type="text/css" />
<script src="../bootstrap.js"></script>
<link rel="stylesheet" href="../bootstrap.css">
<script>
function ShareData(){
let datepicker = document.getElementById("datepicker").value;
let oggetto = document.getElementById("oggetto").value;
let descrizione = document.getElementById("descrizione").value;
// list_added_people is a global variable
const ids = list_added_people.map(o => o.id)
// remove duplicates
const filtered = list_added_people.filter(({ id }, index) => !ids.includes(id, index + 1))
let json_form = { 'date': datepicker, 'oggetto': oggetto, 'descrizione': descrizione, 'people': filtered };
ipcRenderer.send('insert_event', JSON.stringify(json_form));
}
</script>
</head>
<body>
<div id="messages"></div>
<div style="display: none;" id="id_people"></div>
<div class="container">
<form id="myForm" action="#">
<div class="row">
<div class="row" id="title_area">
<h1>Inserisci evento:</h1>
</div>
<div class="row" id="date_area">
<div class="col">
<p>Data: <input type="text" id="datepicker"></p>
</div>
</div>
<div class="row" id="object_area">
<div class="col">
<p>Oggetto: <input type="text" id="oggetto" style=" width:50%"></p>
</div>
</div>
<div class="row" id="description_area">
<div class="col">
<p>Descrizione:</p>
<textarea id = "descrizione" name='mix'></textarea>
</div>
</div>
<div class="row" id="buttons_area">
<div class="col-xs-3">
<button type="button" class="btn btn-dark btn-block">Salva</button>
</div>
<div class="col-xs-3">
<button type="button" id = "prova" class="btn btn-danger btn-block">Cancella</button>
</div>
</div>
<button onclick="ShareData()">ShareData</button>
</div>
</form>
</div>
</div>
<style>
#date_area{
margin-top: 30px;
margin-bottom: 10px;
}
#object_area{
margin-top: 30px;
margin-bottom: 10px;
}
#description_area{
margin-top: 50px;
margin-bottom: 10px;
}
#buttons_area{
margin-top: 30px;
margin-bottom: 10px;
}
.delete_btn_area{
margin-left: 30px;
}
</style>
<script>
Array.prototype.containsArray = function (val) {
var hash = {};
for (var i = 0; i < this.length; i++) {
hash[this[i]] = i;
}
return hash.hasOwnProperty(val);
}
$(document).ready(function(){
var whitelist_1;
list_added_people = []
// AGGIUNGE LE PERSONE SALVATE IN DB NEL TAGGIFY
ipcRenderer.send('get_people', "ciao backend");
window.api.receivePeople((data) => {
let people = JSON.parse(data)
for(i in people){
whitelist_1.push({ value: people[i]['id'], text: people[i]['name'] + " " + people[i]['surname'], title: people[i]['name'] + " " + people[i]['surname'] })
}
})
$( function() {
$( "#datepicker" ).datepicker();
});
// Define two types of whitelists, each used for the dropdown suggestions menu,
// depending on the prefix pattern typed (#/#). See settings below.
var whitelist_1 = [
{ value: 100, text: 'kenny', title: 'Kenny McCormick' },
{ value: 1000, text: 'Mr. Mackey', title: "M'Kay" }
]
// Second whitelist, which is shown only when starting to type "#".
// Thiw whitelist is the most simple one possible.
whitelist_2 = ['Homer simpson', 'Marge simpson', 'Bart', 'Lisa', 'Maggie', 'Mr. Burns', 'Ned', 'Milhouse', 'Moe'];
// initialize Tagify
var input = document.querySelector('[name=mix]'),
// init Tagify script on the above inputs
tagify = new Tagify(input, {
// mixTagsInterpolator: ["{{", "}}"],
mode: 'mix', // <-- Enable mixed-content
pattern: /#|#/, // <-- Text starting with # or # (if single, String can be used here)
tagTextProp: 'text', // <-- the default property (from whitelist item) for the text to be rendered in a tag element.
// Array for initial interpolation, which allows only these tags to be used
whitelist: whitelist_1.concat(whitelist_2).map(function(item){
return typeof item == 'string' ? {value:item} : item
}),
dropdown : {
enabled: 1,
position: 'text', // <-- render the suggestions list next to the typed text ("caret")
mapValueTo: 'text', // <-- similar to above "tagTextProp" setting, but for the dropdown items
highlightFirst: true // automatically highlights first sugegstion item in the dropdown
},
duplicates: true,
callbacks: {
add: console.log, // callback when adding a tag
remove: console.log // callback when removing a tag
}
})
// A good place to pull server suggestion list accoring to the prefix/value
tagify.on('input', function(e){
var prefix = e.detail.prefix;
// first, clean the whitlist array, because the below code, while not, might be async,
// therefore it should be up to you to decide WHEN to render the suggestions dropdown
// tagify.settings.whitelist.length = 0;
if( prefix ){
if( prefix == '#' )
tagify.whitelist = whitelist_1;
if( prefix == '#' )
tagify.whitelist = whitelist_2;
if( e.detail.value.length > 1 )
tagify.dropdown.show(e.detail.value);
}
console.log( tagify.value )
console.log('mix-mode "input" event value: ', e.detail)
})
tagify.on('add', function(e){
let name = e.detail.data.text
let id = e.detail.data.value
console.log(list_added_people)
var flag = 0
list_added_people.push({ id: id, title: name })
})
tagify.on('remove', function(e){
let id_deleted_person = e.detail.data.value
console.log("---")
console.log(e)
console.log(list_added_people)
var keyToFind = id_deleted_person;
var person_to_delete = -1;
for (var i in list_added_people) {
if (list_added_people[i].id == keyToFind) {
person_to_delete = i
break; // If you want to break out of the loop once you've found a match
}
}
if (person_to_delete != -1) {
list_added_people.splice(person_to_delete, 1)
console.log(list_added_people)
}
//list_added_people = list_added_people.filter(e => e !== id_deleted_person)
})
})
</script>
</body>
</html>
I'm a newbie in javascript, i've been following a youtube video of creating a simple project like booklist app using javascript the tutorial is very well explained but when i tried to do it myself i got stuck at one point i can't figure out what's happening
The project is basically about when i submit the details of the book it will be added to the table in the webpage, also it will stored in the local storage too. same like that i need to remove the details of the book from local storage when it is removed from the table.
Here is the code for setting up the class Store with methods getBooks for getting the books from the local storage, addBook for adding new book to local storage, removeBook for removing the book from local storage
class Store{
static getBooks(){
let books;
if (localStorage.getItem('books') == null) {
books = [];
} else{
books = JSON.parse(localStorage.getItem('books'));
}
return books;
}
static addBook(Book) {
const books = Store.getBooks();
books.push(Book);
localStorage.setItem('books', JSON.stringify(books));
}
static removeBook(isbn){
const books = Store.getBooks();
books.forEach((book, index) => {
if (book.isbn === isbn) {
books.splice(index, 1);
}
});
localStorage.setItem('books', JSON.stringify(books));
}
}
The methods getBooks and addBooks are working perfectly fine, but the removeBook method is not working in a way that i wanted.
Here is how i invoked the method,
document.querySelector('#book-list').addEventListener('click', (e) => {
// Delete book from the table in interface
UI.deleteBook(e.target);
Store.removeBook(e.target.parentElement.previousElementSibling.textContent);
});
e.target.parentElement.previousElementSibling.textContent is getting the correct value i needed, so i did made the call to removeBook successfully but i can't pass through the if block inside the method
Here is my complete HTML script
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Book List</title>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.3/css/all.css" integrity="sha384-SZXxX4whJ79/gErwcOYf+zWLeJdY/qpuqC4cAa9rOGUstPomtqpuNWT9wdPEn2fk" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch#4.5.2/dist/yeti/bootstrap.min.css" integrity="undefined" crossorigin="anonymous">
</head>
<body>
<div class="container mt-4">
<h1 class="display-4 text-center">
<i class="fas fa-book-open text-primary"></i> My<span class="text-primary">Book
</Myspan>List</h1>
<form class="book-form">
<div class="form-group">
<label for="title">Title</label>
<input type="text" id="title" class="form-control" autocomplete="off">
</div>
<div class="form-group">
<label for="author">Author</label>
<input type="text" id="author" class="form-control" autocomplete="off">
</div>
<div class="form-group">
<label for="isbn">ISBN#</label>
<input type="text" id="isbn" class="form-control" autocomplete="off">
</div>
<input type="submit" value="Add Book" class="btn btn-primary btn-sm">
</form>
<table class="table table-striped mt-5">
<thead>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th></th>
</thead>
<tbody id="book-list"></tbody>
</table>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
Here is my complete javascript code,
class Book{
constructor(title, author, isbn){
this.title = title;
this.author = author;
this.isbn = isbn;
}
}
class UI{
static displayBooks(){
const books = Store.getBooks();
books.forEach((book) => UI.addBookToList(book))
}
static addBookToList(book){
const list = document.querySelector("#book-list");
const row = document.createElement('tr');
row.innerHTML = `
<td> ${book.title} </td>
<td> ${book.author} </td>
<td> ${book.isbn} </td>
<td>X</td>
`;
list.appendChild(row);
}
static deleteBook(el){
if(el.classList.contains('delete')){
el.parentElement.parentElement.remove();
}
}
static showAlert(message, className) {
const div = document.createElement('div');
div.className = `alert alert-${className}`;
div.appendChild(document.createTextNode(message));
const container = document.querySelector('.container');
const form = document.querySelector('.book-form');
container.insertBefore(div, form);
setTimeout(()=>
document.querySelector('.alert').remove(),
3000
);
}
static clearFields() {
document.querySelector('#title').value = '';
document.querySelector('#author').value = '';
document.querySelector('#isbn').value = '';
}
}
class Store{
static getBooks(){
let books;
if (localStorage.getItem('books') == null) {
books = [];
} else{
books = JSON.parse(localStorage.getItem('books'));
}
return books;
}
static addBook(Book) {
const books = Store.getBooks();
books.push(Book);
localStorage.setItem('books', JSON.stringify(books));
}
static removeBook(isbn){
const books = Store.getBooks();
books.forEach((book, index) => {
if (book.isbn.toString() === isbn.toString()) {
books.splice(index, 1);
}
});
localStorage.setItem('books', JSON.stringify(books));
}
}
document.addEventListener("DOMContentLoaded", UI.displayBooks());
document.querySelector('.book-form').addEventListener('submit', (e) => {
e.preventDefault();
const title = document.querySelector('#title').value;
const author = document.querySelector('#author').value;
const isbn = document.querySelector('#isbn').value;
if (title === '' || author === '' || isbn === '') {
UI.showAlert("Please fill in all fileds", "danger");
} else {
const book = new Book(title, author, isbn);
UI.addBookToList(book);
Store.addBook(book);
UI.clearFields();
UI.showAlert("Succefully added", 'success');
}
});
document.querySelector('#book-list').addEventListener('click', (e) => {
Store.removeBook(e.target.parentElement.previousElementSibling.textContent);
UI.deleteBook(e.target);
UI.showAlert("Succefully removed", 'success');
});
I spent one and half hour to figure out what's wrong in the code but i still can't, I'm completely new to javascript.
The problem is that in your HTML you pad the book ISBN (and other fields) with spaces:
row.innerHTML = `
<td> ${book.title} </td>
<td> ${book.author} </td>
<td> ${book.isbn} </td>
<td>X</td>
`;
This means that the textContent of those td elements will not match with the properties of your book object. Either trim what you get from textContent, or just remove those spaces from your HTML:
row.innerHTML = `
<td>${book.title}</td>
<td>${book.author}</td>
<td>${book.isbn}</td>
<td>X</td>
`;
If your goal was to give these texts a bit of margin, then do that with CSS styling instead.
There are also 2 other issues I bumped into:
Your HTML has </Myspan>, which should be </span>.
You don't correctly set the handler for the DOMContentLoaded event. The argument should be a function, but you actually execute a function instead (immediately). So remove the parentheses at the end:
document.addEventListener("DOMContentLoaded", UI.displayBooks);
The objective is to filter the display of list elements and corresponding markers.I'm unable to understand what is wrong with the logic. The search input should filter and when you undo/cancel the search input then the list should reappear with the markers.
HTML:
enter <html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title> Neighborhood Map</title>
<link href="style.css" rel="stylesheet">
<script src="js/jquery-3.2.1.js"></script>
<script src="js/knockout-3.4.0.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div id="sidebar" class="col-xs-12 col-sm-5 col-md-3">
<h1 id="header">Chennai City Cultural Hubs</h1>
<div class="search-box">
<input class="text-search" type="text" placeholder="Enter here" data-
bind="textInput: query">
</div>
<div class= "list-box">
<div class="menu" data-bind="foreach: filteredItems">
<a class="menu-item"data-bind="text: title, click: $parent.setLoc" >
</a>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6 col-md-8">
<div id="map"></div>
</div>
</div>
<script src="js/app.js"></script>
</body>
</html>
JS:
function appViewModel() {
var self = this;
this.query = ko.observable('');
var filter = self.query().toLowerCase();
this.locationArray = ko.observableArray([]);
locations.forEach(function (item) {
self.locationArray().push(item);
});
self.setLoc = function (clickedLoc) {
var clickedData = clickedLoc.marker;
google.maps.event.trigger(clickedData, 'click')
};
this.filteredItems = ko.computed(function () {
if (!this.filteredItems) {
return self.locationArray();
} else {
return ko.utils.arrayFilter(self.locationArray(), function (item) {
var result = (item.title.toLowerCase().indexOf(filter) > -1)
item.marker.setVisible(result);
return result;
});
}
}, self);
};
ko.applyBindings(new appViewModel());
An explanation and solution would be very helpful.
Issues:
Your filter variable is not observable, so it won't update after instantiation. It's always an empty string, since by the time you assign it, query() returns "".
Checking for !this.filteredItems in the computed does not do anything, because it will never be false. (filteredItems is a ko.computed instance, which will evaluate to true)
Solution
You can rewrite your filteredItems to:
this.filteredItems = ko.computed(function () {
var q = this.query().toLowerCase();
if (!q) {
// TODO (?): reset all `setVisible` props.
return this.locationArray();
}
return this.locationArray()
.filter(function(item) {
var passedFilter = item.title.toLowerCase().indexOf(q) > -1;
item.marker.setVisible(passedFilter);
return passedFilter;
});
}, self);
By calling query you create a dependency to any changes in the search input. By calling locationArray you ensure updates when the data source changes. Note that you'll need to make sure your setVisible logic executes, even when you clear the query...
Remarks & tips
If you want, you can swap out Array.prototype.filter with ko.utils.arrayFilter, but the .filter method is widely supported by now.
You can create more (pure) computeds to separate logic. (E.g.: const lowerQuery = ko.pureComputed(() => this.query().toLowerCase()))
I wouldn't call setVisible in the filter since it's an unexpected side effect. Why not make it computed as well?
Explanation to why this is not a duplicate: My code is already working, I have included as a comment. The question is why the this context change when I include it to click handler function.
I'm attempting a calculator project in React. The goal is to attach onclick handlers to number buttons so the numbers are displayed on the calculator display area. If the handler is written directly to render method it is working, however, if I'm trying from the ComponentDidMount I get an error this.inputDigit is not a function. How do I bind this.inputDigit(digit) properly?
import React from 'react';
import './App.css';
export default class Calculator extends React.Component {
// display of calculator initially zero
state = {
displayValue: '0'
}
//click handler function
inputDigit(digit){
const { displayValue } = this.state;
this.setState({
displayValue: displayValue+String(digit)
})
}
componentDidMount(){
//Get all number keys and attach click handler function
var numberKeys = document.getElementsByClassName("number-keys");
var myFunction = function() {
var targetNumber = Number(this.innerHTML);
return this.inputDigit(targetNumber); // This is not working
};
for (var i = 0; i < numberKeys.length; i++) {
numberKeys[i].onclick = myFunction;
}
}
render() {
const { displayValue } = this.state;
return (
<div className="calculator">
<div className="calculator-display">{displayValue}</div>
<div className="calculator-keypad">
<div className="input-keys">
<div className="digit-keys">
{/*<button className="number-keys" onClick={()=> this.inputDigit(0)}>0</button> This will Work*/}}
<button className="number-keys">0</button>
<button className="number-keys1">1</button>
<button className="number-keys">2</button>
<button className="number-keys">3</button>
<button className="number-keys">4</button>
<button className="number-keys">5</button>
<button className="number-keys">6</button>
<button className="number-keys">7</button>
<button className="number-keys">8</button>
<button className="number-keys">9</button>
</div>
</div>
</div>
</div>
)
}
}
Thats because you are writing it inside a function which is not bound,
Use
var myFunction = function() {
var targetNumber = Number(this.innerHTML);
return this.inputDigit(targetNumber);
}.bind(this);
or
const myFunction = () => {
var targetNumber = Number(this.innerHTML);
return this.inputDigit(targetNumber);
}
After this you need to bind the inputDigit function as well since it also uses setState
//click handler function
inputDigit = (digit) => {
const { displayValue } = this.state;
this.setState({
displayValue: displayValue+String(digit)
})
}
Since you want to use the button text as well, in that case you should use a separate variable in place of this to call the inputDigit function like
class Calculator extends React.Component {
// display of calculator initially zero
state = {
displayValue: '0'
}
//click handler function
inputDigit(digit){
const { displayValue } = this.state;
this.setState({
displayValue: displayValue+String(digit)
})
}
componentDidMount(){
//Get all number keys and attach click handler function
var numberKeys = document.getElementsByClassName("number-keys");
var that = this;
var myFunction = function() {
var targetNumber = Number(this.innerHTML);
console.log(targetNumber);
return that.inputDigit(targetNumber); // This is not working
};
for (var i = 0; i < numberKeys.length; i++) {
numberKeys[i].onclick = myFunction;
}
}
render() {
const { displayValue } = this.state;
return (
<div className="calculator">
<div className="calculator-display">{displayValue}</div>
<div className="calculator-keypad">
<div className="input-keys">
<div className="digit-keys">
{/*<button className="number-keys" onClick={()=> this.inputDigit(0)}>0</button> This will Work*/}
<button className="number-keys">0</button>
<button className="number-keys">1</button>
<button className="number-keys">2</button>
<button className="number-keys">3</button>
<button className="number-keys">4</button>
<button className="number-keys">5</button>
<button className="number-keys">6</button>
<button className="number-keys">7</button>
<button className="number-keys">8</button>
<button className="number-keys">9</button>
</div>
</div>
</div>
</div>
)
}
}
ReactDOM.render(<Calculator/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Bind it in the constructor
constructor(props) {
super(props);
this.inputDigit = this.inputDigit.bind(this);
}
I have multiple questions generated within ractive.js loop. Each question has multiple answers with different prices. I need to calculate total price and recalculate it every time selected answers change. Need help with this.
I made this codepen: http://codepen.io/Nitoiti/pen/GjxXvo
<h1>Get total price</h1>
<div id='container'></div>
<script id='template' type='text/ractive'>
{{#each designQuestions}}
<p><span>{{id}} </span> {{question}}</p>
{{#each answers}}
<label><input type='radio' name='designQuestions-answers-{{id}}' value='{{price}}' {{#if selected}} checked {{/if}} >{{answer}} - {{price}}</label>
{{/each}}
{{/each}}
<table class="total">
<tr>
<td>total price:</td>
<td>{{total}}</td> <!-- how to calculate total price and change it on radio buttons change? -->
</tr>
</table>
</script>
<script src='http://cdn.ractivejs.org/latest/ractive.min.js'></script>
<script>
var ractive, designQuestions;
designQuestions = [
{ id: '1',
question: 'Question1 ?',
answers: [
{answer:'answer1',price:'222', selected: 'selected'},
{answer:'answer2',price:'553'},
{answer:'answer3',price:'22'},
{answer:'answer4',price:'442'}
]
},
{ id: '2',
question: 'Question2 ?',
answers: [
{answer:'answer1',price:'22'},
{answer:'answer2',price:'55', selected: 'selected'},
{answer:'answer3',price:'0'},
{answer:'answer4',price:'44'}
]
}
];
var ractive = new Ractive({
// The `el` option can be a node, an ID, or a CSS selector.
el: '#container',
// We could pass in a string, but for the sake of convenience
// we're passing the ID of the <script> tag above.
template: '#template',
// Here, we're passing in some initial data
data: {
designQuestions: designQuestions
}
});
</script>
Well' i've found solution myself.
<!doctype html>
<html>
<head>
<meta charset='utf-8'>
<title>Ractive test</title>
</head>
<body>
<h1>Калькулятор</h1>
<!--
1. This is the element we'll render our Ractive to.
-->
<div id='container'></div>
<!--
2. You can load a template in many ways. For convenience, we'll include it in
a script tag so that we don't need to mess around with AJAX or multiline strings.
Note that we've set the type attribute to 'text/ractive' - though it can be
just about anything except 'text/javascript'
-->
<script id='template' type='text/ractive'>
{{#each designQuestions}}
<p><span>{{id}} </span> {{question}}</p>
{{#each answers}}
<label><input on-change='calc' type='radio' name='{{select}}' value='{{selected}}' >{{answer}} - {{price}}</label>
{{/each}}
{{/each}}
<table class="total">
<tr>
<td>Итого:</td>
<td>{{sum}}</td>
</tr>
</table>
</script>
<script src='http://cdn.ractivejs.org/latest/ractive.min.js'></script>
<script>
var ractive, designQuestions;
designQuestions = [
{ id: '1',
question: 'Какой дизайн вы предпочитаете?',
answers: [
{answer:'уникальный и лично свой',price:222,selected:1},
{answer:'у меня уже есть готовый дизайн',price:553,selected:2},
{answer:'дизайн не нужен',price:22,selected:3},
{answer:'купим тему на themeforest',price:442,selected:4}
],
select:1
},
{ id: '2',
question: 'есть ли у вас наработки по дизайну?',
answers: [
{answer:'да, есть',price:22,selected:0},
{answer:'нет, ничего нет',price:55,selected:1},
{answer:'дизайн не нужен',price:0,selected:2},
{answer:'еще крутой ответ',price:44,selected:3}
],
select:1
}
];
var ractive = new Ractive({
// The `el` option can be a node, an ID, or a CSS selector.
el: '#container',
// We could pass in a string, but for the sake of convenience
// we're passing the ID of the <script> tag above.
template: '#template',
// Here, we're passing in some initial data
data: {
designQuestions: designQuestions,
sum:0
},
onrender:function(options)
{
var self = this;
// proxy event handlers
self.on(
{
calc: function (e)
{
calc();
}
});
calc();
function calc()
{
var arrayLength = designQuestions.length;
sum = 0;
for (var i = 0; i < arrayLength; i++)
{
var lengthans = designQuestions[i].answers.length;
for (var j = 0; j < lengthans; j++) {
if (designQuestions[i].answers[j].selected === designQuestions[i].select){
sum = sum + designQuestions[i].answers[j].price;
}
}
}
self.set('sum',sum);
}
}
});
</script>
</body>
</html>