in reply to Re^6: Mojolicious refresh
in thread Mojolicious refresh

Here is an example using Mojolicious::Lite and AngularJS.

The Mojolicious controller in this example watches for changes in the file data/content.pl. Only the parts therein that are modified are pushed to connected clients via a web socket.

The index page show the usage of placeholders {{text}} and {{hash}} that will be replaced by the AngularJS module.

The AngularJS module provides the variables text and hash in it's $rootContext for insertion into the HTML index page and has the client side web socket for receiving updates from the server.

The config file data/content.pl defines a hash of values that are transmitted to the client. Changes of text will be visible on the client immediately. When the value of hash is modified and pushed to the client, this causes a modified url parameter in the <img> tag and thus forces a reload of the image. If hash is taken as the SHA sum of the given picture, a reload can be forced if and only if the picture has changed.

Put any picture you like to public/image.png for this demo.

Start the server and point your browser to http://localhost:8880. You may notice the displayed text changing from "text from root context" to the text provided in data/content.pl. Modify the content of data/content.pl and observe the change on the displayed page.

demoserver.pl:
#!/usr/bin/perl use strict; use warnings; use feature 'state'; use Mojolicious::Lite; use Mojo::Server::Daemon; use EV; use AnyEvent; use Linux::Inotify2; use constant FILE_NAME => 'data/content.pl'; use constant LISTEN => 'http://localhost:8880'; use constant TIMEOUT => 600; my %clients; our $content; sub getcontent{ my $init = shift; state $old; my $out = {}; local $content; do FILE_NAME; for my $key (keys %$content) { $out->{$key} = $content->{$key} if $init || $old->{$key} ne $content->{$key}; } app->log->info("read content from file"); $old = $content; return $out;; } sub push_all { my $content = shift; my $data = {msg => $content}; foreach my $client (values %clients) { app->log->info("pushing data to client"); $client->send({json => $data}); Mojo::IOLoop->stream($client->connection)->timeout(TIMEOUT); } }; my $inotify = new Linux::Inotify2 or die $!; sub app_file_changed { my $e = shift; app->log->info("got file modify event"); my $content = getcontent(); push_all($content) if %$content; if ($e->IN_ATTRIB) { $inotify->watch(FILE_NAME, IN_CLOSE_WRITE | IN_ATTRIB, \&app_file_changed); } }; $inotify->watch(FILE_NAME, IN_CLOSE_WRITE | IN_ATTRIB, \&app_file_chan +ged); my $ino_w = AnyEvent->io(fh => $inotify->fileno, poll => 'r', cb => sub {$inotify->poll}); AnyEvent->signal(signal => 'INT', cb => sub {exit 1;}); AnyEvent->signal(signal => 'QUIT', cb => sub {exit 1;}); AnyEvent->signal(signal => 'TERM', cb => sub {exit 1;}); my $cond = AnyEvent->condvar; get "/" => sub { my $self = shift; $self->reply->static("index.html"); }; websocket "/ws/data/" => sub { my $c = shift; my $tx = $c->tx; my $cid = "$tx"; $clients{$cid} = $tx; $c->inactivity_timeout(TIMEOUT); $c->on(json => sub { my ($c, $data) = @_; my $content = getcontent(1); app->log->info("sending initial data to client"); $data->{msg} = $content; $c->send({json => $data}); }); $c->on(finish => sub { delete $clients{$cid}; } ); $c->on(error => sub { delete $clients{$cid}; } ); }; app->secrets(['not so secret']); my $daemon = Mojo::Server::Daemon->new(app => app, listen => [LISTEN]); $daemon->start; $cond->recv;

public/index.html:

<html> <head> <title>Update Demo</title> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1. +3.16/angular.min.js"></script> <script src="demo.js"></script> </head> <body> <div ng-app="demoModule"> <div ng-controller="demoController"> <h1>Update Demo</h1> <h2>Text</h2> <div> {{text}} </div> <h2>Image</h2> <div> <img src="image.png?hash={{hash}}"/> </div> <div> </div> </body> </html>

public/demo.js:

(function() { 'use strict'; var demoModule = angular.module('demoModule', []); demoModule.controller('demoController', function($rootScope, initS +ervice) { $rootScope.text = 'text from root scope'; $rootScope.hash = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'; }); demoModule.service('initService', function($rootScope, data) { }); demoModule.factory('data', function($rootScope) { var Server = {}; var socketUri = 'ws://localhost:8880/ws/data/'; var ws = new WebSocket(socketUri); ws.onopen= function(evt) { ws.send(JSON.stringify({msg: 'init'})); }; ws.onmessage = function(evt) { var msg = JSON.parse(evt.data).msg; for (var name in msg) { $rootScope.$apply(set_parm(name, msg[name])); }; }; function set_parm(name, value) { $rootScope[name] = value; }; return Server; }); })();

data/config.pl:

$content = { text => 'text from server', hash => 'da39a3ee5e6b4b0d3255bfef95601890afd80709', };

EDIT: removed workaround for <script> tags in html file.

Greetings,
-jo

$gryYup$d0ylprbpriprrYpkJl2xyl~rzg??P~5lp2hyl0p$