From 5f9ebd1580349c537422f50ecd09cccde39aebd2 Mon Sep 17 00:00:00 2001 From: Conrad Sollitt Date: Mon, 21 Feb 2022 17:58:59 -0800 Subject: [PATCH 01/51] =?UTF-8?q?=F0=9F=8C=8E=20i18n=20update=20for=20find?= =?UTF-8?q?/replace=20in=20HTML=20attributes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/plugins/i18n.js | 63 +++++++++++++++++++-------- js/plugins/i18n.min.js | 2 +- js/web-components/i18n-service.js | 32 ++++++++++++-- js/web-components/i18n-service.min.js | 2 +- 4 files changed, 76 insertions(+), 23 deletions(-) diff --git a/js/plugins/i18n.js b/js/plugins/i18n.js index 78ef38dc..db5fcd34 100644 --- a/js/plugins/i18n.js +++ b/js/plugins/i18n.js @@ -352,9 +352,6 @@ locales, locale, data, - x, - y, - attr, navLang, hashRouting = (app.routingMode === undefined || app.routingMode() === 'hash'), href, @@ -375,14 +372,8 @@ elements = rootElement.querySelectorAll('[data-i18n-attr]'); for (n = 0, m = elements.length; n < m; n++) { element = elements[n]; - data = element.getAttribute('data-i18n-attr').split(',').map(function(s) { return s.trim(); }); - for (x = 0, y = data.length; x < y; x++) { - attr = data[x]; - key = element.getAttribute(attr); - if (key !== null) { - element.setAttribute(attr, (this.langText[key] ? this.langText[key] : key)); - } - } + i18n.set18nAttr(element, element.getAttribute('data-i18n-attr')); + // Note, for this to work properly with custom controls and plugins // this file will likely need to be included before other plugins so that // it runs first. For example if is used with sorting or filtering. @@ -468,6 +459,47 @@ } }, + /** + * Used Internally for [data-i18n-attr] and [v-i18n-attr] + * + * @param {HTMLElement} el + * @param {string} bindAttr + */ + set18nAttr: function (el, bindAttr) { + var data = bindAttr.split(',').map(function(s) { return s.trim(); }); + for (var x = 0, y = data.length; x < y; x++) { + var attr = data[x]; + var value; + var key = el.getAttribute(attr); + if (key === null) { + // Skip attribute not found + console.warn(el, 'Missing Attribute [' + attr + '] for element'); + continue; + } + // Exact match on key + value = this.langText[key]; + if (value !== undefined) { + el.setAttribute(attr, value); + continue; + } + // Find and replace keys in "[[key]]", example: + // data-export-file-name="[[Report]].csv" + var match; + var regex = /\[\[.*\]\]/g; + while ((match = regex.exec(key)) !== null) { + value = match[0].substring(2, match[0].length - 2); + if (this.langText[value] === undefined) { + // Key not found so skip attribute + continue; + } + var find = match[0].replace(/\[/g, '\\['); + find = new RegExp(find, 'g'); + key = key.replace(find, this.langText[value]); + } + el.setAttribute(attr, key); + } + }, + /** * If using Handlebars then add a Helper. This function gets called * automatically when the page is loaded. @@ -501,13 +533,10 @@ }; var vI18nAttr = function (el, binding) { - var attr = binding.value.split(',').map(function(s) { return s.trim(); }); - for (var x = 0, y = attr.length; x < y; x++) { - var key = el.getAttribute(attr[x]); - if (key !== null) { - el.setAttribute(attr[x], (i18n.langText[key] ? i18n.langText[key] : key)); - } + if (binding.value === undefined) { + return; } + i18n.set18nAttr(el, binding.value); }; // For differences between Vue 2 and Vue 3 see comments in [js\extensions\vue-directives.js] diff --git a/js/plugins/i18n.min.js b/js/plugins/i18n.min.js index cdac0255..8b4629a0 100644 --- a/js/plugins/i18n.min.js +++ b/js/plugins/i18n.min.js @@ -1 +1 @@ -!function(){"use strict";var L={fileName:"_",fileDir:"i18n",defaultLocale:null,supportedLocales:null,currentLocale:null,langText:{},langCache:{},getText:function(e){return this.langText&&this.langText[e]?this.langText[e]:e},getUserDefaultLang:function(){if(navigator.languages&&navigator.languages.length&&this.supportedLocales&&this.supportedLocales.length)for(var e=0,t=navigator.languages.length;e { return s.trim()}); + this.supportedLocales = get(this, 'locales', '').split(',').map(s => { return s.trim(); }); this.currentLocale = null; this.langText = {}; this.langCache = {}; @@ -256,10 +256,34 @@ class I18nService extends WebComponentService { const data = element.getAttribute('data-i18n-attr').split(',').map(function(s) { return s.trim(); }); for (let x = 0, y = data.length; x < y; x++) { const attr = data[x]; - const key = element.getAttribute(attr); - if (key !== null) { - element.setAttribute(attr, (this.langText[key] ? this.langText[key] : key)); + let key = element.getAttribute(attr); + let value; + if (key === null) { + // Skip attribute not found + console.warn(element, 'Missing Attribute [' + attr + '] for element'); + continue; } + // Exact match on key + value = this.langText[key]; + if (value !== undefined) { + element.setAttribute(attr, value); + continue; + } + // Find and replace keys in "[[key]]", example: + // data-export-file-name="[[Report]].csv" + let match; + const regex = /\[\[.*\]\]/g; + while ((match = regex.exec(key)) !== null) { + value = match[0].substring(2, match[0].length - 2); + if (this.langText[value] === undefined) { + // Key not found so skip attribute + continue; + } + let find = match[0].replace(/\[/g, '\\['); + find = new RegExp(find, 'g'); + key = key.replace(find, this.langText[value]); + } + element.setAttribute(attr, key); } } diff --git a/js/web-components/i18n-service.min.js b/js/web-components/i18n-service.min.js index e0077bbe..4cfcc5a3 100644 --- a/js/web-components/i18n-service.min.js +++ b/js/web-components/i18n-service.min.js @@ -1 +1 @@ -import{WebComponentService}from"./WebComponentService.min.js";import{showErrorAlert,setElementText}from"./utils.min.js";class I18nService extends WebComponentService{constructor(){function t(t,e,n=null){const i=t.getAttribute(e);return i||n}super(),this.fileName=t(this,"file","_"),this.fileDir=t(this,"file-dir","i18n"),this.defaultLocale=t(this,"default-locale"),this.supportedLocales=t(this,"locales","").split(",").map((t=>t.trim())),this.currentLocale=null,this.langText={},this.langCache={},this._isRunning=!1,window.i18nText=this.getText.bind(this);const e=document.querySelector("url-router");this.hashRouting=null===e||"history"!==e.getAttribute("mode")}getUserDefaultLang(){if(navigator.languages&&navigator.languages.length&&this.supportedLocales&&this.supportedLocales.length)for(let t=0,e=navigator.languages.length;t without [default-locale] being filled in."),!1):0!==this.supportedLocales.length||(console.warn("Using without [locales] being filled in."),!1)}onLoad(){if(this._isRunning)return;this._isRunning=!0;const t=this.validateSettings(),e=document.querySelector("url-router");if(this.currentLocale=null,this.hashRouting?t&&0===window.location.hash.indexOf("#/")&&(this.currentLocale=window.location.hash.split("/")[1]):t&&window.location.pathname.split("/").length>1&&(this.currentLocale=window.location.pathname.split("/")[1]),null!==this.currentLocale&&(""!==this.currentLocale&&-1!==this.supportedLocales.indexOf(this.currentLocale)||(this.currentLocale=null)),window.i18n_Locale=this.currentLocale,null===this.currentLocale)return this._isRunning=!1,void(e&&e.querySelector('url-route[path="/:lang/"]')&&(this.hashRouting?window.location="#/"+this.defaultLocale+"/":e.changeRoute("/"+this.defaultLocale+"/")));document.documentElement.lang=this.currentLocale;const n=this.fileDir+"/"+this.fileName+"."+this.currentLocale+".json",i=[this.downloadFile(n)],l=e&&e.currentRoute?e.currentRoute.getAttribute("data-i18n-file"):null;let o=null;l&&(o=this.fileDir+"/"+l+"."+this.currentLocale+".json",i.push(this.downloadFile(o))),Promise.all(i).finally((()=>{this.langText=o?Object.assign({},this.langCache[n],this.langCache[o]):this.langCache[n],this.updateContent(document),this.dispatchEvent(new CustomEvent("app:i18nLoaded",{bubbles:!0})),this._isRunning=!1}))}getText(t){return this.langText&&this.langText[t]?this.langText[t]:t}downloadFile(t){return void 0!==this.langCache[t]?new Promise((t=>{t()})):fetch(t,{cache:"no-store",credentials:"same-origin"}).then((t=>{const e=t.status;if(e>=200&&e<300||304===e)return Promise.resolve(t);{const n=`Error loading data. Server Response Code: ${e}, Response Text: ${t.statusText}`;return Promise.reject(n)}})).then((t=>t.json())).then((e=>{this.langCache[t]=e})).catch((e=>{showErrorAlert(`Error Downloading I18N file: [${t}], Response Code Status: ${e.message}`),this.langCache[t]={}}))}updateContent(t){let e=t.querySelectorAll("[data-i18n]");for(const t of e){const e=t.getAttribute("data-i18n"),n=void 0===this.langText[e]?"":this.langText[e];setElementText(t,n)}e=t.querySelectorAll("[data-i18n-attr]");for(const t of e){const e=t.getAttribute("data-i18n-attr").split(",").map((function(t){return t.trim()}));for(let n=0,i=e.length;n1&&(e[1]=this.currentLocale,t.href=e.join("/"))}e=t.querySelectorAll("a[data-i18n-locales]");for(const t of e){const e=t.getAttribute("data-i18n-locales").split(",").map((t=>t.trim()));let n=this.currentLocale;-1===e.indexOf(n)&&(n=-1!==e.indexOf(this.defaultLocale)?this.defaultLocale:e[0]);const i=new RegExp("\\[locale]","g");t.href=t.href.replace(i,n)}e=t.querySelectorAll("[data-i18n-replace-text]");for(const t of e){let e=t.innerHTML;for(const t in this.langText)if(this.langText.hasOwnProperty(t)){let n="[[i18n "+t+"]]";if(-1!==e.indexOf(n)){n="\\[\\[i18n "+t+"]]";const i=new RegExp(n,"g"),l=this.langText[t]?this.langText[t]:t;e=e.replace(i,l)}}t.innerHTML=e}if(e=t.querySelectorAll("[data-i18n-nav-lang]"),e.length>0){const t=this.hashRouting?null:document.querySelector("url-router").handlePushStateClick;for(const n of e){const e=n.getAttribute("data-i18n-nav-lang");this.hashRouting?n.href=window.location.hash.replace("#/"+this.currentLocale+"/","#/"+e+"/"):(n.href=window.location.pathname.replace("/"+this.currentLocale+"/","/"+e+"/"),n.addEventListener("click",t))}}e=t.querySelectorAll("[data-i18n-nav-selected]");for(const t of e)t.textContent=this.currentLocale;e=t.querySelectorAll('nav[is="spa-links"]');for(const t of e)"function"==typeof t.updateLinks&&t.updateLinks()}}window.customElements.define("i18n-service",I18nService); \ No newline at end of file +import{WebComponentService}from"./WebComponentService.min.js";import{showErrorAlert,setElementText}from"./utils.min.js";class I18nService extends WebComponentService{constructor(){function t(t,e,n=null){const i=t.getAttribute(e);return i||n}super(),this.fileName=t(this,"file","_"),this.fileDir=t(this,"file-dir","i18n"),this.defaultLocale=t(this,"default-locale"),this.supportedLocales=t(this,"locales","").split(",").map((t=>t.trim())),this.currentLocale=null,this.langText={},this.langCache={},this._isRunning=!1,window.i18nText=this.getText.bind(this);const e=document.querySelector("url-router");this.hashRouting=null===e||"history"!==e.getAttribute("mode")}getUserDefaultLang(){if(navigator.languages&&navigator.languages.length&&this.supportedLocales&&this.supportedLocales.length)for(let t=0,e=navigator.languages.length;t without [default-locale] being filled in."),!1):0!==this.supportedLocales.length||(console.warn("Using without [locales] being filled in."),!1)}onLoad(){if(this._isRunning)return;this._isRunning=!0;const t=this.validateSettings(),e=document.querySelector("url-router");if(this.currentLocale=null,this.hashRouting?t&&0===window.location.hash.indexOf("#/")&&(this.currentLocale=window.location.hash.split("/")[1]):t&&window.location.pathname.split("/").length>1&&(this.currentLocale=window.location.pathname.split("/")[1]),null!==this.currentLocale&&(""!==this.currentLocale&&-1!==this.supportedLocales.indexOf(this.currentLocale)||(this.currentLocale=null)),window.i18n_Locale=this.currentLocale,null===this.currentLocale)return this._isRunning=!1,void(e&&e.querySelector('url-route[path="/:lang/"]')&&(this.hashRouting?window.location="#/"+this.defaultLocale+"/":e.changeRoute("/"+this.defaultLocale+"/")));document.documentElement.lang=this.currentLocale;const n=this.fileDir+"/"+this.fileName+"."+this.currentLocale+".json",i=[this.downloadFile(n)],l=e&&e.currentRoute?e.currentRoute.getAttribute("data-i18n-file"):null;let o=null;l&&(o=this.fileDir+"/"+l+"."+this.currentLocale+".json",i.push(this.downloadFile(o))),Promise.all(i).finally((()=>{this.langText=o?Object.assign({},this.langCache[n],this.langCache[o]):this.langCache[n],this.updateContent(document),this.dispatchEvent(new CustomEvent("app:i18nLoaded",{bubbles:!0})),this._isRunning=!1}))}getText(t){return this.langText&&this.langText[t]?this.langText[t]:t}downloadFile(t){return void 0!==this.langCache[t]?new Promise((t=>{t()})):fetch(t,{cache:"no-store",credentials:"same-origin"}).then((t=>{const e=t.status;if(e>=200&&e<300||304===e)return Promise.resolve(t);{const n=`Error loading data. Server Response Code: ${e}, Response Text: ${t.statusText}`;return Promise.reject(n)}})).then((t=>t.json())).then((e=>{this.langCache[t]=e})).catch((e=>{showErrorAlert(`Error Downloading I18N file: [${t}], Response Code Status: ${e.message}`),this.langCache[t]={}}))}updateContent(t){let e=t.querySelectorAll("[data-i18n]");for(const t of e){const e=t.getAttribute("data-i18n"),n=void 0===this.langText[e]?"":this.langText[e];setElementText(t,n)}e=t.querySelectorAll("[data-i18n-attr]");for(const t of e){const e=t.getAttribute("data-i18n-attr").split(",").map((function(t){return t.trim()}));for(let n=0,i=e.length;n1&&(e[1]=this.currentLocale,t.href=e.join("/"))}e=t.querySelectorAll("a[data-i18n-locales]");for(const t of e){const e=t.getAttribute("data-i18n-locales").split(",").map((t=>t.trim()));let n=this.currentLocale;-1===e.indexOf(n)&&(n=-1!==e.indexOf(this.defaultLocale)?this.defaultLocale:e[0]);const i=new RegExp("\\[locale]","g");t.href=t.href.replace(i,n)}e=t.querySelectorAll("[data-i18n-replace-text]");for(const t of e){let e=t.innerHTML;for(const t in this.langText)if(this.langText.hasOwnProperty(t)){let n="[[i18n "+t+"]]";if(-1!==e.indexOf(n)){n="\\[\\[i18n "+t+"]]";const i=new RegExp(n,"g"),l=this.langText[t]?this.langText[t]:t;e=e.replace(i,l)}}t.innerHTML=e}if(e=t.querySelectorAll("[data-i18n-nav-lang]"),e.length>0){const t=this.hashRouting?null:document.querySelector("url-router").handlePushStateClick;for(const n of e){const e=n.getAttribute("data-i18n-nav-lang");this.hashRouting?n.href=window.location.hash.replace("#/"+this.currentLocale+"/","#/"+e+"/"):(n.href=window.location.pathname.replace("/"+this.currentLocale+"/","/"+e+"/"),n.addEventListener("click",t))}}e=t.querySelectorAll("[data-i18n-nav-selected]");for(const t of e)t.textContent=this.currentLocale;e=t.querySelectorAll('nav[is="spa-links"]');for(const t of e)"function"==typeof t.updateLinks&&t.updateLinks()}}window.customElements.define("i18n-service",I18nService); \ No newline at end of file From 479f18ba5e4892656ef4b4c4725c4cdf654ecb6f Mon Sep 17 00:00:00 2001 From: Conrad Sollitt Date: Mon, 21 Feb 2022 18:00:11 -0800 Subject: [PATCH 02/51] =?UTF-8?q?=F0=9F=8E=9B=EF=B8=8F=20Excel=20and=20CSV?= =?UTF-8?q?=20Plugin=20support=20for=20JS=20Controls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 16 ++++++++++++++++ docs/to-do-list.txt | 3 ++- js/plugins/exportToCsv.js | 4 ++-- js/plugins/exportToCsv.min.js | 2 +- js/plugins/exportToExcel.js | 4 ++-- js/plugins/exportToExcel.min.js | 2 +- 6 files changed, 24 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7535c74..be4ea3cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ DataFormsJS uses [Semantic Versioning](https://docs.npmjs.com/about-semantic-ver Overall the core Framework files, React Components, and Web Components and API are expected to remain stable however the version number is expected to increase to much larger numbers in the future due to the changes to smaller scripts and components. This change log includes all npm release history and new website features or major changes. +## Next Release (Changes on master branch but not yet npm) + +* Framework Plugins - Excel and CSV Export + * `~/js/plugins/exportToCsv.js` + * `~/js/plugins/exportToExcel.js` + * Add support so that elements using the plugin are refreshed when `app.refreshHtmlControl()` is called. + * Minor fix handled by using `onRendered: function (rootElement)` instead of `onRendered: function ()`. +* I18N update + * Framework Plugin `~/js/plugins/i18n.js` + * Web Component `~/js/web-components/i18n-service.js` + * Added ability to find and replace i18n keys inside of an attribute string by using syntax `[[key]]` + * Example `data-export-file-name="[[Countries]].xlsx" data-i18n-attr="data-export-file-name"` + * Previously both `Countries` and `Countries.xlsx` would have had to be defined for each language + * Now only `Countries` has to be defined + * For Vue apps this applies to the `v-i18n-attr` directive + ## 5.12.1 (February 19, 2022) * Excel Export (Web Component and Framework Plugin) diff --git a/docs/to-do-list.txt b/docs/to-do-list.txt index 074b00be..73431683 100644 --- a/docs/to-do-list.txt +++ b/docs/to-do-list.txt @@ -23,10 +23,11 @@ TODO List -------------------------------------------------------------------------- **) Finish adding Excel and CSV Export to the Places Demo + - Several NPM releases are being made prior to the updates being published on the main site: + After NPM release update all "../js/" to the CDN Page - This is being worked on mid-Feb 2022 and will likely be published by the end of this month - Need to test more browsers, IE, Safari, etc - Need a CSV icon (currently Excel icon is used for both Excel and CSV) - - Add export on more pages **) Update the Getting Started CSS Template used here: https://www.dataformsjs.com/en/getting-started diff --git a/js/plugins/exportToCsv.js b/js/plugins/exportToCsv.js index a2d69261..f1ee18aa 100644 --- a/js/plugins/exportToCsv.js +++ b/js/plugins/exportToCsv.js @@ -127,9 +127,9 @@ * Once the Form Renders find any elements that have attribute * [data-export-csv-selector] and assigning an onclick event to it. */ - onRendered: function () { + onRendered: function (rootElement) { // Find Elements - var actionElements = document.querySelectorAll('[data-export-csv-selector]'); + var actionElements = (rootElement || document).querySelectorAll('[data-export-csv-selector]'); // Are there any on the page? var isSupported = false; diff --git a/js/plugins/exportToCsv.min.js b/js/plugins/exportToCsv.min.js index c62385aa..60cbc18c 100644 --- a/js/plugins/exportToCsv.min.js +++ b/js/plugins/exportToCsv.min.js @@ -1 +1 @@ -!function(){"use strict";function b(e){return e=0<=(e=e.trim().replace(/"/g,'""')).search(/("|,|\n|:)/g)?'"'+e+'"':e}var r={exportTable:function(e,t,s){var o,n,r,u,l,p,v,a,c,e=document.querySelector(e),i=[],d=[];for(t=t||"Report.csv",r=(o=e.tHead.rows[n=0]).cells.length;n","|",":","*","?","\\","/"],n=0;n","|",":","*","?","\\","/"],n=0;n Date: Mon, 21 Feb 2022 18:01:02 -0800 Subject: [PATCH 03/51] =?UTF-8?q?=F0=9F=8C=8FAdd=20missing=20zh-CN=20lang?= =?UTF-8?q?=20support=20to=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/image-classification-react.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/image-classification-react.htm b/examples/image-classification-react.htm index ccd49e01..93249c1f 100644 --- a/examples/image-classification-react.htm +++ b/examples/image-classification-react.htm @@ -52,7 +52,7 @@ const NavLink = window.ReactRouterDOM.NavLink; const defaultLocale = 'en'; - const supportedLocales = ['en', 'ja', 'es', 'pt-BR', 'ar', 'fr']; + const supportedLocales = ['en', 'ja', 'es', 'pt-BR', 'ar', 'fr', 'zh-CN']; const fileName = 'image-classification'; const i18n = new I18n(defaultLocale, supportedLocales, fileName); const defaultLang = i18n.getUserDefaultLang; From 4d42d5b02fd9f892bdcfdde7645da73d762765d7 Mon Sep 17 00:00:00 2001 From: Conrad Sollitt Date: Mon, 21 Feb 2022 18:03:09 -0800 Subject: [PATCH 04/51] =?UTF-8?q?=F0=9F=93=92=20Add=20Excel=20and=20CSV=20?= =?UTF-8?q?Export=20to=20Places=20Examples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/html/cities-hbs.htm | 17 ++++++++ examples/html/cities-js.htm | 19 +++++++++ examples/html/cities-react.jsx | 1 + examples/html/cities-vue.htm | 19 +++++++++ examples/html/cities-web.htm | 19 +++++++++ examples/html/regions-hbs.htm | 17 ++++++++ examples/html/regions-js.htm | 19 +++++++++ examples/html/regions-react.jsx | 1 + examples/html/regions-vue.htm | 19 +++++++++ examples/html/regions-web.htm | 19 +++++++++ examples/html/search-places-js.htm | 24 +++++++++++ examples/html/search-places-react.jsx | 6 +++ examples/html/search-places-results-hbs.htm | 22 +++++++++++ examples/html/search-places-vue.htm | 26 +++++++++++- examples/html/search-places-web.htm | 24 +++++++++++ examples/places-demo-graphql.htm | 4 ++ examples/places-demo-hbs.htm | 4 +- examples/places-demo-js.htm | 15 +++---- examples/places-demo-preact.htm | 44 ++++++++++++++++++++- examples/places-demo-react.htm | 42 +++++++++++++++++++- examples/places-demo-vue.htm | 13 +++--- examples/places-demo-web.htm | 20 +++++----- 22 files changed, 366 insertions(+), 28 deletions(-) diff --git a/examples/html/cities-hbs.htm b/examples/html/cities-hbs.htm index 37ed1c70..347e7bbc 100644 --- a/examples/html/cities-hbs.htm +++ b/examples/html/cities-hbs.htm @@ -6,6 +6,23 @@

{{i18n 'Largest Cities in'}} {{country}}, {{region}}

diff --git a/examples/html/cities-js.htm b/examples/html/cities-js.htm index cc0b5c56..36a772a2 100644 --- a/examples/html/cities-js.htm +++ b/examples/html/cities-js.htm @@ -6,6 +6,25 @@

+ + Download to Excel + Download to CSV diff --git a/examples/html/cities-react.jsx b/examples/html/cities-react.jsx index ace4017d..1e9cb179 100644 --- a/examples/html/cities-react.jsx +++ b/examples/html/cities-react.jsx @@ -54,6 +54,7 @@ export function ShowCities(props) { {{ country }}, {{ region }}

diff --git a/examples/html/cities-web.htm b/examples/html/cities-web.htm index 68d27783..203cedfc 100644 --- a/examples/html/cities-web.htm +++ b/examples/html/cities-web.htm @@ -3,6 +3,25 @@

+ + Download to Excel + Download to CSV diff --git a/examples/html/regions-hbs.htm b/examples/html/regions-hbs.htm index 5bc25da2..f8967b6f 100644 --- a/examples/html/regions-hbs.htm +++ b/examples/html/regions-hbs.htm @@ -5,6 +5,23 @@

{i18n.text('Countries')} + Regions for Country Code + + - - + + - + + - - + + + + + + + + - - + + + + - - - - + + + + + + \ No newline at end of file diff --git a/examples/countries-no-spa-js.htm b/examples/countries-no-spa-js.htm index efa4284d..ce878f04 100644 --- a/examples/countries-no-spa-js.htm +++ b/examples/countries-no-spa-js.htm @@ -29,6 +29,28 @@

Countries

data-filter-results-text-filtered="Showing {displayCount} of {totalCount} Countries" placeholder="Enter filter, example 'North America'"> +
+ +
+

Click on a column to sort rows based on column values.

Countries + + + \ No newline at end of file diff --git a/examples/countries-no-spa-vue.htm b/examples/countries-no-spa-vue.htm index 0cb087f8..32ee6bdc 100644 --- a/examples/countries-no-spa-vue.htm +++ b/examples/countries-no-spa-vue.htm @@ -44,6 +44,28 @@

Countries

data-filter-results-text-filtered="Showing {displayCount} of {totalCount} Countries" placeholder="Enter filter, example 'North America'"> +
+ +
+

Click on a column to sort rows based on column values.

@@ -89,5 +111,9 @@

Countries

+ + + \ No newline at end of file diff --git a/examples/countries-no-spa-web.htm b/examples/countries-no-spa-web.htm index de5bba7b..b96c1d0e 100644 --- a/examples/countries-no-spa-web.htm +++ b/examples/countries-no-spa-web.htm @@ -17,6 +17,9 @@
+ + +

Countries

@@ -35,6 +38,28 @@

Countries

filter-results-text-filtered="Showing {displayCount} of {totalCount} Countries" placeholder="Enter filter, example 'North America'"> +
+ +
+

Click on a column to sort rows based on column values.

+ \ No newline at end of file diff --git a/examples/export-table-js.htm b/examples/export-table-js.htm index bd9074d3..00bd0f0a 100644 --- a/examples/export-table-js.htm +++ b/examples/export-table-js.htm @@ -41,13 +41,13 @@

Export Table Demo | DataFormsJS Framework

- +
@@ -77,7 +77,8 @@

Export Table Demo | DataFormsJS Framework

- + + diff --git a/examples/export-table-web.htm b/examples/export-table-web.htm index 569d936c..917a12ce 100644 --- a/examples/export-table-web.htm +++ b/examples/export-table-web.htm @@ -46,13 +46,13 @@

Export Table Demo | Web Components

- +
@@ -76,7 +76,8 @@

Export Table Demo | Web Components

- + + From ef1d2a3811a330fe2fe20abb99f51039c0193d0c Mon Sep 17 00:00:00 2001 From: Conrad Sollitt <57777521+ConradSollitt@users.noreply.github.com> Date: Mon, 21 Feb 2022 21:42:35 -0800 Subject: [PATCH 07/51] =?UTF-8?q?=F0=9F=8E=A8=20Update=20CSV=20Icon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/to-do-list.txt | 3 +-- examples/countries-no-spa-hbs.htm | 2 +- examples/countries-no-spa-js.htm | 2 +- examples/countries-no-spa-vue.htm | 2 +- examples/countries-no-spa-web.htm | 2 +- examples/html/cities-hbs.htm | 2 +- examples/html/cities-js.htm | 2 +- examples/html/cities-vue.htm | 2 +- examples/html/cities-web.htm | 2 +- examples/html/countries-hbs.htm | 2 +- examples/html/regions-hbs.htm | 2 +- examples/html/regions-js.htm | 2 +- examples/html/regions-vue.htm | 2 +- examples/html/regions-web.htm | 2 +- examples/html/search-places-js.htm | 2 +- examples/html/search-places-results-hbs.htm | 2 +- examples/html/search-places-vue.htm | 2 +- examples/html/search-places-web.htm | 2 +- examples/img/CSV.svg | 18 ++++++++++++++++++ examples/img/Sources.txt | 7 ++++++- examples/places-demo-hbs.htm | 2 +- examples/places-demo-js.htm | 2 +- examples/places-demo-preact.htm | 2 +- examples/places-demo-react.htm | 2 +- examples/places-demo-vue.htm | 2 +- examples/places-demo-web.htm | 2 +- 26 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 examples/img/CSV.svg diff --git a/docs/to-do-list.txt b/docs/to-do-list.txt index f46b4c52..72f5eb73 100644 --- a/docs/to-do-list.txt +++ b/docs/to-do-list.txt @@ -27,8 +27,7 @@ TODO List After NPM release update all "../js/" to the CDN Page Use global search of "../js/" to find files that need to be updated - This is being worked on mid-Feb 2022 and will likely be published by the end of this month - - Need to test more browsers, IE, Safari, etc - - Need a CSV icon (currently Excel icon is used for both Excel and CSV) + - Verify new CSV icon with IE and different screens **) Update the Getting Started CSS Template used here: https://www.dataformsjs.com/en/getting-started diff --git a/examples/countries-no-spa-hbs.htm b/examples/countries-no-spa-hbs.htm index 516870a8..a46466ae 100644 --- a/examples/countries-no-spa-hbs.htm +++ b/examples/countries-no-spa-hbs.htm @@ -42,7 +42,7 @@

Countries

data-export-excel-selector="table" data-export-file-name="Countries.xlsx"> Download to CSVCountries data-export-excel-selector="table" data-export-file-name="Countries.xlsx"> Download to CSVCountries data-export-excel-selector="table" data-export-file-name="Countries.xlsx"> Download to CSVCountries data-export-excel-selector="table" data-export-file-name="Countries.xlsx"> Download to CSV{{i18n 'Largest Cities in'}} {{country}}, {{region}} data-export-excel-selector="table" data-export-file-name="{{i18n 'Cities'}}.xlsx"> {{i18n 'Download to CSV'}} Download to CSV {{ country }}, {{ region }} data-export-file-name="[[Cities]].xlsx" v-i18n-attr="'alt, title, data-export-file-name'"> Download to CSV Download to CSV{{i18n 'Countries'}} data-export-excel-selector="table" data-export-file-name="{{i18n 'Countries'}}.xlsx"> {{i18n 'Download to CSV'}} data-export-excel-selector="table" data-export-file-name="{{i18n 'Regions'}}.xlsx"> {{i18n 'Download to CSV'}} data-export-file-name="[[Regions]].xlsx" data-i18n-attr="alt, title, data-export-file-name"> Download to CSV data-export-file-name="[[Regions]].xlsx" v-i18n-attr="'alt, title, data-export-file-name'"> Download to CSVRegions for Country Code data-export-file-name="[[Regions]].xlsx" data-i18n-attr="alt, title, data-export-file-name"> Download to CSV Download to CSV{{search.cities.length}} {{i18n 'Cities Found'}} data-export-excel-selector="table" data-export-file-name="{{i18n 'Search'}}.xlsx"> {{i18n 'Download to CSV'}}{{ cities.length }} data-export-file-name="[[Search]].xlsx" v-i18n-attr="'alt, title, data-export-file-name'"> Download to CSV Download to CSV + + + CSV + Created with Sketch. + + + + + + + + + + CSV + + + \ No newline at end of file diff --git a/examples/img/Sources.txt b/examples/img/Sources.txt index ba9913d3..d76a0908 100644 --- a/examples/img/Sources.txt +++ b/examples/img/Sources.txt @@ -1,7 +1,12 @@ -** Files in the folder are licensed under the original publisher and included here +** Most files in the folder are licensed under the original publisher and included here for examples. They do not fall under the same license as DataFormsJS. https://github.com/logos https://www.w3.org/html/logo/ https://commons.wikimedia.org/wiki/File:Unofficial_JavaScript_logo_2.svg https://www.office.com/ + +* CSV Icon was created for DataFormsJS and is licensed under both MIT + and Creative Commons Attribution 4.0 + +https://github.com/dataformsjs/static-files/tree/master/img/icons diff --git a/examples/places-demo-hbs.htm b/examples/places-demo-hbs.htm index 0b91ce3f..57e80335 100644 --- a/examples/places-demo-hbs.htm +++ b/examples/places-demo-hbs.htm @@ -72,7 +72,7 @@

{{i18n 'Countries'}}

data-export-excel-selector="table" data-export-file-name="{{i18n 'Countries'}}.xlsx"> {{i18n 'Download to CSV'}} data-export-file-name="[[Countries]].xlsx" data-i18n-attr="alt, title, data-export-file-name"> Download to CSV{i18n.text('Countries')} data-export-excel-selector="table" data-export-file-name={i18n.text(fileName) + '.xlsx'} /> {i18n.text('Download{i18n.text('Countries')} data-export-excel-selector="table" data-export-file-name={i18n.text(fileName) + '.xlsx'} /> {i18n.text('Download data-export-file-name="[[Countries]].xlsx" v-i18n-attr="'alt, title, data-export-file-name'"> Download to CSV data-export-file-name="[[Countries]].xlsx" data-i18n-attr="alt, title, data-export-file-name"> Download to CSV Date: Mon, 21 Feb 2022 22:20:26 -0800 Subject: [PATCH 08/51] =?UTF-8?q?=F0=9F=90=9B=20Fix=20typo=20in=20error=20?= =?UTF-8?q?message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +++ js/DataFormsJS.js | 2 +- js/DataFormsJS.min.js | 2 +- test/js/unit-testing.js | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9818a19b..ba79ca28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ Overall the core Framework files, React Components, and Web Components and API a * Previously both `Countries` and `Countries.xlsx` would have had to be defined for each language * Now only `Countries` has to be defined * For Vue apps this applies to the `v-i18n-attr` directive +* DataFormsJS App Object + * `~/js/DataFormsJS.js` + * Fix typo in error message ## 5.12.1 (February 19, 2022) diff --git a/js/DataFormsJS.js b/js/DataFormsJS.js index b6f4f305..58e0f27f 100644 --- a/js/DataFormsJS.js +++ b/js/DataFormsJS.js @@ -2181,7 +2181,7 @@ if (templateId !== null && templateUrl !== null) { throw new TypeError('A control must have only one of the template attribute defined; either [data-template-id] or [data-template-url]. Both attributes are defined on the control.'); } else if (templateId === null && templateUrl === null) { - throw new TypeError('A control must have either attribute [data-template-id] or [data-template-url]. Niether attribute is defined for the control.'); + throw new TypeError('A control must have either attribute [data-template-id] or [data-template-url]. Neither attribute is defined for the control.'); } // Get the active model if one was not passed diff --git a/js/DataFormsJS.min.js b/js/DataFormsJS.min.js index 0f41a67f..1eb39bc2 100644 --- a/js/DataFormsJS.min.js +++ b/js/DataFormsJS.min.js @@ -2,4 +2,4 @@ // @version 5.12.1 // @author Conrad Sollitt (https://conradsollitt.com) // @license MIT -!function(){"use strict";var h,g={NotSet:"Not Set",Unknown:"Unknown",Mixed:"Mixed",Handlebars:"Handlebars",Vue:"Vue",Nunjucks:"Nunjucks",Underscore:"Underscore",Text:"Text"},c=g.NotSet,p=[g.Handlebars,g.Vue,g.Nunjucks,g.Underscore,g.Text],v=!1,s=!1,m=!1,C=null,E=0,t=null,w=!1,b=0,n=-1!==navigator.userAgent.indexOf("Trident/"),y=null,a=!1;function l(e,t,o,r){if(typeof e!==t)throw console.log(e),new TypeError("["+o+"] was not defined as a "+t+" when the function "+r+" was called")}function d(e,t,o){if(void 0===e)throw new TypeError("["+t+"] must first be defined before the function "+o+" is called");if("object"!=typeof e)throw console.log(e),new TypeError("["+t+"] was not defined as an object when the function "+o+" was called")}function i(e,t,o){if(""===e)throw new TypeError("["+t+"] must have a value when defined when the function "+o+" is called")}function f(e,t,o){if(void 0!==e[o])throw console.log(e),new TypeError("["+t+"."+o+"] is already defined")}function T(e,t,o,r){for(var n=0,a=r.length;n - "),n}catch(e){return console.error(e),n}}function r(e,t,o){e=document.createElement(e);return e.textContent=t,o&&(e.className=o),e}function L(e,t){console.error(e),window.setTimeout(function(){t&&null===t.parentNode&&((document.querySelector("body")||document.documentElement).appendChild(t),h.activeTemplate&&(h.activeTemplate.error=!0,h.activeTemplate.errorMessage=e.toString())),h.showError(t,e)},500)}function R(e,t,o){var r,n,a=null,l=null,i=null,s=null,d=null;if(null===t)i=e.getAttribute("data-template-id"),s=e.getAttribute("data-template-url"),null!==h.activeController&&(d=h.activeController.viewEngine);else{if(i=t.viewId,s=t.viewUrl,void 0!==t.viewEngine&&(d=t.viewEngine),void 0===s&&(s=null),null!==(i=void 0===i?null:i)&&null!==s)return void o(u(null,i,s,d,!0,"A controller must have either [viewId] or [viewUrl] defined but not both properties. This error is not possible when calling the [addController()] so one or more of the properties were modified by JavaScript code after the controller was already added."));if(null===i&&null===s)return"function"==typeof t.onRouteLoad||"function"==typeof t.onBeforeRender||"function"==typeof t.onRendered?void o(null):void o(u(null,i,s,d,!0,"A controller must have either [viewId] or [viewUrl] defined but neither property is defined. This error is not possible when calling the [addController()] so one or more of the properties were modified by JavaScript code after the controller was already added."))}for(r=0,n=h.compiledTemplates.length;r')}}catch(e){s=!(i=l=null),d=e}t={id:t,url:o,type:r,engine:l,html:i,error:n||s,errorMessage:n?a:d};return h.compiledTemplates.push(t),t}function U(t,o,e,r){var n="";try{if(e.error)return void h.showError(t,A(t,o)+e.errorMessage);switch(e.type){case g.Handlebars:case g.Underscore:n=e.engine(r);break;case g.Nunjucks:n=e.engine.render(r);break;case g.Text:case g.Vue:n=e.html;break;default:throw new TypeError("Unsupported or Unknown Template View Engine: "+e.type)}null===t.getAttribute("data-set-text-content")?t.innerHTML=n:t.textContent=n,h.updateTemplatesForIE(t)}catch(e){h.showError(t,A(t,o)+e)}}function V(){var e,s,d,t,o,r,n,u,a,l,p,i="hash"===y?window.location.hash:window.location.pathname,c=null,f=-1;if(200 element or [Controller] defined. Check to make sure that a controller or script for default route ["+h.settings.defaultRoute+"] exists.",h.showError(document.querySelector(h.settings.viewSelector),r)),m=!1}else h.activeController=c,u=function(){var e,t,o,r,n,a=c;if(a.modelName)h.activeModel=h.models[a.modelName];else{if(a.pageType&&void 0!==(o=h.pages[a.pageType]))if(a.viewEngine===g.Vue)if(e={},a.methods=void 0===a.methods?{}:a.methods,"function"==typeof o){e=new o;for(var l=h.getClassFunctionNames(o),i=["constructor","onRouteLoad","onBeforeRender","onRendered","onRouteUnload"],s=0;s tags are allowed."))),l=n.settings.lazyLoad.split(",").map(function(e){return e.trim()}),p=[],l.forEach(function(e){void 0===h.lazyLoad[e]?console.error("Missing [app.LazyLoad] scripts for: "+e):p.push(h.loadScripts(h.lazyLoad[e]))}),Promise.all(p).finally(function(){x(n),u()})):(x(n),u())}function x(e){if(null!==e.pageType&&void 0!==e.pageType&&void 0===e._pageCopied){var t=null;if(void 0===h.pages[e.pageType]?t="The page ["+e.pageType+"] has not been loaded for Controller[path="+e.path+"].":"object"==typeof h.pages[e.pageType]&&"object"!=typeof h.pages[e.pageType].model&&(t="Error - The [model] property for page object ["+e.pageType+"] must be a valid JavaScript Object."),null!==t)return e.settings=e.settings||{},e.settings.errorMessage=t,e.settings.hasError=!0,void h.showErrorAlert(t);var o=h.pages[e.pageType];if("function"==typeof o)for(var r=h.getClassFunctionNames(o),n=["onRouteLoad","onBeforeRender","onRendered","onRouteUnload"],a=0;a/g,">")},fetch:function(t,e,o){var r=h.deepClone({},h.settings.fetchOptions);return r.headers=h.getRequestHeaders(t),e&&(Object.assign(r,e),void 0!==e.headers&&(r.headers=Object.assign({},h.getRequestHeaders(t),e.headers))),!n||void 0!==r.method&&"GET"!==r.method&&"HEAD"!==r.method||"no-store"!==r.cache&&"no-cache"!==r.cache||((e=/([?&])_=[^&]*/).test(t)?t=t.replace(e,"$1_="+(new Date).getTime()):t+=(/\?/.test(t)?"&":"?")+"_="+(new Date).getTime()),fetch(t,r).then(function(e){return h.settings.logFetchRequests&&h.events&&"function"==typeof h.events.dispatch&&h.events.dispatch("fetch",{url:t,status:e.status}),Promise.resolve(e)}).then(function(e){var t=e.status;return 200<=t&&t<300||304===t?Promise.resolve(e):(t="Error loading data. Server Response Code: "+t+", Response Text: "+e.statusText,Promise.reject(t))}).then(function(e){var t=void 0===o?"application/json":o;return 0===(t=e.headers.has("Content-Type")?e.headers.get("Content-Type"):t).indexOf("application/json")?e.json():e.text()})},routingMode:function(){return y},changeRoute:function(e){if("string"!=typeof e)throw new TypeError("Expected string for app.changeRoute(path)");"history"===y?(window.history.pushState(null,null,e),V()):window.location.hash=0===e.indexOf("#")?e:"#"+e},pushStateClick:function(e){if(!0!==e.ctrlKey&&!e.metaKey)return e.preventDefault(),e.stopPropagation(),e.currentTarget.href?h.changeRoute(e.currentTarget.href):console.error("app.pushStateClick() called for an unknown link"),!1},addPage:function(e,t){var o,r=["onRouteLoad","onBeforeRender","onRendered","onRouteUnload"],n=(l(e,"string","name","app.addPage()"),i(e,"name","app.addPage()"),!1);if("object"==typeof t)o=t,n=!0;else{if("function"!=typeof t||"class"!==t.toString().substring(0,5))throw new TypeError("Page ["+e+"] must be defined as an object or a class when the function app.addPage() is called");o=t.prototype}return T(o,e,"page",r),r.pop(),S(o,e,"page",r),n&&d(t.model,"page."+e+".model","app.addPage()"),f(this.pages,"app.pages",e),this.pages[e]=t,this},addPlugin:function(e,t){function o(){"function"==typeof this.onRouteUnload&&this.onRouteUnload(),this.onRendered()}if(l(e,"string","name","app.addPlugin()"),i(e,"name","app.addPlugin()"),f(this.plugins,"app.plugins",e),"function"==typeof(t="function"==typeof t&&"class"===t.toString().substring(0,5)?new t:t))return this.plugins[e]={onRendered:t,reload:o},this;var r=["onRouteLoad","onBeforeRender","onRendered","onRouteUnload"];return l(t,"object","plugin","app.addPlugin()"),T(t,e,"plugin",r),r.pop(),S(t,e,"plugin",r),void 0===t.reload&&(t.reload=o),this.plugins[e]=t,this},addControl:function(e,t){if(l(e,"string","name","app.addControl()"),i(e,"name","app.addControl()"),e!==e.toLowerCase())throw new TypeError("Control names must be all lower-case. [app.addControl()] was called with: ["+e+"]");if(-1===e.indexOf("-"))throw new TypeError("Control names must contain a dash [-] character. [app.addControl()] was called with: ["+e+"]");if(-1!==e.indexOf(" "))throw new TypeError("Control names cannot contain a space. [app.addControl()] was called with: ["+e+"]");if(!1!==/[&<>"'/]/.test(e))throw new TypeError("Control names cannot contain HTML characters that need to be escaped. Invalid characters are [& < > \" ' /]. [app.addControl()] was called with: ["+e+"]");l(t,"object","control","app.addControl()"),void 0!==t.css&&l(t.css,"string","control.css","app.addControl()");var o=["onLoad","html","onUnload"];return S(t,e,"control",o),T(t,e,"control",o),o.pop(),S(t,e,"control",o),f(this.controls,"app.controls",e),this.controls[e]=t,this},addModel:function(e,t){return l(e,"string","name","app.addModel()"),i(e,"name","app.addModel()"),l(t,"object","model","app.addModel()"),f(this.models,"app.models",e),this.models[e]=t,this},addController:function(t){var e,o=["onRouteLoad","onBeforeRender","onRendered","onRouteUnload"];if(l(t,"object","controller","app.addController()"),l(t.path,"string","controller.path","app.addController()"),i(t.path,"controller.path","app.addController()"),this.controllers.forEach(function(e){if(e.path===t.path)throw new TypeError("[app.controllers(path="+t.path+")] is already defined")}),e="app.addController(path="+t.path+")",void 0===t.viewId)t.viewId=null;else{l(t.viewId,"string","controller.viewId",e),i(t.viewId,"controller.viewId",e);var r=t.viewId,n="controller.viewId",a=e;if(null===document.getElementById(r))throw new TypeError("An element was not found on the page with ["+n+"][id="+r+"] when the function "+a+" was called")}if(void 0===t.viewUrl?t.viewUrl=null:(l(t.viewUrl,"string","controller.viewUrl",e),i(t.viewUrl,"controller.viewUrl",e)),null!==t.viewId&&null!==t.viewUrl)throw new TypeError("A controller cannot have both [viewId] and [viewUrl] defined when calling "+e);if(void 0!==t.viewEngine){if(null===t.viewId&&null===t.viewUrl)throw new TypeError("When a controller uses the [viewEngine] property either [viewId] or [viewUrl] must also be defined when calling "+e);if(-1===p.indexOf(t.viewEngine))throw new TypeError("Invalid [viewEngine] property when calling "+e+". Valid values are: "+p.join(", "))}return null!==t.modelName&&void 0!==t.modelName&&(l(t.modelName,"string","controller.modelName",e),i(t.modelName,"controller.modelName",e),d(this.models[t.modelName],"app.models."+t.modelName,e)),null!==t.pageType&&void 0!==t.pageType&&(l(t.pageType,"string","controller.pageType",e),i(t.pageType,"controller.pageType",e)),T(t,t.path,"controller",o),null!==t.pageType&&void 0!==t.pageType||(o.pop(),o.push("viewId"),o.push("viewUrl"),S(t,t.path,"controller",o)),this.controllers.push(t),this},controller:function(e){for(var t=0,o=this.controllers.length;t or