diff --git a/.jshintrc b/.jshintrc index b8c39d2f02..44e550bc7f 100644 --- a/.jshintrc +++ b/.jshintrc @@ -23,7 +23,9 @@ "Document": true, "window": true, "$":true, + "t7": true, "Framework7":true, + "Template7":true, "Event":true, "DocumentTouch":true, "app":true diff --git a/Gruntfile.js b/Gruntfile.js index 3a0c73cd18..50a15d8986 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -63,7 +63,8 @@ module.exports = function (grunt) { 'src/js/proto-support.js', 'src/js/proto-device.js', 'src/js/proto-plugins.js', - 'src/js/wrap-end.js' + 'src/js/wrap-end.js', + 'src/js/template7.js' ]; // Project configuration. @@ -208,7 +209,7 @@ module.exports = function (grunt) { if (filename.indexOf('.js') >= 0) { var addIndent = ' '; filename = filename.replace('src/js/', ''); - if (filename === 'wrap-start.js' || filename === 'wrap-end.js') { + if (filename === 'wrap-start.js' || filename === 'wrap-end.js' || filename === 'template7.js') { addIndent = ''; } var add4spaces = ('f7-intro.js f7-outro.js proto-device.js proto-plugins.js proto-support.js dom7-intro.js dom7-outro.js').split(' '); diff --git a/modules.json b/modules.json index 838de03c0e..5e2777212b 100644 --- a/modules.json +++ b/modules.json @@ -43,7 +43,8 @@ "src/js/proto-support.js", "src/js/proto-device.js", "src/js/proto-plugins.js", - "src/js/wrap-end.js" + "src/js/wrap-end.js", + "src/js/template7.js" ], "less" : [ "src/less/login-screen.less", diff --git a/src/js/f7-intro.js b/src/js/f7-intro.js index 75d1c8b58e..e8fbd32fc8 100644 --- a/src/js/f7-intro.js +++ b/src/js/f7-intro.js @@ -56,15 +56,6 @@ window.Framework7 = function (params) { swipePanelThreshold: 0, panelsCloseByOutside: true, // Modals - modalTemplate: '', - modalActionsTemplate: '
{{buttons}}
', modalButtonOk: 'OK', modalButtonCancel: 'Cancel', modalUsernamePlaceholder: 'Username', @@ -96,6 +87,9 @@ window.Framework7 = function (params) { // DOM lib var $ = Dom7; + // Template7 lib + var t7 = Template7; + // Touch events app.touchEvents = { start: app.support.touch ? 'touchstart' : 'mousedown', diff --git a/src/js/modals.js b/src/js/modals.js index b9b21439c3..efdb8b903a 100644 --- a/src/js/modals.js +++ b/src/js/modals.js @@ -4,39 +4,24 @@ var _modalTemplateTempDiv = document.createElement('div'); app.modal = function (params) { params = params || {}; - /* @params example - { - title: 'Modal title', - text: 'Modal text', - afterText: 'Custom content after text', - buttons: [{ - text:'Cancel', - bold: true, - onClick: function (){}, - close:false - }], - onClick: function(index){} - } - */ - var buttonsHTML = ''; - if (params.buttons && params.buttons.length > 0) { - for (var i = 0; i < params.buttons.length; i++) { - buttonsHTML += '' + params.buttons[i].text + ''; - } - } - var modalTemplate = app.params.modalTemplate; - if (!params.title) { - modalTemplate = modalTemplate.split('{{if title}}')[0] + modalTemplate.split('{{/if title}}')[1]; + var modalHTML = ''; + if (app.params.modalTemplate) { + modalHTML = t7(app.params.modalTemplate, params); } else { - modalTemplate = modalTemplate.replace(/{{if\ title}}/g, '').replace(/{{\/if\ title}}/g, ''); + var buttonsHTML = ''; + if (params.buttons && params.buttons.length > 0) { + for (var i = 0; i < params.buttons.length; i++) { + buttonsHTML += '' + params.buttons[i].text + ''; + } + } + var titleHTML = params.title ? '' : ''; + var textHTML = params.title ? '' : ''; + var afterTextHTML = params.afterText ? params.afterText : ''; + var noButtons = !params.buttons || params.buttons.length === 0 ? 'modal-no-buttons' : ''; + modalHTML = ''; } - var modalHTML = modalTemplate - .replace(/{{title}}/g, params.title || '') - .replace(/{{text}}/g, params.text || '') - .replace(/{{afterText}}/g, params.afterText || '') - .replace(/{{buttons}}/g, buttonsHTML) - .replace(/{{noButtons}}/g, !params.buttons || params.buttons.length === 0 ? 'modal-no-buttons' : ''); + _modalTemplateTempDiv.innerHTML = modalHTML; var modal = $(_modalTemplateTempDiv).children(); @@ -180,21 +165,27 @@ app.actions = function (params) { if (params.length > 0 && !$.isArray(params[0])) { params = [params]; } + var modalHTML; - var actionsTemplate = app.params.modalActionsTemplate; - var buttonsHTML = ''; - for (var i = 0; i < params.length; i++) { - for (var j = 0; j < params[i].length; j++) { - if (j === 0) buttonsHTML += '
'; - var button = params[i][j]; - var buttonClass = button.label ? 'actions-modal-label' : 'actions-modal-button'; - if (button.bold) buttonClass += ' actions-modal-button-bold'; - if (button.color) buttonClass += ' color-' + button.color; - buttonsHTML += '' + button.text + ''; - if (j === params[i].length - 1) buttonsHTML += '
'; + if (app.params.modalActionsTemplate) { + modalHTML = t7(app.params.modalActionsTemplate, params); + } + else { + var buttonsHTML = ''; + for (var i = 0; i < params.length; i++) { + for (var j = 0; j < params[i].length; j++) { + if (j === 0) buttonsHTML += '
'; + var button = params[i][j]; + var buttonClass = button.label ? 'actions-modal-label' : 'actions-modal-button'; + if (button.bold) buttonClass += ' actions-modal-button-bold'; + if (button.color) buttonClass += ' color-' + button.color; + buttonsHTML += '' + button.text + ''; + if (j === params[i].length - 1) buttonsHTML += '
'; + } } + modalHTML = '
' + buttonsHTML + '
'; } - var modalHTML = actionsTemplate.replace(/{{buttons}}/g, buttonsHTML); + _modalTemplateTempDiv.innerHTML = modalHTML; var modal = $(_modalTemplateTempDiv).children(); @@ -406,7 +397,7 @@ app.closeModal = function (modal) { overlay.removeClass('modal-overlay-visible'); modal.trigger('close'); - + if (!isPopover) { modal.removeClass('modal-in').addClass('modal-out').transitionEnd(function (e) { if (modal.hasClass('modal-out')) modal.trigger('closed'); @@ -414,7 +405,9 @@ app.closeModal = function (modal) { if (isPopup || isLoginScreen) { modal.removeClass('modal-out').hide(); - if (removeOnClose && modal.length > 0) modal.remove(); + if (removeOnClose && modal.length > 0) { + modal.remove(); + } } else { modal.remove(); @@ -423,7 +416,9 @@ app.closeModal = function (modal) { } else { modal.removeClass('modal-in modal-out').trigger('closed').hide(); - if (removeOnClose) modal.remove(); + if (removeOnClose) { + modal.remove(); + } } return true; }; diff --git a/src/js/template7.js b/src/js/template7.js new file mode 100644 index 0000000000..874ce33d6b --- /dev/null +++ b/src/js/template7.js @@ -0,0 +1,341 @@ +/*=========================== +Template7 Template engine +===========================*/ +window.Template7 = (function () { + 'use strict'; + function isArray(arr) { + return Object.prototype.toString.apply(arr) === '[object Array]'; + } + function isObject(obj) { + return obj instanceof Object; + } + function isFunction(func) { + return typeof func === 'function'; + } + var cache = {}; + function stringToBlocks(string) { + var blocks = [], i, j, k; + if (!string) return []; + var _blocks = string.split(/({{[^{^}]*}})/); + for (i = 0; i < _blocks.length; i++) { + var block = _blocks[i]; + if (block === '') continue; + if (block.indexOf('{{') < 0) { + blocks.push({ + type: 'plain', + content: block + }); + } + else { + if (block.indexOf('{/') >= 0) { + continue; + } + if (block.indexOf('{#') < 0 && block.indexOf(' ') < 0 && block.indexOf('else') < 0) { + // Simple variable + blocks.push({ + type: 'variable', + contextName: block.replace(/[{}]/g, '') + }); + continue; + } + // Helpers + var helperSlices = block.replace(/[{}#}]/g, '').split(' '); + var helperName = helperSlices[0]; + var helperContext = helperSlices[1]; + var helperHash = {}; + + if (helperSlices.length > 2) { + var hashRes; + var reg = new RegExp(/\b(\w+)=["']([^"']+)(?=["'])/g); + while ((hashRes = reg.exec(block)) !== null) { + helperHash[hashRes[1]] = hashRes[2] === 'false' ? false : hashRes[2]; + } + } + + if (block.indexOf('{#') >= 0) { + // Condition/Helper + var helperStartIndex = i; + var helperContent = ''; + var elseContent = ''; + var toSkip = 0; + var shiftIndex; + var foundClosed = false, foundElse = false, foundClosedElse = false, depth = 0; + for (j = i + 1; j < _blocks.length; j++) { + if (_blocks[j].indexOf('{{#') >= 0) { + depth ++; + } + if (_blocks[j].indexOf('{{/') >= 0) { + depth --; + } + if (_blocks[j].indexOf('{{#' + helperName) >= 0) { + helperContent += _blocks[j]; + if (foundElse) elseContent += _blocks[j]; + toSkip ++; + } + else if (_blocks[j].indexOf('{{/' + helperName) >= 0) { + if (toSkip > 0) { + toSkip--; + helperContent += _blocks[j]; + if (foundElse) elseContent += _blocks[j]; + } + else { + shiftIndex = j; + foundClosed = true; + break; + } + } + else if (_blocks[j].indexOf('else') >= 0 && depth === 0) { + foundElse = true; + } + else { + if (!foundElse) helperContent += _blocks[j]; + if (foundElse) elseContent += _blocks[j]; + } + + } + if (foundClosed) { + if (shiftIndex) i = shiftIndex; + blocks.push({ + type: 'helper', + helperName: helperName, + contextName: helperContext, + content: helperContent, + inverseContent: elseContent, + hash: helperHash + }); + } + } + else if (block.indexOf(' ') > 0) { + blocks.push({ + type: 'helper', + helperName: helperName, + contextName: helperContext, + hash: helperHash + }); + } + } + } + return blocks; + } + var Template7 = function (template, data) { + var t = this; + t.template = template; + t.context = data; + + function getContext(contextName, context, privateData) { + if (contextName === 'this' || typeof contextName === 'undefined') { + return context; + } + + if (contextName.indexOf('@') >= 0) { + //private data + var privateVarName = contextName.replace(/[@ ]/g, ''); + if (privateData && typeof privateData[privateVarName] !== 'undefined') return privateData[privateVarName]; + } + + if (contextName.indexOf('.') > 0 && contextName.indexOf('../') < 0) { + var dataPath = contextName.split('.'); + var _context = context; + for (var j = 0; j < dataPath.length; j++) { + if (dataPath[j] === 'this') { + _context = context; + } + else if (typeof _context !== 'undefined' && isObject(context) && dataPath[j] in _context) { + _context = _context[dataPath[j]]; + } + else { + _context = undefined; + } + } + context = _context; + } + else if (contextName.indexOf('../') >= 0) { + } + else if (context[contextName]) { + context = context[contextName]; + } + else { + context = undefined; + } + return context; + } + function createOptions(block, privateData) { + return { + fn: function (newContext, privateData) { + return render(block.content, newContext, privateData); + }, + inverse: function (newContext, privateData) { + return block.inverseContent ? render(block.inverseContent, newContext, privateData) : ''; + }, + content: block.content, + inverseContent: block.inverseContent, + hash: block.hash || {}, + helpers: t.helpers, + data: privateData + }; + } + function render(template, data, privateData) { + template = template || t.template; + var scope = data || t.context; + var blocks = stringToBlocks(template); + var resultString = ''; + var i, j, context; + for (i = 0; i < blocks.length; i++) { + var block = blocks[i]; + // Plain block + if (block.type === 'plain') { + resultString += block.content; + continue; + } + // Variable block + if (block.type === 'variable') { + context = getContext(block.contextName, scope, privateData); + if (typeof context !== 'undefined') { + resultString += context.toString(); + } + } + // Helpers block + if (block.type === 'helper') { + context = getContext(block.contextName, scope, privateData); + // Create options + var options = createOptions(block, privateData); + var helperResult; + var helperName = block.helperName; + if (block.helperName in t.helpers) { + helperResult = t.helpers[helperName].call(scope, context, options); + } + else { + if (!block.contextName && (helperName === 'this' || (isObject(context) && helperName in context))) { + var _context = helperName === 'this' ? context : context[helperName]; + if (isArray(_context)) helperResult = t.helpers.each.call(scope, context[helperName], options); + else helperResult = render(block.content, context[helperName]); + } + else { + throw new Error('Missing helper: "' + helperName + '"'); + } + } + if (typeof helperResult !== 'undefined') { + resultString += helperResult.toString(); + } + } + } + return resultString; + } + t.render = function (data) { + t.context = data; + t.prevContext = undefined; + var rendered, cached, id; + if (t.options.cache) { + for (var key in cache) { + if (key.indexOf('template_') >= 0 && cache[key] === t.template) { + id = key.split('template_')[1]; + if (cache['data_' + id] === JSON.stringify(data)) cached = cache['rendered_' + id]; + } + } + if (cached) return cached; + } + rendered = render(t.template, data); + if (t.options.cache) { + id = new Date().getTime(); + cache['template_' + id] = t.template; + cache['data_' + id] = JSON.stringify(data); + cache['rendered_' + id] = rendered; + } + return rendered; + }; + }; + Template7.prototype = { + options: { + cache: true + }, + helpers: { + 'if': function (context, options) { + if (isFunction(context)) { context = context.call(this); } + if (context) { + return options.fn(this, options.data); + } + else { + return options.inverse(this, options.data); + } + }, + 'unless': function (context, options) { + if (isFunction(context)) { context = context.call(this); } + if (!context) { + return options.fn(this, options.data); + } + else { + return options.inverse(this, options.data); + } + }, + 'each': function (context, options) { + var ret = '', i = 0; + if (isFunction(context)) { context = context.call(this); } + if (isArray(context)) { + if (options.hash.reverse && !context.t7Reversed) { + context = context.reverse(); + context.t7Reversed = true; + } + if (!options.hash.reverse && context.t7Reversed) { + context = context.reverse(); + context.t7Reversed = false; + } + for (i = 0; i < context.length; i++) { + ret += options.fn(context[i], {first: i === 0, last: i === context.length - 1, index: i}); + } + } + else { + for (var key in context) { + i++; + ret += options.fn(context[key], {key: key}); + } + } + if (i > 0) return ret; + else return options.inverse(this); + }, + 'with': function (context, options) { + if (isFunction(context)) { context = context.call(this); } + return options.fn(context); + }, + 'join': function (context, options) { + if (isFunction(context)) { context = context.call(this); } + return context.join(options.hash.delimeter); + } + } + }; + var t7 = function (template, data) { + if (arguments.length === 2) { + var instance = new Template7(template); + var rendered = instance.render(data); + instance = null; + return (rendered); + } + else return new Template7(template, data); + }; + t7.registerHelper = function (name, fn) { + Template7.prototype.helpers[name] = fn; + }; + t7.cache = cache; + t7.clearCache = function (arg) { + if (arguments.length === 0) { + cache = {}; + return; + } + var key, id; + // Clear by passed data + for (key in cache) { + var lookFor = typeof arg === 'string' ? 'template_' : 'data_'; + var compareWith = typeof arg === 'string' ? arg : JSON.strigify(arg); + if (key.indexOf(lookFor) >= 0 && cache[key] === arg) { + id = key.split('_')[1]; + } + } + if (id) { + delete cache['template_' + id]; + delete cache['data_' + id]; + delete cache['rendered_' + id]; + } + }; + t7.options = Template7.prototype.options; + t7.helpers = Template7.prototype.helpers; + return t7; +})(); \ No newline at end of file diff --git a/src/js/wrap-end.js b/src/js/wrap-end.js index 158693a025..0319a0fe5f 100644 --- a/src/js/wrap-end.js +++ b/src/js/wrap-end.js @@ -1 +1 @@ -})(); \ No newline at end of file +})();