list in View does not update when the Model changes - javascript

I am trying to build a MVVM using knockout.js. The idea is that the script in $(document).ready(function() {...} adds a new item model.addElement("value"); - "value" to the model every 3 seconds and that should be shown in HTML. Alhough in console output you can see that model is changing and elements are pushed to the items list, the HTML stays the same.
At the same time if I uncomment self.items.push($('#new_item').val()); line - the HTML page will be changing every time the element is added to the model.
Please, help me to understand what am I doing wrong, why cannot i pass this "value" string through the model.addElement("value"); line?
// Overall viewmodel for this screen, along with initial state
var ListsViewModel = function(item) {
var self = this;
self.item = item;
self.items = ko.observableArray(["First", "Second", "Third"]);
self.addElement = function(item) {
self.items().push(item);
//self.items.push($('#new_item').val()); //--> uncomment for adding element with a button click
}
};
var model = new ListsViewModel()
ko.applyBindings(model, document.getElementById("one"));
$(document).ready(function() {
//comment this if you want add elements only when you click on button
setInterval(function() {
model.addElement("value");
console.log(model.items());
}, 3000);
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<title>Updating list to the server (automatically)</title>
</head>
<body>
<div id="one">
<h1>Records:</h1>
<div class="list-group" data-bind="foreach: items">
</div>
<div class="form-group">
<label for="new_item">Item name:</label>
<input type="text" class="form-control" id="new_item">
</div>
<form>
<input type="button" class="btn btn-info" value="Add element" data-bind="click: addElement">
</form>
</div>
</body>
<script src="static/js/viewModel.js"></script>
<script src="static/js/main.js"></script>
</html>
UPD:
Maybe I have made it unclear, what I wanted was that every 3 seconds new values are added to the model and this change is shown on view (HTML).
I achieved it changing self.items().push(item); to self.items.push(item);, as #user3297291 suggested.
Here is the final version of the code which works exactly as I wanted:
// Overall viewmodel for this screen, along with initial state
var ListsViewModel = function() {
var self = this;
self.items = ko.observableArray(["First", "Second", "Third"]);
//self.item = ko.observable("");
self.addElement = function(item) {
self.items.push(item);
}
};
var model = new ListsViewModel()
ko.applyBindings(model, document.getElementById("one"));
$(document).ready(function() {
//comment this if you want add elements only when you click on button
setInterval(function() {
model.addElement("value");
console.log(model.items());
}, 3000);
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<title>Updating list to the server (automatically)</title>
</head>
<body>
<div id="one">
<h1>Records:</h1>
<div class="list-group" data-bind="foreach: items">
</div>
</div>
</body>
<script src="static/js/viewModel.js"></script>
<script src="static/js/main.js"></script>
</html>

Some minor error fixes that make your snippet work:
Don't push to the array inside self.items, since knockout won't be able to notice its contents changed. Push to the observableArray directly: self.items.push instead of self.items().push
Make self.item observable so you can bind its value to the text input: self.item = ko.observable("")
Use the value binding to sync self.item and <input/>.value: <input data-bind="value: item">
addElement doesn't receive an item, it receives an event and the current binding context. Instead, you can retrieve the text box' value using self.item(), push it, and clear it using self.item("")
When working with knockout, you set the rule to not touch the DOM any other ways except via knockout bindings. Whenever you use jQuery to set or retrieve values, the first question you should ask is "what knockout binding can I use instead".
The fixed version:
// Overall viewmodel for this screen, along with initial state
var ListsViewModel = function(item) {
var self = this;
self.item = ko.observable("");
self.items = ko.observableArray(["First", "Second", "Third"]);
self.addElement = function() {
self.items.push(self.item());
self.item("");
}
};
var model = new ListsViewModel()
ko.applyBindings(model, document.getElementById("one"));
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<title>Updating list to the server (automatically)</title>
</head>
<body>
<div id="one">
<h1>Records:</h1>
<div class="list-group" data-bind="foreach: items">
</div>
<div class="form-group">
<label for="new_item">Item name:</label>
<input type="text" class="form-control" id="new_item" data-bind="value: item">
</div>
<form>
<input type="button" class="btn btn-info" value="Add element" data-bind="click: addElement">
</form>
</div>
</body>
<script src="static/js/viewModel.js"></script>
<script src="static/js/main.js"></script>
</html>

Related

How can I join together values in submit text box into a url API call?

I'm working on a project where you ask the National Parks API for info of parks based on the state abbreviation you enter, as in OR(Oregon) or WA (Washington), etc. I understand how to write it for one value, using template literals, but if I search for more than one state at a time, that when writing the code gets tricky. Im looking to get it to send a call to the api like this:
https://developer.nps.gov/api/v1/parks?stateCode=or%2Cwa
I would be putting "or,wa" into my search box
(Documentation for National Parks API endpoint I'm using:
https://www.nps.gov/subjects/developer/api-documentation.htm#/parks/getPark)
JS:
'use strict'
$(watchForm());
function watchForm(){
$('form').submit(event => {
event.preventDefault();
getParkInfo();
})
}
function getParkInfo(){
var searchBox = $('.inputBox').val();
var numResults = $('.numPerPage').val();
const url = *** How do I write this???***
fetch(url)
.then(response => response.json())
.then(response => {
console.log(response)
displayResults(response);
})
}
function displayResults(response){
$('#results-list').empty();
for(let i = 0; i < response.data.length; i++){
$('#results-list').append(
`<li>
<h3>${response.data[i].fullName}</h3>
<p>${response.data[i].description}</p>
<p>${response.data[i].addresses[i]}</p>
<a href="${response.data[i].directionsUrl}"<p>Address</p></a>
</li>`
)
}
$('#results').removeClass('hidden');
}
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Search Your Favorite National Parks</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>NPS Search Up</h1>
<form>
<input class="inputBox" type="text" required> Search for Park
<br><br>
<input class="numPerPage" type="text" value="10" required> Results per page
<br><br>
<input class="submitBox" type="submit">
</form>
<section id="results" class="hidden">
<h2>Search Results</h2>
<ul id="results-list">
</ul>
</section>
</div>
<script
src="https://code.jquery.com/jquery-3.4.1.js"
integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU="
crossorigin="anonymous"></script>
<script src="script.js" async defer></script>
</body>
</html>
Create a new URL instance with your base url.
var url = new URL("https://developer.nps.gov/api/v1/parks");
and then add your query parameters to this URL instance like this
url.searchParams.append('stateCode', searchBox)
url.searchParams.append('limit', numResults);
var joinedSearchEntry = searchBox.split(',').join('%2C')
var url = `https://developer.nps.gov/api/v1/parks?stateCode=${joinedSearchEntry}`
or save a line
var url = `https://developer.nps.gov/api/v1/parks?stateCode=${searchBox.split(',').join('%2C')}`
Let me know if I'm missing something but this should be fine.

Getting Function.prototype.bind.apply(...) is not a constructor error

I am trying to simulate Controller Inheritance in AngularJS (1.6.9), but I am getting an error on console as : Function.prototype.bind.apply(...) is not a constructor Here is the HTML file:
<!-- Controller Inheritance -->
<!DOCTYPE html>
<html lang="en" ng-app="app7">
<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>Tutorial 7</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
</head>
<body>
<div ng-controller="mainCtrl as parent">
<p>Name: {{parent.name}}</p>
<p>Sound: {{parent.sound}}</p>
<button ng-click="parent.animalClick()">Animal Data</button>
</div>
<br><br>
<div ng-controller="dogCtrl as dog">
<p>Name: {{dog.child.name}}</p>
<p>Sound: {{dog.child.sound}}</p>
<button ng-click="dog.child.animalClick()">Dog Data</button>
<button ng-click="dog.child.dogData()">Get More Data</button>
</div>
<script src="js/exam7.js"></script>
</body>
</html>
Here is the JS file:
//Controller Inheritance Demonstration
let app7 = angular.module('app7',[]);
//Parent Controller
app7.controller('mainCtrl',()=>{
this.name="Animal";
this.sound="Silent";
this.animalClick= ()=>{
alert(this.name+' says '+this.sound);
};
});
//Child Controller
app7.controller('dogCtrl',($controller)=>{
let childCtrl = this;
childCtrl.child=$controller('mainCtrl',{});
childCtrl.child.name="Dog";
childCtrl.child.bark="Woof"; //child`s own variable
childCtrl.child.dogData = ()=>{
alert(this.name+' says '+this.sound+' and '+this.bark);
};
});
I am trying to inherit mainCtrl in childCtrl but unable to do so. Output is not as expected. What could be the possible reason behind such an error?
You can't use the arrow notation everywhere in AngularJS.
AngularJS tries to call a function with new your_function(){...} method, treating it like a class, and it fails to do that with the arrow notation new ()=>{...}.
Simply change
app7.controller('mainCtrl',()=>{
to
app7.controller('mainCtrl',function(){
(as well as in other places)
You also had the wrong logic with the printing child values. You needed to access .child. sub-property first before you could print anything.
Here is a working example of your code:
let app7 = angular.module('app7', []);
//Parent Controller
app7.controller('mainCtrl', function() {
this.name = "Animal";
this.sound = "Silent";
this.animalClick = () => {
alert(this.name + ' says ' + this.sound);
};
});
//Child Controller
app7.controller('dogCtrl', function($controller) {
let childCtrl = this;
childCtrl.child = $controller('mainCtrl', {});
childCtrl.child.name = "Dog";
childCtrl.child.bark = "Woof"; //child`s own variable
childCtrl.child.dogData = () => {
alert(this.child.name + ' says ' + this.child.sound + ' and ' + this.child.bark);
};
});
<!DOCTYPE html>
<html lang="en" ng-app="app7">
<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>Tutorial 7</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
</head>
<body>
<div ng-controller="mainCtrl as parent">
<p>Name: {{parent.name}}</p>
<p>Sound: {{parent.sound}}</p>
<button ng-click="parent.animalClick()">Animal Data</button>
</div>
<br><br>
<div ng-controller="dogCtrl as dog">
<p>Name: {{dog.child.name}}</p>
<p>Sound: {{dog.child.sound}}</p>
<button ng-click="dog.child.animalClick()">Dog Data</button>
<button ng-click="dog.child.dogData()">Get More Data</button>
</div>
</body>
</html>

Input text value into a multiple data list and have option to remove them?

I have an input text box where user enter model number and the model number must be displayed in multiple data lists when user click add button. The user has must also have the option to remove the selected model number in the multiple data lists. I have created the HTML code and Javascript code, but the javascript is not adding.
What is is the best approach to my problem? I'm very newbie to javascript.
Hey is my code:
<html lang=en>
<head>
<title>Add To Datalist</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="bootstrap_3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="bootstrap_3.3.7/fonts/font-awesome.min.css" rel="stylesheet" >
</head>
<body>
<div class="container">
<div class="content">
<br/>
<div class="col-sm-6">
<legend>Compatible Devices </legend>
<input type="text" class="form-control" id="modelNo" name="modelNo" placeholder="Enter Name Here"><br/>
<button class="btn btn-info">Add </button>
<button class="btn btn-danger">Remove</button>
</div>
<div class="col-sm-6">
<div class="listfromPopulatedModelNumber" id="listfromPopulatedModelNumber">
<select id="listfromPopulatedModelNo" multiple="multiple" col=10 rows=10>
<option></option>
<option></option>
<option></option>
</select>
</div>
</div>
</div>
</div>
</body>
JavaScript Code:
<script type="text/javascript">
$(document)
.ready(
function() {
var count = 2;
$("#addModNo")
.click(
function() {
$('#listfromPopulatedModelNo')
.last()
.after(
'#modelNo');
count++;
});
$("#removeModNo").click(function() {
$('#modelNumber > option:selected').remove();
count--;
});
});
</script>
All help will be appreciated.
This should work for you. I have updated solution for you here Updated Solution
$(document).ready(function(){
$('#addModNo').click( function(){
var input = $("input[name='modelNo']").val();
console.log(input);
$('#listfromPopulatedModelNo').append("<option value='"+$(this).val()+"'>"+ input +"</option>");
});
$('#removeModNo').click(function(){
$('option:selected').each( function() {
var input = $("input[name='modelNo']").val();
$('#listfromPopulatedModelNo').append("<option value='"+$(this).val()+"'>"+ input +"</option>");
$(this).remove();
});
});
});

Why is parseInt not working for me?

No matter what I try I get NAN when I try to parse an input value into a variable. I am trying to make a calculator to determine fuel economy.
<!DOCTYPE html>
<html>
<head>
<title>Fuel Calculator</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="description" content="Demo project">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
<link rel="stylesheet" href="style.css">
<style type="text/css"></style>
</head>
<body>
<div class="wrapper">
<h1>Fuel Usage</h1>
<div class="row">
<div class="col-md-12">
<form>
<div class="form-group">
<label>Miles Driven Per Year</label>
<input type="text" class="form-control" id="mpy">
</div>
<div class="form-group">
<label>MPG</label>
<input type="text" class="form-control" id="mpg">
</div>
<p>Gallons Used: <span id="used"></span</p>
<div class="form-group">
</div>
<button id="submit" type="submit" class="btn btn-default">Submit</button>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="main.js" type="text/javascript"></script>
</body>
</html>
js file
var mpy = parseInt($('#mpy').val(), 10);
var mpg = parseInt($('#mpg').val(), 10);
var gallonsUsed = mpy/mpg;
$('#submit').click(function(){
$('#used').text(gallonsUsed);
});
Since you have the input fields declared globally, the initial value in the text value is empty. So the result of parseInt would give you a NAN value.
You need to calculate the parsed values once you invoke the click event.
$('#submit').click(function(e){
var mpy = parseInt($('#mpy').val(), 10);
var mpg = parseInt($('#mpg').val(), 10);
var gallonsUsed = mpy/mpg;
$('#used').text(gallonsUsed);
});
Working example : https://jsfiddle.net/DinoMyte/b9bhs4s3/
The way you have your code, mpy/mpg/gallonsUsed are all calculated when the page is loaded (before any user input can even take place)
Your code would make more sense like this
$('#submit').click(function(){
var mpy = parseInt($('#mpy').val(), 10);
var mpg = parseInt($('#mpg').val(), 10);
var gallonsUsed = mpy/mpg;
$('#used').text(gallonsUsed);
});
So that the calculation is done once user has entered data and hit submit

Javascript: DOM elements updated via jquery.get() aren't responding to extant page listeners

I'm trying to update the content of a div (#slide-content) on clicking certain navigation elements (h2.pagenav). The content structure is uniform, and each requested HTML document contains two such nav elements ('Previous','Next'). However, my listener only fires onceā€”for click events on the initial nav button present at page load, and I don't understand why.
My understanding is that when using jQuery's append() function instead of html(), the appended content should be registered with the DOM and thus available for event listening. Lil' help?
The page structure is pretty simple:
The HTML
<?php require('common.php'); ?>
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Ethogram Lab</title>
<link rel="stylesheet" href="css/foundation.css"/>
<link rel="stylesheet" href="css/foundation-icons.css"/>
<link rel="stylesheet" href="css/app.css"/>
<script src="js/vendor/modernizr.js"></script>
</head>
<body>
<div id="slide-content">
<div class="slide">
<div class="row">
<div class="row">
<!---- content ---->
</div>
<div class="row">
<div class="small-4 small-centered columns">
<h2 data-link="slide_0" class="medium button expand text-center pagenav">Start <i class="fi-play"></i></h2>
</div>
</div>
</div>
</div>
</div>
</div>
<script src='js/vendor/jquery.js'></script>
<script src='js/foundation.min.js'></script>
<script>
$(document).foundation();
</script>
<script src='js/app.js'></script>
</body>
</html>
The Javascript
And here is the relevant it of app.js
/**
* PAGE VARS
*/
var DS = '/';
var WEBROOT = "127.0.0.1"+DS+"ethogram"+DS;
var PHP = ".php";
/**
* DOM LISTENERS
*/
$(".pagenav").click(function() {
console.log("*** pagenav listener has fired");
var loc = $(this).attr('data-link');
var pageNum = loc.substr(-2) == "_" ? loc.substr(-1) : loc.substr(-2);
var URL = WEBROOT+"slides"+DS+loc+PHP;
$.get(URL, function(data) {
$("#slide-content").html('').append(data).fadeIn();
});
});
The ".on()" function should be used. ".live()" is deprecated.
.on() function
$("body").on("click", ".pagenav", function() {
// do something
});
You should use
$("body").on ("click",".pagenav",function(){
// Your code go here
})
This function will detect the items created programatically
See Documentation

Categories