I am creating a html-only(no server sided code) website that supposed to have multiple themes,
Wherein user can select/change a theme to view the website.
Can you suggest some concepts on how to do this or at least point me into some helpful articles.
Thank you for your help in advance.
The most common approach is to use different external CSS stylesheets, that will get switched based on the selected theme. You also need to structure your DOM wisely, so as to allow different layouts provided by themes.
Probably overkill... but here is a font selector example I came up with using Google Font API and a Document Fragment Builder script I wrote a while ago.
var FragBuilder = (function() {
var applyStyles = function(element, style_object) {
for (var prop in style_object) {
element.style[prop] = style_object[prop];
}
};
var generateFragmentFromJSON = function(json) {
var tree = document.createDocumentFragment();
json.forEach(function(obj) {
if (!('tagName' in obj) && 'textContent' in obj) {
tree.appendChild(document.createTextNode(obj['textContent']));
} else if ('tagName' in obj) {
var el = document.createElement(obj.tagName);
delete obj.tagName;
for (part in obj) {
var val = obj[part];
switch (part) {
case ('textContent'):
el.appendChild(document.createTextNode(val));
break;
case ('style'):
applyStyles(el, val);
break;
case ('childNodes'):
el.appendChild(generateFragmentFromJSON(val));
break;
default:
if (part in el) {
el[part] = val;
}
break;
}
}
tree.appendChild(el);
} else {
throw "Error: Malformed JSON Fragment";
}
});
return tree;
};
var generateFragmentFromString = function(HTMLstring) {
var div = document.createElement("div"),
tree = document.createDocumentFragment();
div.innerHTML = HTMLstring;
while (div.hasChildNodes()) {
tree.appendChild(div.firstChild);
}
return tree;
};
return function(fragment) {
if (typeof fragment === 'string') {
return generateFragmentFromString(fragment);
} else {
return generateFragmentFromJSON(fragment);
}
};
}());
function jsonp(url) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
document.getElementsByTagName('body')[0].appendChild(script);
}
function replacestyle(url) {
if (!document.getElementById('style_tag')) {
var style_tag = document.createElement('link');
style_tag.rel = 'stylesheet';
style_tag.id = 'style_tag';
style_tag.type = 'text/css';
document.getElementsByTagName('head')[0].appendChild(style_tag);
replacestyle(url);
}
document.getElementById('style_tag').href = url;
}
function loadFonts(json) {
var select_frag = [
{
'tagName': 'select',
'id': 'font-selection',
'childNodes': [
{
'tagName': 'option',
'value': 'default',
'textContent': 'Default'}
]}
];
json['items'].forEach(function(item) {
var family_name = item.family,
value = family_name.replace(/ /g, '+');
if (item.variants.length > 0) {
item.variants.forEach(function(variant) {
value += ':' + variant;
});
}
select_frag[0].childNodes.push({
'tagName': 'option',
'value': value,
'textContent': family_name
});
});
document.getElementById('container').appendChild(FragBuilder(select_frag));
document.getElementById('font-selection').onchange = function(e) {
var font = this.options[this.selectedIndex].value,
name = this.options[this.selectedIndex].textContent;
if (font === 'default') {
document.getElementById('sink').style.fontFamily = 'inherit';
} else {
document.getElementById('sink').style.fontFamily = name;
replacestyle('https://fonts.googleapis.com/css?family=' + font);
}
};
}
jsonp("https://www.googleapis.com/webfonts/v1/webfonts?key=AIzaSyDBzzPRqWl2eU_pBMDr_8mo1TbJgDkgst4&sort=trending&callback=loadFonts");
Here is the Kitchen Sink example...
You could use jQuery style switchers. Check out below link which also have step-by-step tutorials on how to do it:
http://www.net-kit.com/10-practical-jquery-style-switchers/
There is also an article of how to do it here:
http://net.tutsplus.com/tutorials/javascript-ajax/jquery-style-switcher/
Cudos answered already to this question but I'll post this anyway.
You can modify this tutorial (http://net.tutsplus.com/tutorials/javascript-ajax/jquery-style-switcher/) posted by Cudos to consist only of client side code with these modifications to index.php file.
Remove the PHP block and add this function.
<script language="javascript" type="text/javascript">
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
var js_style = readCookie(style);
</script>
And replace the PHP style switcher with this.
<script language="javascript" type="text/javascript">
if (typeof js_style == 'undefined') { js_style = 'day'; }
document.write('<link id="stylesheet" type="text/css" href="css/' + js_style + '.css" rel="stylesheet" />');
</script>
Worked for me...
As Alexander Pavlov writes in his answer, use different external style sheets. To make it possible to users to select a theme persistently (i.e., so that the theme is preserved when moving to another page of the site, or when returning to the site next day), use either HTTP cookies or HTML5-style localStorage. They let you store information locally in the user’s computer, without any server-side code.
There are many tutorials on localStorage (also known as HTML storage or Web storage), and it’s difficult to pick up any particular. They are more flexible than cookies, and they could be used to store large amounts of data, even user-tailored stylesheets. The main problem with localStorage is lack of support on IE 7 and earlier. You might decide that people using them just won’t get the customizability. Alternatively, you can use cookiers, userData (IE), dojox.storage, or other tools to simulate localStorage on antique browsers.
Related
I wanted to make my webpage a multi language page so I used the following js code:
let langs = ['en', 'it', 'sp', 'sv', 'de', 'pt', 'nl'];
let lang = 'en';
setLangStyles(lang);
function setStyles(styles) {
var elementId = '__lang_styles';
var element = document.getElementById(elementId);
if (element) {
element.remove();
}
let style = document.createElement('style');
style.id = elementId;
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = styles;
} else {
style.appendChild(document.createTextNode(styles));
}
document.getElementsByTagName('head')[0].appendChild(style);
}
function setLang(lang) {
setLangStyles(lang);
}
function setLangStyles(lang) {
let styles = langs
.filter(function (l) {
return l != lang;
})
.map(function (l) {
return ':lang('+ l +') { display: none; }';
})
.join(' ');
setStyles(styles);
}
I named it "lang.js" and I tagged it on my html this way:
<!-- jquery -->
<script src="js/jquery-2.1.4.min.js"></script>
<!-- Bootstrap -->
<script src="js/bootstrap.min.js"></script>
<script src="js/scripts.js"></script>
<script src="js/lang.js"></script>
Locally it works perfectly, but when it's on server, it doesn't open the webpage at all, all I see is a blank page.
I tried also to write it inside the html with script> tag, it didn't work either.
In network tab of dev tools, the jquery-2.1.4.min.js file and bootstrap files showing as 200 status.
There are no errors on console
Can anyone help please?
Thank you in advance
Here is my version of your function. It loops through the array of langs, then grabs a queryselectorAll of the matching lang elements. If the passed language matches the array element in the loop the display is set to block, else its set to none.
langs = ["en", "es"];
function setLangStyles(lang) {
for (z = 0; z < langs.length; z++) {
l = langs[z]
objs = document.querySelectorAll(":lang(" + l + ")");
objs.forEach(function(el) {
el.style.display = (l == lang) ? "block" : "none";
});
}
}
btns = document.querySelectorAll("button");
btns.forEach(function(e){
e.addEventListener("click",function(ev){
setLangStyles(ev.target.dataset.lang);
});
});
setLangStyles("es");
<div lang="en">EN</div>
<div lang="es">ES</div>
<button type="button" data-lang="es">View ES</button>
<button type="button" data-lang="en">View EN</button>
I have the following tag in my view.jsp:
<liferay-ui:input-localized id="message" name="message" xml="" />
And I know that I can set a XML and have a default value on my input localized. My problem is that I want to change this attribute with javascript. I am listening for some changes and call the function "update()" to update my information:
function update(index) {
var localizedInput= document.getElementById('message');
localizedInput.value = 'myXMLString';
}
Changing the value is only updating the currently selected language input (with the whole XML String). The XML String is correct, but I am not sure on how to update the XML for the input with javascript.
Is this possible?
PS: I have posted this in the Liferay Dev forum to try and reach more people.
After a week of studying the case and some tests, I think that I found a workaround for this. Not sure if this is the correct approach, but it is working for me so I will post my current solution for future reference.
After inspecting the HTML, I noticed that the Liferay-UI:input-localized tag creates an input tag by default, and then one more input tag for each language, each time you select a new language. Knowing that I created some functions with Javascript to help me update the inputs created from my liferay-ui:input-localized. Here is the relevant code:
function updateAnnouncementInformation(index) {
var announcement = announcements[index];
// the announcement['message'] is a XML String
updateInputLocalized('message', announcement['message']);
}
function updateInputLocalized(input, message) {
var inputId = '<portlet:namespace/>' + input;
var xml = $.parseXML(message);
var inputCurrent = document.getElementById(inputId);
var selectedLanguage = getSelectedLanguage(inputId);
var inputPT = document.getElementById(inputId + '_pt_PT');
inputPT.value = $(xml).find("Title[language-id='pt_PT']").text();
var inputEN = document.getElementById(inputId + '_en_US');
if (inputEN !== null) inputEN.value = $(xml).find("Title[language-id='en_US']").text();
else waitForElement(inputId + '_en_US', inputCurrent, inputId, xml);
var inputLabel = getInputLabel(inputId);
if (selectedLanguage == 'pt-PT') inputLabel.innerHTML = '';
else inputLabel.innerHTML = inputPT.value;
if (selectedLanguage == 'pt-PT') inputCurrent.value = inputPT.value;
else if (inputEN !== null) inputCurrent.value = inputEN.value;
else waitForElement(inputId + '_en_US', inputCurrent, inputId, xml);
}
function getSelectedLanguage(inputId) {
var languageContainer = document.getElementById('<portlet:namespace/>' + inputId + 'Menu');
return languageContainer.getElementsByClassName('btn-section')[0].innerHTML;
}
function getInputLabel(inputId) {
var boundingBoxContainer = document.getElementById(inputId + 'BoundingBox').parentElement;
return boundingBoxContainer.getElementsByClassName('form-text')[0];
}
function waitForElement(elementId, inputCurrent, inputId, xml) {
window.setTimeout(function() {
var element = document.getElementById(elementId);
if (element) elementCreated(element, inputCurrent, inputId, xml);
else waitForElement(elementId, inputCurrent, inputId, xml);
}, 500);
}
function elementCreated(inputEN, inputCurrent, inputId, xml) {
inputEN.value = $(xml).find("Title[language-id='en_US']").text();
var selectedLanguage = getSelectedLanguage(inputId);
if (selectedLanguage == 'en-US') inputCurrent.value = inputEN.value;
}
With this I am able to update the liferay-ui:input-localized inputs according to a pre-built XML String. I hope that someone finds this useful and if you have anything to add, please let me know!
To change the text value of an element, you must change the value of the elements's text node.
Example -
xmlDoc.getElementsByTagName("title")[0].childNodes[0].nodeValue = "new content"
Suppose "books.xml" is loaded into xmlDoc
Get the first child node of the element
Change the node value to "new content"
I'm working on a site where users can paste in embed codes from the likes of twitter, youtube, instagram, facebook, etc. The Embed code is validated and saved if valid.
The users can then see and edit the code and this is where some code fails validation. E.g. Twitter embed codes may contain < (aka '<') in the post name/text. When pasting in the code originally it passes validation as it contains <, but when displaying the code back to the user the browser shows < in the textarea and this is then submitted if the user clicks save. Our validation function treats this as the start of a tag and the validation fails.
Possible solution 1:
Better validation. The validation we use now looks like this It basically finds the tags (by looking for '<' etc) and checks that each open tag has a closing tag. There must be a better/standard/commonly used way:
(function($) {
$.validateEmbedCode = function(code) {
//validating
var input = code;
var tags = [];
$.each(input.split('\n'), function (i, line) {
$.each(line.match(/<[^>]*[^/]>/g) || [], function (j, tag) {
var matches = tag.match(/<\/?([a-z0-9]+)/i);
if (matches) {
tags.push({tag: tag, name: matches[1], line: i+1, closing: tag[1] == '/'});
}
});
});
if (tags.length == 0) {
return true;
}
var openTags = [];
var error = false;
var indent = 0;
for (var i = 0; i < tags.length; i++) {
var tag = tags[i];
if (tag.closing) {
// This tag is a closing tag. Decide what to do accordingly.
var closingTag = tag;
if (isSelfClosingTag(closingTag.name)) {
continue;
}
if (openTags.length == 0) {
return false;
}
var openTag = openTags[openTags.length - 1];
if (closingTag.name != openTag.name) {
return false;
} else {
openTags.pop();
}
} else {
var openTag = tag;
if (isSelfClosingTag(openTag.name)) {
continue;
}
openTags.push(openTag);
}
}
if (openTags.length > 0) {
var openTag = openTags[openTags.length - 1];
return false;
}
return true
};
}
Possible solution 2:
Encode the text containing '<' (i.e. textLine.replace(/</g, '<')) without encoding tags like <blockquote class="...>.
I've been experimenting with something like:
$(widget.find("textarea[name='code']").val()).find('*')
.each(function(){
// validate $(this).text() here. Need to get text only line by
// line as some elements look like <p>some text <a ...>text
// </a>more text etc</p>
});
Possible solution 3:
Display < as < and not < in the browser/textarea. We use icanhaz for templating (much like moustache).
Using date.code = '<' with <textarea name="code">{{{code}}}</textarea> in the template does not work, neither does {{code}}.
So I played some more and the following works, but I am still interested in suggestions for better embed code validation or better answers.
After the edit form (inc textarea) code is created using the icanhaz template (i.e. after widget = ich.editEmbedWidgetTemplate(encoded_data);) I do the following to encode instances of < etc into < etc. ' has to be encoded manually using replace.
var embedCode = '';
$( widget.find("textarea[name='code']").val() )
.filter('*')
.each(function(){
embedCode += this.outerHTML.replace(/'/g, ''');
});
widget.find("textarea[name='code']").val(embedCode);
I'm not too good on the whole JavaScript (I can do some basic validations) but this isn't my zone
I've got a piece of code below that I'm trying to understand what it does, I can read any code and understand a few parts, but this just stumped me.
Here:
function tm_search_click() {
if (document.getElementById('tm').value == 'Enter your trademark') {
document.getElementById('tm').style.backgroundColor = '#fcc';
return false;
} else {
window.location = '?tm=' + escape(document.getElementById('tm').value);
return true;
}
}
function qs(a) {
a = a.replace(/[[]/, "\[").replace(/[]]/, "\]");
var b = "[\?&]" + a + "=([^&#]*)";
var c = new RegExp(b);
var d = c.exec(window.location.href);
return d == null ? "" : decodeURIComponent(d[1]).replace(/+/g, " ")
}
if (qs("tm") != "") {
tm_trademark = document.getElementById("tm").value = unescape(qs("tm"));
tm_partner = "migu2008";
tm_frame_width = 630;
tm_frame_height = "auto";
tm_trademark_country_code = "GB";
tm_css_url = "http://remarqueble.com/api/theme/search_corporate.css";
document.getElementById("tmLoading").style.display = "block";
tm_on_search_result = function () {
document.getElementById("tmLoading").style.display = "none";
document.getElementById("tmLoaded").style.display = "block"
}
} else {
tm_search_method = "none"
}
That is all of it without the <script> tags.
Could I also edit this code so that it searches are made based on what option the user inputs?
I think it works like this (assuming that this is in tags inside html page)
Page loads.
The script checks if URL has 'tm' parameter. If it has, then it sets bunch of tm_.. parameters and callback function. I don't know how they are used.
User clicks something that triggers the tm_search_click
Script sets new URL for the page and browser starts loading that
Goto step 1.
I have two scripts in a file active_form.js
The first script hides a text entry when a radiobutton is checked and the second does the same thing when a value is selected in a list.
When there are alone, the both work but together my function GereControleRadio do nothing.
edit : the two scripts are called in the same form.
The code of my scripts :
function GereControleRadio(Controleur, LabelControle, Controle, Masquer) {
var objLabelControle = document.getElementById(LabelControle);
var objControle = document.getElementById(Controle);
if (Masquer=='1') {
objControle.style.visibility=(objControleur.checked==true)?'visible':'hidden';
objLabelControle.style.visibility=(objControleur.checked==true)?'visible':'hidden';
}
else {
objControle.disabled=(objControleur.checked==true)?false:true;
objLabelControle.disabled=(objControleur.checked==true)?false:true;
}
return true;
};
function GereControleList(LabelControle, Controle, val) {
var objLabelControle = document.getElementById(LabelControle);
var objControle = document.getElementById(Controle);
if (val != '1% Patronal') {
objControle.style.visibility='hidden';
objLabelControle.style.visibility='hidden';
}
else {
objControle.style.visibility='visible';
objLabelControle.style.visibility='visible';
}
return true;
};
The .js is called in my view.yml
And I call the functions :
echo $form['etage']->render(array("onCLick" => "GereControleRadio('logement_etage_Etage', 'numetage_label', 'numetage_form, '1');"))
echo $form['reservataire']->render(array("onChange" => "GereControleList('patronal', 'patronal_form', 'this.value');"))
I believe you just have 2 functions with conflicting global scope variable names. Try replacing "GereControleList" with this...
function GereControleList(LabelControle, Controle, val) {
var objLabelControle_ = document.getElementById(LabelControle);
var objControle_ = document.getElementById(Controle);
if (val != '1% Patronal') {
objControle_.style.visibility='hidden';
objLabelControle_.style.visibility='hidden';
}
else {
objControle_.style.visibility='visible';
objLabelControle_.style.visibility='visible';
}
return true;
};
I have found the error : in GereControleRadio, I have deleted a line.
var objControleur = document.getElementById(Controleur);