Management of CSS, Javascript and their naming conventions in MVC 3 - javascript

Recently I am building a project heavily relies on javascript/jquery. When some of the partial views are rendered, they need to apply some javascripts or CSS styles. Do you have any ideas on an efficient/effective way to manage those files?

I found a need to do this in my own project, because I wanted to be able to have a separate javascript\css file for each view.
What I ended up doing was creating a controller to aggregate the files on the server for me and then send only one css\js file to the browser. The answer is perhaps more intricate than you requested though, so I'll recommend the first part.
You can make an extension method that you can call at the top of each page to add a JS file to a list in the TempData dictionary for the request. You then call a separate method from the Layout that will render any additional links.
This works because the TempData is kept just for the request, and the layout's View is rendered last (after all the views and partials run).
I have a full example of a class here: http://pastebin.com/EUC2fAca but i'm also forming links to my aggregator, so you'll need to modify. The gist is as follows:
public static string JSKey = "pageJSList";
private static void AddToDictionary(HtmlHelper helper, string[] files, string key)
{
if (files == null || files.Length == 0)
return;
TempDataDictionary dict = helper.ViewContext.TempData;
if (!dict.ContainsKey(key))
dict.Add(key, new List<string>());
(dict[key] as List<string>).AddRange(files);
}
private static void InsertToDictionary(HtmlHelper helper, string[] files, string key)
{
if (files == null || files.Length == 0)
return;
TempDataDictionary dict = helper.ViewContext.TempData;
if (!dict.ContainsKey(key))
dict.Add(key, new List<string>());
(dict[key] as List<string>).InsertRange(0, files);
}
public static void AddJS(this HtmlHelper helper, params string[] files)
{
AddToDictionary(helper, files, JSKey);
}
public static void AddJSToTop(this HtmlHelper helper, params string[] files)
{
InsertToDictionary(helper, files, JSKey);
}
public static MvcHtmlString GetJsTagHtml(HtmlHelper helper)
{
var files = helper.ViewContext.TempData[JSKey] as List<string>;
StringBuilder tags = new StringBuilder();
string jsTemplate = "<script type=\"text/javascript\" src=\"/Scripts/{0}\"></script>";
foreach (string file in files)
{
tags.AppendLine(String.Format(jsTemplate, file));
}
return MvcHtmlString.Create(tags.ToString());
}
You'll want the Insert method to run it on the layout because, again, it runs last so you'll want to Insert jquery libraries or other dependencies that should be first on the list.
Your GetJS method should probably return an MvcHtmlString that contains all of the tags you need.
Hope that helps and wasn't too long winded =)

Management of CSS files and JavaScript files and almost any other file depends on your categorization favorites, or in other terms, your taxonomy. I think the better question is, what guidelines should I follow, to make my project become more successful. For example, however you manage and categorize your JavaScript files, try to not have many of'em in a page, as each of'em make a separate HTTP request to the server. Or, try to be as consistent in naming as possible, so that, in future you won't get puzzled by your code. For CSS and JavaScript, I suggest LDSW.

Use webpack to manage your files. A simple webpack setup could look like
# /webpack.config.js
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const postcssPresetEnv = require("postcss-preset-env");
module.exports = (env, argv) => {
const devMode = argv.mode !== "production";
return {
mode: argv.mode,
entry: {
// Main / Shared
'main': './Content/styles/main.scss',
'theme': './Content/styles/theme.scss',
// Pages
'page-home': './Content/styles/pages/home/home.scss',
'page-register': './Content/styles/pages/register/register.scss',
// If we import _register-forms.scss at the top of register.scss,
// there is no need to include it here. That has nothing to do with webpack.
// Components / Partials
'component-search': './Content/styles/components/search/search.scss',
// Javscripts
'client': './Content/client/client.js',
'home': './Content/client/pages/home/index.js',
'component-search': './Content/client/components/search/search.js',
// Got more partial files? Just import { Helper } from './_search-helpers.js' inside
// search.js, and the code will be included.
},
resolve: {
alias: {
// Setup optional aliases
pages: path.resolve(__dirname, 'Content/styles/pages'),
components: path.resolve(__dirname, 'Content/styles/components'),
}
},
output: {
path: path.resolve(__dirname, "Content/dist"),
publicPath: "/css",
filename: devMode ? "js/[name].js" : "js/[name].min.js"
},
... more webpack stuff here.
Then, you don't need to reference all of your scss files in webpack. For example, now you can just import smaller files with this:
# /Content/styles/pages/register/register.scss
#import './_register-form';
#import './_register-layout';
You don't have to include them in your webpack.config.js, because they are pulled into register.scss with the scss #import.
And likewise, in your javascript files you can just import what you need.
# /Content/client/components/search/search.js
import { SearchHelpers } from '_search-helpers';
search.js will now contain everything from _search-helpers.js.
To me, this seems like a logical structure for front-end code in an MVC project.

Related

module.export in typescript. import json list

It seems simple, this operates in javascript, but I'm a bit confused about typescript's closest equivalent. I'm trying to use module.exports in the way I know from javascript, passing data the json list data between 3 files.
in javascript the main file basically works as this: -
main.js :
const { mainnet: addresses } = require("./addresses");
const token0 = addresses.tokens.busd;
so, main.ts would be? (i believe main issue is here):
import { mainnet } from "./addresses/index";
token0 = mainnet.tokens.busd;
then typescript index.ts in ./address/index.ts (i believe this functions properly):
import tokensMainnet from './tokens-mainnet.json';
declare var module: any;
// "allowSyntheticDefaultImports" = true
module.exports = {
mainnet: {
tokens: tokensMainnet
}
};
and tokensmainnet.json
{
"busd": "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56",
"wbnb": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"
}
i can see from the metadata it's functioning:
so I believe the main problem with with importing this module in the main.ts
I've grazed over some sources such as with no luck https://www.typescriptlang.org/docs/handbook/modules.html
in typescript
add
"resolveJsonModule":true
to package.json:
" Allows importing modules with a ‘.json’ extension, which is a common practice in node projects. This includes generating a type for the import based on the static JSON shape." -https://www.typescriptlang.org/tsconfig#resolveJsonModule
so add to main.ts:
import * as data from "./addresses/tokens-mainnet.json"
//...program code...
constructor(){
let tokenAddress = data
console.log(tokenAddress.busd)
}
:)

Vue 3: defineAsyncComponent not resolving .vue files and not splitting chunks

I'm trying to load Vue 3 components in a asynchronous way. I've found that there is a function called
defineAsyncComponent
which is supposed to be used as follows:
const GameUI = defineAsyncComponent(()=>import(filePath));
app.component("GameUI", GameUI);
filePath in this context is exactly: './components/GameUI/GameUI.element.vue'
Running the app like this leads to the following error:
Uncaught (in promise) Error: Cannot find module './components/GameUI/GameUI.element.vue'
at eval (eval at ./src lazy recursive)
But... if I change the filePath code to import the path as a string:
const GameUI = defineAsyncComponent(()=>import('./components/GameUI/GameUI.element.vue'));
The app works, it does find the component.
I don't want to use constant strings, because I have a lot of components and I want to load them asynchronously.
One of my main goals to achieve this, is to load the webapp by parts, component by component whenever they are needed, instead of loading them all on start.
I've also find that if I append a comment as follows:
const GameUI = defineAsyncComponent(()=>import(/* webpackChunkName: "GameUI" */ './components/GameUI/GameUI.element.vue'));
The JavaScript part for the GameUI component, should have it's own chunk.js file, but I always keep getting everything in a couple .js chunk files, which contradicts the async loading I want to achieve.
I'm using vue-cli-service, and my vue.config.js looks like this:
module.exports = {
productionSourceMap: false,
css: {
loaderOptions: {
sass: {
additionalData: process.env.NODE_ENV === 'production'
? '$baseURL:"/dist/";'
: '$baseURL:"/";'
}
}
},
publicPath: process.env.NODE_ENV === 'production'
? '/dist/'
: '/',
devServer: {
https: true,
port:"",
host:'website.com',
disableHostCheck: true,
cert: (fs.readFileSync('cert.pem')+""),
key: (fs.readFileSync('privkey.pem')+""),
ca: (fs.readFileSync('ca.pem')+""),
}
};
I've already tried multiple stuff I've found online, but they are not that much explanatory. I'm literally doing the same as some online articles I've found and cannot find the problem on my side.
The two main problems are:
Cannot load .vue files from variables, only full strings.
Cannot split the code into different .js files for every async loaded component.
The answer to the first question:
Because there have some limitations of async import
.
What you are doing cannot work because you are using a variable as value to defineAsyncComponent.
According to the limitations of async import, you cannot import your component usinig a variable. Instead what you can do is:
// If the component name to call is GameUI.element
const component = 'GameUI.element' // can be comed from anyting
const GameUI = defineAsyncComponent(()=>import(`./components/GameUI/${component}.vue`));
app.component("GameUI", GameUI);

Reference relative path inside JS config file in MVC

I'm trying to incorporate this scanning software into an application.
The folder which contains all the necessary .js, .css and binary files is called Resources.
In my MVC app - I have placed the Resources file inside my Scripts folder.
In my .cshtml, I have the following:
#section scripts {
<script src="~/Scripts/Resources/dynamsoft.webtwain.config.js"></script>
<script src="~/Scripts/Resources/dynamsoft.webtwain.initiate.js"></script>
}
Which loads the scripts successfully.
The issue I'm facing is the scripts themselves reference relative paths within the Resources folder.
In dynamsoft.webtwain.config.js - you can set the path to the resources folder - I have mine set to the following:
Dynamsoft.WebTwainEnv.ResourcesPath = '~/Scripts/Resources';
However when the page loads - I'm receiving 404 errors for some of the files because it's trying to literally interpret the path:
I have also tried the following but with no luck:
Dynamsoft.WebTwainEnv.ResourcesPath = '#Url.Content("~/Scripts/Resources")';
As far as I know you can't use relative paths starting with tilde (~) in separate JS files because #Url.Content() helper and ASP.NET relative paths only work inside Razor view page, but you can pass the relative path by creating root path in JS global scope (i.e. Razor view page's <script> tag) like this:
<script>
var baseUrl = '#Url.Content("~")';
</script>
Then you can include the path inside JS files using that variable:
// custom JS file
if (typeof baseUrl !== 'undefined') {
Dynamsoft.WebTwainEnv.ResourcesPath = baseUrl + '/Scripts/Resources';
}
Or simply mentioning full path & pass it:
#* Razor page *#
<script>
var resourcesPath = '#Url.Content("~/Scripts/Resources")';
</script>
// custom JS file
if (typeof resourcesPath !== 'undefined') {
Dynamsoft.WebTwainEnv.ResourcesPath = resourcesPath;
}
Another alternative is using custom JS view engine together with file handler for JS scripts like example below:
// custom JS engine
public class CustomJSEngine : BuildManagerViewEngine
{
public CustomJSEngine()
{
ViewLocationFormats = new[]
{
"~/Scripts/{0}.js",
"~/Scripts/Resources/{0}.js"
};
FileExtensions = new[]
{
"js"
};
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
var view = new RazorView(controllerContext, viewPath,
layoutPath: masterPath, runViewStartPages: true, viewStartFileExtensions: FileExtensions,
viewPageActivator: ViewPageActivator);
return view;
}
}
// put these lines below inside Application_Start()
RazorCodeLanguage.Languages.Add("js", new CSharpRazorCodeLanguage());
ViewEngines.Engines.Add(new CustomJSEngine());
// add this line if necessary
WebPageHttpHandler.RegisterExtension(".js");
References:
#Url.Content in separate javascript file using ASPNET MVC 3 and Razor
Returning razor-parsed Javascript as a ViewResult from a controller

Workaround to dynamic includes in Pug/Jade

I understand that Pug does not support dynamic includes or extends in templates. Ie
extend path/to/template
works but not
extend #{dynamic_path_to_template}
Is there a workaround (however convoluted) that will allow the same goal of modifying the template used by a view at runtime
Context: My use case is that I am developing an npm module and the template being used to extend other views is located inside the module. After the module is published and installed, the path will be defined (ie. node_modules/my_module/path/to/template) but during the development phase, I need to just be able to "npm link" to the module and have the templates work. I also would prefer not to hard code the links so I can publish the same code as tested.
I had this issue aswell and found this question while searching for a solution. My solution is similar to Nikolay Schambergs Answer, but i thought i should share it.
I've created a function that renders templates by giving it a path and passed it to the options object. Maybe it helps in your case aswell
const includeFunc = (pathToPug, options = {}) => {
return pug.renderFile(pathToPug, options); //render the pug file
}
const html = pug.renderFile('template.pug', {include: includeFunc});
and then use it as followed in your template:
body
h1 Hello World
|!{include(dynamicPugFilePathFromVariable)}
There is no way to do this for now, but you can work out your application architecture without dynamic extends.
Possible solution #1
Make a layout.jade that conditionally include multiple layouts:
layout.jade:
if conditionalVariable
include firstLayout.jade
else
include otherLayout
In your view, extend layout.jade, and define conditionalVariable in the controller (true/false):
view.jade:
extends layout
block content
p here goes my content!
Possible solution #2
Pass configurations to the layout
- var lang = req.getLocale();
doctype html
block modifyLayout
split the project into multiple entrances, each entrance extends the layout and passes its different configs, and includes different things in different blocks
extends ../layout
block modifyLayout
- var lang = "en" //force language to be en in this page.
block body
include my-page-body
Possible solution #3
use something like terraform which uses pug as its rendering engine, but it enables you to use dynamic partials like this
!= partial(dynamicFileFromVariable)
It works!
First, set res.locals middleware.
middlewares/setResLocals.js
const pug = require('pug')
const path = require('path')
module.exports = function(req, res, next) {
res.locals.include = (pathToPug, options = {}) => { // used for imitate includ pug function
return pug.renderFile(pathToPug, options); //render the pug file
}
res.locals.__PATH__ = path.join(__dirname, '../')
next()
}
server/index.js
app.use(require('../middlewares/setResLocals'))
file.pug
|!{include(`${__PATH__}/${something}`)}
In order to do dynamic include, you will have to use Unescaped String Interpolation, inserting pug contents that are pre-compiled before your main .pug file inside your route. In other words it works as follows:
1) Some .pug files are pre-compiled into HTML
2) The HTML gets fed into another .pug file compilation process
Here's an example how to do it
Inside your router file (routes.js or whatever)
var pug = require('pug')
var html = []
var files = ['file1','file2'] // file names in your views folders
let dir = path.resolve(path.dirname(require.main.filename) + `/app/server/views/`)
//dir is the folder with your templates
app.get('/some-route', (req,res) => {
for (let n = 0; n < files.length; n++) {
let file = path.resolve(dir + '/' + files[n] + `.pug`)
fs.access(file, fs.constants.F_OK, (err) => {
if (!err) {
html.push(pug.renderFile(file, data))
if (n === files.length - 1) {
res.render('dashboard', {html})
}
}
else {
res.status(500).json({code:500,status:"error", error:"system-error"})
}
})
}
})
Inside your desired .pug file:
for item in html
.
!{item}
The example above is specific to my own use case, but it should be easy enough to adapt it.
I know, this is a bit late for answering. But I found a possibility suitable for my purpose by this bit of information from the pug docs:
If the path is absolute (e.g., include /root.pug), it is resolved by
prepending options.basedir. Otherwise, paths are resolved relative to
the current file being compiled.
So, I provide most of my pug modules by relative paths and the stuff I want to exchange dynamically is organised in pug files of the same name but in different folders (think theme) and include them by absolute paths . Then I change the basedir option to dynamically choose a set of pug files (like choosing the theme).
May this help others, too.

Webpack plugin to attach static JSON object to output

I am trying to write a webpack plugin that will go into a directory containing html files, open each, remove new lines, and generate an object to attach as a static property to my output file (which is a var).
The object would look something like:
{
htmlFile1: '<p>some html one!</p>',
htmlFile2: '<span>some html two!</span>
}
Then I would like it to be attached to my output like:
App.templates = { .... }
The creation of the object is done. I'm just struggling with webpack and figuring out how to attach it to the main object. Should I just write it to disk and require it? Or is there a way to add it to the bundle via the plugin?
I'm using Rivets.js as a template engine and I have not been able to find anything out there that does something like this already.
Also worth noting, all I'm using is webpack and npm. No Grunt/Gulp/etc
Thanks so much!
Mike
You could use webpack's require.context to import all html files in a directory as text, process the text, and export the results as a single object:
const requireTemplate = require.context('./templates', false, /\.html$/);
module.exports = requireTemplate.keys().reduce((templateMap, templatePath) => {
const templateName = templatePath.match(/\/(\w*?)\.html/)[1]; // get filename without path and extention
templateMap[templateName] = require(templatePath);
return templateMap;
}, {});

Categories