How to register service-worker in rails 7 used importmap? - progressive-web-apps

Updated on 1/18/2023 to reflect the answer from Bijan
Facing problem
I want to convert a web application created with Rails 7 that uses importmap into a PWA.
However, I'm having trouble registering the service-worker using importmap.
I'd appreciate it if you could give me some advice.
Status quo
At first, I tried to use this gem, but it didn't work in the importmap environment, so I referred to this site, but it didn't work.
The result of lighthouse check is as below.
Development environment
ruby 3.1.3
rails 7.0.3
importmap-rails 1.1.2
tailwindcss-rails 2.0.10
Sprockets 4.1.1.
Related source code
config/route.rb
get '/service_worker', to: 'service_workers#service_worker'
get '/offline', to: 'service_workers#offline'
./app/controllers
├── service_workers_controller.rb
class ServiceWorkerController < ApplicationController
protect_from_forgery except: :service_worker
skip_before_action :require_login
def service_worker; end
def offline; end
end
./app/views/service_worker
├── offline.html.erb
└── service-worker.html.erb
<script>
const VERSION = 'v1';
const NAME = 'app_name-';
const CACHE_NAME = NAME + VERSION;
const urlsToCache = [
"./offline.html"
];
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', function (event)
if (event.request.cache === 'only-if-cached' &&
event.request.mode !== 'same-origin')
return;
event.respondWith(
cache.match(event.request).then(function (response) {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
self.addEventListener('activate', event => {
event.waitUntil(
cache.keys().then(keys => Promise.all(
keys.map(key => {
if (!CACHE_NAME.includes(key)) {
return cache.delete(key);
}
})
)).then(() => {
console.log(CACHE_NAME + "activated");
})
);
});
</script>
/app/javascript/controllers
└── service-worker_controller.js
import { Controller } from "#hotwired/stimulus"
export default class extends Controller {
connect () {
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register("/service-worker", {scope: "/" }).then(function (registration) {
console.log("ServiceWorker registration successful with scope: ", registration.scope);
}, function (err) {
console.log("ServiceWorker registration failed: ", err);
});
});
}
}
}
app/views/layouts/
└── application.html.erb
<!DOCTYPE html>
<html lang="ja">
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
<title><%= page_title(yield(:title)) %></title>
<%= render 'application/favicon' %>
<%= display_meta_tags(default_meta_tags) %>
</head>
<body class="dark:bg-gray-800 break-words flex flex-col min-h-screen">
<%= render 'static_pages/navbar' %>
<div id="flash" class="z-50 flex-col fixed top-16 right-1 ">
<%= render 'shared/flash' %>
</div>
<%= tag.main class: "mb-auto relative" do %>
<div class="w-full" data-controller="service-worker">
<%= yield %>
</div>
<% end %>
<%= render 'static_pages/footer' %>
</body>
</html>
Error message
when I reload the page, The following error appears on the console of chrome DV tool.
The script has an unsupported MIME type ('text/html').
Also, the following message appears on chrome DV tool console.
Please let me know if you find anything strange.

So far your example seems fine except the rendering of your service-worker.js file via the controller. Your controller complains that it didn’t find a template. You could create a view for the service_worker method and paste the content of your service-worker.js file inside. You could also make your controller method send the file down directly. Should be an easy fix.

Related

Express Router .delete returns 404 Not Found?

Encountered a strange issue while using delete method in Express app.
Here is my app.js document. I am using elevatorRouter for "/elevators" routes.
app.js
app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use("/passwordgenerator", passwordgeneratorRouter);
app.use("/elevators", elevatorRouter);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "development" ? err : {};
// render the error page
res.status(err.status || 500);
res.render("error");
});
module.exports = app;
Here is my route file. I am including the elevator router from app.js. It seems that edit route is working fine. I have only issue with "delete" method.
elevators.js
const express = require("express");
const router = express.Router();
const Elevator = require("../models/elevator");
const middleware = require("../middleware");
// Edit Elevator Route
router.get("/:id/edit", (req, res) => {
Elevator.findById(req.params.id, (err, foundElevator) => {
res.render("elevators/edit", { elevator: foundElevator });
});
});
// Delete Elevator Route
router.delete("/:id", (req, res) => {
Elevator.findByIdAndRemove(req.params.id, (err) => {
if (err) {
res.redirect("/elevators");
} else {
res.redirect("/elevators");
}
});
});
Here is my views. I didnt completely post the entire html. Instead, I have copied partially. I beleive this will be enough. I used postman to send "POST" method directly but still receiving the same 404 error. I beleive it is not related to the view
view
<div class="col-md-9">
<div class="card">
<img src="<%= elevator.image %>" class="card-img-top" alt="..." />
<div class="card-body">
<h4 class="card-title"><%= elevator.projectName%></h4>
<p><%= elevator.projectNumber %></p>
<% if(user) { %>
<form
class="delete-form"
action="/elevators/<%= elevator._id %>?_method=DELETE"
method="post"
>
<button class="btn btn-danger">Delete</button>
</form>
<% } %>
</div>
<div class="card-body">
Back
</div>
</div>
</div>
You cannot get response from a DELETE route with a POST HTTP request. It is a well-known issue of browsers, but HTML forms can only send FormData via POST and DELETE is not compatible.
Therefore, Express routing does not match and says POST:“/:id” route does not exist.
Try changing .delete to .post and it will work.
Edit: There is a method-overwrite module to convert POST requests to DELETE via a query param _method (or any other name you choose).

How to dynamically create an EJS file from a database

New web developer here; I am working with Mongo/Express/Node.
I am trying to make an e-commerce site where the admin will create new "categories" and add them to the database.
Whenever a new "category" is added to the database, I want a new EJS page to be created for that category (and all other categories in the database), that will then load all of the "products" that are of that category in that EJS page.
The category page should conform to a template; only changing its name and the products that are loaded into it.
Something like this:
<html>
<body>
<nav>
<h2>Categories</h2>
<div class="menu">
<a>Men</a>
<a>Women</a>
<a>Children</a>
</div>
</nav>
<% products.forEach(function(product) { %>
<% if (product.category === x) { %>
<h5><%= product.name %></h5>
<img src='someurl'>
<% } %>
<% }) %>
</body>
</html>
Thanks.
Luckily you don't need to dynamically create EJS files. You can use parameterised route matching and then return the right data for that category in the render function of the EJS page. That's the beauty of using dynamic web pages!
server.js
const express = require("express");
const app = express();
app.get("/example/:category", (req, res) => {
let { category } = req.params;
let products = /* search db for category that matches that "category" var */;
res.render("products.ejs", { products });
});
products.ejs
...
<% products.forEach(product => { %>
<h5><%= product.name %></h5>
<img src="<%= product.imageURL %>">
<% }); %>
...
If a user visits /example/food, for example, the category variable parameter will be equal to "food". You can then pass this in to the EJS render function.

gmaps4rails callback doesn't always work

Probably an easy question but it's been nagging at me for ages and I can't find an answer anywhere. I'm using the gmaps4rails gem and I want to add a callback function after the map has loaded. When I use the basic helper everything works fine, like this:
<%= gmaps4rails(#json) %>
<% content_for :scripts do %>
<script type="text/javascript">
Gmaps.map.callback = function() {
alert('callback');
}
</script>
<% end %>
<%= yield :scripts %>
But if I replace <%= gmaps4rails(#json) %> with the gmaps helper, the callback doesn't happen:
<%= gmaps("markers" => { "data" => #json }) %>
<% content_for :scripts do %>
<script type="text/javascript">
Gmaps.map.callback = function() {
alert('callback');
}
</script>
<% end %>
<%= yield :scripts %>
Can anyone suggest why this might happen?
Of course the answer always comes the moment you pull your hair out and ask here. The problem is the version of the gem I was using. I still don't know why it wasn't working with 1.4.6, but 1.5.5 seems to work fine.

MVC2 ajax partial view does not register jquery scripts

I have my page where i have ajax call to display form.
My code works when the register the code for form but not from postback (validation error in controller).
any idea how to fix this?
My container page has:
Scripts:
<%= Html.JQuery() %>
<script src="../../../../Scripts/jquery.ui.core.js" type="text/javascript"></script>
<script src="../../../../Scripts/jquery.ui.datepicker.min.js" type="text/javascript"></script>
<script src="../../../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="../../../../Scripts/MicrosoftMvcAjax.js" type="text/javascript">/script>
<script src="../../../../Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>
<script type="text/javascript">
function registerStuff() {
//this gets triggered when onsuccess is called
$("#date").datepicker({ dateFormat: 'dd/mm/yy' });
$("#sla").datepicker();
}
</script>
Content:
<%= Ajax.AjaxButton("New action", "CreateWorkFlowAction", new {
controller = "CaseWorkFlow" }, new AjaxOptions() { UpdateTargetId =
"divTableContainer", HttpMethod = "Get", OnSuccess =
"registerStuff"})%>
<div id="divTableContainer">
response will appear here
</div>
my form looks like
<script type="text/javascript">
function RegisterJs() {
$("#date").datepicker({ dateFormat: 'dd/mm/yy' });
$("#sla").datepicker({ dateFormat: 'dd/mm/yy' });
}
</script>
<% Html.EnableClientValidation(); %>
<% using (Ajax.BeginForm("CreateWorkFlowAction", new AjaxOptions() { UpdateTargetId = "divTableContainer", HttpMethod = "Post" }))
{%>
<%= Html.ValidationSummary() %>
<fieldset>
<div>
<label for="title">
Title</label>
<%= Html.TextBoxFor(x=>x.Title) %>
</div>
<div>
<label for="SLA">
SLA(Due Date)</label>
<%= Html.TextBoxFor(x => x.SelectSlaDate, new { id = "sla", #Value = string.Empty} )%>
</div>
<div>
<label for="date">
Date</label>
<%= Html.TextBoxFor(x => x.SelectDate, new { id = "date", #Value = string.Empty})%>
</div>
<div>
<button type="submit">Save action</button>
</div>
</fieldset>
<% } %>
the problem is
i have found the solution to my question.
I have to reregister my javascript inside of a partial view.
this means that i had to update add register scripts into the parent page to which i will insert partial view.
and therfore if my view page contains partial view i had to move
function registerStuff() {
//this gets triggered when onsuccess is called
$("#date").datepicker({ dateFormat: 'dd/mm/yy' });
$("#sla").datepicker();
}
to the index page.
After on success call this function "registerStuff" from my oncomplete,
and after then everything is working like charm
hope this helps to you too

MVC2 Client side validation within Jquery modal dialog

Using MVC2 I currently have a view creating a jquery dialog box containing an Edit partial view. On submit I am looking for it to perform client side validation on the Email class which has a required data annotation attribute for email address. Server side validation works fine but I want the user to have to fix the error in the modal dialog.
Below is the code
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm())
<div>
<label for="EmailAddress">
Email Address :
</label>
<%= Html.TextBoxFor(model => model.Email.EmailAddress)%>
<%= Html.ValidationMessageFor(model => model.Email.EmailAddress)%>
</div>
Scripts I am loading up are
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/jquery-1.3.2.min.js")%>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/jqueryUI/js/jquery-ui-1.7.2.custom.min.js")%>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/Splitter/splitter-1.5.js")%>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/Scripts/Start.js")%>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/Scripts/extended/ExtendedControls.js")%>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/jquery.validate.js")%>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/MicrosoftMvcJQueryValidation.js")%>"></script>
Looking at the html generated I am not getting any of the JSON data generated for the client side validation to work.
Any solutions gladly appreciated. S
I strongly recommend you to use jquery validation script.
jquery.validate.js has all the features for client-side validation within a jquery dialog.
First of all, add the jquery.validate.js to your Site.Master :
<script src="/Scripts/Using/jquery.validate.js" type="text/javascript"></script>
and then write your script something like that :
<script type="text/javascript">
var createLinkObj;
$(function () {
$('#mydialog').dialog({
autoOpen: false,
width: 500,
modal: true,
buttons: {
"OK": function () {
$("#myForm").validate({
rules: {
Name: {
required: true
},
Email: {
required: true,
email: true
}
},
messages: {
Name: " * ",
Email: {
required: " * ",
email: " Invalid e-mail."
}
});
$("#myForm").submit();
},
"Cancel": function () {
$(this).dialog("close");
}
}
});
$(".mylink").click(function () {
//change the title of the dialog
createLinkObj = $(this);
var dialogDiv = $('#mydialog');
var viewUrl = createLinkObj.attr('href');
$.get(viewUrl, function (data) {
dialogDiv.html(data);
dialogDiv.dialog('open');
});
return false;
});
});
</script>
As you can see when I click the mylink, mydialog appears and before submitting the myForm, I validated the myForm elements namely Name and Email.
Think that your form only contains Name and Email and then you can validate these elments by using jquery validate script.