Commit 3ef68e80 authored by Romain Courteaud's avatar Romain Courteaud

erp5_web_js_style: handle language change and sitemap navigation

Changing the language and browsing to another web section require to fully refresh the page.
parent 741b05d2
......@@ -15,7 +15,9 @@ def generateSectionListHTML(result_list, section_list):
if (section_list):
result_list.append('<ul>')
for section in section_list:
result_list.append('<li><a href="%s">%s</a>' % (_(section['url']), __(section['translated_title'])))
# Add missing / suffix to get correct relative url generation
# XXX Fix WebSection_getSiteMapTree instead, but no idea what would be the site effects
result_list.append('<li><a href="%s/">%s</a>' % (_(section['url']), __(section['translated_title'])))
generateSectionListHTML(result_list, section['subsection'])
result_list.append('</li>')
result_list.append('</ul>')
......
......@@ -74,6 +74,53 @@
}
function parseLanguageElement(language_element) {
var language_list = [],
li_list = language_element.querySelectorAll('a'),
i;
for (i = 0; i < li_list.length; i += 1) {
language_list.push({
href: li_list[i].href,
text: li_list[i].hreflang
});
}
return language_list;
}
function parseSitemapElement(sitemap_element) {
var a = sitemap_element.querySelector('a'),
sitemap = {
href: a.href,
text: a.textContent,
child_list: []
},
ul = a.nextElementSibling,
li_list,
i;
if (ul === null) {
li_list = [];
} else {
li_list = ul.children;
}
for (i = 0; i < li_list.length; i += 1) {
sitemap.child_list.push(parseSitemapElement(li_list[i]));
}
return sitemap;
}
function parsePageContent(body_element) {
return {
html_content: body_element.querySelector('main').innerHTML,
language_list: parseLanguageElement(
body_element.querySelector('nav#language')
),
sitemap: parseSitemapElement(
body_element.querySelector('nav#sitemap')
)
};
}
function renderPage(gadget, page_url, hash) {
return new RSVP.Queue(RSVP.hash({
xhr: ajax(page_url),
......@@ -83,17 +130,44 @@
var dom_parser = (new DOMParser()).parseFromString(
result_dict.xhr.responseText,
'text/html'
);
document.title = dom_parser.title;
return result_dict.style_gadget.render(
dom_parser.body.querySelector('main').innerHTML
);
),
parsed_content = parsePageContent(dom_parser.body);
gadget.parsed_content = parsed_content;
parsed_content.page_title = dom_parser.title;
return result_dict.style_gadget.render(parsed_content.html_content,
parsed_content);
})
.push(function () {
return scrollToHash(hash);
});
}
function isAnotherSitemapLocation(sitemap, url1, url2) {
var is_url1_matching = (url1.indexOf(sitemap.href) === 0),
is_child_another_location,
i;
if (is_url1_matching && (url2.indexOf(sitemap.href) !== 0)) {
return true;
}
if (!is_url1_matching) {
// Both url do not match
return false;
}
// If both match, check sub urls
for (i = 0; i < sitemap.child_list.length; i += 1) {
is_child_another_location = isAnotherSitemapLocation(
sitemap.child_list[i],
url1,
url2
);
if (is_child_another_location) {
return true;
}
}
return false;
}
function listenURLChange() {
var gadget = this;
......@@ -109,7 +183,9 @@
function handleClick(evt) {
var target_element = evt.target.closest('a'),
base_uri = document.baseURI,
link_url;
link_url,
matching_language_count = 0,
matching_language_base_uri_count = 0;
if (!target_element) {
// Only handle link
......@@ -126,6 +202,30 @@
link_url = new URL(target_element.href, base_uri);
if (link_url.href.indexOf(base_uri) !== 0) {
// Only handle sub path of the base url
// Meaning it will also reload when going from a non default language
// to the default one
return;
}
// Check if going from the default language to another one
// Check if url is suburl from 2 languages (default + the expected one)
gadget.parsed_content.language_list.map(function (language) {
if (link_url.href.indexOf(language.href) === 0) {
matching_language_count += 1;
}
// Ensure current url is in the default language
if (base_uri.indexOf(language.href) === 0) {
matching_language_base_uri_count += 1;
}
});
if ((1 < matching_language_count) &&
(matching_language_base_uri_count === 1)) {
return;
}
// Check if going from a section to a child one
if (isAnotherSitemapLocation(gadget.parsed_content.sitemap,
link_url.href, base_uri)) {
return;
}
......@@ -145,7 +245,7 @@
// to ensure popstate listener is correctly working
// when the user will click on back/forward browser buttons
history.pushState(null, null, target_element.href);
}, function (error) {
}, function () {
// Implement support for managed error
// (like URL is not an HTML document parsable)
// and redirect in such case
......@@ -161,11 +261,6 @@
}
rJS(window)
.ready(function () {
// Hide the page as fast as possible
// this.element.hidden = true;
this.main_element = this.element.querySelector('main');
})
.allowPublicAcquisition("reportServiceError", function () {
this.element.hidden = false;
throw rJS.AcquisitionError();
......@@ -175,13 +270,17 @@
var gadget = this,
style_gadget,
body = gadget.element,
style_gadget_url = body.getAttribute("data-nostyle-gadget-url");
style_gadget_url = body.getAttribute("data-nostyle-gadget-url"),
parsed_content;
if (!style_gadget_url) {
// No style configured, use backend only rendering
return rJS.declareCSS("jsstyle.css", document.head);
}
parsed_content = parsePageContent(gadget.element);
gadget.parsed_content = parsed_content;
parsed_content.page_title = document.title;
// Clear the DOM
while (body.firstChild) {
body.firstChild.remove();
......@@ -189,7 +288,8 @@
return gadget.declareGadget(style_gadget_url, {scope: 'renderer'})
.push(function (result) {
style_gadget = result;
return style_gadget.render(gadget.main_element.innerHTML);
return style_gadget.render(parsed_content.html_content,
parsed_content);
})
.push(function () {
// Trigger URL handling
......
/*globals window, document, RSVP, rJS*/
/*jslint indent: 2, maxlen: 80*/
(function () {
"use strict";
rJS(window)
.declareMethod("render", function (main_innerhtml) {
var gadget = this;
var div = document.createElement('div');
div.innerHTML = main_innerhtml;
gadget.element.querySelector('main').innerHTML = div.querySelector('div.input').firstChild.innerHTML;
});
}());
\ No newline at end of file
......@@ -8,11 +8,12 @@
dummy python: request.set('ignore_layout', False);
dummy python: request.set('editable_mode', False);
web_site python: here.getWebSiteValue();
web_section python: here.getWebSectionValue();
global_definitions_macros here/global_definitions/macros;">
<tal:block metal:use-macro="global_definitions_macros/header_definitions" />
<html>
<head>
<base tal:attributes="href python: '%s/' % web_site.absolute_url()" />
<base tal:attributes="href python: '%s/' % web_section.absolute_url()" />
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1" />
<title tal:content="python: here.getTranslatedTitle() or web_site.getTranslatedTitle()"></title>
<noscript>
......
......@@ -11,10 +11,24 @@
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tr>
<td>open</td>
<td>${base_url}/ERP5Site_createWebJSStyleZuiteTestData?configuration=section</td>
<td></td>
</tr>
<tr>
<td>assertTextPresent</td>
<td>Web Site created.</td>
<td></td>
</tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/wait_for_activities" />
<!-- Initialize -->
<tr>
<td>open</td>
<td>${base_url}/web_site_module/test_js_style_demo_style/</td>
<td>${base_url}/web_site_module/erp5_web_js_style_test_site/</td>
<td></td>
</tr>
......
......@@ -11,13 +11,26 @@
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<!-- Initialize -->
<tr>
<td>open</td>
<td>${base_url}/web_site_module/test_js_style_no_style/</td>
<td>${base_url}/ERP5Site_createWebJSStyleZuiteTestData?configuration=nostyle</td>
<td></td>
</tr>
<tr>
<td>assertTextPresent</td>
<td>Web Site created.</td>
<td></td>
</tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/wait_for_activities" />
<!-- Initialize -->
<tr>
<td>open</td>
<td>${base_url}/web_site_module/erp5_web_js_style_test_site/</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//nav[@id='sitemap']/a[text()='No Style']</td>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>testJsStyleWebSectionNavigateBaseLink</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test JS Style Demo Style</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">Test JS Style Demo Style</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tr>
<td>open</td>
<td>${base_url}/ERP5Site_createWebJSStyleZuiteTestData?configuration=language</td>
<td></td>
</tr>
<tr>
<td>assertTextPresent</td>
<td>Web Site created.</td>
<td></td>
</tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/wait_for_activities" />
<!-- Initialize -->
<tr>
<td>open</td>
<td>${base_url}/web_site_module/erp5_web_js_style_test_site/erp5_web_js_style_test_section_1/</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//header/h1[text()='JS Style Demo']</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Check the page content uses the default language (en)</b></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//main//p[text()='Frontpage content']</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//main//p[text()='Frontpage content']</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//p[@id='gadget_style_url'][contains(text(), 'erp5_web_js_style_test_site/erp5_web_js_style_test_section_1/jsstyle_demo.html')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//p[@id='render_count'][contains(text(), '1')]</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Click on the absolute home link</b></td>
</tr>
<tr>
<td>click</td>
<td>//main//a[text()='base link']</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//p[@id='render_count'][contains(text(), '2')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//main//p[text()='Frontpage content']</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//p[@id='gadget_style_url'][contains(text(), 'erp5_web_js_style_test_site/erp5_web_js_style_test_section_1/jsstyle_demo.html')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//p[@id='render_count'][contains(text(), '2')]</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Change to the not default language</b></td>
</tr>
<tr>
<td>open</td>
<td>${base_url}/web_site_module/erp5_web_js_style_test_site/fr/erp5_web_js_style_test_section_1/</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//header/h1[text()='JS Style Demo']</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Check the page content uses the second language (fr)</b></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//main//p[text()="Contenu de la page d'accueil"]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//main//p[text()="Contenu de la page d'accueil"]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//p[@id='gadget_style_url'][contains(text(), 'erp5_web_js_style_test_site/fr/erp5_web_js_style_test_section_1/jsstyle_demo.html')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//p[@id='render_count'][contains(text(), '1')]</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Click on the absolute home link</b></td>
</tr>
<tr>
<td>click</td>
<td>//main//a[text()='base link']</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//p[@id='render_count'][contains(text(), '2')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//main//p[text()="Contenu de la page d'accueil"]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//p[@id='gadget_style_url'][contains(text(), 'erp5_web_js_style_test_site/fr/erp5_web_js_style_test_section_1/jsstyle_demo.html')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//p[@id='render_count'][contains(text(), '2')]</td>
<td></td>
</tr>
</tbody></table>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>testJsStyleWebSectionNavigateByReference</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test JS Style Demo Style</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">Test JS Style Demo Style</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tr>
<td>open</td>
<td>${base_url}/ERP5Site_createWebJSStyleZuiteTestData?configuration=language</td>
<td></td>
</tr>
<tr>
<td>assertTextPresent</td>
<td>Web Site created.</td>
<td></td>
</tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/wait_for_activities" />
<!-- Initialize -->
<tr>
<td>open</td>
<td>${base_url}/web_site_module/erp5_web_js_style_test_site/erp5_web_js_style_test_section_1/</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//header/h1[text()='JS Style Demo']</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Check the page content uses the default language (en)</b></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//main//p[text()='Frontpage content']</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//main//p[text()='Frontpage content']</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//p[@id='gadget_style_url'][contains(text(), 'erp5_web_js_style_test_site/erp5_web_js_style_test_section_1/jsstyle_demo.html')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//p[@id='render_count'][contains(text(), '1')]</td>
<td></td>
</tr>
<tr>
<td colspan="3"><b>Click on the document reference</b></td>
</tr>
<tr>
<td>click</td>
<td>//a[text()='erp5_web_js_style_test_contentpage']</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//main//p[text()='Subpage content']</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//main//p[text()='Subpage content']</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//p[@id='gadget_style_url'][contains(text(), 'erp5_web_js_style_test_site/erp5_web_js_style_test_section_1/jsstyle_demo.html')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>