How to disable available times looping through an array using watch? - javascript

I'm creating a booking application in Vue CLI. I decided to use vue-ctk-date-time-picker for choosing date and time. I'm planning to disable some times, depending on the date, but i'm running to a problem. My code only disables the times of last date specified in array and ignores the rest.
I've logged the times array to console depending on the date and it prints correct values. Other than that console displays no errors.
<VueCtkDateTimePicker only-date v-model="date"/>
<VueCtkDateTimePicker only-time :disabled-hours="disabledHours"/>
date: null,
disabledHours: [],
testArray: [
{
date: "2019-05-28",
times: ["10", "11"]
},
{
date: "2019-05-29",
times: ["10", "11", "12"]
}
]
watch: {
date(newVal, oldVal) {
for (let i = 0; i < this.testArray.length; i++) {
if (newVal == this.testArray[i].date) {
for (let j = 0; j < this.testArray[i].times.length; j++) {
this.disabledHours.push(this.testArray[i].times[j]);
}
} else {
this.defaultHours();
}
}
}
},
created() {
this.defaultHours();
}
defaultHours() {
this.disabledHours = ["00","01","02","03"]
}
If date is "2019-05-28", then i expect disabled hours to be 10 and 11.
If date is "2019-05-29", then i expect disabled hours to be 10, 11 and 12 etc.
But what happen is, that it takes the last date specified in the array and only disables its hours.

The code you've posted will always loop through all entries in testArray and take some action for each entry. I think the behavior you want is for the code to only take action on an entry that matches, and default if no entry matches. There are many ways to achieve that behavior but one way is the following
date(newValue) {
const matched = testArray.find(entry => entry.date === newValue);
if (matched) {
this.disabledHours = matched.times;
} else {
this.defaultHours();
}
}

Related

How do I change a value in an object but still keep certain values?

I'm trying to create to practice and create a function where in that if the count matches that price or is half the initial price to open and will also not reset to false, if the count drops back to 0.
Here is some example data:
openData = {
count: 0,
chest: [
{ id: 'Chest1', price: 50, open: false },
{ id: 'Chest2', price: 200, open: false },
{ id: 'Chest3', price: 500, open: false }
]
};
("changes `open` to `true` when the count is equal to or larger than half the initial price of the id", function() {
openData.count = 100;
code.unlockedChest(openData.chest, openData.count);
expect(openData.chest[0].open).to.equal(true);
expect(openData.chest[1].open).to.equal(true);
expect(openData.chest[2].open).to.equal(false);
});
('does not set `open` to `false` once a chest has been opened, even if the count drops again', function() {
openData.count = 100;
code.unlockedChest(openData.chest, openData.count);
openData.count = 0;
code.unlockedChest(openData.chest, openData.count);
expect(openData.chest[0].open).to.equal(true);
expect(openData.chest[1].open).to.equal(true);
expect(openData.chest[2].open).to.equal(false);
});
});
Here is the idea that I have so far:
function unlockedChest (id, count) {
if (count === chest.price || count <= chest.price) {
return chest.open === true;
}
if (chest.open === true) {
return true; // trying to keep the value that it was changed to
}
}
I have been trying to play around with this for a few hours and I don't think my logic is correct here as well, because what is throwing me off is the wording of 'half'.The output that I'm getting is expecting false to be true for the first spec.
I'm also wondering if this would be a case of using a loop possibly?
Any additional guidance would be appreciated on both specs!
If I understand you correctly, you want to update the value of chest.open based on the value of chest.price, but only if the chest is not already open (chest.open is not true).
You can accomplish this by checking for the open condition before you perform any other logic. If chest.open == true then there is no need to check the price, at least not as part of a function that updates chest.open.

Weird array output TS/Angular

I'm trying to sort data from API for chart, so I need to filter it based on currency name so I can get their rates.
Algo is working properly but I'm getting weird output in my browser and chart also.
Output from tempRates in browser looks like this: [7.4429, 7.4429, 7.4392, 7.4392] which is the correct output.
But when I expand the same array in the browser console I get this output:
(4)[7.4429, 7.4429, 7.4392, 7.4392]
0: 1
1: 1
2: 1
3: 1
And that goes for all arrays.
But value of last array is (4)[1, 1, 1, 1] and that is the expected output.
Object.keys(data.rates).forEach((key) => {
this.data.labels.push(key); // date
});
Object.values(data.rates).forEach((value) => {
tempCurrency = Object.keys(value)[i];
Object.values(data.rates).forEach((rate) => {
if (tempCurrency === Object.keys(rate)[i]) {
tempRates.push(rate[tempCurrency]);
//tempRates.push(Math.round(Math.random() * 100));
}
});
console.log("log before push", tempRates);
this.data.datasets.push({
label: tempCurrency,
data: tempRates,
});
i++;
if (!(i === Object.keys(data.rates).length)) {
tempRates.length = 0;
}
});
I also have tried with random numbers and the output still has the same problem. All arrays have the value of last array.
Console screen shoot
All I had to do was change the tempRates.length = 0; to tampRates = [].
I just don't know what is the difference between those two solutions. I would be grateful if someone could explain it to me.

How to filter last day in an array?

I have an array of objects like this:
[
{
created: "2019-08-14T13:24:36Z",
email: "test1#gmail.com"
},
{
created: "2019-08-15T13:24:36Z",
email: "test2#gmail.com"
},
{
created: "2019-08-16T13:24:36Z",
email: "test1#gmail.com"
},
{
created: "2019-08-22T13:24:36Z",
email: "test4#gmail.com"
},
{
created: "2019-08-22T15:29:66Z",
email: "test1#gmail.com"
}
]
The array is sorted by created. I want to filter those records which are on the last day, irrespective of the time on that day. I added the timestamp using moment.js. Something on these lines:
router.get('/GetLastDayRecords', (req, res) => {
res.json(allRecords.filter(record => record.created.max()));
});
Split the task: first get the maximum date which you'll find at the end of the sorted array (just getting the "YYYY-MM-DD" part of it is enough) and then launch the filter:
let max = allRecords.length ? allRecords[allRecords.length-1].created.slice(0,10) : "";
res.json(allRecords.filter(({created}) => created >= max));
First you need to figure out which day is the last day. If you can assume the records are already sorted, then this is pretty simple:
// Assuming your records are stored in the variable "records"
var lastDay = records[records.length - 1].created;
Now here's where your specific answer may differ based on how you want to handle time zones. Suppose one event happened at 11 PM EST (3 AM GMT) and another event happened at 1 AM EST (5 AM GMT). Are these the same day? In Europe they are, but in America they aren't!
What you need to do is create some cipher from the date+time listed to a "day". This way you can compare two "days" to see if they're the same:
lastDay = new Date(lastDay);
// Setting hours, minutes, and seconds to 0 will give you just the "day" without the time, but by default will use the system timezone
lastDay.setHours(0);
lastDay.setMinutes(0);
lastDay.setSeconds(0);
Once you know which day was the last, it's a simple filter:
// Using a for loop
var results = []
for (var i = 0; i < records.length; i++)
{
if (records[i].created > lastDay) {
results.push(records[i]);
}
}
// Using .filter
var results = records.filter(x => x.created > lastDay);
Alternatively, since we know it's already sorted, we can do it a bit more efficiently by binary searching for the first record on the last day, then grabbing all records after that:
var test = records.length / 2;
var step = records.length / 4;
var found = false;
while (!found) {
if (records[test].created < lastDay) {
test += step;
step /= 2;
}
else if (records[test].created > lastDay) {
if (step == 1) {
// We found the exact cut-off
found = true;
}
else {
test -= step;
step /= 2;
}
}
}
var results = records.slice(test);
Because you're only interested in the "last" day, the logic is a bit simpler. If you wanted the "third" day, you would need to check if created was after the start of the third day and before the end of the third day. We can just check if it's after the start of the last day.
I would create a function to turn your created properties into data be easily compared.
I would also avoid trying to do the entire filter operation in one or two lines as it will difficult to read by other developers.
const dateToInt = date => parseInt( date.split('T').shift().replace(/-/g, '') );
The above will:
Split your created property into an array of date and time.
Select the first element, which happens to be the date.
Remove the dashes in the date.
Coerce the value into a number.
With this you can find the maximum value and filter based on that value.
const nums = foo.map( ({ created }) => dateToInt(created) )
First get a list of numbers from the dataset.
const max = Math.max( ...nums )
Get the biggest number in the list.
const lastDays = foo.filter( ({ created }) => dateToInt(created) === max )
With all that setup, getting the max date is very easy and readable.
Of course, since the list is already sorted. You could have just done this as well.
const last = foo[foo.length -1].created;
const lastDays = foo.filter( ({ created }) => created === last )
I wrote a solution using reduce and filter:
const lastDay = arr.reduce((acc, el) => {
const date = el.created.substr(0,10);
const oldDate = new Date(acc);
const nextDate = new Date(date);
if(oldDate.getTime() > nextDate.getTime()) {
return oldDate;
} else {
return nextDate;
}
}, '1900-01-01');
const lastDayArr = arr.filter(el => {
const date = el.created.substr(0,10);
const oldDate = new Date(lastDay);
const nextDate = new Date(date);
return (oldDate.getTime() === nextDate.getTime());
});
First, you find the most recent date, reducing the original array by comparing which date is the most recent, for this you drop the part of the created string that specifies the hours/minutes/seconds.
You can use a very distant in time date as initial value, or you can set it to null and add another validation in your callback function.
As a second step, you use filter, using the same technique of dropping the hours/minutes/seconds of the created string.
The end result is an array of the elements with the most recent date in your original array.
If you can assume the array is sorted, you can skip the reduce method and just do:
const lastDay = arr[arr.length - 1].created.substr(0,10);
This should work:
allRecords.filter( record => {
let last_date = allRecords[ allRecords.length - 1].created
return last_date.slice(0, 10) === record.created.slice(0, 10)
})
Basically, you are getting the last element from your array and slicing its created value down to its date. Then you are slicing your current record's created value down to its date and comparing if they are the same.
Assuming that the array is already ASC ordered:
const onLastDay = values.filter( v => {
const last = moment(values[ values.length - 1 ].created)
const differenceInDays = last.diff(moment(v.created), 'days')
return differenceInDays < 1
})
console.log(onLastDay)
NOTE: If you try with the reported array you get an error due the fact that the last date is not valid! There are 66 seconds!

Display of Multiple JavaScript Countdowns

I found a way to display multiple countdowns, using javascript.
Having multiple countdown timer events intervals in javascript
I am trying to create a "warmer" display, in a grid. I opted for an HTML table. I have a draft here
I can't get the countdowns to display in the table cells, with separated time components. I just have the entire SPAN tag in the "Days" column.
I'd take a different approach. First of all, instead of creating the table in your HTML, I would store the data about the countdown timers in an array of objects in your JavaScript code and generate the table using JS. Doing this will make it cleaner and more maintainable; to add new items you can just add an object to the array instead of mucking about with HTML.
Secondly, instead of starting an interval for each timer, I would create a single interval that updates all of the timers. Using a single interval means your DOM updates will be batched together and will minimize page reflow.
This code also recalculates the time left each time it updates. If you calculate it once and then just subtract each round, it could introduce drift into your counter. This is because setInterval only guarantees that it will wait at least as many milliseconds as you specify in the delay parameter, it could be more. It probably wouldn't matter much unless your timer was running continuously for a very long time, but over time it would be come inaccurate.
// data is an array of objects, each representing one of your categories.
// Each category has a .title to store its title and a .counters that
// stores an object for each counter in that category.
var data = [
{
title: 'ASTRONOMY',
counters: [
// Each counter has a .title and a .date that is parsed by new Date()
{
title: 'Total Solar Eclipse - (NE US)',
date: 'August 21, 2017'
},
{
title: 'Next Supermoon - Full',
date: 'December 3, 2017'
}
]
},
{
title: 'POLITICS',
counters: [
{
title: 'Next Presidential Election',
date: 'November 3, 2020'
}
]
},
{
title: 'TEST',
counters: [
{
title: '30 seconds from page load',
date: (new Date()).getTime() + (30 * 1000)
},
{
title: 'Unix Epoch',
date: 'January 1, 1970'
}
]
}
];
// this reduce generates the table
let table = data.reduce((acc, category, categoryIndex) => {
return acc + `<tr><td colspan="6" class="category">${category.title}</td></tr>` +
category.counters.reduce((acc, counter, index) => {
return acc + `<tr id="counter-${categoryIndex}-${index}">
<td>${counter.title}</td>
<td>${counter.date}</td>
<td class="days"></td>
<td class="hours"></td>
<td class="minutes"></td>
<td class="seconds"></td>
</tr>`;
}, '');
}, '<table class="countdown"><tr><th>Event</th><th>Date</th><th>Days</th><th>Hours</th><th>Minutes</th><th>Seconds</th></tr>');
table += '</table>';
// insert the table after the noscript tag
document.getElementById('countdown').insertAdjacentHTML('afterend', table);
// generate a flat list of counters
let counters = data.reduce((acc, category, categoryIndex) => {
return acc.concat(category.counters.reduce((counterAcc, counter, index) => {
return counterAcc.concat([{
// counters will be an array of the objects we generate here.
// node contains a reference to the tr element for this counter
node: document.getElementById(`counter-${categoryIndex}-${index}`),
// date is the date for this counter parsed by Date and then converted
// into a timestamp
date: (new Date(counter.date)).getTime()
}]);
}, []));
}, []);
const msSecond = 1000,
msMinute = msSecond * 60,
msHour = msMinute * 60,
msDay = msHour * 24;
let intervalId;
function updateCounters () {
counters.forEach((counter, counterIndex) => {
let remaining = counter.date - Date.now(),
node = counter.node;
let setText = (selector, text) => node.querySelector(selector).textContent = text;
if (remaining > 0) {
setText('.days', Math.floor(remaining / msDay));
remaining %= msDay;
setText('.hours', Math.floor(remaining / msHour));
remaining %= msHour;
setText('.minutes', Math.floor(remaining / msMinute));
remaining %= msMinute;
setText('.seconds', Math.floor(remaining / msSecond));
} else {
// make sure we don't accidentally display negative numbers if a timer
// firing late returns a past timestamp (or the data contains a past date)
setText('.days', 0);
setText('.hours', 0);
setText('.minutes', 0);
setText('.seconds', 0);
// This countdown has reached 0 seconds, stop updating it.
counters.splice(counterIndex, 1);
// no more counters? Stop the timer
if (counters.length === 0) {
clearInterval(intervalId);
}
}
});
}
// display counters right away without waiting a second
updateCounters();
intervalId = setInterval(updateCounters, 1000);
table {
border-collapse: collapse;
}
tr:nth-child(even) {
background-color: #edf;
}
.category {
font-weight: bold;
}
td, th {
padding: .5em;
}
.days, .hours, .minutes, .seconds {
text-align: right;
}
<noscript id="countdown">Sorry, you need JavaScript enabled to view the count
downs</noscript>
More Reading
Creating Accurate Timers in JavaScript
Critical rendering path
Repaints and Reflows: Manipulating the DOM responsibly
If you are ok to use any javascript library why not check FlipClock.js out.
As per the text provided on their site, the following are the logical requirements that were considered when creating the API.
Use as a clock
Use as a timer
Use as a countdown
Themeable using pure CSS
Clean & Dry Syntax
Abstract everything into reusable objects
Full-Featured Developer API to create custom “Clock Faces”
And if you are not ok to use any library here is what you can learn from about how to create a countdown timer using javascript
https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_countdown

How to sort dojo dgrid as floats rather than strings

In dojo dgrid, I have a column of floats that I want sorted. I found this sample instruction from Ken Franqueiro from SitePen on sorting at https://github.com/SitePen/dgrid/issues/276 .
grid.set("sort", function(a,b) {
if(a.col5 > b.col5) { return 1; }
if(a.col5 < b.col5) { return -1 }
return 0;
});
Since I want to sort them as floats, I made this change.
grid.set("sort", function(a,b) {
var first = parseFloat(a.col5);
var second = parseFloat(b.col5);
if (first > second) { return 1; }
if (first < second) { return -1 }
return 0;
});
However, it still sorts them as strings with this result:
-0.11
-7.15
0.12
1.25
10.9
2.02
I'm not actually certain that it calls my function; dojo doesn't always allow breakpoints in similar code. Therefore I changed it and created a function, compareMyFloats(a,b) with the comparator code and called it from my grid.set code. I was not able to stop at a breakpoint in my function.
Therefore, what is the correct way to sort floats in a column?
What you described totally seems to work for me.
If your data is supposed to be treated as numbers, though, I'd ideally suggest outputting it as such rather than strings in the first place.
Meanwhile, RE "Dojo doesn't always allow breakpoints", the only thing I can think of that you might be referring to is running against a minified version of Dojo, but all recent versions of Dojo support source maps, which should still allow you to see the unminified code and breakpoint within browsers' developer tools.
The fiddle I linked is using pretty much exactly what's in the original post, but SO nags me to provide code inline to accompany a JSFiddle, so here it is.
var grid = new OnDemandGrid({
columns: {
id: 'ID',
value: 'Value'
},
collection: new Memory({ data: [
{ id: 1, value: '-0.11' },
{ id: 2, value: '-7.15' },
{ id: 3, value: '0.12' },
{ id: 4, value: '1.25' },
{ id: 5, value: '10.9' },
{ id: 6, value: '2.02' }
] }),
sort: function (a, b) {
var first = parseFloat(a.value);
var second = parseFloat(b.value);
if (first > second) {
return 1;
}
if (first < second) {
return -1;
}
return 0;
}
}, 'grid');

Categories

Resources