Alpine Expression Error: createFormComponent is not defined - asp.net-core

I am using Alpine JS in .NET Core 6 MVC project. I have loaded the alpine js file from https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js. I then added the following code in the Home/Index.cshtml file.
<form id="myForm"
x-data="createFormComponent()"
x-on:submit.prevent="onSubmit">
<input id="email" type="text" name="email"
x-model="email" />
<span class="error" style="display: none"
x-show="error"
x-text="error"
></span>
<button type="submit">Submit</button>
</form>
#section Scripts {
<script>
(function () {
'use strict';
window.createFormComponent = function () {
return {
email: '',
error: '',
onSubmit($event) {
this.error = !this.email
? 'You must enter an email address'
: '';
},
};
};
})();
</script>
}
But when I access the page in the browser, it always throws the error "Alpine Expression Error: createFormComponent is not defined". I have tried to put it into a js file and load it. Also tried to define the component in alpine:init event. None of them worked. The same code works in plain html file served from IIS. I am not sure what is wrong here. Can anyone please help.

Be sure add js in your _Layout.cshtml or in your current page #section Scripts {...} like below:
<script src="https://unpkg.com/alpinejs" defer></script>
Note: HTML defer attribute must be added.

Related

Composable for checking Laravel Sanctum's CSRF token in Nuxt 3

I'm working on a Nuxt 3 app with Laravel 9 as API with Sanctum and Fortify.
I wrote a composable to check the response to the Laravel Sanctum's CSRF token HTTP route ('/sanctum/csrf-cookie') and I'm trying to check if it's working by logging via console.log() the composable's state, but it always shows an empty Proxy received as error.
Please help, what am I doing wrong?
Here is what I've done so far:
The composable: auth.ts :
export function useCsrfToken() {
const baseURL = useRuntimeConfig().public.LARAVEL_BASE_URL
const options = {
baseURL,
credentials: 'include',
headers: {
Accept: 'application/json',
} as HeadersInit,
method: 'GET',
}
const state = ref({
status: '',
error: {},
})
async function getCsrfToken() {
state.value.error = {}
state.value.status = ''
await $fetch('/sanctum/csrf-cookie', options)
.then(() => (state.value.status = 'ok'))
.catch((error) => {
console.log('error from useCsrfToken', error)
state.value.error = { error }
})
}
getCsrfToken()
return { state }
}
The login Nuxt page, where I'm using the above composable :
<script setup>
definePageMeta({
layout: false,
})
const submit = () => {
form.processing = true
const { state } = useCsrfToken()
console.log('error from login', state.value.error)
// console.log('status from login', status)
}
const form = reactive({
email: '',
password: '',
remember: false,
processing: false,
errors: {},
})
</script>
<template>
<div>
<!-- <TheTwoFactorChallenge v-if="verification" #2fapassed="submit" /> -->
<NuxtLayout name="auth">
<template #title>
<p class="card-header-title">Inicie Sesión</p>
</template>
<form novalidate #submit.prevent="submit">
<div class="field">
<label class="label" for="email">Usuario</label>
<div class="control has-icons-left has-icons-right">
<AppInput
type="email"
id="email"
v-model="form.email"
:error="form.errors?.email"
autocomplete="email"
required
/>
<AppIconLeft icon="fa-solid fa-envelope" />
<AppIconError v-if="form.errors?.email" class="is-right" />
</div>
<AppHelpError :errors="form.errors?.email" />
</div>
<div class="field">
<label class="label" for="password">Contraseña</label>
<div class="control has-icons-left has-icons-right">
<AppInput
type="password"
id="password"
v-model="form.password"
:error="form.errors?.password"
autocomplete="new-password"
required
/>
<AppIconLeft icon="fa-solid fa-lock" />
<AppIconError v-if="form.errors?.password" class="is-right" />
</div>
<AppHelpError :errors="form.errors?.password" />
</div>
<div class="field">
<div class="control">
<AppSwitch
id="remember"
v-model:checked="form.remember"
class="is-small is-link"
/>
<label for="remember">Recuérdame</label>
</div>
</div>
<div class="is-flex is-justify-content-flex-end mb-4">
<NuxtLink to="#" class="has-text-link">
¿Olvidó su contraseña?
</NuxtLink>
</div>
<div class="field">
<div class="control">
<AppButton
class="is-link is-fullwidth"
type="submit"
:is-loading="form.processing"
>Entrar</AppButton
>
</div>
</div>
</form>
</NuxtLayout>
</div>
</template>
And here are the outputs :
Since Nuxt3 provides a Server function which could use as an API, it could be easily confused with Laravel.
You may need to add a proxy_pass (if you use Nginx) to distinguish between Nuxt and Laravel requests in order to share the same origin between Nuxt and Laravel (to avoid wasting a second on Same-Origin-Policy )
For example:
http://example.com/ // -> nuxt
http://example.com/api/ // -> laravel
(Since you said you are using Docker, I'm going to assume you're using laravel sail)
First, add a domain (for example http://example.test) for your site instead of http://localhost.
Custom the /sanctum/csrf-cookie route to /api/sanctum/csrf-cookie (here you can see how to change laravel sanctum csrf cookie route)
Add a proxy_pass for http://example.test/api/* in Nginx configs:
location ~* ^/(api|broadcasting|storage)/ {
proxy_pass http://localhost:8080; // where your laravel running
}
But, in my view, the SSR Nuxt should be considered as a client just equal to an Android app or IOS app, because the frontend and the backend are entire two different projects compared with the previous time when putting your frontend code and backend code in the same project.
So, you may auth the web just like auth the Android app, instead of booting the CRSF protection.
And here is a full example of Nuxt3 + Laravel, which is using Laravel Sanctum and also SSR with authorizations. 👉 Laravel + SSR Nuxt3 with authorizations

How do I build an Asp.Net Core Razor page control that allows users to sign their names?

I am building an Asp.Net Core web application using Razor.
The intended audience for this app will be using it on tablets.
Part of the application consists of several pages/forms that will require user signatures.
We could retrieve an image of a user's signature and display that on demand in the web page.
Is it possible to be more interactive and allow users to "sign" the form/page within the browser? Are there any 3rd party control libraries that would support this functionality?
I pretty sure this can be done on native applications, but can I achieve this through Asp.Net Core?
I found signature_pad in github, and it works for me.
You can take a look at the screenshots of my test steps first, and I will add the test code at the bottom.
Test Code
1. signature.cshtml
#*
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*#
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/signature_pad#2.3.2/dist/signature_pad.min.js"></script>
<form method="POST">
<p>
<canvas width="500" height="400" id="signature"
style="border:1px solid black"></canvas><br>
<button type="button" id="accept"
class="btn btn-primary">
Accept signature
</button>
<button type="submit" id="save"
class="btn btn-primary">
Save
</button><br>
<img width="500" height="400" id="savetarget"
style="border:1px solid black"><br>
<input id="SignatureDataUrl" type="text">
</p>
</form>
<script>
$(function () {
var canvas = document.querySelector('#signature');
var pad = new SignaturePad(canvas);
$('#accept').click(function () {
var data = pad.toDataURL();
$('#savetarget').attr('src', data);
$('#SignatureDataUrl').val(data);
pad.off();
});
$('#save').click(function () {
$.ajax({
url: "/ForTest/get_signature",
type: "POST",
data: { base64png:$('#SignatureDataUrl').val()},
success: function (data) {
console.log("success");
},
error: function (hata, ajaxoptions, throwerror) {
alert("failed");
}
});
});
});
</script>
2. C# code
[HttpPost]
public string get_signature(string base64png) {
var dataUri = base64png;//"data:image/png;base64,iVBORw0K...";
var encodedImage = dataUri.Split(',')[1];
var decodedImage = Convert.FromBase64String(encodedImage);
System.IO.File.WriteAllBytes("signature_pic/"+DateTime.Now.ToString("yyyyMMddHHmmss")+"signature.png", decodedImage);
return "ok";
}
Tips
If you want test my code, you need create signature_pic folder like me.

Getting form data on submit?

When my form is submitted I wish to get an input value:
<input type="text" id="name">
I know I can use form input bindings to update the values to a variable, but how can I just do this on submit. I currently have:
<form v-on:submit.prevent="getFormValues">
But how can I get the value inside of the getFormValues method?
Also, side question, is there any benefit to doing it on submit rather than updating variable when user enters the data via binding?
The form submit action emits a submit event, which provides you with the event target, among other things.
The submit event's target is an HTMLFormElement, which has an elements property. See this MDN link for how to iterate over, or access specific elements by name or index.
If you add a name property to your input, you can access the field like this in your form submit handler:
<form #submit.prevent="getFormValues">
<input type="text" name="name">
</form>
new Vue({
el: '#app',
data: {
name: ''
},
methods: {
getFormValues (submitEvent) {
this.name = submitEvent.target.elements.name.value
}
}
}
As to why you'd want to do this: HTML forms already provide helpful logic like disabling the submit action when a form is not valid, which I prefer not to re-implement in Javascript. So, if I find myself generating a list of items that require a small amount of input before performing an action (like selecting the number of items you'd like to add to a cart), I can put a form in each item, use the native form validation, and then grab the value off of the target form coming in from the submit action.
You should use model binding, especially here as mentioned by Schlangguru in his response.
However, there are other techniques that you can use, like normal Javascript or references. But I really don't see why you would want to do that instead of model binding, it makes no sense to me:
<div id="app">
<form>
<input type="text" ref="my_input">
<button #click.prevent="getFormValues()">Get values</button>
</form>
Output: {{ output }}
</div>
As you see, I put ref="my_input" to get the input DOM element:
new Vue({
el: '#app',
data: {
output: ''
},
methods: {
getFormValues () {
this.output = this.$refs.my_input.value
}
}
})
I made a small jsFiddle if you want to try it out: https://jsfiddle.net/sh70oe4n/
But once again, my response is far from something you could call "good practice"
You have to define a model for your input.
<input type="text" id="name" v-model="name">
Then you you can access the value with
this.name inside your getFormValues method.
This is at least how they do it in the official TodoMVC example: https://v2.vuejs.org/v2/examples/todomvc.html (See v-model="newTodo" in HTML and addTodo() in JS)
Please see below for sample solution, I combined the use of v-model and "submitEvent" i.e. <input type="submit" value="Submit">. Used submitEvent to benefit from the built in form validation.
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<div id="app">
<form #submit.prevent="getFormValues">
<div class="form-group">
<input type="email" class="form-control form-control-user"
v-model="exampleInputEmail"
placeholder="Enter Email Address...">
</div>
<div class="form-group">
<input type="password" class="form-control"
v-model="exampleInputPassword" placeholder="Password"> </div>
<input type="submit" value="Submit">
</form>
</div>
<script>
const vm = new Vue({
el: '#app',
methods: {
getFormValues (submitEvent) {
alert("Email: "+this.exampleInputEmail+" "+"Password: "+this.exampleInputPassword);
}
}
});
</script>
</body>
</html>
The other answers suggest assembling your json POST body from input or model values, one by one. This is fine, but you also have the option of grabbing the whole FormData of your form and whopping it off to the server in one hit. The following working example uses Vue 3 with Axios, typescript, the composition API and setup, but the same trick will work anywhere.
I like this method because there's less handling. If you're old skool, you can specify the endpoint and the encoding type directly on the form tag.
You'll note that we grab the form from the submit event, so there's no ref, and no document.getElementById(), the horror.
I've left the console.log() there to show that you need the spread operator to see what's inside your FormData before you send it.
<template>
<form #submit.prevent="formOnSubmit">
<input type="file" name="aGrid" />
<input type="text" name="aMessage" />
<input type="submit" />
</form>
</template>
<script setup lang="ts">
import axiosClient from '../../stores/http-common';
const formOnSubmit = (event: SubmitEvent) => {
const formData = new FormData(event.target as HTMLFormElement);
console.log({...formData});
axiosClient.post(`api/my-endpoint`, formData, {
headers: {
"Content-Type": "multipart/form-data",
}
})
}
</script>

Dropzone inside a html form with other form fields not working

I want to add a dropzone inside an existing form but it doesn't seem to work.
When I view the console I get error throw new Error("No URL provided"). When I click upload I get no preview either - all I get is a normal file input.
<link href="../dropzone.css" rel="stylesheet">
<form action="/" enctype="multipart/form-data" method="POST">
<input type="text" id ="Username" name ="Username" />
<div class="dropzone" id="my-dropzone" name="mainFileUploader">
<div class="fallback">
<input name="file" type="file" />
</div>
</div>
<div>
<button type="submit" id="submit"> upload </button>
</div>
</form>
<script src="../jquery.min.js"></script>
<script src="../dropzone.js"></script>
<script>
$("my-dropzone").dropzone({
url: "/file/upload",
paramName: "file"
});
</script>
No url provided error is because $("my-dropzone") is wrong instead it must be $('#mydropzone')
dropzone along with other form, yes this is very much possible, you have to post the data using the URL provided in the dropzone not in the form action. That means all your form data along with the files uploaded shall be posted back to the url provided for the dropzone. A simple untested solution is as below;
<link href="../dropzone.css" rel="stylesheet">
<form action="/" enctype="multipart/form-data" method="POST">
<input type="text" id ="Username" name ="Username" />
<div class="dropzone" id="my-dropzone" name="mainFileUploader">
<div id="previewDiv></div>
<div class="fallback">
<input name="file" type="file" />
</div>
</div>
<div>
<button type="submit" id="submitForm"> upload </button>
</div>
</form>
<script src="../jquery.min.js"></script>
<script src="../dropzone.js"></script>
<script>
$("#mydropzone").dropzone({
url: "/<controller>/action/" ,
autoProcessQueue: false,
uploadMultiple: true, //if you want more than a file to be uploaded
addRemoveLinks:true,
maxFiles: 10,
previewsContainer: '#previewDiv',
init: function () {
var submitButton = document.querySelector("#submitForm");
var wrapperThis = this;
submitButton.addEventListener("click", function () {
wrapperThis.processQueue();
});
this.on("addedfile", function (file) {
// Create the remove button
var removeButton = Dropzone.createElement("<button class="yourclass"> Remove File</button>");
// Listen to the click event
removeButton.addEventListener("click", function (e) {
// Make sure the button click doesn't submit the form:
e.preventDefault();
e.stopPropagation();
// Remove the file preview.
wrapperThis.removeFile(file);
});
file.previewElement.appendChild(removeButton);
});
// Also if you want to post any additional data, you can do it here
this.on('sending', function (data, xhr, formData) {
formData.append("PKId", $("#PKId").val());
});
this.on("maxfilesexceeded", function(file) {
alert('max files exceeded');
// handle max+1 file.
});
}
});
</script>
The script where you initialize dropzone can be inside $document.ready or wrap it as a function and call when you want to initialize it.
Happy coding!!

ASP.NET MVC4 and Knockout js

I am trying to use knockout js in my project so I tried the simple Hello World example but i couldnt get it to work. I created a new MVC4 project and just copy do a simple binding below is my code
<script src="~/Scripts/knockout-2.1.0.js" type="text/javascript"></script>
<script type="text/javascript">
// Here's my data model
var viewModel = function (first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
//this.fullName = ko.computed(function () {
// Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName.
//return this.firstName() + " " + this.lastName();
//}, this);
};
$(document).ready(function() {
ko.applyBindings(new viewModel("Planet", "Earth")); // This makes Knockout get to work
});​
</script>
<div class="liveExample">
<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
#*<h2>Hello, <span data-bind='text: fullName'> </span>!</h2>*#
</div>
Basically it will just display the value of the model on a textbox.
I already referenced the knockout.js in my project but it does not work
I also added the knockout js in my BundleConfig.cs
bundles.Add(new ScriptBundle("~/bundles/knockout").Include("~/Scripts/knockout-2.1.0.js"));
I didnt work
If you are using MVC, use the scripts section to declare your JS. This will move the declarations to the bottom of the HTML page, letting the HTML render first. Here's my version of your code that worked first time out of the box:
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<div class="liveExample">
<p>First name:
<input data-bind="value: firstName" /></p>
<p>Last name:
<input data-bind="value: lastName" /></p>
#*<h2>Hello, <span data-bind='text: fullName'> </span>!</h2>*#
</div>
#section scripts {
<script src="~/Scripts/knockout-2.2.1.js"></script>
<script type="text/javascript">
var viewModel = function (firstName, lastName) {
this.firstName = ko.observable(firstName);
this.lastName = ko.observable(lastName);
};
$(function () {
ko.applyBindings(new viewModel("Planet", "Earth"));
});
</script>
}
try putting knockout in the of your document. Without any error messages the only thing I can say is I ran into a similar problem and that was the fix for me.
My example was driving me crazy because it worked in fiddle but not in MVC, I mentioned it to a designer friend of mine and he said it made since to him, basically that knockout needed to be fully downloaded before the page began to render.
Hope this helps