I am new to javascript.
I am trying to understand how the value is set to 'USD' for 'outCurr' argument in the following code, am I missing anything. I understood the rest of the code but could not figure this out.
The example is taken from https://docs.angularjs.org/guide/concepts#model
angular.module('invoice1', [])
.controller('InvoiceController', function()
{
this.qty = 1;
this.cost = 2;
this.inCurr = 'EUR';
this.currencies = ['USD', 'EUR', 'CNY'];
this.usdToForeignRates = { USD: 1, EUR: 0.74, CNY: 6.09 };
this.total = function total(outCurr)
{
return this.convertCurrency(this.qty * this.cost, this.inCurr, outCurr);
};
this.convertCurrency = function convertCurrency(amount, inCurr, outCurr)
{
return amount * this.usdToForeignRates[outCurr] / this.usdToForeignRates[inCurr];
};
this.pay = function pay() {
window.alert("Thanks!");
};
});
What I understand is that both function take 'outCurr' as argument/parameter but I cant see any value assigned to it. Please let me know if I have missed anything.
In the index.html file, there is an ng-repeat that loops through the current currencies (['USD', 'EUR', 'CNY'] array and calls total(c) function and pass each currency.
<b>Total:</b>
<span ng-repeat="c in invoice.currencies">
{{invoice.total(c) | currency:c}}
</span>
So, you need to look at the html file too, as with AngularJS binding and expressions happens in the HTML file.
Hope that helps.
The variable "outCurr" is assigned automatically once the function is called. In the code snippet you see here, the function convertCurr is never called in the Javascript. The special thing about AngularJS is that it enables these functions to be called in the HTML itself. You'll notice that, as explained by Omar, in the snippet below, it passes in the outCurr for each currency in the "currencies" array, thus displaying the resulting value in every currency.
<span ng-repeat="c in invoice.currencies">
{{invoice.total(c) | currency:c}}
</span>
Related
i try to build a simple javasciprt random quote app but in the very first test of my code i saw this in console : Uncaught TypeError: quotesData[currentQuote] is undefined
showquote http://127.0.0.1:5500/js/main.js:31
http://127.0.0.1:5500/js/main.js:37
this is js code source :
quotesData = [{
quote: `There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.`,
name: 'Albert Einstein '
},
{
quote: `Good friends, good books, and a sleepy conscience: this is the ideal life.`,
name: 'Mark Twain'
},
{
quote: `Life is what happens to us while we are making other plans.`,
name: 'Allen Saunders '
},
{
quote: `It's not the load that breaks you down, it's the way you carry it.`,
name: 'Lou Holt'
},
{
quote: `Try not to become a man of success. Rather become a man of value.`,
name: 'Albert Einstein '
},
]
/* important variables */
const currentQuote = quotesData[0];
const quoteText = document.getElementById('quote');
const quotebtn = document.getElementById('q-btn');
const quotepan = document.getElementById('q-span');
/* this function is for show the quote and author name */
function showquote() {
quoteText.innerText = quotesData[currentQuote].quote;
quotespan.innerText = quotesData[currentQuote].name;
currentQuote++;
};
/* this function is for change the quote and author name with evrey click */
quotebtn.addEventListener('click', showquote())
currentQuote isn't an array index, it's an element of the array.
You need to set it to 0, and it can't be const if you want to increment it.
let currentQuote = 0;
Also, the second argument to addEventListener should be a reference to a function. You're calling the function immediately instead of saving it as a listener.
quotebtn.addEventListener('click', showquote);
After you increment currentQuote, you need to check if you've reached the end of the array and wrap around. You can do this using the modulus operator.
function showquote() {
quoteText.innerText = quotesData[currentQuote].quote;
quotespan.innerText = quotesData[currentQuote].name;
currentQuote = (currentQuote + 1) % quotesData.length;
};
A couple problems with your code -
Replace quotebtn.addEventListener('click', showquote()) with quotebtn.addEventListener('click', showquote) because otherwise you are passing the return value of showquote to the function.
currentQuote is an object which cannot be passed as an index. You need to set currentQuote to 0 so you can increment it.
This is still not random quotes, but it solves your problems.
currentQuote is a constant variable - which means you can't increment it because ++ is actually just syntactic sugar for += 1 which in itself is syntactic sugar for currentQuote = currentQuote + 1. Change it to let.
TIP:
Do not mix ES5 and ES6. Old functions should only be used when access to the this keyword is needed. Otherwise, stick to one version for semantic purposes.
const days = document.querySelector('#days');
const hours = document.querySelector('#hours');
const minutes = document.querySelector('#minutes');
document.querySelector('#years').addEventListener('input', function (e) {
let years = e.target.value;
days.lastElementChild.innerHTML = years * 365;
hours.lastElementChild.innerHTML = years * 8760;
minutes.lastElementChild.innerHTML = years * 525600;
});
I really need help with this one. When using the e parameter i can target the value from when the event is triggered using this code. I get that i can call the parameter anything and use target to access all the juicy information about the event in the console. What i do nott understand is , why cant i pass arguments with the e to make my function reusable. I want to pass my variables via arguments and store placeholders as parameters and work against them. Instead if I store (e) as a parameter, unless i am missing something , I am forced to reference my variables inside my function because I cannot seem to pass other arguments with (e) . Is there a way i can e.target.value and still pass arguments to my function? This one has really got me stuck , thanks
One method is to record the id attribute values for output parent elements as a data attribute on the input element used to enter a year. Another method could involve setting up a table of parent elements using the id of the input as key.
A third option is to add the event listener (which is passed an event object as argument) inside a closure: i.e. inside an outer, reusable function which is called with all necessary argument values to add an event listener to a specific elements and handle events that are raised.
Here's an example of the first approach:
"use strict";
function showDHM(event) {
let years = event.target.value;
event.target.dataset.for.trim().split(/\s*,\s*/)
.map( id => document.getElementById( id))
.forEach( (element, index) => {
const multiplicand = [ 365, 8760, 525600];
element.lastElementChild.innerHTML = years * multiplicand[index];
});
}
const year1 = document.querySelector('#year1')
year1.dataset.for = "days1, hours1, mins1";
year1.addEventListener('input', showDHM);
<label> Year: <input type="number" id="year1"></lable>
<p>
<span id="days1"><span>days</span></span>,
<span id="hours1"><span>hours</span></span>,
<span id="mins1"><span>mins</span></span>
Note the code is for demonstration only: adjustment for leap years not included!
The next snippet demonstrates the closure option. The conversion process used to modify the posted code was largely mechanistic: replace hard-coded values with argument names and include the modified code in an outer function.
"use strict";
function handleYears( yearsSel, daysSel, hoursSel, minutesSel) {
const days = document.querySelector(daysSel);
const hours = document.querySelector(hoursSel);
const minutes = document.querySelector( minutesSel);
document.querySelector(yearsSel).addEventListener('input', function (e) {
let years = e.target.value;
days.lastElementChild.innerHTML = years * 365;
hours.lastElementChild.innerHTML = years * 8760;
minutes.lastElementChild.innerHTML = years * 525600;
});
}
handleYears("#year1", "#days1", "#hours1", "#mins1");
<label> Year: <input type="number" id="year1"></lable>
<p>
<span id="days1"><span>days</span></span>,
<span id="hours1"><span>hours</span></span>,
<span id="mins1"><span>mins</span></span>
Would you be able to help me, I have been using a book and they have shown me a way to create an object in JavaScript and I have used there method but I am having trouble displaying it. My aim is to display it in a table using HTML 5 and I am going to create 9 more items to call upon and display. The book tells me to use product1.ShowDetails to display it but I have tried but having issues
Thank you for your time,
James
<script>
function Item(product, description, stockLevel, price)
{
this.Product = product
this.Description = description
this.Stock_Level = stockLevel
this.Price = price
this.showHeading = function()
{
document.write(this.product )
document.write(this.description)
document.write(this.stockLevel)
document.write(this.price)
}
this.showDetails = function()
{
document.write(this.product)
document.write(this.description)
document.write(this.stockLevel)
document.write(this.price)
}
product1 = new Item("Shorts (F)", "Stone Wash Dmin Shorts", 20, 25.90);
}
JavaScript properties are case sensitive. In your constructor you set this.Product to the product parameter but the showDetails() function references the this.product parameter.
Also, your code seems to have a typo, should the last bracket not be before product1 is declared?
I have a ng-repeat loop for a number of values. While looping, I fetch a variable from another array of values and use that:
<div ng-repeat="i in [1,2,3]">
<div ng-init="obj = getObject(i)">
<pre>{{ obj }} </pre>
</div>
</div>
My goal is now to change a property of that variable and do a POST request containing the updated variable. The response for that from the server then contains all values, which I bind to $scope in order to update the view.
<a ng-click="change(obj, 5)">Set property to 5</a>
$scope.change = function(o, value) {
o.prop = value;
// save() sends a POST requests and returns a JSON with all values
$scope.values = save(o);
}
This works, but only the first time I do it. All other changes will be reflected in the $scope.variables, but not in the {{ obj }} variables in the template. $scope.$apply() has no effect, either.
I've created a JS Fiddle to show my problem, which only mocks the HTTP requests. However, I have found that even when I run this code against my REST backend, the first time everything works fine but every time after that reflects no changes at all.
I think the issue is caused because you are using ng-init, which probably sets a non-changing value since you are calling a function. It will work once you change {{ obj }} to {{ getObject(i) }}. The only issue is that your variables are also being referenced and modified in the script allTwo and allThree are being modified since you directly assign them. I fixed that by cloning the objects, but it will probably not be an issue when you are using AJAX.
Here is an updated version of your fiddle: http://jsfiddle.net/0ps2d7Lp/6/
I have made changes to your fiddle.
<div ng-repeat="i in [1,2,3]">
<div>
<pre>{{ getObject(i) }} </pre>
</div>
</div>
Controller changes:
$scope.changeType = function(ids, type) {
angular.forEach($scope.objects, function(o) {
if (ids.indexOf(o.id) > -1) {
o.type = type;
var response = (type === 2) ? allTwo : allThree
$scope.objects = angular.copy(response);
}
});
};
Link to your updated fiddle is here
In your case, getObject() is necessary, but I excluded it in my answer for simplicity sake. I understand that you need to perform a PUT/POST request to update the objects on the server-side, but I don't believe it's necessary at all to re-bind the view to the server's response. Fundamentally, a PUT doesn't require a response other than 200 OK in most cases. The point is you're telling the server to update objects, not create them. Thus, no primary keys change, so, you don't need to rebind the objects. Changes are already resident in memory.
HTML
<div class="example" ng-controller="MyCtrl">
<div ng-repeat="obj in objects">
<div><pre>{{ obj }}</pre></div>
</div>
Change all to Type 2
Change all to Type 3
</div>
JavaScript
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
// initial objects
$scope.objects = [
{ id: 1, type: 1 },
{ id: 2, type: 2 },
{ id: 3, type: 3 }
];
$scope.changeType = function(ids, type) {
angular.forEach($scope.objects, function(o) {
if (ids.indexOf(o.id) > -1) {
o.type = type;
// Perform your PUT/POST request here for each
// updated object to update it on the server-side.
// There is no need to bind to a server response.
}
});
};
}
I want to convert lenghts from mm to ft and inches or vice versa. User can input either mm or ft&in.The case is I always want to save data in mm to database but use angular to see it in both format.
I have already created a solution for it. http://plnkr.co/edit/thgx8vjsjxwfx6cLVVn1?p=preview
But it is using ng-change everytime to convert the values.
I was wondering if some angular expert has better idea of doing similar stuff. Please note that I am only planning to save single value $scope.lengthmm
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
// $scope.lengthtodb = 0;
$scope.mmToFtIn =function(){
toInch = $scope.lengthmm * 0.0393701;
toFt = toInch/12;
OutputFT = Math.floor(toFt);
OutputInch = (toFt - Math.floor(toFt))*12;
$scope.lengthft = OutputFT;
$scope.lengthin = OutputInch;
$scope.Result1 = OutputFT + "ft" + OutputInch + "Inches";
};
$scope.FtInTomm =function(){
tomm = (($scope.lengthft *12)+ $scope.lengthin)*25.4;
$scope.lengthmm = tomm;
$scope.Result2 = tomm;
};
});
In addition, as there will be lots of fields using the the same logic maybe method mmToFTIn needs to split in two methods to bind ft and inches separately. But I am really looking forward to see a smarted solution.
Formatting the Output onto the view is best done with filters.
JS:
app.filter('inchFilter', function() {
return function(input) {
return Math.floor(input * 0.0393701);
};
});
HTML:
<input name="mm" type="text" value="{{lengthmm | inchFilter}}">
Edit:
For a more complete and powerful solution I extended the plunker with a directive to now allow two-way-binding on the non-metric fields aswell.
app.directive('enhancedInput', function($parse){
return {
restrict: 'A',
require: 'ngModel',
link : function(scope, element, attr, ngModelCtrl){
ngModelCtrl.$parsers.unshift(scope.$eval(attr.fromMm));
ngModelCtrl.$formatters.unshift(scope.$eval(attr.toMm));
}
};
});
This is achieved by first "requiring" the ngModelController and then using its $parsers and $formatters to intercept communication in between model and view.
plunker
One option is to create a $watch on the millimeter variable and then update the inches model. This is probably a better option than ng-change because the $watch will fire any time the variable is changed within the scope, not just when the input to your text box changes.
I have created an example for you. While you state you only wish to use one variable, I believe using a second variable in this manner is really the better approach. Alternatively you could use an object or an array to maintain a single variable while storing two values.
For this example I defined the variables as follows
$scope.inches = 0;
$scope.mm = 0;
Then we just create a watch on both variables. To reiterate, this function gets called any time there is a change to the mm variable which allows you to ensure that the relationship between mm and inches is maintained.
$scope.$watch('mm', function(newVal, oldVal) {
$scope.inches = newVal / 25.4;
});
You could also create a custom filter as follows... this might even be a better solution if you only need to display mm in a formatted manner (as opposed to using it for calculations or something else).
angular.module('myFilters', []).filter('mmToInches', function() {
return (function (input) {
return (input / 25.4);
});
});
Then later on do...
Inches Filter : {{ mm | mmToInches }}
You can add arguments to filters to a single filter can do both conversions.
angular.module('myFilters', []).filter('convert', function() {
return (function (input, type) {
if (type == 'mm2inch')
{
return (input / 25.4);
} else if (type == 'inch2mm') {
return (input * 25.4);
}
});
});
Inches Filter : {{ mm | convert:'mm2inch' }}
MM Filter : {{ inches | convert:'inch2mm' }}
Here is an improvement I made to Christoph Hegemann's answer.
The directive only uses one attribute, which points to a scope object inchesShowFeet (you can call it anything you want).
<input type="text" ng-model="data.beamLength"
ng-enhanced-input="inchesShowFeet"
ng-model-options="{ debounce: 500 }" />
The directive itself, as he mentioned, uses the ng-model attribute's parsers
app.directive('ngEnhancedInput', function($parse){
return {
restrict: 'A',
require: 'ngModel',
link : function(scope, element, attr, ngModelCtrl){
ngModelCtrl.$parsers.unshift(scope.$eval(attr.ngEnhancedInput).store);
ngModelCtrl.$formatters.unshift(scope.$eval(attr.ngEnhancedInput).show);
}
};
});
The object, usually set in the controller, has a show and a store function. The name is funny, but it's the best I could think of. The one returns the value to show in the text box, the other one returns the value to store in the model.
$scope.inchesShowFeet = {
show: function(val){ return val / 12; },
store: function(val){ return val * 12; }
};
So, say I have 25 inches stored in my model. The show function get's called with a val of 25. It divides it by 12 and returns it, which get's displayed in the text box. When you type in 4, the store function get's called with a val of 4. It multiplies it by 12 and returns 48, which get's stored in the model.