As the fizz has been done to death I will review this in terms of a page that displays a list of items, could be anything, dates, measurements, or whatnot.
Problems and bugs
There is a sizing problem.
Not all the values indicated by the height slider can be scrolled to. This is because you set the container size incorrectly <div id="listContainer" v-bind:style="{ height: height + 'px'}">
it should be height: height * listItemHeight + 'px'
with listItemHeight
matching the height of a list item.
Better yet don't set the height let the layout engine do that. You use colon property :class="getClass(number)"
You can add another class named Empty
and return that if the function is called with a number greater than height.
The containing div will size itself to fit the content.
You only display 100 items
Changing the height slider (min value is 200) I imagine changes the number of items in the list. However only 100 items are displayed no matter what the height
value is.
The initial setting is incorrect
When the page loads you set the slider height to 3000 but the array you set to 100. Maybe a constant in the JS to set up the height
, and numbers
array would help. (See first example)
Use a label
Use a label to associate the height slider with the height value display rather than an unassociated div. You can just nest the input within the label to make the association implicit.
JavaScript style
- Delimit single line statement blocks with curlies
if (foo) {...}
- isNaN does not require a number, the explicit convertion is supluflorouse.
isNaN(parseInt(output, 10))
is the same a isNaN(output)
Also use Number to convert base 10 numbers if you know you are not rounding Number(output)
is better than parseInt(output, 10)
- Be consistency in style. In the function
getOutput
you use inline undelimited single
line statements if (number % 5) return 'Buzz';
yet in getClass
you use 3 line delimited single line statements if (isNaN(output)) {\n return output;\n }
with \n
for new lines.
- Less is best. Use the short form of code where you can. Eg last 4 lines of
getClass
can be on ternary. (see example)
Use dynamic views for large data sets.
I think that the approach is a little over the top. Having very long pages has a cost in resources. Considering that you can never see more than a screen full at a time it would be more effective to generate the list as a view in place as the user scrolls. That way you only need to create as many elements as can be seen at once. You could use the height slider to replace the scroll and us it to reference the top view item.
With a little more effort such a list should be zoomable as well, only ever displaying one screenfull. (Really scroll bars are old, ugly, and awkward. Good design means weighted intuitive gesture control, even with a mouse)
Example 1
Addressing some of the problems and bugs. Uses a complete list (set to 1000 for practicality)
const getIndex = (number, index) => index + 1;
const MIN_HEIGHT = 100;
const MAX_HEIGHT = 1000;
const app = new Vue({
el: "#app",
data: {
numbers: new Array(MAX_HEIGHT).fill(1).map(getIndex),
height: MAX_HEIGHT,
minHeight : MIN_HEIGHT,
maxHeight : MAX_HEIGHT,
},
filters: {
getOutput: function(number) {
if (number % 3 === 0 && number % 5 === 0) { return 'FizzBuzz' }
if (number % 3 === 0) { return 'Fizz' }
if (number % 5 === 0) { return 'Buzz' }
return number;
}
},
methods: {
getClass: function(number) {
if (number > this.height) { return "Empty" }
const output = this.$options.filters.getOutput(number);
return isNaN(output) ? output : "";
}
}
});
body {
padding: 4px;
font-family: serif;
}
h1 {
font: 400 20px cursive;
}
input[type="range"] {
width: 100%;
}
li {
background: #fff;
color: #000000;
border-radius: 4px;
border: 2px solid #6a737c;
padding: 3px;
}
li.FizzBuzz {
border-color: #f800ff;
}
li.Fizz {
border-color: #f80000;
}
li.Buzz {
border-color: #0000ff;
}
li.Empty {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h1>
FizzBuzz with dynamic height container
</h1>
<label>
Height: {{ height }}
<input type="range" :min="minHeight" :max="maxHeight" v-model="height" />
</label>
<div id="listContainer" >
<ul>
<li v-for="number in numbers" :class="getClass(number)">
{{ number | getOutput }}
</li>
</ul>
</div>
</div>
Example 2
This does not create the long list, rather it uses a view controlled by the height slider replacing the scroll bar. This lets you display a much larger range of values without having to tax the device with a huge lists of elements.
I have removed the title (not needed we know what the page does) and height label as that value is now the first fizzBuzz item. This gives needed realestate back to the app.
Increased the view range to 10000. Also using HEX CSS alpha format colors thus will look a little ugly for some older browsers.
const getIndex = (number, index) => index;
const MIN_HEIGHT = 1;
const MAX_HEIGHT = 10000;
const DATA_VIEW_SIZE = 7;
const app = new Vue({
el: "#app",
data: {
numbers: new Array(DATA_VIEW_SIZE).fill(1).map(getIndex),
height: MIN_HEIGHT,
minHeight : MIN_HEIGHT,
maxHeight : MAX_HEIGHT,
},
filters: {
getOutput: function(number) {
if (number % 3 === 0 && number % 5 === 0) { return 'FizzBuzz' }
if (number % 3 === 0) { return 'Fizz' }
if (number % 5 === 0) { return 'Buzz' }
return number;
}
},
methods: {
getClass: function(number) {
const output = this.$options.filters.getOutput(number + Number(this.height));
return isNaN(output) ? output : "";
},
getItem: function(number) {
return this.$options.filters.getOutput(Number(number) + Number(this.height));
},
}
});
body {
padding: 0px;
font-family: serif;
}
input[type="range"] {
width: 100%;
margin-top: -8px;
}
ul {
margin-top: -2px;
}
li {
background: #0003;
color: #000000;
border-radius: 4px;
border: 1px solid #6a737c;
padding: 1px;
}
li.FizzBuzz {
border-color: #f800ff;
background: #f800ff33;
}
li.Fizz {
border-color: #f80000;
background: #f8000033;
}
li.Buzz {
border-color: #0000ff;
background: #00f3;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="range" :min="minHeight" :max="maxHeight" v-model="height" />
<ul>
<li v-for="number in numbers" :class="getClass(number)">
{{ getItem(number) }}
</li>
</ul>
</div>