An approach to handling long lists in Vue.js (2.x) (hint: Vanilla JavaScript)

Vue.js is an excellent front-end web application framework and my framework of choice.

However, one area where it falls down is rendering long lists. Typically, I experience issues when a list has over 1,500 times.

My understanding is the problem revolves primarily around reactivity of properties and how that impacts rendering. I can’t remember the technical details but a Google/Bing search for “vue.js render long lists” will soon provide StackOverflow answers that explain it better than I can.

I also get the impression that long lists is not a common problem in most web applications. Unfortunately, my development for Lotus has me needing to render a few very long lists for back-end reporting and debugging. A typical timesheet (the core business of Lotus) can include over 2,000 (and sometimes up to 4,000) data records which need to be rendered in a single, continuous (un-paginated) scroll list for analysis.
And we now have a couple of internal reports that can include up to 10,000 items.

Ideally some of these reports will be eventually re-architected to include either pagination or better sub-filters. For now it’s a matter just making them available for viewing and download, so the long-list problem is one I need to contend with.

 

My Approach: Vanilla JavaScript

Earlier attempts to remove Vue.js reactivity via Object.freeze() (after following per blog and StackOverflow suggestions) didn’t seem to improve performance. I don’t know why, and I don’t have time to dig into it.

So I came up with a simpler solution that simply cut out Vue altogether: I used plain old vanilla JavaScript.

Now, an important factor to remember is: once I’ve rendered a list, I don’t need to update it again. So reactivity and and event binding are not a concern for me in these situations.
Well, that’s not quite true. In once instance I do need to update a CSS class based on a click event in a table row, but that too was handled easily with Vanilla JS (explained below).

Rendering is super simple.

1. Create a target element with an ID in your app/component, like this: <div id="vanillaJsRender"></div>

2. Load your data in your Vue app/component as normal, then create a Vue component method to call after the data is loaded, that iterates over the data, generates a string with your HTML to render, like the following simple example:

var html = "<ul>";
for (var i = 0; i < data.length; i++)
{
    html = html + "<li class='row-" + data[i].ID + "' onClick='onReportResultsRowSelectVanillaJs(" + data[i].ID+ ")'>" + data[i].MyValueToDisplay + "</li>";
}
html = html + "</ul>";

3. The insert the HTML into the target element like so: document.getElementById("vanillaJsRender").innerHTML = html;

It’s not pretty, but it’s simple and it works. And render is much, much, much faster than a Vue v-for loop.

Something I’ve noted with this approach is scrolling 10,000 records with of rendered HTML still causes some level of lag in the browser, though to be expected, and it’s manageable and not at bad as the Vue-based alternative.

 

Binding click events/updating CSS classes

Note the following class='row-" + data[i].ID + "' onClick='onReportResultsRowSelectVanillaJs(" + data[i].ID+ ")' added to the <li> in the sample above.

If I want to apply or remove a CSS class to a row in the HTML rendered outside of Vue, I just need a function like the following within the standard <script> section of our Vue app or component.

//-- Vanilla JS onClick handler
window.onReportResultsRowSelectVanillaJs = function(id)
{
    // Vanilla JavaScript to add/remove the `selected` class on the row
    var row = document.querySelector('.row-' + rowId);

    if (row.classList.contains("selected"))
    {
        row.classList.remove("selected");
    }
    else
    {
        row.classList.add("selected");
    }
}

Again, we’re using Vanilla JavaScript to manipulate the DOM and it works fine alongside Vue.

Note the window in window.onReportResultsRowSelectVanillaJs. We can’t simply define a function onReportResultsRowSelectVanillaJs() { ... } because the scope is not global. We need a globally scoped function (hence the attaching to window) for it to be accessible via onClick.

Just remember to name the function something that won’t cause potential naming conflicts in the global namespace.

I could have bound the function to the target element via a DOM addEventListener() method, but the way I did it was simple and it works perfectly well for my situation.

 

Another Approach: Server-Side Rendering

Before trying the above JavaScript approach I also tried generating the HTML a server-side.

To be clear, the app is a very clearly separated HTML front-end that is driven entirely by Vue.js, and backend that is pure API and ordinarily performs no HTML rendering (e.g. it’s not an MVC web application).

So in my server-side rendering approach, the API that is called would normally return a set of structure data but instead returns a string that is the HTML to be injected into the page. Again, the injection is performed via document.getElementById("vanillaJsRender").innerHTML = html.

Why take this approach? I come from an old-school web application background where all HTML used to be server side rendered, so this is always an option. And recently I’ve been revisiting blogs and talks by DHH (David Heinemeier Hansson) and has reminded that rendering on the server is often a fuck load faster than in the browser.

Also, why not?

 

Conclusion

There’s no profound conclusion. I simply took other experiences and thought a little ways out of the box (the Vue box) to solve a problem.

The solutions may not be elegant to some people but they’re simple, they work, and they solve the problem, which at the end of the day is what we’re all about as software developers.

Adding Rollbar to ASP.NET Core 2+: “Some services are not able to be constructed” and “Unable to resolve service for type ‘Microsoft.AspNetCore.Http.IHttpContextAccessor'”

I’m adding Rollbar to a new ASP.NET Core 5 (which is Core 3+) API and following the guide at https://docs.rollbar.com/docs/integrating-with-aspnet-core-2-and-newer.

After adding the required code I received the following exception when starting the API:

System.AggregateException
HResult=0x80131500
Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Rollbar.NetCore.AspNet.RollbarLoggerProvider Lifetime: Singleton ImplementationType: Rollbar.NetCore.AspNet.RollbarLoggerProvider': Unable to resolve service for type 'Microsoft.AspNetCore.Http.IHttpContextAccessor' while attempting to activate 'Rollbar.NetCore.AspNet.RollbarLoggerProvider'.)
Source=Microsoft.Extensions.DependencyInjection
StackTrace:
at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, IServiceProviderEngine engine, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
at Lotus.API.Integration.Program.Main(String[] args) in D:\WorkspacesLotusAI\Management\Lotus.API.Integration\Program.cs:line 16

This exception was originally thrown at this call stack:
[External Code]

Inner Exception 1:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: Rollbar.NetCore.AspNet.RollbarLoggerProvider Lifetime: Singleton ImplementationType: Rollbar.NetCore.AspNet.RollbarLoggerProvider': Unable to resolve service for type 'Microsoft.AspNetCore.Http.IHttpContextAccessor' while attempting to activate 'Rollbar.NetCore.AspNet.RollbarLoggerProvider'.

Inner Exception 2:
InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Http.IHttpContextAccessor' while attempting to activate 'Rollbar.NetCore.AspNet.RollbarLoggerProvider'.

A search for “Unable to resolve service for type ‘Microsoft.AspNetCore.Http.IHttpContextAccessor’ while attempting to activate ‘Rollbar.NetCore.AspNet.RollbarLoggerProvider’” lead me to answers at https://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc.

The accepted answer revealed “IHttpContextAccessor is no longer wired up by default, you have to register it yourself” with a sample services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>(); to be added in the ConfigureServices()  method of the StartUp class. Another answer points out “as of .NET Core 2.1 there is an extension method” that has been added to do the same thing: services.AddHttpContextAccessor();.

 

This is also yet another example of the craziness and breaking changes between the different versions of .NET Core. And while I’m thankful I’m only now making the jump from .NET Framework to .NET Core/5, it well highlights the difficulties developers face when entering the industry and looking up documentation, or when finally make a step up from earlier version – i.e. it looks like even steps from Core 2 to 2.1, and .NET 2 to 3 will give developers some unexpected surprises.

Swagger error in ASP.NET Core 5 MVC API Web App (MVC)

I created an ASP.NET Core 5 Web App for “API” (similar to MVC) with the OpenAPI (aka Swagger) option turned on.

To note: I have nearly 2 decades experience with .NET but this is my first real-world dive into .NET Core/5, so I’m skipping the whole “learning from first principals” and figuring it out as I go off the back of what I already know.
In this case I’m setting up a new project to move some part of an existing ASP.NET 4.6 MVC app into it.

It was all working fine until I wanted to add an OnActionExecuting() override to the Controller, from the IActionFilter interface.
Once I added the interface and the 2 required method the calls to the actual controller, calling the endpoints worked fine. But F5 debug runs of the project, which loaded the https://localhost:xxxxx/swagger/index.html page, would produce the error:

Failed to load API definition.

Errors

Fetch errorundefined /swagger/v1/swagger.json

 

Which in typical 3rd party framework fashion is fucking useless for consuming and gives use no useful information to work with.

So off to StackOverflow I go.

After a search for OnActionExecuting Fetch errorundefined /swagger/v1/swagger.json I came across this page: https://stackoverflow.com/questions/48450262/asp-net-core-swashbuckle-not-creating-swagger-json-file, specifically the answer https://stackoverflow.com/a/53632914/115704 which mentioned [NoAction]. Things then clicked for me.

The [NoAction] attribute on the 2 interface methods didn’t work for me, but after a bit more searching I found [NonAction] and which solved the problem.

Then I was back up and working with Swagger again.

 

 

Screenshots

Code before the fix

(Noting the [NonAction] is not impmented)

 

The error from Swagger

Honestly, how fucking useless is this an an error? About as good as the classic “an unexpected error occurred” (to which I always exclaim: “no shit, Sherlock!” Like any developer actually expects an error).

 

Code after the Fix

[NonAction] attributes have been implemented.

 

Swagger after the fix

And look, Swagger works again.

 

At the end of the day this was a couple of hours of my night lost.

Partly my fault for implementing a new version of .NET without doing the obligatory 40 hours of training. Also partly Swaggers fault because, well, it pissed me off for not giving a more detailed error message, especially in a development environment.

Parcel.js “ENOENT: no such file or directory” on build

Problem

Running Parcel.js build command (e.g. I run `npm run dev` script) to build and start the site with generates an error similar to the following:

D:\Workspaces\my-project\my-folder\my-file.vue: ENOENT: no such file or directory, open 'D:\Workspaces\my-project\my-folder\my-file.vue'
Error: ENOENT: no such file or directory, open 'D:\Workspaces\my-project\my-folder\my-file.vue'

Solution

Delete the .cache and dist folders and re-run the build command.

References

https://stackoverflow.com/questions/59196917/parcel-bundler-enoent-no-such-file-or-directory-when-delete-files-from-projec

Vue.js (2.x) v-model not updating for jQuery plugin input

Problem

I have a range slider HTML input (<input type="range">) I’ve inherited that uses the rangeslider.js jQuery plugin to render it.

I’m re-integrating it into a Vue.js (v2.x) project and using v-model to bind to the input.

However, to change the range value the user doesn’t interact directly with the HTML element but a rendered overlay which in turn changes the input value.

The problem is the v-model value was not updating when the range overlay is dragged to change the value.

 

Solution

My inherited code contained the following event handlers on the input elements.

$document.on('input', 'input[type="range"]', function(e) {
	valueOutput(e.target);
});

 

I added the following bold line to the called valueOutput() function.

function valueOutput(element) {
	element.dispatchEvent(new Event('input'));
}

The dispatchEvent ensures Vue is now aware of the change to the input.

 

I found this solution vis this StackOverflow answer: https://stackoverflow.com/a/56348565/115704 (article “How to change v-model value from JS“).

Sample of Static Header, Static Sidebar, Scrolling Content (CSS)

I’m working on what seems like a basic web layout: a static page header (stays in place when scrolling), static side menu (also stays in place) and the main column of content scrolls.

There’s a Codepen below that is “in progress” and shows it working. Bear in mind it’s not responsible yet – you need a screen >1024px wide. I’ll keep working on it to create a responsive option, so check back soon.

Something I want to point out is CSS frameworks can help with this situation. And they can hinder. I’ve taken to using the Bulma utility CSS framework lately and it’s great. But using its built in container/grid system I think (TBC) seems to be working against me. I also find myself needing to override the resets to un-reset certain typography decisions.

Sometimes it’s better to just do it yourself.

View the source code at https://codepen.io/jsnelders/pen/WNQrRbo.

See the Pen
Static Header, Static Sidebar, Scrolling Content
by Jason (@jsnelders)
on CodePen.

[Vue warn]: Error in nextTick: “NotFoundError: Failed to execute ‘insertBefore’ on ‘Node’: The node before which the new node is to be inserted is not a child of this node.

I just had to deal with the following Vue generated warning:

[Vue warn]: Error in nextTick: “NotFoundError: Failed to execute ‘insertBefore’ on ‘Node’: The node before which the new node is to be inserted is not a child of this node.”

DOMException: Failed to execute ‘insertBefore’ on ‘Node’: The node before which the new node is to be inserted is not a child of this node.

It related to the following line of code, where I was conditionally displaying a Font Awesome icon:

<i class="fas fa-spinner fa-spin" v-if="bookmarkMetaState == 'retrieving'"></i>

The problem, I believe, is the original <i> element ended up rendering as an <svg> element. So the error makes sense, because the the original code no longer existed.

I simply wrapped my my <i> Front Awesome icon in a <span>, and applied the v-if to the <span>. Problem solved.

 

 

 

Is data returned by JavaScript (ES6) array.filter() immutable?

Is data returned by JavaScript (ES6) array.filter() immutable? It depends.

If the original array contains “primitive” values (eg. strings, numbers and boolean) then a new array with “copies” of the values is returned in the filtered array. Changing data in the returned array will not affect the original array or its data.

If the original array contains objects then the a new array is still returned from .filter() but each element still references the object in the original array. Changing data in either the original or returned array will affect the data in both array because it’s the same piece of data being referenced by both arrays.

 

I have a Pen at https://codepen.io/jsnelders/pen/jOPeXXw demonstrating both situations.

 

See the Pen
Is data returned by JavaScript (ES6) array.filter() immutable?
by Jason (@jsnelders)
on CodePen.