From 451ffafb9ec074b30aee8308fc4eda0c9f0b818d Mon Sep 17 00:00:00 2001 From: psychobunny Date: Fri, 20 Sep 2013 16:01:52 -0400 Subject: [PATCH] finished initial client side & server side language parsing methods; integrated preloading into ajaxify and server app.js --- public/src/ajaxify.js | 27 ++++++------ public/src/app.js | 92 ++++++++++++++++++++-------------------- public/src/translator.js | 92 +++++++++++++++++++++++++++++++++------- 3 files changed, 139 insertions(+), 72 deletions(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 5cf650703a..e42e927274 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -1,7 +1,8 @@ var ajaxify = {}; -(function($) { +(function ($) { + /*global app, templates, utils*/ var location = document.location || window.location, rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''), @@ -11,7 +12,7 @@ var ajaxify = {}; var executed = {}; var events = []; - ajaxify.register_events = function(new_page_events) { + ajaxify.register_events = function (new_page_events) { for (var i = 0, ii = events.length; i < ii; i++) { socket.removeAllListeners(events[i]); // optimize this to user removeListener(event, listener) instead. } @@ -20,14 +21,14 @@ var ajaxify = {}; }; - window.onpopstate = function(event) { + window.onpopstate = function (event) { // "quiet": If set to true, will not call pushState if (event !== null && event.state && event.state.url !== undefined) ajaxify.go(event.state.url, null, null, true); }; var pagination; - ajaxify.go = function(url, callback, template, quiet) { + ajaxify.go = function (url, callback, template, quiet) { // start: the following should be set like so: ajaxify.onchange(function(){}); where the code actually belongs $(window).off('scroll'); app.enter_room('global'); @@ -66,10 +67,12 @@ var ajaxify = {}; }, url, RELATIVE_PATH + "/" + url); } + translator.load(tpl_url); + jQuery('#footer, #content').fadeOut(100); templates.flush(); - templates.load_template(function() { + templates.load_template(function () { exec_body_scripts(content); if (callback) { @@ -78,7 +81,7 @@ var ajaxify = {}; app.process_page(); - jQuery('#content, #footer').stop(true, true).fadeIn(200, function() { + jQuery('#content, #footer').stop(true, true).fadeIn(200, function () { if (window.location.hash) hash = window.location.hash; if (hash) @@ -93,15 +96,15 @@ var ajaxify = {}; } return false; - } + }; - $('document').ready(function() { + $('document').ready(function () { if (!window.history || !window.history.pushState) return; // no ajaxification for old browsers content = content || document.getElementById('content'); // Enhancing all anchors to ajaxify... - $(document.body).on('click', 'a', function(e) { + $(document.body).on('click', 'a', function (e) { function hrefEmpty(href) { return href == 'javascript:;' || href == window.location.href + "#" || href.slice(-1) === "#"; @@ -127,7 +130,7 @@ var ajaxify = {}; function nodeName(elem, name) { return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); - }; + } function evalScript(elem) { var data = (elem.text || elem.textContent || elem.innerHTML || ""), @@ -149,7 +152,7 @@ var ajaxify = {}; head.insertBefore(script, head.firstChild); //TODO: remove from head before inserting?, doing this breaks scripts in safari so commented out for now //head.removeChild(script); - }; + } var scripts = [], script, @@ -172,6 +175,6 @@ var ajaxify = {}; } evalScript(scripts[i]); } - }; + } }(jQuery)); \ No newline at end of file diff --git a/public/src/app.js b/public/src/app.js index 00aaf7c47c..950b3b6d44 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -4,14 +4,14 @@ var socket, API_URL = null; -(function() { +(function () { var showWelcomeMessage = false; function loadConfig() { $.ajax({ url: RELATIVE_PATH + '/api/config', - success: function(data) { + success: function (data) { API_URL = data.api_url; config = data; @@ -20,19 +20,19 @@ var socket, var reconnecting = false; var reconnectTries = 0; - socket.on('event:connect', function(data) { + socket.on('event:connect', function (data) { console.log('connected to nodebb socket: ', data); app.username = data.username; app.showLoginMessage(); }); - socket.on('event:alert', function(data) { + socket.on('event:alert', function (data) { app.alert(data); }); - socket.on('connect', function(data) { + socket.on('connect', function (data) { if (reconnecting) { - setTimeout(function() { + setTimeout(function () { app.alert({ alert_id: 'connection_alert', title: 'Connected', @@ -49,14 +49,14 @@ var socket, } }); - socket.on('reconnecting', function(data) { + socket.on('reconnecting', function (data) { function showDisconnectModal() { $('#disconnect-modal').modal({ backdrop: 'static', show: true }); - $('#reload-button').on('click', function() { + $('#reload-button').on('click', function () { $('#disconnect-modal').modal('hide'); window.location.reload(); }); @@ -79,8 +79,8 @@ var socket, }); }); - socket.on('api:user.get_online_users', function(users) { - jQuery('a.username-field').each(function() { + socket.on('api:user.get_online_users', function (users) { + jQuery('a.username-field').each(function () { if (this.processed === true) return; @@ -97,7 +97,7 @@ var socket, el.processed = true; }); - jQuery('button .username-field').each(function() { + jQuery('button .username-field').each(function () { //DRY FAIL if (this.processed === true) return; @@ -124,17 +124,17 @@ var socket, } // takes a string like 1000 and returns 1,000 - app.addCommas = function(text) { + app.addCommas = function (text) { return text.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,"); } // Willingly stolen from: http://phpjs.org/functions/strip_tags/ - app.strip_tags = function(input, allowed) { + app.strip_tags = function (input, allowed) { allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join(''); // making sure the allowed arg is a string containing only tags in lowercase () var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, commentsAndPhpTags = /|<\?(?:php)?[\s\S]*?\?>/gi; - return input.replace(commentsAndPhpTags, '').replace(tags, function($0, $1) { + return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) { return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''; }); } @@ -145,14 +145,14 @@ var socket, // message = alert message content // timeout default = permanent // location : alert_window (default) or content - app.alert = function(params) { + app.alert = function (params) { var alert_id = 'alert_button_' + ((params.alert_id) ? params.alert_id : new Date().getTime()); var alert = $('#' + alert_id); function startTimeout(div, timeout) { - var timeoutId = setTimeout(function() { - $(div).fadeOut(1000, function() { + var timeoutId = setTimeout(function () { + $(div).fadeOut(1000, function () { $(this).remove(); }); }, timeout); @@ -185,7 +185,7 @@ var socket, button.className = 'close'; button.innerHTML = '×'; - button.onclick = function(ev) { + button.onclick = function (ev) { div.parentNode.removeChild(div); } @@ -199,9 +199,9 @@ var socket, } if (params.clickfn) { - div.onclick = function() { + div.onclick = function () { params.clickfn(); - jQuery(div).fadeOut(500, function() { + jQuery(div).fadeOut(500, function () { this.remove(); }); } @@ -209,7 +209,7 @@ var socket, } } - app.alertSuccess = function(message, timeout) { + app.alertSuccess = function (message, timeout) { if (!timeout) timeout = 2000; @@ -221,7 +221,7 @@ var socket, }); } - app.alertError = function(message, timeout) { + app.alertError = function (message, timeout) { if (!timeout) timeout = 2000; @@ -234,7 +234,7 @@ var socket, } app.current_room = null; - app.enter_room = function(room) { + app.enter_room = function (room) { if (socket) { if (app.current_room === room) return; @@ -248,20 +248,20 @@ var socket, } }; - app.populate_online_users = function() { + app.populate_online_users = function () { var uids = []; - jQuery('.post-row').each(function() { + jQuery('.post-row').each(function () { uids.push(this.getAttribute('data-uid')); }); socket.emit('api:user.get_online_users', uids); } - app.process_page = function() { + app.process_page = function () { // here is where all modules' onNavigate should be called, I think. - require(['mobileMenu'], function(mobileMenu) { + require(['mobileMenu'], function (mobileMenu) { mobileMenu.onNavigate(); }); @@ -273,9 +273,9 @@ var socket, jQuery('#main-nav li').removeClass('active'); if (active) { - jQuery('#main-nav li a').each(function() { + jQuery('#main-nav li a').each(function () { var href = this.getAttribute('href'); - if (active == "sort-posts" || active == "sort-reputation" || active == "search" || active== "latest") + if (active == "sort-posts" || active == "sort-reputation" || active == "search" || active == "latest") active = 'users'; if (href && href.match(active)) { jQuery(this.parentNode).addClass('active'); @@ -286,12 +286,12 @@ var socket, $('span.timeago').timeago(); - setTimeout(function() { + setTimeout(function () { window.scrollTo(0, 1); // rehide address bar on mobile after page load completes. }, 100); } - app.showLoginMessage = function() { + app.showLoginMessage = function () { function showAlert() { app.alert({ type: 'success', @@ -311,14 +311,14 @@ var socket, } } - app.addCommasToNumbers = function() { - $('.formatted-number').each(function(index, element) { + app.addCommasToNumbers = function () { + $('.formatted-number').each(function (index, element) { $(element).html(app.addCommas($(element).html())); }); } - app.openChat = function(username, touid) { - require(['chat'], function(chat) { + app.openChat = function (username, touid) { + require(['chat'], function (chat) { var chatModal; if (!chat.modalExists(touid)) { chatModal = chat.createModal(username, touid); @@ -329,7 +329,7 @@ var socket, }); } - app.createNewPosts = function(data) { + app.createNewPosts = function (data) { data.posts[0].display_moderator_tools = 'none'; var html = templates.prepare(templates['topic'].blocks['posts']).parse(data), uniqueid = new Date().getTime(), @@ -353,7 +353,7 @@ var socket, app.infiniteLoaderActive = false; - app.loadMorePosts = function(tid, callback) { + app.loadMorePosts = function (tid, callback) { if (app.infiniteLoaderActive) return; app.infiniteLoaderActive = true; @@ -364,7 +364,7 @@ var socket, socket.emit('api:topic.loadMore', { tid: tid, after: document.querySelectorAll('#post-container li[data-pid]').length - }, function(data) { + }, function (data) { app.infiniteLoaderActive = false; if (data.posts.length) { $('#loading-indicator').attr('done', '0'); @@ -378,19 +378,19 @@ var socket, }); } - app.scrollToTop = function() { + app.scrollToTop = function () { $('body,html').animate({ scrollTop: 0 }); }; - app.scrollToBottom = function() { + app.scrollToBottom = function () { $('body,html').animate({ scrollTop: $('html').height() - 100 }); } - app.scrollToPost = function(pid) { + app.scrollToPost = function (pid) { if (!pid) return; @@ -407,8 +407,8 @@ var socket, if (!scrollTo.length && tid) { - var intervalID = setInterval(function() { - app.loadMorePosts(tid, function(posts) { + var intervalID = setInterval(function () { + app.loadMorePosts(tid, function (posts) { scrollTo = $('#post_anchor_' + pid); if (tid && scrollTo.length) { @@ -426,8 +426,10 @@ var socket, } - jQuery('document').ready(function() { - $('#search-form').on('submit', function() { + jQuery('document').ready(function () { + translator.load('global'); + + $('#search-form').on('submit', function () { var input = $(this).find('input'); ajaxify.go("search/" + input.val(), null, "search"); input.val(''); diff --git a/public/src/translator.js b/public/src/translator.js index b94b846c12..ea2709e95d 100644 --- a/public/src/translator.js +++ b/public/src/translator.js @@ -2,33 +2,96 @@ "use strict"; /*global RELATIVE_PATH*/ + /* + * TODO: language en is hardcoded while system is developed. + */ + var translator = {}, - loaded = {}; + files = { + loaded: {}, + loading: {}, + callbacks: {} + }; module.exports = translator; - - translator.load = function (file, callback) { - if (loaded[file]) { - callback(loaded[file]); + translator.load = function (filename, callback) { + if (files.loaded[filename] && !files.loading[filename]) { + if (callback) { + callback(files.loaded[filename]); + } + } else if (files.loading[filename]) { + if (callback) { + files.callbacks[filename] = files.callbacks[filename] || []; + files.callbacks[filename].push(callback); + } } else { var timestamp = new Date().getTime(); //debug - jQuery.getJSON(RELATIVE_PATH + '/language/en/' + file + '.json?v=' + timestamp, function (language) { - loaded[file] = language; - callback(language); + files.loading[filename] = true; + + jQuery.getJSON(RELATIVE_PATH + '/language/en/' + filename + '.json?v=' + timestamp, function (language) { + files.loaded[filename] = language; + + if (callback) { + callback(language); + } + + while (files.callbacks[filename] && files.callbacks[filename].length) { + files.callbacks[filename].pop()(language); + } + + files.loading[filename] = false; }); } }; + translator.loadAll = function (callback) { + var utils = require('./utils.js'), + path = require('path'), + fs = require('fs'); + + utils.walk(path.join(__dirname, '../../', 'public/language/en'), function (err, data) { + var loaded = data.length; + + for (var d in data) { + if (data.hasOwnProperty(d)) { + (function (file) { + fs.readFile(file, function (err, json) { + files.loaded[path.basename(file).replace('json', '')] = json; + + loaded--; + if (loaded === 0) { + callback(); + } + }); + }(data[d])); + } + } + }); + }; + + /* + * TODO: DRY, see translator.translate. The hard part is to make sure both work node.js / js side + */ + translator.get = function (key) { + var parsedKey = key.split(':'), + languageFile = parsedKey[0]; + + parsedKey = parsedKey[1]; + + return files.loaded[languageFile][parsedKey]; + }; - + /* + * TODO: Not fully converted to server side yet, ideally server should be able to parse whole templates on demand if necessary + * fix: translator.load should determine if server side and immediately return appropriate language file. + */ translator.translate = function (data, callback) { var keys = data.match(/\[\[.*?\]\]/g), loading = 0; - for (var key in keys) { if (keys.hasOwnProperty(key)) { var parsedKey = keys[key].replace('[[', '').replace(']]', '').split(':'), @@ -36,8 +99,8 @@ parsedKey = parsedKey[1]; - if (loaded[languageFile]) { - data = data.replace(keys[key], loaded[file][parsedKey]); + if (files.loaded[languageFile]) { + data = data.replace(keys[key], files.loaded[languageFile][parsedKey]); } else { loading++; @@ -51,12 +114,11 @@ } } - - checkComplete(); } - function checkComplete() { + checkComplete(); + function checkComplete() { if (loading === 0) { callback(data); }