Assemble: register handlebar helper function - handlebars.js

I'm using assemble 0.4.17 which has bundled handlebar 1.3.0.
I'm trying to add a custom handlebar helper as documented here.
So I added this to my Gruntfile (at the bottom of the file, outside of module.exports = function(grunt) {)
Gruntfile.js
module.exports.asdf = function (str) { return 'asdf here!'; };
And added this to
index.hbs
{{#asdf}}
bobo
{{/asdf}}
And I would suggest that asdf here! would show up in the generated html, but it does not, instead only a blank line is printed. I also tried the module.exports.register = function (Handlebars, options) method, but this didn't work as well. Do I need to add something else to add this handlebar helper?
I'm new to Assemble and grunt and handlebar, so I might just be missing the obvious

The helpers should be declared in another file and added to the helpers option in your assemble target:
my-helper.js
module.exports.asdf = function (str) { return 'asdf here!'; };
Gruntfile.js
grunt.initConfig({
assemble: {
options: {
helpers: ['./my-helper.js']
},
someTarget: {
...
}
}
});

Related

How to stop Webpack wrapping my JS in an IIFE so I can call my functions in .cshtml views

I've recently added WebPack 5 to my build process in my .NET Core MVC 7 application.
My goal is to be able to call my javascript functions from the javascript files WebPack generates inside my views.
I have a simple Index.cshtml file that includes a partial view and the generated javascript from webpack:
Index.cshtml
<div>
#await Html.PartialAsync("SettingsTab")
</div>
#section Scripts
{
<script defer src="~/dist/settings.entry.js"></script>
}
The SettingsTab constains a button that is trying to trigger a method from the settings.entry.js file:
SettingsTab.cshtml
<div>
<button type="button" onclick="saveProfileSettings()">Save Profile</button>
</div>
The settings.js file before webpack bundles it into the dist folder looks like this:
settings.js
import ('../css/settings.css')
function saveProfileSettings() {
// do stuff
}
When I wasn't using webpack I could directly call this function like I am trying to above.
However now when I reference the bundled js file it cannot call it.
Looking at the end of the settings.entry.js file it looks like webpack has bundled my code into an IIFE:
End of settings.entry.js
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be in strict mode.
(() => {
"use strict";
// omitted contents of settings.js
})();
/******/ })()
;
I would like to instruct WebPack not to wrap my code like this so I can directly call the functions elsewhere.
Now I did find out that I can use library in my webpack.config.js like so:
output: {
filename: "[name].entry.js",
path: path.resolve('wwwroot', 'dist'),
publicPath: "/dist/",
library: ['WebPack', '[name]']
},
With this I can successfully reference my functions using WebPack.settings.saveProfileSettings() but I do not want to do this. I want to be able to reference my functions directly as saveProfileSettings().
How can I change how WebPack packs my code so I can do this?

Apply global variable to Vuejs

I have a javascript variable which I want to pass globally to Vue components upon instantiation thus either each registered component has it as a property or it can be accessed globally.
Note:: I need to set this global variable for vuejs as a READ ONLY property
Just Adding Instance Properties
vue2
For example, all components can access a global appName, you just write one line code:
Vue.prototype.$appName = 'My App'
Define that in your app.js file and IF you use the $ sign be sure to use it in your template as well: {{ $appName }}
vue3
app.config.globalProperties.$http = axios.create({ /* ... */ })
$ isn't magic, it's a convention Vue uses for properties that are available to all instances.
Alternatively, you can write a plugin that includes all global methods or properties. See the other answers as well and find the solution that suits best to your requirements (mixin, store, ...)
You can use a Global Mixin to affect every Vue instance. You can add data to this mixin, making a value/values available to all vue components.
To make that value Read Only, you can use the method described in this Stack Overflow answer.
Here is an example:
// This is a global mixin, it is applied to every vue instance.
// Mixins must be instantiated *before* your call to new Vue(...)
Vue.mixin({
data: function() {
return {
get globalReadOnlyProperty() {
return "Can't change me!";
}
}
}
})
Vue.component('child', {
template: "<div>In Child: {{globalReadOnlyProperty}}</div>"
});
new Vue({
el: '#app',
created: function() {
this.globalReadOnlyProperty = "This won't change it";
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
In Root: {{globalReadOnlyProperty}}
<child></child>
</div>
In VueJS 3 with createApp() you can use app.config.globalProperties
Like this:
const app = createApp(App);
app.config.globalProperties.foo = 'bar';
app.use(store).use(router).mount('#app');
and call your variable like this:
app.component('child-component', {
mounted() {
console.log(this.foo) // 'bar'
}
})
doc: https://v3.vuejs.org/api/application-config.html#warnhandler
If your data is reactive, you may want to use VueX.
You can use mixin and change var in something like this.
// This is a global mixin, it is applied to every vue instance
Vue.mixin({
data: function() {
return {
globalVar:'global'
}
}
})
Vue.component('child', {
template: "<div>In Child: {{globalVar}}</div>"
});
new Vue({
el: '#app',
created: function() {
this.globalVar = "It's will change global var";
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
In Root: {{globalVar}}
<child></child>
</div>
If the global variable should not be written to by anything, including Vuejs, you can use Object.freeze to freeze your object. Adding it to Vue's viewmodel won't unfreeze it.
Another option is to provide Vuejs with a frozen copy of the object, if the object is intended to be written globally but just not by Vue: var frozenCopy = Object.freeze(Object.assign({}, globalObject))
you can use Vuex to handle all your global data
In your main.js file, you have to import Vue like this :
import Vue from 'vue'
Then you have to declare your global variable in the main.js file like this :
Vue.prototype.$actionButton = 'Not Approved'
If you want to change the value of the global variable from another component, you can do it like this :
Vue.prototype.$actionButton = 'approved'
https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html#Base-Example
If you’d like to use a variable in many components, but you don’t want to pollute the global scope. In these cases, you can make them available to each Vue instance by defining them on the Vue prototype:
Vue.prototype.$yourVariable = 'Your Variable'
Please remember to add this line before creating your Vue instance in your project entry point, most of time it's main.js
Now $yourVariable is available on all Vue instances, even before creation. If we run:
new Vue({
beforeCreate: function() {
console.log(this.$yourVariable)
}
})
Then "Your Variable" will be logged to the console!
doc: https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html#Base-Example
If you want to make this variable immutable, you can use the static method Object.defineProperty():
Object.defineProperty(Vue.prototype, '$yourVariable', {
get() {
return "Your immutable variable"
}
})
This method by default will prevent your variable from being removed or replaced from the Vue prototype
If you want to take it a step further, let's say your variable is an object, and you don't want any changes applied to your object, you can use Object.freeze():
Object.defineProperty(Vue.prototype, '$yourVariable', {
get() {
return Object.freeze(yourGlobalImmutableObject)
}
})
A possibility is to declare the variable at the index.html because it is really global. It can be done adding a javascript method to return the value of the variable, and it will be READ ONLY. I did like that:
Supposing that I have 2 global variables (var1 and var2). Just add to the index.html header this code:
<script>
function getVar1() {
return 123;
}
function getVar2() {
return 456;
}
function getGlobal(varName) {
switch (varName) {
case 'var1': return 123;
case 'var2': return 456;
// ...
default: return 'unknown'
}
}
</script>
It's possible to do a method for each variable or use one single method with a parameter.
This solution works between different vuejs mixins, it a really global value.
in main.js (or any other js file)
export const variale ='someting'
in app.vue (or any other component)
import {key} from '../main.js' (file location)
define the key to a variable in data method and use it.
Simply define it in vite configuration
export default defineConfig({
root:'/var/www/html/a1.biz/admin',
define: {
appSubURL: JSON.stringify('/admin')
}, ..../// your other configurations
});
Now appSubURL will be accessible everywhere

"moment is undefined" when launching angular app in node-webkit

I want to launch my angular application which works in general, but when I get to use moment I got the error that "moment" is undefined.
I am using "angular-moment" from here
var app = angular.module("MyApp",
[
"ngRoute",
"ui.bootstrap",
"angularMoment",
'angular-jwt',
'angular-storage'
]);
My package.json looks like this
{
"name": "myapp",
"main": "index.html",
"toolbar":"true",
"dependencies": {
"moment": "*"
}
}
I am trying to use it with
app.config(function (moment) {
moment().format();
});
which says that moment ist undefined.
How do I have to modify my package.json to get node-webkit find moment? Or Angular-Moment?
Thanks in advance.
Make sure you have both moment and angular-moment loading in your HTML file.
Follow the instructions on the angular-moment github page. I don't think moment().format(); is valid because moment should not be a function..
Also try including "node-remote": "<local>" in your package.json file.
I encountered the same problem, I use this code snippet to solve it. You should replace vendor.js with your own files suce as angular, moment.
<script>
//hide global object
try {
window.globalTmp = global;
global = undefined;
} catch (e) {}
</script>
<script src="vendor.js"></script>
<script>
//recover global object
try {
global = window.globalTmp;
window.globalTmp = undefined;
} catch (e) {}
</script>
moment is undefined because it's added to global other than window. global is an object of node-webkit.If you type global in console, you will find global.moment in output.
I found this snippet in moment's source code which can support my explain.
var moment,
VERSION = '2.8.4',
// the global-scope this is NOT the global object in Node.js
globalScope = typeof global !== 'undefined' ? global : this,

Use wiredep to inject custom CSS

I need to inject custom CSS files everytime they are created after being compiled by gulp-less. So I tried to use wiredep with custom configurations, but without success.
I changed the tag from 'bower:css' to 'custom:css', specifically to my custom task. The bower:css for default wiredep injection is still there. But after run myinjection task nothing is injected even running the task without errors.
Another weird thing, when I run my task, the files injected by wiredep (the default) disappear.
What I'm missing?
Basicaly my files structure is like this:
|---app
|---styles
|---***
*.css
*.html
.custom.json
I'm not sure if I really need a file similar to bower.json, but I made may own custom.json
{
"name": "custom",
"version": "0.0.1",
"main": [
"app/styles/**/*.css",
"app/scripts/**/*.js //pretend to inject custom js later
]
}
The task in gulpfile.js is like this:
gulp.task('myinject', function () {
var myinject = require('wiredep').stream;
var combined = Combine (
gulp.src('app/*.html'),
myinject({
directory: 'app',
bowerJson: require('./custom.json'),
dependencies: false,
html:
{
block: /(([ \t]*)<!--\s*custom:*(\S*)\s*-->)(\n|\r|.)*?(<!--\s*endcustom\s*-->)/gi,
detect: {
js: /<script.*src=['"](.+)['"]>/gi,
css: /<link.*href=['"](.+)['"]/gi
},
replace: {
js: '<script src="{{filePath}}"></script>',
css: '<link rel="stylesheet" href="{{filePath}}" />'
}
}
}),
gulp.dest('app')
);
combined.on('error', function(err) {
console.warn(err.message);
});
return combined;
});
Thanks in advance
I had a similar issue that I resolved with gulp-inject rather than wiredep.
Wiredep works fine with me when I need to include a third-party dependency (e.g. a bower package with a valid main file declared in the bower.json or a file with the same name as the directory).
However, when you want to include only a specific file (e.g. only the main.css of html5-boilerplate) gulp-inject is much simpler. Here is an extract of the doc :
gulp.task('index', function () {
var target = gulp.src('./src/index.html');
var sources = gulp.src(['./src/**/*.js', './src/**/*.css'], {read: false});
return target.pipe(inject(sources))
.pipe(gulp.dest('./src'));
});

Can I instruct Grunt to concat all JS files defined in index.html?

Can I instruct Grunt to concatenate all JS files defined in
index.html without specifically naming them?
Can Grunt also create new index.html file that will load the concatenated JS file instead
of the previous multiple files?
Can Grunt also uglify the JS file at a same time?
Can Grunt do this not only for JS files but also CSS files used in a given html file?
I spent significant time googling but the Grunt ecosystem seems to be so fragmented and so unfamiliar to me :(.
PS: I have decided to use Grunt because there is direct integration in WebStorm 8 but maybe other tool would be more suitable for this task?
There are many different solutions available which is why it seems fragmented. I'll describe a couple of the seemingly popular methods.
Use grunt-usemin
You specify blocks within your HTML that it reads and feeds to your other Grunt tasks (concat, uglify, etc). Their docs have extensive examples to handle a lot of different scenarios.
Use a module bundler such as grunt-webpack, grunt-browserify or grunt-contrib-requirejs
Instead of adding script tags to your HTML, use a require() syntax to include files when needed. Which, depending on the method, will add the scripts to your page or bundle into a single file. These methods only require including, usually, a single javascript file.
Explore and figure out which solution makes the most sense for your needs.
I solved this problem by adding this function at the top of my Gruntfile:
var isCssRegex = /^\s*<\s*link.*href=["']([^"']*)["'].*$/i;
var isJsRegex = /^\s*<\s*script.*src=["']([^"']*)["'].*$/i;
var extractJsRegex = /src\s*=\s*"(.+?)"/
var extractCssRegex = /href\s*=\s*"(.+?)"/
function extractFilenames(src, type) {
var filenames = [];
var data = require('fs').readFileSync(src, 'utf8');
var lines = data.replace(/\r\n/g, '\n').split(/\n/);
var webContent = require('path').dirname(src);
lines.forEach(function (line) {
if (line.match(type === 'css' ? isCssRegex : isJsRegex)) {
var src = line.match(type === 'css' ? extractCssRegex : extractJsRegex)[1];
filenames.push(webContent + '/' + src);
}
});
return filenames;
};
Then in my concat task, I can do this:
concat: {
js: {
src: extractFilenames('src/main/resources/public/index.html', 'js'),
dest: 'build/app.js'
},
css: {
src: extractFilenames('src/main/resources/public/index.html', 'css'),
dest: 'build/style.css'
}
},

Resources