summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2019-11-03 14:12:35 +0100
committerYorhel <git@yorhel.nl>2019-11-10 12:44:55 +0100
commitcc4607635279792b59953bba2e3cb4a3928e76df (patch)
tree227627601aa35df51e2ca10ff0006495bb933e9e
parent6ae025a405ea6e40c37a183ba7f3a98c4d9e39cb (diff)
Reorganize JS and Elm instantiation + more efficient ulist loading
The order in which .js files are concatenated is (and always has been) important, so rather than relying on name order I've changed that to an explicit $JS_FILES list in the Makefile. Less convenient, but at least we have more freedom regarding .js file naming and organization now. Rather than encoding the Elm flags as JSON in 'data-' attributes, I've added a system for global page variables (pagevars) where the Elm flags are now stored separately from their HTML tags. This has the advantage of more efficient encoding (no more &quot;s), faster parsing (just a single JSON.parse()), and easier/more efficient modification of the Elm arguments from JS. The pagevars provide a unified way of passing other variables to JS as well, which I've used to deduplicate the list of labels on the new ulist page. That shaves off a good 40 KiB on the page size for a typical listing. Whether all of this actually improves the page loading time, I don't really know. Most of the slowness in Firefox seems to come from simply instantiating many <input time="date"> objects.
-rw-r--r--Makefile12
-rw-r--r--elm/Lib/Ffi.elm2
-rw-r--r--elm/Lib/Ffi.js (renamed from elm/1-ffi.js)0
-rw-r--r--elm/ULists/LabelEdit.elm2
-rw-r--r--elm/ULists/LabelEdit.js6
-rw-r--r--elm/ULists/ManageLabels.js8
-rw-r--r--elm/checkall.js16
-rw-r--r--elm/checkhidden.js13
-rw-r--r--elm/elm-init.js29
-rw-r--r--elm/global.js59
-rw-r--r--elm/pagevars.js3
-rw-r--r--elm/polyfills.js (renamed from elm/0-compat.js)0
-rw-r--r--lib/VNWeb/HTML.pm17
-rw-r--r--lib/VNWeb/User/Lists.pm11
14 files changed, 106 insertions, 72 deletions
diff --git a/Makefile b/Makefile
index 9b928b9d..9fe5a933 100644
--- a/Makefile
+++ b/Makefile
@@ -116,7 +116,17 @@ static/f/vndb.min.js: static/f/vndb.js
# v2-rw
-JS_FILES=elm/*.js elm/*/*.js
+# Order matters!
+JS_FILES=\
+ elm/polyfills.js \
+ elm/pagevars.js \
+ elm/ULists/ManageLabels.js \
+ elm/ULists/LabelEdit.js \
+ elm/Lib/Ffi.js \
+ elm/elm-init.js \
+ elm/checkall.js \
+ elm/checkhidden.js
+
ELM_FILES=elm/*.elm elm/*/*.elm
ELM_MODULES=$(shell grep -l '^main =' ${ELM_FILES} | sed 's/^elm\///')
diff --git a/elm/Lib/Ffi.elm b/elm/Lib/Ffi.elm
index 9c3b1c23..c073b4a6 100644
--- a/elm/Lib/Ffi.elm
+++ b/elm/Lib/Ffi.elm
@@ -5,7 +5,7 @@
-- This module is a hack to work around the lack of an FFI (Foreign Function
-- Interface) in Elm. The functions in this module are stubs, their
-- implementations are replaced by the Makefile with calls to
--- window.elmFfi_<name> and the actual implementations are in 1-ffi.js.
+-- window.elmFfi_<name> and the actual implementations are in Ffi.js.
--
-- Use sparingly, all of this will likely break in future Elm versions.
module Lib.Ffi exposing (..)
diff --git a/elm/1-ffi.js b/elm/Lib/Ffi.js
index c06314ff..c06314ff 100644
--- a/elm/1-ffi.js
+++ b/elm/Lib/Ffi.js
diff --git a/elm/ULists/LabelEdit.elm b/elm/ULists/LabelEdit.elm
index eb59c823..e138b01a 100644
--- a/elm/ULists/LabelEdit.elm
+++ b/elm/ULists/LabelEdit.elm
@@ -112,6 +112,6 @@ view model =
]
, div []
[ ul [ classList [("hidden", not model.opened)] ]
- <| List.map item model.labels
+ <| List.map item <| List.filter (\l -> l.id /= 7) model.labels
]
]
diff --git a/elm/ULists/LabelEdit.js b/elm/ULists/LabelEdit.js
new file mode 100644
index 00000000..c40aa615
--- /dev/null
+++ b/elm/ULists/LabelEdit.js
@@ -0,0 +1,6 @@
+var init = Elm.ULists.LabelEdit.init;
+Elm.ULists.LabelEdit.init = function(opt) {
+ opt.flags.uid = pageVars.uid;
+ opt.flags.labels = pageVars.labels;
+ init(opt);
+};
diff --git a/elm/ULists/ManageLabels.js b/elm/ULists/ManageLabels.js
index 76ee960c..1061a1e3 100644
--- a/elm/ULists/ManageLabels.js
+++ b/elm/ULists/ManageLabels.js
@@ -3,4 +3,10 @@ document.querySelectorAll('#managelabels').forEach(function(b) {
document.querySelectorAll('.managelabels').forEach(function(e) { e.classList.toggle('hidden') })
};
return false;
-})
+});
+
+var init = Elm.ULists.ManageLabels.init;
+Elm.ULists.ManageLabels.init = function(opt) {
+ opt.flags = { uid: pageVars.uid, labels: pageVars.labels };
+ init(opt);
+};
diff --git a/elm/checkall.js b/elm/checkall.js
new file mode 100644
index 00000000..4343ffc8
--- /dev/null
+++ b/elm/checkall.js
@@ -0,0 +1,16 @@
+/* "checkall" checkbox, usage:
+ *
+ * <input type="checkbox" class="checkall" name="$somename">
+ *
+ * Checking that will synchronize all other checkboxes with name="$somename".
+ */
+document.querySelectorAll('input[type=checkbox].checkall').forEach(function(el) {
+ el.onclick = function() {
+ document.querySelectorAll('input[type=checkbox][name="'+el.name+'"]').forEach(function(el2) {
+ if(!el2.classList.contains('hidden')) {
+ if(el2.checked != el.checked)
+ el2.click();
+ }
+ });
+ };
+});
diff --git a/elm/checkhidden.js b/elm/checkhidden.js
new file mode 100644
index 00000000..0f76d646
--- /dev/null
+++ b/elm/checkhidden.js
@@ -0,0 +1,13 @@
+/* "checkhidden" checkbox, usage:
+ *
+ * <input type="checkbox" class="checkhidden" value="$somename">
+ *
+ * Checking that will toggle the 'hidden' class of all elements with the "$somename" class.
+ */
+document.querySelectorAll('input[type=checkbox].checkhidden').forEach(function(el) {
+ el.onclick = function() {
+ document.querySelectorAll('.'+el.value).forEach(function(el2) {
+ el2.classList.toggle('hidden', !el.checked);
+ });
+ };
+});
diff --git a/elm/elm-init.js b/elm/elm-init.js
new file mode 100644
index 00000000..1aefbb09
--- /dev/null
+++ b/elm/elm-init.js
@@ -0,0 +1,29 @@
+/* Add the X-CSRF-Token header to every POST request. Based on:
+ * https://stackoverflow.com/questions/24196140/adding-x-csrf-token-header-globally-to-all-instances-of-xmlhttprequest/24196317#24196317
+ */
+(function() {
+ var open = XMLHttpRequest.prototype.open,
+ token = document.querySelector('meta[name=csrf-token]').content;
+
+ XMLHttpRequest.prototype.open = function(method, url) {
+ var ret = open.apply(this, arguments);
+ this.dataUrl = url;
+ if(method.toLowerCase() == 'post' && /^\//.test(url))
+ this.setRequestHeader('X-CSRF-Token', token);
+ return ret;
+ };
+})();
+
+
+/* Load all Elm modules listed in the pageVars.elm array */
+if(pageVars.elm) {
+ for(var i=0; i<pageVars.elm.length; i++) {
+ var e = pageVars.elm[i];
+ var mod = e[0].split('.').reduce(function(p, c) { return p[c] }, Elm);
+ var node = document.getElementById('elm'+i);
+ if(e.length > 1)
+ mod.init({ node: node, flags: e[1] });
+ else
+ mod.init({ node: node });
+ }
+}
diff --git a/elm/global.js b/elm/global.js
deleted file mode 100644
index 367507c5..00000000
--- a/elm/global.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/* Add the X-CSRF-Token header to every POST request. Based on:
- * https://stackoverflow.com/questions/24196140/adding-x-csrf-token-header-globally-to-all-instances-of-xmlhttprequest/24196317#24196317
- */
-(function() {
- var open = XMLHttpRequest.prototype.open,
- token = document.querySelector('meta[name=csrf-token]').content;
-
- XMLHttpRequest.prototype.open = function(method, url) {
- var ret = open.apply(this, arguments);
- this.dataUrl = url;
- if(method.toLowerCase() == 'post' && /^\//.test(url))
- this.setRequestHeader('X-CSRF-Token', token);
- return ret;
- };
-})();
-
-
-/* Find all divs with a data-elm-module and embed the given Elm module in the div */
-document.querySelectorAll('div[data-elm-module]').forEach(function(el) {
- var mod = el.getAttribute('data-elm-module').split('.').reduce(function(p, c) { return p[c] }, window.Elm);
- var flags = el.getAttribute('data-elm-flags');
- if(flags)
- mod.init({ node: el, flags: JSON.parse(flags)});
- else
- mod.init({ node: el });
-});
-
-
-/* "checkall" checkbox, usage:
- *
- * <input type="checkbox" class="checkall" name="$somename">
- *
- * Checking that will synchronize all other checkboxes with name="$somename".
- */
-document.querySelectorAll('input[type=checkbox].checkall').forEach(function(el) {
- el.onclick = function() {
- document.querySelectorAll('input[type=checkbox][name="'+el.name+'"]').forEach(function(el2) {
- if(!el2.classList.contains('hidden')) {
- if(el2.checked != el.checked)
- el2.click();
- }
- });
- };
-});
-
-
-/* "checkhidden" checkbox, usage:
- *
- * <input type="checkbox" class="checkhidden" value="$somename">
- *
- * Checking that will toggle the 'hidden' class of all elements with the "$somename" class.
- */
-document.querySelectorAll('input[type=checkbox].checkhidden').forEach(function(el) {
- el.onclick = function() {
- document.querySelectorAll('.'+el.value).forEach(function(el2) {
- el2.classList.toggle('hidden', !el.checked);
- });
- };
-});
diff --git a/elm/pagevars.js b/elm/pagevars.js
new file mode 100644
index 00000000..8fc92d88
--- /dev/null
+++ b/elm/pagevars.js
@@ -0,0 +1,3 @@
+/* Load global page-wide variables from <script id="pagevars">...</script> and store them into window.pageVars */
+var e = document.getElementById('pagevars');
+window.pageVars = e ? JSON.parse(e.innerHTML) : {};
diff --git a/elm/0-compat.js b/elm/polyfills.js
index 02179ee3..02179ee3 100644
--- a/elm/0-compat.js
+++ b/elm/polyfills.js
diff --git a/lib/VNWeb/HTML.pm b/lib/VNWeb/HTML.pm
index 23432627..30813aac 100644
--- a/lib/VNWeb/HTML.pm
+++ b/lib/VNWeb/HTML.pm
@@ -33,6 +33,9 @@ our @EXPORT = qw/
/;
+# Encoded as JSON and appended to the end of the page, to be read by pagevars.js.
+my %pagevars;
+
# Ugly hack to move rendering down below the float object.
sub clearfloat_ { div_ class => 'clearfloat', '' }
@@ -93,10 +96,9 @@ sub user_displayname {
# Instantiate an Elm module
sub elm_ {
my($mod, $schema, $data, $placeholder) = @_;
- div_ 'data-elm-module' => $mod,
- $data ? (
- 'data-elm-flags' => JSON::XS->new->allow_nonref->encode($schema ? $schema->analyze->coerce_for_json($data, unknown => 'remove') : $data)
- ) : (), $placeholder//'';
+ $pagevars{elm} ||= [];
+ push $pagevars{elm}->@*, [ $mod, $data ? ($schema ? $schema->analyze->coerce_for_json($data, unknown => 'remove') : $data) : () ];
+ div_ id => "elm$#{$pagevars{elm}}", $placeholder//'';
}
@@ -282,6 +284,8 @@ sub _footer_ {
my $modules = uri_escape join "\n", sort keys %INC;
a_ href => 'data:text/plain,'.$modules, 'Modules';
+ lit_ ' | ';
+ debug_ \%pagevars;
}
}
@@ -394,6 +398,7 @@ sub _hidden_msg_ {
sub framework_ {
my $cont = pop;
my %o = @_;
+ %pagevars = $o{pagevars} ? $o{pagevars}->%* : ();
html_ lang => 'en', sub {
head_ sub { _head_ \%o };
@@ -406,6 +411,10 @@ sub framework_ {
$cont->() unless $o{hiddenmsg} && _hidden_msg_ \%o;
div_ id => 'footer', \&_footer_;
};
+ script_ type => 'application/json', id => 'pagevars', sub {
+ # Escaping rules for a JSON <script> context are kinda weird, but more efficient than regular xml_escape().
+ lit_(JSON::XS->new->canonical->encode(\%pagevars) =~ s{</}{<\\/}rg =~ s/<!--/<\\u0021--/rg);
+ } if keys %pagevars;
script_ type => 'application/javascript', src => config->{url_static}.'/f/v2rw.js?'.config->{version}, '';
}
}
diff --git a/lib/VNWeb/User/Lists.pm b/lib/VNWeb/User/Lists.pm
index 7d599536..2558963b 100644
--- a/lib/VNWeb/User/Lists.pm
+++ b/lib/VNWeb/User/Lists.pm
@@ -125,10 +125,7 @@ sub vn_ {
my @l = grep $l{$_->{id}} && $_->{id} != 7, @$labels;
my $txt = @l ? join ', ', map $_->{label}, @l : '-';
if($own) {
- # XXX: Copying the entire $labels list for each entry is rather inefficient, would be nice if we could store that globally.
- my @labels = grep $_->{id} != 7, @$labels;
- elm_ 'ULists.LabelEdit' => $VNLABELS_OUT,
- { uid => $uid, vid => $v->{id}, labels => \@labels, selected => [ grep $_ != 7, $v->{labels}->@* ] }, $txt;
+ elm_ 'ULists.LabelEdit' => $VNLABELS_OUT, { vid => $v->{id}, selected => [ grep $_ != 7, $v->{labels}->@* ] }, $txt;
} else {
txt_ $txt;
}
@@ -240,12 +237,16 @@ TUWF::get qr{/$RE{uid}/ulist}, sub {
my $title = $own ? 'My list' : user_displayname($u)."'s list";
framework_ title => $title, type => 'u', dbobj => $u, tab => 'list',
+ $own ? ( pagevars => {
+ uid => $u->{id}*1,
+ labels => $LABELS->analyze->{keys}{labels}->coerce_for_json($labels),
+ } ) : (),
sub {
my $opt;
div_ class => 'mainbox', sub {
h1_ $title;
$opt = filters_ $own, $labels;
- elm_ 'ULists.ManageLabels', $LABELS, { uid => $u->{id}, labels => $labels } if $own;
+ elm_ 'ULists.ManageLabels' if $own;
};
listing_ $u->{id}, $own, $opt, $labels;
};