From d2b53ccac2aee18f4ee415f4310257e1994231d8 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Thu, 30 Nov 2023 10:54:29 -0800 Subject: [PATCH] DEV: Port `discourse-table-builder` theme component to core (#24441) --- .../components/modal/spreadsheet-editor.gjs | 389 + .../instance-initializers/post-decorations.js | 135 +- .../discourse/app/lib/utilities.js | 76 + .../discourse/app/services/composer.js | 19 + .../acceptance/post-table-wrapper-test.js | 4 +- .../tests/acceptance/table-builder-test.js | 33 + .../discourse/tests/fixtures/md-table.js | 3 + .../tests/unit/lib/utilities-test.js | 162 + app/assets/stylesheets/common.scss | 1 + .../stylesheets/common/base/topic-post.scss | 5 +- .../common/table-builder/_index.scss | 5 + .../table-builder/insert-table-modal.scss | 78 + .../table-builder/jspreadsheet-theme.scss | 272 + .../table-builder/table-edit-decorator.scss | 36 + .../table-builder/vendor/jspreadsheet.scss | 699 + .../common/table-builder/vendor/jsuites.scss | 3038 +++ config/locales/client.en.yml | 59 + .../javascripts/jspreadsheet/jspreadsheet.js | 16905 ++++++++++++++++ public/javascripts/jsuites/jsuites.js | 12414 ++++++++++++ script/table_builder_libraries.bash | 97 + .../page_objects/modals/insert_table.rb | 44 + spec/system/table_builder_spec.rb | 144 + 22 files changed, 34592 insertions(+), 26 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/components/modal/spreadsheet-editor.gjs create mode 100644 app/assets/javascripts/discourse/tests/acceptance/table-builder-test.js create mode 100644 app/assets/javascripts/discourse/tests/fixtures/md-table.js create mode 100644 app/assets/stylesheets/common/table-builder/_index.scss create mode 100644 app/assets/stylesheets/common/table-builder/insert-table-modal.scss create mode 100644 app/assets/stylesheets/common/table-builder/jspreadsheet-theme.scss create mode 100644 app/assets/stylesheets/common/table-builder/table-edit-decorator.scss create mode 100644 app/assets/stylesheets/common/table-builder/vendor/jspreadsheet.scss create mode 100644 app/assets/stylesheets/common/table-builder/vendor/jsuites.scss create mode 100644 public/javascripts/jspreadsheet/jspreadsheet.js create mode 100644 public/javascripts/jsuites/jsuites.js create mode 100755 script/table_builder_libraries.bash create mode 100644 spec/system/page_objects/modals/insert_table.rb create mode 100644 spec/system/table_builder_spec.rb diff --git a/app/assets/javascripts/discourse/app/components/modal/spreadsheet-editor.gjs b/app/assets/javascripts/discourse/app/components/modal/spreadsheet-editor.gjs new file mode 100644 index 00000000000..eaa1043e7a7 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/modal/spreadsheet-editor.gjs @@ -0,0 +1,389 @@ +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { action } from "@ember/object"; +import didInsert from "@ember/render-modifiers/modifiers/did-insert"; +import { schedule } from "@ember/runloop"; +import { inject as service } from "@ember/service"; +import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner"; +import DButton from "discourse/components/d-button"; +import DModal from "discourse/components/d-modal"; +import DModalCancel from "discourse/components/d-modal-cancel"; +import TextField from "discourse/components/text-field"; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import loadScript from "discourse/lib/load-script"; +import { + arrayToTable, + findTableRegex, + tokenRange, +} from "discourse/lib/utilities"; +import i18n from "discourse-common/helpers/i18n"; +import I18n from "discourse-i18n"; +import DTooltip from "float-kit/components/d-tooltip"; + +export default class SpreadsheetEditor extends Component { + @service dialog; + @tracked showEditReason = false; + @tracked loading = null; + spreadsheet = null; + defaultColWidth = 150; + isEditingTable = !!this.args.model.tableTokens; + + get modalAttributes() { + if (this.isEditingTable) { + return { + title: "table_builder.edit.modal.title", + insertTable: { + title: "table_builder.edit.modal.create", + icon: "pencil-alt", + }, + }; + } else { + return { + title: "table_builder.modal.title", + insertTable: { + title: "table_builder.modal.create", + icon: "plus", + }, + }; + } + } + + @action + createSpreadsheet(spreadsheet) { + this.spreadsheet = spreadsheet; + + schedule("afterRender", () => { + this.loadLibraries().then(() => { + if (this.isEditingTable) { + this.buildPopulatedTable(this.args.model.tableTokens); + } else { + this.buildNewTable(); + } + }); + }); + } + + @action + showEditReasonField() { + this.showEditReason = !this.showEditReason; + } + + @action + interceptCloseModal() { + if (this._hasChanges()) { + this.dialog.yesNoConfirm({ + message: I18n.t("table_builder.modal.confirm_close"), + didConfirm: () => this.args.closeModal(), + }); + } else { + this.args.closeModal(); + } + } + + @action + insertTable() { + const updatedHeaders = this.spreadsheet.getHeaders().split(","); // keys + const updatedData = this.spreadsheet.getData(); // values + const markdownTable = this.buildTableMarkdown(updatedHeaders, updatedData); + + if (!this.isEditingTable) { + this.args.model.toolbarEvent.addText(markdownTable); + return this.args.closeModal(); + } else { + return this.updateTable(markdownTable); + } + } + + _hasChanges() { + if (this.isEditingTable) { + const originalSpreadsheetData = this.extractTableContent( + tokenRange(this.args.model.tableTokens, "tr_open", "tr_close") + ); + const currentHeaders = this.spreadsheet.getHeaders().split(","); + const currentRows = this.spreadsheet.getData(); + const currentSpreadsheetData = currentHeaders.concat(currentRows.flat()); + + return ( + JSON.stringify(currentSpreadsheetData) !== + JSON.stringify(originalSpreadsheetData) + ); + } else { + return this.spreadsheet + .getData() + .flat() + .some((element) => element !== ""); + } + } + + loadLibraries() { + this.loading = true; + return loadScript("/javascripts/jsuites/jsuites.js") + .then(() => { + return loadScript("/javascripts/jspreadsheet/jspreadsheet.js"); + }) + .finally(() => (this.loading = false)); + } + + buildNewTable() { + const data = [ + ["", "", ""], + ["", "", ""], + ["", "", ""], + ["", "", ""], + ["", "", ""], + ["", "", ""], + ]; + + const columns = [ + { + title: I18n.t("table_builder.default_header.col_1"), + width: this.defaultColWidth, + }, + { + title: I18n.t("table_builder.default_header.col_2"), + width: this.defaultColWidth, + }, + { + title: I18n.t("table_builder.default_header.col_3"), + + width: this.defaultColWidth, + }, + { + title: I18n.t("table_builder.default_header.col_4"), + + width: this.defaultColWidth, + }, + ]; + + return this.buildSpreadsheet(data, columns); + } + + extractTableContent(data) { + return data + .flat() + .filter((t) => t.type === "inline") + .map((t) => t.content); + } + + buildPopulatedTable(tableTokens) { + const contentRows = tokenRange(tableTokens, "tr_open", "tr_close"); + const rows = []; + let headings; + const rowWidthFactor = 8; + + contentRows.forEach((row, index) => { + if (index === 0) { + // headings + headings = this.extractTableContent(row).map((heading) => { + return { + title: heading, + width: Math.max( + heading.length * rowWidthFactor, + this.defaultColWidth + ), + align: "left", + }; + }); + } else { + // rows: + rows.push(this.extractTableContent(row)); + } + }); + + return this.buildSpreadsheet(rows, headings); + } + + buildSpreadsheet(data, columns, opts = {}) { + const postNumber = this.args.model?.post_number; + const exportFileName = postNumber + ? `post-${postNumber}-table-export` + : `post-table-export`; + + // eslint-disable-next-line no-undef + this.spreadsheet = jspreadsheet(this.spreadsheet, { + data, + columns, + defaultColAlign: "left", + wordWrap: true, + csvFileName: exportFileName, + text: this.localeMapping(), + ...opts, + }); + } + + buildUpdatedPost(tableIndex, raw, newRaw) { + const tableToEdit = raw.match(findTableRegex()); + let editedTable; + + if (tableToEdit.length) { + editedTable = raw.replace(tableToEdit[tableIndex], newRaw); + } else { + return raw; + } + + // replace null characters + editedTable = editedTable.replace(/\0/g, "\ufffd"); + return editedTable; + } + + updateTable(markdownTable) { + const tableIndex = this.args.model.tableIndex; + const postId = this.args.model.post.id; + const newRaw = markdownTable; + + const editReason = + this.editReason || I18n.t("table_builder.edit.default_edit_reason"); + const raw = this.args.model.post.raw; + const newPostRaw = this.buildUpdatedPost(tableIndex, raw, newRaw); + + return this.sendTableUpdate(postId, newPostRaw, editReason); + } + + sendTableUpdate(postId, raw, edit_reason) { + return ajax(`/posts/${postId}.json`, { + type: "PUT", + data: { + post: { + raw, + edit_reason, + }, + }, + }) + .catch(popupAjaxError) + .finally(() => { + this.args.closeModal(); + }); + } + + buildTableMarkdown(headers, data) { + const table = []; + data.forEach((row) => { + const result = {}; + + headers.forEach((_key, index) => { + const columnKey = `col${index}`; + return (result[columnKey] = row[index]); + }); + table.push(result); + }); + + return arrayToTable(table, headers); + } + + localeMapping() { + return { + noRecordsFound: prefixedLocale("no_records_found"), + show: prefixedLocale("show"), + entries: prefixedLocale("entries"), + insertANewColumnBefore: prefixedLocale("context_menu.col.before"), + insertANewColumnAfter: prefixedLocale("context_menu.col.after"), + deleteSelectedColumns: prefixedLocale("context_menu.col.delete"), + renameThisColumn: prefixedLocale("context_menu.col.rename"), + orderAscending: prefixedLocale("context_menu.order.ascending"), + orderDescending: prefixedLocale("context_menu.order.descending"), + insertANewRowBefore: prefixedLocale("context_menu.row.before"), + insertANewRowAfter: prefixedLocale("context_menu.row.after"), + deleteSelectedRows: prefixedLocale("context_menu.row.delete"), + copy: prefixedLocale("context_menu.copy"), + paste: prefixedLocale("context_menu.paste"), + saveAs: prefixedLocale("context_menu.save"), + about: prefixedLocale("about"), + areYouSureToDeleteTheSelectedRows: prefixedLocale( + "prompts.delete_selected_rows" + ), + areYouSureToDeleteTheSelectedColumns: prefixedLocale( + "prompts.delete_selected_cols" + ), + thisActionWillDestroyAnyExistingMergedCellsAreYouSure: prefixedLocale( + "prompts.will_destroy_merged_cells" + ), + thisActionWillClearYourSearchResultsAreYouSure: prefixedLocale( + "prompts.will_clear_search_results" + ), + thereIsAConflictWithAnotherMergedCell: prefixedLocale( + "prompts.conflict_with_merged_cells" + ), + invalidMergeProperties: prefixedLocale("invalid_merge_props"), + cellAlreadyMerged: prefixedLocale("cells_already_merged"), + noCellsSelected: prefixedLocale("no_cells_selected"), + }; + } + + +} + +function prefixedLocale(localeString) { + return I18n.t(`table_builder.spreadsheet.${localeString}`); +} diff --git a/app/assets/javascripts/discourse/app/instance-initializers/post-decorations.js b/app/assets/javascripts/discourse/app/instance-initializers/post-decorations.js index 0ecf7f88544..9c04cdad135 100644 --- a/app/assets/javascripts/discourse/app/instance-initializers/post-decorations.js +++ b/app/assets/javascripts/discourse/app/instance-initializers/post-decorations.js @@ -1,13 +1,18 @@ import { schedule } from "@ember/runloop"; import { create } from "virtual-dom"; import FullscreenTableModal from "discourse/components/modal/fullscreen-table"; +import SpreadsheetEditor from "discourse/components/modal/spreadsheet-editor"; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; import Columns from "discourse/lib/columns"; import highlightSyntax from "discourse/lib/highlight-syntax"; import { nativeLazyLoading } from "discourse/lib/lazy-load-images"; import lightbox from "discourse/lib/lightbox"; import { SELECTORS } from "discourse/lib/lightbox/constants"; import { withPluginApi } from "discourse/lib/plugin-api"; +import { parseAsync } from "discourse/lib/text"; import { setTextDirections } from "discourse/lib/text-direction"; +import { tokenRange } from "discourse/lib/utilities"; import { iconHTML, iconNode } from "discourse-common/lib/icon-library"; import I18n from "discourse-i18n"; @@ -106,21 +111,33 @@ export default { }); }); - function _createButton() { + function _createButton(props) { const openPopupBtn = document.createElement("button"); - openPopupBtn.classList.add( + const defaultClasses = [ "open-popup-link", "btn-default", "btn", "btn-icon", - "btn-expand-table", - "no-text" - ); - const expandIcon = create( - iconNode("discourse-expand", { class: "expand-table-icon" }) - ); - openPopupBtn.title = I18n.t("fullscreen_table.expand_btn"); - openPopupBtn.append(expandIcon); + "no-text", + ]; + + openPopupBtn.classList.add(...defaultClasses); + + if (props.classes) { + openPopupBtn.classList.add(...props.classes); + } + + if (props.title) { + openPopupBtn.title = I18n.t(props.title); + } + + if (props.icon) { + const icon = create( + iconNode(props.icon.name, { class: props.icon?.class }) + ); + openPopupBtn.append(icon); + } + return openPopupBtn; } @@ -128,14 +145,64 @@ export default { return scrollWidth > clientWidth; } - function generateModal(event) { + function generateFullScreenTableModal(event) { const table = event.currentTarget.parentElement.nextElementSibling; const tempTable = table.cloneNode(true); modal.show(FullscreenTableModal, { model: { tableHtml: tempTable } }); } - function generatePopups(tables) { - tables.forEach((table) => { + function generateSpreadsheetModal() { + const tableIndex = this.tableIndex; + + return ajax(`/posts/${this.id}`, { type: "GET" }) + .then((post) => { + parseAsync(post.raw).then((tokens) => { + const allTables = tokenRange(tokens, "table_open", "table_close"); + const tableTokens = allTables[tableIndex]; + + modal.show(SpreadsheetEditor, { + model: { + post, + tableIndex, + tableTokens, + }, + }); + }); + }) + .catch(popupAjaxError); + } + + function generatePopups(tables, attrs) { + tables.forEach((table, index) => { + const buttonWrapper = document.createElement("div"); + buttonWrapper.classList.add("fullscreen-table-wrapper__buttons"); + + const tableEditorBtn = _createButton({ + classes: ["btn-edit-table"], + title: "table_builder.edit.btn_edit", + icon: { + name: "pencil-alt", + class: "edit-table-icon", + }, + }); + + table.parentNode.setAttribute("data-table-index", index); + table.parentNode.classList.add("fullscreen-table-wrapper"); + + if (attrs.canEdit) { + buttonWrapper.append(tableEditorBtn); + tableEditorBtn.addEventListener( + "click", + generateSpreadsheetModal.bind({ + tableIndex: index, + ...attrs, + }), + false + ); + } + + table.parentNode.insertBefore(buttonWrapper, table); + if (!isOverflown(table.parentNode)) { return; } @@ -144,28 +211,50 @@ export default { return; } - const popupBtn = _createButton(); - table.parentNode.classList.add("fullscreen-table-wrapper"); - // Create a button wrapper for case of multiple buttons (i.e. table builder extension) - const buttonWrapper = document.createElement("div"); - buttonWrapper.classList.add("fullscreen-table-wrapper--buttons"); - buttonWrapper.append(popupBtn); - popupBtn.addEventListener("click", generateModal, false); + const expandTableBtn = _createButton({ + classes: ["btn-expand-table"], + title: "fullscreen_table.expand_btn", + icon: { name: "discourse-expand", class: "expand-table-icon" }, + }); + buttonWrapper.append(expandTableBtn); + expandTableBtn.addEventListener( + "click", + generateFullScreenTableModal, + false + ); table.parentNode.insertBefore(buttonWrapper, table); }); } + function cleanupPopupBtns() { + const editTableBtn = document.querySelector( + ".open-popup-link.btn-edit-table" + ); + const expandTableBtn = document.querySelector( + ".open-popup-link.btn-expand-table" + ); + + expandTableBtn?.removeEventListener( + "click", + generateFullScreenTableModal + ); + editTableBtn?.removeEventListener("click", generateSpreadsheetModal); + } + api.decorateCookedElement( - (post) => { + (post, helper) => { schedule("afterRender", () => { - const tables = post.querySelectorAll("table"); - generatePopups(tables); + const tables = post.querySelectorAll(".md-table table"); + generatePopups(tables, helper.widget.attrs); }); }, { onlyStream: true, + id: "table-wrapper", } ); + + api.cleanupStream(cleanupPopupBtns); }); }, }; diff --git a/app/assets/javascripts/discourse/app/lib/utilities.js b/app/assets/javascripts/discourse/app/lib/utilities.js index 9b5fe6235b9..9d6e9d7d5a9 100644 --- a/app/assets/javascripts/discourse/app/lib/utilities.js +++ b/app/assets/javascripts/discourse/app/lib/utilities.js @@ -613,3 +613,79 @@ export function getCaretPosition(element, options) { return adjustedPosition; } + +/** + * Generate markdown table from an array of objects + * Inspired by https://github.com/Ygilany/array-to-table + * + * @param {Array} array Array of objects + * @param {Array} columns Column headings + * @param {String} colPrefix Table column prefix + * + * @return {String} Markdown table + */ +export function arrayToTable(array, cols, colPrefix = "col") { + let table = ""; + + // Generate table headers + table += "|"; + table += cols.join(" | "); + table += "|\r\n|"; + + // Generate table header separator + table += cols + .map(function () { + return "---"; + }) + .join(" | "); + table += "|\r\n"; + + // Generate table body + array.forEach(function (item) { + table += "|"; + + table += + cols + .map(function (_key, index) { + return String(item[`${colPrefix}${index}`] || "").replace( + /\r?\n|\r/g, + " " + ); + }) + .join(" | ") + "|\r\n"; + }); + + return table; +} + +/** + * + * @returns a regular expression finding all markdown tables + */ +export function findTableRegex() { + return /((\r?){2}|^)(^\|[^\r\n]*(\r?\n)?)+(?=(\r?\n){2}|$)/gm; +} + +export function tokenRange(tokens, start, end) { + const contents = []; + let startPushing = false; + let items = []; + + tokens.forEach((token) => { + if (token.type === start) { + startPushing = true; + } + + if (token.type === end) { + contents.push(items); + items = []; + startPushing = false; + } + + if (startPushing) { + items.push(token); + } + }); + + return contents; +} diff --git a/app/assets/javascripts/discourse/app/services/composer.js b/app/assets/javascripts/discourse/app/services/composer.js index b52d5f4de94..534a587dd54 100644 --- a/app/assets/javascripts/discourse/app/services/composer.js +++ b/app/assets/javascripts/discourse/app/services/composer.js @@ -9,6 +9,7 @@ import $ from "jquery"; import { Promise } from "rsvp"; import DiscardDraftModal from "discourse/components/modal/discard-draft"; import PostEnqueuedModal from "discourse/components/modal/post-enqueued"; +import SpreadsheetEditor from "discourse/components/modal/spreadsheet-editor"; import { categoryBadgeHTML } from "discourse/helpers/category-link"; import { cannotPostAgain, @@ -424,6 +425,14 @@ export default class ComposerService extends Service { }) ); + options.push( + this._setupPopupMenuOption({ + action: "toggleSpreadsheet", + icon: "table", + label: "composer.insert_table", + }) + ); + return options.concat( customPopupMenuOptions .map((option) => this._setupPopupMenuOption(option)) @@ -723,6 +732,16 @@ export default class ComposerService extends Service { this.toggleProperty("model.whisper"); } + @action + toggleSpreadsheet() { + this.modal.show(SpreadsheetEditor, { + model: { + toolbarEvent: this.toolbarEvent, + tableTokens: null, + }, + }); + } + @action toggleInvisible() { this.toggleProperty("model.unlistTopic"); diff --git a/app/assets/javascripts/discourse/tests/acceptance/post-table-wrapper-test.js b/app/assets/javascripts/discourse/tests/acceptance/post-table-wrapper-test.js index 74d4b1719f5..38415c4c666 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/post-table-wrapper-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/post-table-wrapper-test.js @@ -17,13 +17,13 @@ acceptance("Post Table Wrapper Test", function () { assert.ok( exists( - `${postWithLargeTable} .fullscreen-table-wrapper .fullscreen-table-wrapper--buttons .open-popup-link` + `${postWithLargeTable} .fullscreen-table-wrapper .fullscreen-table-wrapper__buttons .open-popup-link` ), "buttons for the table wrapper appear inside a separate div" ); const fullscreenButtonWrapper = query( - `${postWithLargeTable} .fullscreen-table-wrapper .fullscreen-table-wrapper--buttons` + `${postWithLargeTable} .fullscreen-table-wrapper .fullscreen-table-wrapper__buttons` ); assert.strictEqual( diff --git a/app/assets/javascripts/discourse/tests/acceptance/table-builder-test.js b/app/assets/javascripts/discourse/tests/acceptance/table-builder-test.js new file mode 100644 index 00000000000..c509104c25c --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/table-builder-test.js @@ -0,0 +1,33 @@ +import { click, visit } from "@ember/test-helpers"; +import { test } from "qunit"; +import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import I18n from "discourse-i18n"; + +acceptance("Table Builder", function (needs) { + needs.user(); + + test("Can see table builder button when creating a topic", async function (assert) { + await visit("/"); + await click("#create-topic"); + await click(".d-editor-button-bar .options"); + await selectKit(".toolbar-popup-menu-options").expand(); + + assert + .dom(`.select-kit-row[data-name='${I18n.t("composer.insert_table")}']`) + .exists("it shows the builder button"); + }); + + test("Can see table builder button when editing post", async function (assert) { + await visit("/t/internationalization-localization/280"); + await click("#post_1 .show-more-actions"); + await click("#post_1 .edit"); + assert.ok(exists("#reply-control")); + await click(".d-editor-button-bar .options"); + await selectKit(".toolbar-popup-menu-options").expand(); + + assert + .dom(`.select-kit-row[data-name='${I18n.t("composer.insert_table")}']`) + .exists("it shows the builder button"); + }); +}); diff --git a/app/assets/javascripts/discourse/tests/fixtures/md-table.js b/app/assets/javascripts/discourse/tests/fixtures/md-table.js new file mode 100644 index 00000000000..cf410f5c6a4 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/fixtures/md-table.js @@ -0,0 +1,3 @@ +export const mdTable = `|Make | Model | Year|\r\n|--- | --- | ---|\r\n|Toyota | Supra | 1998|\r\n|Nissan | Skyline | 1999|\r\n|Honda | S2000 | 2001|\r\n`; +export const mdTableSpecialChars = `|Make | Model | Price|\r\n|--- | --- | ---|\r\n|Toyota | Supra | $50,000|\r\n| | Celica | $20,000|\r\n|Nissan | GTR | $80,000|\r\n`; +export const mdTableNonUniqueHeadings = `|col1 | col2 | col1|\r\n|--- | --- | ---|\r\n|Col A | Col B | Col C|\r\n`; diff --git a/app/assets/javascripts/discourse/tests/unit/lib/utilities-test.js b/app/assets/javascripts/discourse/tests/unit/lib/utilities-test.js index b1c379fe57c..4019f393d80 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/utilities-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/utilities-test.js @@ -6,6 +6,7 @@ import Handlebars from "handlebars"; import { module, test } from "qunit"; import sinon from "sinon"; import { + arrayToTable, caretRowCol, clipboardCopyAsync, defaultHomepage, @@ -13,6 +14,7 @@ import { escapeExpression, extractDomainFromUrl, fillMissingDates, + findTableRegex, inCodeBlock, initializeDefaultHomepage, mergeSortedLists, @@ -22,6 +24,11 @@ import { slugify, toAsciiPrintable, } from "discourse/lib/utilities"; +import { + mdTable, + mdTableNonUniqueHeadings, + mdTableSpecialChars, +} from "discourse/tests/fixtures/md-table"; import { setupRenderingTest } from "discourse/tests/helpers/component-test"; import { chromeTest } from "discourse/tests/helpers/qunit-helpers"; @@ -373,3 +380,158 @@ module("Unit | Utilities | clipboard", function (hooks) { } ); }); + +module("Unit | Utilities | table-builder", function (hooks) { + setupTest(hooks); + + test("arrayToTable", function (assert) { + const tableData = [ + { + col0: "Toyota", + col1: "Supra", + col2: "1998", + }, + { + col0: "Nissan", + col1: "Skyline", + col2: "1999", + }, + { + col0: "Honda", + col1: "S2000", + col2: "2001", + }, + ]; + + assert.strictEqual( + arrayToTable(tableData, ["Make", "Model", "Year"]), + mdTable, + "it creates a markdown table from an array of objects (with headers as keys)" + ); + + const specialCharsTableData = [ + { + col0: "Toyota", + col1: "Supra", + col2: "$50,000", + }, + { + col0: "", + col1: "Celica", + col2: "$20,000", + }, + { + col0: "Nissan", + col1: "GTR", + col2: "$80,000", + }, + ]; + + assert.strictEqual( + arrayToTable(specialCharsTableData, ["Make", "Model", "Price"]), + mdTableSpecialChars, + "it creates a markdown table with special characters in correct alignment" + ); + + const nonUniqueColumns = ["col1", "col2", "col1"]; + + assert.strictEqual( + arrayToTable( + [{ col0: "Col A", col1: "Col B", col2: "Col C" }], + nonUniqueColumns + ), + mdTableNonUniqueHeadings, + "it does not suppress a column if heading is the same as another column" + ); + }); + test("arrayToTable with custom column prefix", function (assert) { + const tableData = [ + { + A0: "hey", + A1: "you", + }, + { + A0: "over", + A1: "there", + }, + ]; + + assert.strictEqual( + arrayToTable(tableData, ["Col 1", "Col 2"], "A"), + `|Col 1 | Col 2|\r\n|--- | ---|\r\n|hey | you|\r\n|over | there|\r\n`, + "it works" + ); + }); + + test("arrayToTable returns valid table with multiline cell data", function (assert) { + const tableData = [ + { + col0: "Jane\nDoe", + col1: "Teri", + }, + { + col0: "Finch", + col1: "Sami", + }, + ]; + + assert.strictEqual( + arrayToTable(tableData, ["Col 1", "Col 2"]), + `|Col 1 | Col 2|\r\n|--- | ---|\r\n|Jane Doe | Teri|\r\n|Finch | Sami|\r\n`, + "it creates a valid table" + ); + }); + + test("findTableRegex", function (assert) { + const oneTable = `|Make|Model|Year|\r\n|--- | --- | ---|\r\n|Toyota|Supra|1998|`; + + assert.strictEqual( + oneTable.match(findTableRegex()).length, + 1, + "finds one table in markdown" + ); + + const threeTables = `## Heading +|Table1 | PP Port | Device | DP | Medium| +|--- | --- | --- | --- | ---| +| Something | (1+2) | Dude | Mate | Bro | + +|Table2 | PP Port | Device | DP | Medium| +|--- | --- | --- | --- | ---| +| Something | (1+2) | Dude | Mate | Bro | +| ✅ | (1+2) | Dude | Mate | Bro | +| ✅ | (1+2) | Dude | Mate | Bro | + +|Table3 | PP Port | Device | DP | +|--- | --- | --- | --- | +| Something | (1+2) | Dude | Sound | +| | (1+2) | Dude | OW | +| | (1+2) | Dude | OI | + +Random extras + `; + + assert.strictEqual( + threeTables.match(findTableRegex()).length, + 3, + "finds three tables in markdown" + ); + + const ignoreUploads = ` +:information_source: Something + +[details=Example of a cross-connect in Equinix] +![image|603x500, 100%](upload://fURYa9mt00rXZITdYhhyeHFJE8J.png) +[/details] + +|Table1 | PP Port | Device | DP | Medium| +|--- | --- | --- | --- | ---| +| Something | (1+2) | Dude | Mate | Bro | +`; + assert.strictEqual( + ignoreUploads.match(findTableRegex()).length, + 1, + "finds on table, ignoring upload markup" + ); + }); +}); diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 18c72861311..dc113567074 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -19,3 +19,4 @@ @import "common/loading-slider"; @import "common/float-kit/_index"; @import "common/login/_index"; +@import "common/table-builder/_index"; diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index 73131be273c..3ee5ca6638d 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -1648,10 +1648,13 @@ iframe { } .open-popup-link { + display: inline; + margin-inline: 0.25em; position: sticky; left: 1rem; opacity: 0%; white-space: nowrap; + transition: 0.25s ease-in-out opacity; } .fullscreen-table-wrapper { @@ -1659,7 +1662,7 @@ iframe { display: block; position: relative; - &--buttons { + &__buttons { position: absolute; top: 0.5rem; right: 0.5rem; diff --git a/app/assets/stylesheets/common/table-builder/_index.scss b/app/assets/stylesheets/common/table-builder/_index.scss new file mode 100644 index 00000000000..34264181789 --- /dev/null +++ b/app/assets/stylesheets/common/table-builder/_index.scss @@ -0,0 +1,5 @@ +@import "vendor/jspreadsheet"; +@import "vendor/jsuites"; +@import "jspreadsheet-theme"; +@import "table-edit-decorator"; +@import "insert-table-modal"; diff --git a/app/assets/stylesheets/common/table-builder/insert-table-modal.scss b/app/assets/stylesheets/common/table-builder/insert-table-modal.scss new file mode 100644 index 00000000000..60fe5f7dd73 --- /dev/null +++ b/app/assets/stylesheets/common/table-builder/insert-table-modal.scss @@ -0,0 +1,78 @@ +.btn-insert-table { + background: var(--tertiary); + color: var(--secondary); + + .d-icon { + color: var(--secondary); + } + + .discourse-no-touch & { + &:hover { + background-color: var(--tertiary-hover); + color: var(--secondary); + + .d-icon { + color: var(--secondary); + } + } + } +} + +.insert-table-modal { + display: flex; + flex-direction: column; + align-items: flex-start; + + .d-modal__container, + .modal-inner-container { + --modal-max-width: $reply-area-max-width; + width: 100%; + height: 100%; + max-height: unset; + + display: grid; + grid-template-rows: auto 1fr auto; + } + + .modal-body { + padding: 0; + margin: 0; + } + + .d-modal__footer, + .modal-footer { + display: flex; + align-items: center; + justify-content: space-between; + + .secondary-actions { + display: flex; + align-items: center; + gap: 0.5rem; + + .edit-reason { + display: flex; + align-items: center; + gap: 0.5rem; + } + + .btn { + margin-right: 0; + } + + input { + margin: 0; + } + } + + .secondary-actions .tippy-content { + h4 { + color: var(--primary); + } + li { + margin-block: 0.25rem; + color: var(--primary-high); + } + } + } +} diff --git a/app/assets/stylesheets/common/table-builder/jspreadsheet-theme.scss b/app/assets/stylesheets/common/table-builder/jspreadsheet-theme.scss new file mode 100644 index 00000000000..a05f4552798 --- /dev/null +++ b/app/assets/stylesheets/common/table-builder/jspreadsheet-theme.scss @@ -0,0 +1,272 @@ +:root { + --jexcel_header_color: var(--primary-high); + --jexcel_header_color_highlighted: var(--primary-high); + --jexcel_header_background: var(--primary-very-low); + --jexcel_header_background_highlighted: var(--primary-low); + + --jexcel_content_color: var(--primary); + --jexcel_content_color_highlighted: var(--primary-high); + --jexcel_content_background: var(--secondary); + --jexcel_content_background_highlighted: var(--tertiary-very-low); + + --jexcel_menu_background: var(--secondary); + --jexcel_menu_background_highlighted: var(--secondary-very-high); + --jexcel_menu_color: var(--primary-medium); + --jexcel_menu_color_highlighted: var(--primary); + + --jexcel_border_color: var(--primary-low-mid); + --jexcel_border_color_highlighted: var(--tertiary-high); + + --active_color: var(--primary-very-low); + --active-color: var(--active_color); +} + +.jexcel { + border-bottom: 1px solid var(--jexcel_border_color); + border-right: transparent; + background-color: transparent; +} + +table.jexcel > thead > tr > td { + border-top: 1px solid transparent; + border-left: 1px solid transparent; + border-right: 1px solid transparent; + border-bottom: 1px solid #000; + background-color: #fff; + padding: 10px; + font-weight: bold; +} +table.jexcel > tbody > tr > td { + padding: 8px; + border-right: 1px solid transparent; + border-left: 1px solid transparent; +} + +table.jexcel { + border-bottom: 1px solid var(--jexcel_border_color); +} + +.jcontextmenu.jexcel_contextmenu hr { + border-color: var(--jexcel_border_color); +} + +.jexcel_container .jcontextmenu > div a { + color: var(--jexcel_menu_color); +} + +.jexcel_corner { + background-color: var(--tertiary); +} + +.jexcel > tbody > tr > td, +.jexcel > thead > tr > td { + border-top: 1px solid var(--jexcel_border_color); + border-left: 1px solid var(--jexcel_border_color); + background-color: var(--jexcel_content_background); + color: var(--jexcel_content_color); +} + +.jexcel > tbody > tr > td:first-child, +.jexcel > thead > tr > td { + background-color: var(--jexcel_header_background); + color: var(--jexcel_header_color); +} + +.jexcel > thead > tr > td.selected, +.jexcel > tbody > tr.selected > td:first-child { + background-color: var(--jexcel_header_background_highlighted); + color: var(--jexcel_header_color_highlighted); +} + +table.jexcel > tbody > tr > td:first-child { + background-color: var(--jexcel_header_background); +} + +table.jexcel > tbody > tr.selected > td:first-child { + background-color: var(--jexcel_header_background_highlighted); +} + +.jexcel > tbody > tr > td.jexcel_cursor a { + color: var(--active-color); +} + +.jexcel_pagination > div > div { + color: var(--jexcel_header_color); + background: var(--jexcel_header_background); + border: 1px solid var(--jexcel_border_color); +} + +.jexcel_page, +.jexcel_container input, +.jexcel_container select { + color: var(--jexcel_header_color); + background: var(--jexcel_header_background); + border: 1px solid var(--jexcel_border_color); +} + +.jexcel_contextmenu.jcontextmenu { + border: 1px solid var(--jexcel_border_color); + background: var(--jexcel_menu_background); + color: var(--jexcel_menu_color); + box-shadow: 0 12px 12px rgba(0, 0, 0, 0.15); +} + +.jcontextmenu > div a { + color: var(--jexcel_menu_color); +} + +.jcontextmenu > div:not(.contextmenu-line):hover a { + color: var(--jexcel_menu_color_highlighted); +} + +.jcontextmenu > div:not(.contextmenu-line):hover { + background: var(--jexcel_menu_background_highlighted); +} + +.jexcel_dropdown .jdropdown-container, +.jexcel_dropdown .jdropdown-content { + background-color: var(--jexcel_content_background); + color: var(--jexcel_content_color); +} + +.jexcel_dropdown .jdropdown-item { + color: var(--jexcel_content_color); +} + +.jexcel_dropdown .jdropdown-item:hover, +.jexcel_dropdown .jdropdown-selected, +.jexcel_dropdown .jdropdown-cursor { + background-color: var(--jexcel_content_background_highlighted); + color: var(--jexcel_content_color_highlighted); +} + +.jexcel .jcalendar-content { + background-color: var(--jexcel_header_background); + color: var(--jexcel_header_color); +} + +.jexcel .jcalendar-content > table { + background-color: var(--jexcel_content_background); + color: var(--jexcel_content_color); +} + +.jexcel .jcalendar-weekday { + background-color: var(--jexcel_content_background_highlighted); + color: var(--jexcel_content_color_highlighted); +} + +.jexcel .jcalendar-sunday { + color: var(--jexcel_header_color); +} + +.jexcel .jcalendar-selected { + background-color: var(--jexcel_content_background_highlighted); + color: var(--jexcel_content_color_highlighted); +} + +.jexcel_toolbar i.jexcel_toolbar_item { + color: var(--jexcel_content_color); +} + +.jexcel_toolbar i.jexcel_toolbar_item:hover { + background: var(--jexcel_content_background_highlighted); + color: var(--jexcel_content_color_highlighted); +} + +.jexcel_toolbar { + background: var(--jexcel_header_background); +} + +.jexcel_content::-webkit-scrollbar-track { + background: var(--jexcel_background_head); +} + +.jexcel_content::-webkit-scrollbar-thumb { + background: var(--jexcel_background_head_highlighted); +} + +.jexcel_border_main { + border: 1px solid #000; + border-color: var(--jexcel_border_color_highlighted); +} + +.jexcel .highlight { + background-color: var(--jexcel_content_background_highlighted); +} + +.jexcel .highlight-bottom { + border-bottom: 1.5px solid var(--jexcel_border_color_highlighted); +} +.jexcel .highlight-right { + border-right: 1.5px solid var(--jexcel_border_color_highlighted); +} +.jexcel .highlight-left { + border-left: 1.5px solid var(--jexcel_border_color_highlighted); +} +.jexcel .highlight-top { + border-top: 1.5px solid var(--jexcel_border_color_highlighted); +} +.jexcel .copying-top { + border-top-color: var(--jexcel_border_color_highlighted); +} +.jexcel .copying-right { + border-right-color: var(--jexcel_border_color_highlighted); +} +.jexcel .copying-left { + border-left-color: var(--jexcel_border_color_highlighted); +} +.jexcel .copying-bottom { + border-bottom-color: var(--jexcel_border_color_highlighted); +} +.jexcel_border_main, +.jexcel .highlight-top.highlight-left, +.jexcel .highlight-top, +.jexcel .highlight-left { + -webkit-box-shadow: unset; + box-shadow: unset; +} + +table.jexcel > thead > tr > td { + border-top: 1px solid var(--jexcel_border_color); + border-right: 1px solid var(--jexcel_border_color); + border-bottom: 1px solid var(--jexcel_border_color); + background-color: var(--jexcel_header_background); + + &:first-child { + border-left: 1px solid var(--jexcel_border_color); + } +} + +table.jexcel > thead > tr > td.selected { + background-color: var(--jexcel_header_background_highlighted); + color: var(--jexcel_header_color_highlighted); +} + +table.jexcel > tbody > tr > td { + border-right: 1px solid var(--jexcel_border_color); + + &:first-child { + border-left: 1px solid var(--jexcel_border_color); + } +} + +// Hides about item in context menu +.jcontextmenu > div:not(.contextmenu-line):last-child { + display: none; +} + +.jexcel_container { + padding: 0.5em; + min-width: 100%; + .jexcel_content { + min-width: 100%; + padding: 0; + table.jexcel { + min-width: 100%; + } + } +} + +.jexcel_container { + padding: 0; +} diff --git a/app/assets/stylesheets/common/table-builder/table-edit-decorator.scss b/app/assets/stylesheets/common/table-builder/table-edit-decorator.scss new file mode 100644 index 00000000000..3ee65d9e2b2 --- /dev/null +++ b/app/assets/stylesheets/common/table-builder/table-edit-decorator.scss @@ -0,0 +1,36 @@ +.btn-edit-table { + -webkit-user-select: none; + -webkit-touch-callout: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.fullscreen-table-wrapper:hover .fullscreen-table-wrapper__buttons button { + opacity: 100%; +} + +.mobile-view { + .btn-edit-table { + display: none; + z-index: 2; + position: absolute; + top: 1rem; + left: 1rem; + } + + .fullscreen-table-wrapper { + position: relative; + + &:hover { + table { + opacity: 0.5; + } + + .btn-edit-table { + display: block; + z-index: 2; + } + } + } +} diff --git a/app/assets/stylesheets/common/table-builder/vendor/jspreadsheet.scss b/app/assets/stylesheets/common/table-builder/vendor/jspreadsheet.scss new file mode 100644 index 00000000000..92572f0831f --- /dev/null +++ b/app/assets/stylesheets/common/table-builder/vendor/jspreadsheet.scss @@ -0,0 +1,699 @@ +:root { + --jexcel-border-color: #000; +} + +.jexcel_container { + display: inline-block; + padding-right: 2px; + box-sizing: border-box; + overscroll-behavior: contain; + outline: none; +} + +.jexcel_container.fullscreen { + position: fixed; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + z-index: 21; +} + +.jexcel_container.fullscreen .jexcel_content { + overflow: auto; + width: 100%; + height: 100%; + background-color: #ffffff; +} + +.jexcel_container.with-toolbar .jexcel > thead > tr > td { + top: 0; +} + +.jexcel_container.fullscreen.with-toolbar { + height: calc(100% - 46px); +} + +.jexcel_content { + display: inline-block; + box-sizing: border-box; + padding-right: 3px; + padding-bottom: 3px; + position: relative; + scrollbar-width: thin; + scrollbar-color: #666 transparent; +} + +@supports (-moz-appearance: none) { + .jexcel_content { + padding-right: 10px; + } +} + +.jexcel_content::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.jexcel_content::-webkit-scrollbar-track { + background: #eee; +} + +.jexcel_content::-webkit-scrollbar-thumb { + background: #666; +} + +.jexcel { + border-collapse: separate; + table-layout: fixed; + white-space: nowrap; + empty-cells: show; + border: 0px; + background-color: #fff; + width: 0; + + border-top: 1px solid transparent; + border-left: 1px solid transparent; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; +} + +.jexcel > thead > tr > td { + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + border-right: 1px solid transparent; + border-bottom: 1px solid transparent; + background-color: #f3f3f3; + padding: 2px; + cursor: pointer; + box-sizing: border-box; + overflow: hidden; + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 2; +} + +.jexcel_container.with-toolbar .jexcel > thead > tr > td { + top: 42px; +} + +.jexcel > thead > tr > td.dragging { + background-color: #fff; + opacity: 0.5; +} + +.jexcel > thead > tr > td.selected { + background-color: #dcdcdc; +} + +.jexcel > thead > tr > td.arrow-up { + background-repeat: no-repeat; + background-position: center right 5px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 14l5-5 5 5H7z' fill='gray'/%3E%3C/svg%3E"); + text-decoration: underline; +} + +.jexcel > thead > tr > td.arrow-down { + background-repeat: no-repeat; + background-position: center right 5px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='gray'/%3E%3C/svg%3E"); + text-decoration: underline; +} + +.jexcel > tbody > tr > td:first-child { + position: relative; + background-color: #f3f3f3; + text-align: center; +} + +.jexcel > tbody.resizable > tr > td:first-child::before { + content: "\00a0"; + width: 100%; + height: 3px; + position: absolute; + bottom: 0px; + left: 0px; + cursor: row-resize; +} + +.jexcel > tbody.draggable > tr > td:first-child::after { + content: "\00a0"; + width: 3px; + height: 100%; + position: absolute; + top: 0px; + right: 0px; + cursor: move; +} + +.jexcel > tbody > tr.dragging > td { + background-color: #eee; + opacity: 0.5; +} + +.jexcel > tbody > tr > td { + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + border-right: 1px solid transparent; + border-bottom: 1px solid transparent; + padding: 4px; + white-space: nowrap; + box-sizing: border-box; + line-height: 1em; +} + +.jexcel_overflow > tbody > tr > td { + overflow: hidden; +} + +.jexcel > tbody > tr > td:last-child { + overflow: hidden; +} + +.jexcel > tbody > tr > td > img { + display: inline-block; + max-width: 100px; +} + +.jexcel > tbody > tr > td.readonly { + color: rgba(0, 0, 0, 0.3); +} +.jexcel > tbody > tr.selected > td:first-child { + background-color: #dcdcdc; +} +.jexcel > tbody > tr > td > select, +.jexcel > tbody > tr > td > input, +.jexcel > tbody > tr > td > textarea { + border: 0px; + border-radius: 0px; + outline: 0px; + width: 100%; + margin: 0px; + padding: 0px; + padding-right: 2px; + background-color: transparent; + box-sizing: border-box; +} + +.jexcel > tbody > tr > td > textarea { + resize: none; + padding-top: 6px !important; +} + +.jexcel > tbody > tr > td > input[type="checkbox"] { + width: 12px; + margin-top: 2px; +} +.jexcel > tbody > tr > td > input[type="radio"] { + width: 12px; + margin-top: 2px; +} + +.jexcel > tbody > tr > td > select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-repeat: no-repeat; + background-position-x: 100%; + background-position-y: 40%; + background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSdibGFjaycgaGVpZ2h0PScyNCcgdmlld0JveD0nMCAwIDI0IDI0JyB3aWR0aD0nMjQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Zyc+PHBhdGggZD0nTTcgMTBsNSA1IDUtNXonLz48cGF0aCBkPSdNMCAwaDI0djI0SDB6JyBmaWxsPSdub25lJy8+PC9zdmc+); +} + +.jexcel > tbody > tr > td.jexcel_dropdown { + background-repeat: no-repeat; + background-position: top 50% right 5px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='lightgray'/%3E%3C/svg%3E"); + text-overflow: ellipsis; + overflow-x: hidden; +} + +.jexcel > tbody > tr > td.jexcel_dropdown.jexcel_comments { + background: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='lightgray'/%3E%3C/svg%3E") + top 50% right 5px no-repeat, + url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII=") + top right no-repeat; +} + +.jexcel > tbody > tr > td > .color { + width: 90%; + height: 10px; + margin: auto; +} + +.jexcel > tbody > tr > td > a { + text-decoration: underline; +} + +.jexcel > tbody > tr > td.highlight > a { + color: blue; + cursor: pointer; +} + +.jexcel > tfoot > tr > td { + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + border-right: 1px solid transparent; + border-bottom: 1px solid transparent; + background-color: #f3f3f3; + padding: 2px; + cursor: pointer; + box-sizing: border-box; + overflow: hidden; +} + +.jexcel .highlight { + background-color: rgba(0, 0, 0, 0.05); +} + +.jexcel .highlight-top { + border-top: 1px solid #000; /* var(--jexcel-border-color);*/ + box-shadow: 0px -1px #ccc; +} + +.jexcel .highlight-left { + border-left: 1px solid #000; /* var(--jexcel-border-color);*/ + box-shadow: -1px 0px #ccc; +} + +.jexcel .highlight-right { + border-right: 1px solid #000; /* var(--jexcel-border-color);*/ +} + +.jexcel .highlight-bottom { + border-bottom: 1px solid #000; /* var(--jexcel-border-color);*/ +} + +.jexcel .highlight-top.highlight-left { + box-shadow: -1px -1px #ccc; + -webkit-box-shadow: -1px -1px #ccc; + -moz-box-shadow: -1px -1px #ccc; +} + +.jexcel .highlight-selected { + background-color: rgba(0, 0, 0, 0); +} +.jexcel .selection { + background-color: rgba(0, 0, 0, 0.05); +} +.jexcel .selection-left { + border-left: 1px dotted #000; +} +.jexcel .selection-right { + border-right: 1px dotted #000; +} +.jexcel .selection-top { + border-top: 1px dotted #000; +} +.jexcel .selection-bottom { + border-bottom: 1px dotted #000; +} +.jexcel_corner { + position: absolute; + background-color: rgb(0, 0, 0); + height: 1px; + width: 1px; + border: 1px solid rgb(255, 255, 255); + top: -2000px; + left: -2000px; + cursor: crosshair; + box-sizing: initial; + z-index: 20; + padding: 2px; +} + +.jexcel .editor { + outline: 0px solid transparent; + overflow: visible; + white-space: nowrap; + text-align: left; + padding: 0px; + box-sizing: border-box; + overflow: visible !important; +} + +.jexcel .editor > input { + padding-left: 4px; +} + +.jexcel .editor .jupload { + position: fixed; + top: 100%; + z-index: 40; + user-select: none; + -webkit-font-smoothing: antialiased; + font-size: 0.875rem; + letter-spacing: 0.2px; + -webkit-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); + box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); + padding: 10px; + background-color: #fff; + width: 300px; + min-height: 225px; + margin-top: 2px; +} + +.jexcel .editor .jupload img { + width: 100%; + height: auto; +} + +.jexcel .editor .jexcel_richtext { + position: fixed; + top: 100%; + z-index: 40; + user-select: none; + -webkit-font-smoothing: antialiased; + font-size: 0.875rem; + letter-spacing: 0.2px; + -webkit-box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); + box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); + padding: 10px; + background-color: #fff; + min-width: 280px; + max-width: 310px; + margin-top: 2px; + text-align: left; +} + +.jexcel .editor .jclose:after { + position: absolute; + top: 0; + right: 0; + margin: 10px; + content: "close"; + font-family: "Material icons"; + font-size: 24px; + width: 24px; + height: 24px; + line-height: 24px; + cursor: pointer; + text-shadow: 0px 0px 5px #fff; +} + +.jexcel, +.jexcel td, +.jexcel_corner { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-user-drag: none; + -khtml-user-drag: none; + -moz-user-drag: none; + -o-user-drag: none; + user-drag: none; +} + +.jexcel_textarea { + position: absolute; + top: -999px; + left: -999px; + width: 1px; + height: 1px; +} +.jexcel .dragline { + position: absolute; +} +.jexcel .dragline div { + position: relative; + top: -6px; + height: 5px; + width: 22px; +} +.jexcel .dragline div:hover { + cursor: move; +} + +.jexcel .onDrag { + background-color: rgba(0, 0, 0, 0.6); +} + +.jexcel .error { + border: 1px solid red; +} + +.jexcel thead td.resizing { + border-right-style: dotted !important; + border-right-color: red !important; +} + +.jexcel tbody tr.resizing > td { + border-bottom-style: dotted !important; + border-bottom-color: red !important; +} + +.jexcel tbody td.resizing { + border-right-style: dotted !important; + border-right-color: red !important; +} + +.jexcel .jdropdown-header { + border: 0px !important; + outline: none !important; + width: 100% !important; + height: 100% !important; + padding: 0px !important; + padding-left: 8px !important; +} + +.jexcel .jdropdown-container { + margin-top: 1px; +} + +.jexcel .jdropdown-container-header { + padding: 0px; + margin: 0px; + height: inherit; +} + +.jexcel .jdropdown-picker { + border: 0px !important; + padding: 0px !important; + width: inherit; + height: inherit; +} + +.jexcel .jexcel_comments { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII="); + background-repeat: no-repeat; + background-position: top right; +} + +.jexcel .sp-replacer { + margin: 2px; + border: 0px; +} + +.jexcel > thead > tr.jexcel_filter > td > input { + border: 0px; + width: 100%; + outline: none; +} + +.jexcel_about { + float: right; + font-size: 0.7em; + padding: 2px; + text-transform: uppercase; + letter-spacing: 1px; + display: none; +} +.jexcel_about a { + color: #ccc; + text-decoration: none; +} + +.jexcel_about img { + display: none; +} + +.jexcel_filter { + display: flex; + justify-content: space-between; + margin-bottom: 4px; +} + +.jexcel_filter > div { + padding: 8px; + align-items: center; +} + +.jexcel_pagination { + display: flex; + justify-content: space-between; + align-items: center; +} + +.jexcel_pagination > div { + display: flex; + padding: 10px; +} + +.jexcel_pagination > div:last-child { + padding-right: 10px; + padding-top: 10px; +} + +.jexcel_pagination > div > div { + text-align: center; + width: 36px; + height: 36px; + line-height: 34px; + border: 1px solid #ccc; + box-sizing: border-box; + margin-left: 2px; + cursor: pointer; +} + +.jexcel_page { + font-size: 0.8em; +} + +.jexcel_page_selected { + font-weight: bold; + background-color: #f3f3f3; +} + +.jexcel_toolbar { + display: flex; + background-color: #f3f3f3; + border: 1px solid #ccc; + padding: 4px; + margin: 0px 2px 4px 1px; + position: sticky; + top: 0px; + z-index: 21; +} + +.jexcel_toolbar:empty { + display: none; +} + +.jexcel_toolbar i.jexcel_toolbar_item { + width: 24px; + height: 24px; + padding: 4px; + cursor: pointer; + display: inline-block; +} + +.jexcel_toolbar i.jexcel_toolbar_item:hover { + background-color: #ddd; +} + +.jexcel_toolbar select.jexcel_toolbar_item { + margin-left: 2px; + margin-right: 2px; + display: inline-block; + border: 0px; + background-color: transparent; + padding-right: 10px; +} + +.jexcel .dragging-left { + background-repeat: no-repeat; + background-position: top 50% left 0px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M14 7l-5 5 5 5V7z'/%3E%3Cpath fill='none' d='M24 0v24H0V0h24z'/%3E%3C/svg%3E"); +} + +.jexcel .dragging-right { + background-repeat: no-repeat; + background-position: top 50% right 0px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 17l5-5-5-5v10z'/%3E%3Cpath fill='none' d='M0 24V0h24v24H0z'/%3E%3C/svg%3E"); +} + +.jexcel_tabs .jexcel_tab { + display: none; +} + +.jexcel_tabs .jexcel_tab_link { + display: inline-block; + padding: 10px; + padding-left: 20px; + padding-right: 20px; + margin-right: 5px; + margin-bottom: 5px; + background-color: #f3f3f3; + cursor: pointer; +} + +.jexcel_tabs .jexcel_tab_link.selected { + background-color: #ddd; +} + +.jexcel_hidden_index > tbody > tr > td:first-child, +.jexcel_hidden_index > thead > tr > td:first-child, +.jexcel_hidden_index > tfoot > tr > td:first-child, +.jexcel_hidden_index > colgroup > col:first-child { + display: none; +} + +.jexcel .jrating { + display: inline-flex; +} +.jexcel .jrating > div { + zoom: 0.55; +} + +.jexcel .copying-top { + border-top: 1px dashed #000; +} + +.jexcel .copying-left { + border-left: 1px dashed #000; +} + +.jexcel .copying-right { + border-right: 1px dashed #000; +} + +.jexcel .copying-bottom { + border-bottom: 1px dashed #000; +} + +.jexcel .jexcel_column_filter { + background-repeat: no-repeat; + background-position: top 50% right 5px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='gray' width='18px' height='18px'%3E%3Cpath d='M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + text-overflow: ellipsis; + overflow: hidden; + padding: 0px; + padding-left: 6px; + padding-right: 20px; +} + +.jexcel thead .jexcel_freezed, +.jexcel tfoot .jexcel_freezed { + left: 0px; + z-index: 3 !important; + box-shadow: 2px 0px 2px 0.2px #ccc !important; + -webkit-box-shadow: 2px 0px 2px 0.2px #ccc !important; + -moz-box-shadow: 2px 0px 2px 0.2px #ccc !important; +} + +.jexcel tbody .jexcel_freezed { + position: relative; + background-color: #fff; + box-shadow: 1px 1px 1px 1px #ccc !important; + -webkit-box-shadow: 2px 4px 4px 0.1px #ccc !important; + -moz-box-shadow: 2px 4px 4px 0.1px #ccc !important; +} + +.red { + color: red; +} + +.jexcel > tbody > tr > td.readonly > input[type="checkbox"], +.jexcel > tbody > tr > td.readonly > input[type="radio"] { + pointer-events: none; + opacity: 0.5; +} diff --git a/app/assets/stylesheets/common/table-builder/vendor/jsuites.scss b/app/assets/stylesheets/common/table-builder/vendor/jsuites.scss new file mode 100644 index 00000000000..dcbd6c05b85 --- /dev/null +++ b/app/assets/stylesheets/common/table-builder/vendor/jsuites.scss @@ -0,0 +1,3038 @@ +.jexcel_container { + :root { + --button-color: #298ba8; + --active-color: #007aff; + --safe-area-top: env(safe-area-inset-top); + --safe-area-bottom: env(safe-area-inset-bottom); + } + + [data-visible="false"] { + display: none; + } + + div[data-before]:before { + content: attr(data-before); + } + + .unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .jreadonly { + pointer-events: none; + } + + .jdragging { + opacity: 0.2; + filter: alpha(opacity=20); + } + + .jupload.input { + position: relative; + box-sizing: border-box; + background-size: initial; + height: 33px; + min-height: initial; + padding: 6px; + padding-right: 30px; + } + + .jupload.input:before { + content: "save"; + font-size: 18px; + font-family: "Material Icons"; + color: #000; + position: absolute; + right: 5px; + } + + .jupload:empty:before { + z-index: 0; + } + + .jupload img { + width: 100%; + } + + .jupload.input img { + width: initial; + max-width: 100%; + height: 100%; + } + + .jupload[data-multiple] { + padding: 10px; + } + + .jupload[data-multiple] img { + height: 70px; + width: 100px; + object-fit: cover; + margin-right: 5px; + margin-bottom: 5px; + } + + .jupload { + position: relative; + border: 1px dotted #eee; + cursor: pointer; + box-sizing: border-box; + width: 100%; + max-width: 100%; + max-height: 100%; + min-height: 180px; + } + + .jupload:not(.input):before { + content: "\e2c3"; + font-family: "Material Icons"; + font-size: 90px; + color: #eee; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + z-index: -1; + } + + .jupload-item { + padding-right: 22px; + border-radius: 1px; + display: inline-block; + position: relative; + } + + .jphoto { + position: relative; + border: 1px dotted #eee; + cursor: pointer; + box-sizing: border-box; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + } + + .jphoto:empty:before { + content: "\e2c3"; + font-family: "Material Icons"; + font-size: 90px; + color: #eee; + width: 100%; + height: 100%; + text-align: center; + } + + .jremove { + opacity: 0.2; + filter: alpha(opacity=20); + } + + .round img { + border-radius: 1000px; + } + + .jtooltip { + position: fixed; + top: 10px; + left: 10px; + z-index: 5; + + font-family: initial; + font-size: 12px; + color: #000; + background-color: #fff; + border: 1px solid black; + padding: 8px; + margin: 10px; + + display: block; + animation: fadeIn 0.5s; + pointer-events: none; + } + + .jtooltip:empty { + display: none; + } + + @keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + /** Loading */ + .jloading { + position: fixed; + z-index: 10001; + width: 100%; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + } + + .jloading::after { + content: ""; + display: block; + margin: 0 auto; + margin-top: 50vh; + width: 40px; + height: 40px; + border-style: solid; + border-color: white; + border-top-color: transparent; + border-width: 4px; + border-radius: 50%; + -webkit-animation: spin 0.8s linear infinite; + animation: spin 0.8s linear infinite; + } + + .jloading.spin { + background-color: transparent; + } + + .jloading.spin::after { + margin: 0 auto; + margin-top: 80px; + border-color: #aaa; + border-top-color: transparent; + } + + /** Animations **/ + .fade-in { + animation: fade-in 2s forwards; + } + + .fade-out { + animation: fade-out 1s forwards; + } + + .slide-left-in { + position: relative; + animation: slide-left-in 0.4s forwards; + } + + .slide-left-out { + position: relative; + animation: slide-left-out 0.4s forwards; + } + + .slide-right-in { + position: relative; + animation: slide-right-in 0.4s forwards; + } + + .slide-right-out { + position: relative; + animation: slide-right-out 0.4s forwards; + } + + .slide-top-in { + position: relative; + animation: slide-top-in 0.4s forwards; + } + + .slide-top-out { + position: relative; + animation: slide-top-out 0.2s forwards; + } + + .slide-bottom-in { + position: relative; + animation: slide-bottom-in 0.4s forwards; + } + + .slide-bottom-out { + position: relative; + animation: slide-bottom-out 0.1s forwards; + } + + .slide-left-in > div { + -webkit-transform: translateZ(0px); + -webkit-transform: translate3d(0, 0, 0); + } + + .slide-left-out > div { + -webkit-transform: translateZ(0px); + -webkit-transform: translate3d(0, 0, 0); + } + + .slide-right-in > div { + -webkit-transform: translateZ(0px); + -webkit-transform: translate3d(0, 0, 0); + } + + .slide-right-out > div { + -webkit-transform: translateZ(0px); + -webkit-transform: translate3d(0, 0, 0); + } + + .spin { + animation: spin 2s infinite linear; + } + + /** Fadein and Fadeout **/ + @keyframes fade-in { + 0% { + opacity: 0; + } + 100% { + opacity: 100; + } + } + + @-webkit-keyframes fade-in { + 0% { + opacity: 0; + } + 100% { + opacity: 100; + } + } + + @keyframes fade-out { + 0% { + opacity: 100; + } + 100% { + opacity: 0; + } + } + + @-webkit-keyframes fade-out { + 0% { + opacity: 100; + } + 100% { + opacity: 0; + } + } + + /** Keyframes Left to Right **/ + @keyframes slide-left-in { + 0% { + left: -100%; + } + 100% { + left: 0%; + } + } + + @-webkit-keyframes slide-left-in { + 0% { + left: -100%; + } + 100% { + left: 0%; + } + } + + @keyframes slide-left-out { + 0% { + left: 0%; + } + 100% { + left: -100%; + } + } + + @-webkit-keyframes slide-left-out { + 0% { + left: 0%; + } + 100% { + left: -100%; + } + } + + /** Keyframes Right to Left **/ + @keyframes slide-right-in { + 0% { + left: 100%; + } + 100% { + left: 0%; + } + } + + @-webkit-keyframes slide-right-in { + 0% { + left: 100%; + } + 100% { + left: 0%; + } + } + + @keyframes slide-right-out { + 0% { + left: 0%; + } + 100% { + left: 100%; + } + } + + @-webkit-keyframes slide-right-out { + 0% { + left: 0%; + } + 100% { + left: 100%; + } + } + + /** Keyframes Top to Bottom **/ + @keyframes slide-top-in { + 0% { + transform: translateY(-100%); + } + 100% { + transform: translateY(0%); + } + } + + @-webkit-keyframes slide-top-in { + 0% { + transform: translateY(-100%); + } + 100% { + -webkit-transform: translateY(0%); + } + } + + @keyframes slide-top-out { + 0% { + transform: translateY(0%); + } + 100% { + transform: translateY(-100%); + } + } + + @-webkit-keyframes slide-top-out { + 0% { + -webkit-transform: translateY(0%); + } + 100% { + -webkit-transform: translateY(-100%); + } + } + + /** Keyframes Bottom to Top **/ + @keyframes slide-bottom-in { + 0% { + transform: translateY(100%); + } + 100% { + transform: translateY(0%); + } + } + + @-webkit-keyframes slide-bottom-in { + 0% { + transform: translateY(100%); + } + 100% { + -webkit-transform: translateY(0%); + } + } + + @keyframes slide-bottom-out { + 0% { + transform: translateY(0%); + } + 100% { + transform: translateY(100%); + } + } + + @-webkit-keyframes slide-bottom-out { + 0% { + -webkit-transform: translateY(0%); + } + 100% { + -webkit-transform: translateY(100%); + } + } + + @-webkit-keyframes spin { + from { + -webkit-transform: rotate(0deg); + } + to { + -webkit-transform: rotate(359deg); + } + } + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(359deg); + } + } + .jcalendar { + position: absolute; + z-index: 9000; + display: none; + box-sizing: border-box; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-tap-highlight-color: transparent; + min-width: 280px; + } + + .jcalendar.jcalendar-focus { + display: block; + } + + .jcalendar .jcalendar-backdrop { + position: fixed; + top: 0px; + left: 0px; + z-index: 9000; + min-width: 100%; + min-height: 100%; + background-color: rgba(0, 0, 0, 0.5); + border: 0px; + padding: 0px; + display: none; + } + + .jcalendar .jcalendar-container { + position: relative; + box-sizing: border-box; + } + + .jcalendar .jcalendar-content { + position: absolute; + z-index: 9001; + -webkit-box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.39); + -moz-box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.39); + box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.39); + background-color: #fff; + } + + .jcalendar-header { + text-align: center; + } + + .jcalendar-header span { + margin-right: 4px; + font-size: 1.1em; + font-weight: bold; + } + + .jcalendar-prev { + cursor: pointer; + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2724%27 height=%2724%27 viewBox=%270 0 24 24%27%3E%3Cpath d=%27M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z%27 fill=%27%23000%27 /%3E%3Cpath fill=%27none%27 d=%27M0 0h24v24H0V0z%27/%3E%3C/svg%3E"); + background-position: center; + background-repeat: no-repeat; + } + + .jcalendar-next { + cursor: pointer; + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2724%27 height=%2724%27 viewBox=%270 0 24 24%27%3E%3Cpath d=%27M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z%27 fill=%27%23000%27 /%3E%3Cpath fill=%27none%27 d=%27M0 0h24v24H0V0z%27/%3E%3C/svg%3E"); + background-position: center; + background-repeat: no-repeat; + } + + .jcalendar-weekday { + font-weight: 600; + background-color: #fcfcfc; + padding: 14px; + } + + .jcalendar-table { + padding: 10px; + } + + .jcalendar-table > table { + width: 100%; + background-color: #fff; + } + + .jcalendar-table > table > thead { + cursor: pointer; + } + + .jcalendar-table thead td { + padding: 10px; + height: 40px; + } + + .jcalendar-table > table > tbody > tr { + height: 34px; + } + + .jcalendar-table > table > tbody td { + box-sizing: border-box; + cursor: pointer; + padding: 9px; + font-size: 0.9em; + } + + .jcalendar-table tfoot td { + padding: 10px; + } + + .jcalendar-months td, + .jcalendar-years td { + height: 24px; + } + + .jcalendar-input { + padding-right: 18px; + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2716%27 height=%2716%27 viewBox=%270 0 24 24%27 fill=%27gray%27%3E%3Cpath d=%27M20 3h-1V1h-2v2H7V1H5v2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 18H4V8h16v13z%27/%3E%3Cpath fill=%27none%27 d=%27M0 0h24v24H0z%27/%3E%3C/svg%3E"); + background-position: top 50% right 5px; + background-repeat: no-repeat; + box-sizing: border-box; + } + + .jcalendar-done { + -webkit-box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.39); + -moz-box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.39); + box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.39); + background-color: #fff; + } + + .jcalendar-update { + border: 1px solid #ccc; + background-color: #fff; + border-radius: 4px; + padding: 5px; + width: 100%; + } + + .jcalendar-container select { + width: 55px; + display: inline-block; + border: 0px; + padding: 4px; + text-align: center; + font-size: 1.1em; + user-select: none; + margin-right: 10px; + } + + .jcalendar-container select:first-child { + margin-right: 2px; + } + + .jcalendar-selected { + background-color: #eee; + } + + .jcalendar-reset, + .jcalendar-confirm { + text-transform: uppercase; + cursor: pointer; + color: var(--active-color); + } + + .jcalendar-controls { + padding: 15px; + + -webkit-box-sizing: border-box; + box-sizing: border-box; + vertical-align: middle; + + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + + -webkit-flex-flow: row wrap; + justify-content: space-between; + align-items: center; + } + + .jcalendar-controls div { + font-weight: bold; + } + + .jcalendar-fullsize { + position: fixed; + width: 100%; + top: 0px; + left: 0px; + } + + .jcalendar-fullsize .jcalendar-content { + position: fixed; + width: 100%; + left: 0px; + bottom: 0px; + } + + .jcalendar-focus.jcalendar-fullsize .jcalendar-backdrop { + display: block; + } + + .jcalendar-sunday { + color: red; + } + .jcalendar-disabled { + color: #ccc; + } + + .jcalendar-time { + display: flex; + } + + .jcalendar_warning { + color: red; + } + + .jcalendar-hide-controls .jcalendar-controls { + display: none; + } + + .jcolor { + display: none; + outline: none; + position: absolute; + } + + .jcolor-input { + padding-right: 24px !important; + background: url("data:image/svg+xml,%0A%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27 fill=%27black%27 width=%2718px%27 height=%2718px%27%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3Cpath d=%27M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z%27/%3E%3C/svg%3E") + top 50% right 4px no-repeat, + content-box; + box-sizing: border-box; + } + + .jcolor-content { + position: absolute; + z-index: 9000; + user-select: none; + -webkit-font-smoothing: antialiased; + font-size: 0.875rem; + letter-spacing: 0.2px; + -webkit-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); + box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); + background-color: #fff; + box-sizing: border-box; + min-width: 260px; + } + + .jmodal .jcolor-content { + position: fixed; + } + + .jcolor-controls { + display: flex; + padding: 10px; + border-bottom: 1px solid #eee; + margin-bottom: 5px; + } + + .jcolor-controls div { + flex: 1; + font-size: 1em; + color: var(--active-color); + text-transform: uppercase; + font-weight: bold; + box-sizing: border-box; + } + + .jcolor-content table { + border-collapse: collapse; + box-sizing: border-box; + } + + .jcolor-focus { + display: block; + } + + .jcolor table { + width: 100%; + height: 100%; + min-height: 160px; + } + + .jcolor td { + padding: 7px; + } + + .jcolor-selected { + background-repeat: no-repeat; + background-size: 16px; + background-position: center; + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2724%27 height=%2724%27 viewBox=%270 0 24 24%27%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3Cpath d=%27M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z%27 fill=%27white%27/%3E%3C/svg%3E"); + } + + .jcolor-fullscreen { + position: fixed; + left: 0px; + bottom: 0px; + width: 100%; + max-height: 290px; + border-radius: 0px; + box-sizing: border-box; + } + + .jcolor-fullscreen .jcolor-controls { + padding: 15px; + -webkit-box-shadow: 1px 0px 1px 0px rgba(0, 0, 0, 0.39); + -moz-box-shadow: 1px 0px 1px 0px rgba(0, 0, 0, 0.39); + box-shadow: 1px 0px 1px 0px rgba(0, 0, 0, 0.39); + } + + .jcolor-reset { + text-align: left; + } + + .jcolor-close { + text-align: right; + } + + .jcolor-backdrop { + position: fixed; + top: 0px; + left: 0px; + min-width: 100%; + min-height: 100%; + background-color: rgba(0, 0, 0, 0.5); + border: 0px; + padding: 0px; + z-index: 8000; + display: none; + + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + supported by Chrome and Opera */ + } + + .jcolor-content .jtabs-content { + padding: 7px; + } + + .jcolor-grid tr:first-child > td:first-child { + border-top-left-radius: 3px; + } + + .jcolor-grid tr:first-child > td:last-child { + border-top-right-radius: 3px; + } + + .jcolor-grid tr:last-child > td:first-child { + border-bottom-left-radius: 3px; + } + + .jcolor-grid tr:last-child > td:last-child { + border-bottom-right-radius: 3px; + } + + .jcolor-hsl { + box-sizing: border-box; + } + + .jcolor-hsl > div { + height: 100%; + position: relative; + } + + .jcolor-hsl canvas { + display: block; + border-radius: 4px; + -webkit-user-drag: none; + } + + .jcolor-point { + height: 5px; + width: 5px; + background-color: #000; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 50%; + } + + .jcolor-sliders { + padding: 10px 20px 10px 10px; + } + + .jcolor-sliders input { + -webkit-appearance: none; + + height: 12px; + width: 80%; + + background: #d3d3d3; + opacity: 1; + + border-radius: 30px; + outline: none; + } + + .jcolor-sliders-input-subcontainer { + display: flex; + justify-content: space-between; + align-items: center; + } + + .jcolor-sliders-input-container { + margin-top: 4px; + line-height: 0.8em; + text-align: left; + } + + .jcolor-sliders-input-container > label { + font-size: 10px; + text-transform: uppercase; + color: #bbbbbd; + } + + .jcolor-sliders-input-subcontainer > input { + border: 0px; + padding: 1px; + } + + .jcolor-sliders-input-container input::-webkit-slider-thumb { + -webkit-appearance: none; + height: 12px; + width: 12px; + border-radius: 50%; + background: #000; + border: 2px solid #fff; + cursor: pointer; + } + + .jcolor-sliders-input-container input::-moz-range-thumb { + -webkit-appearance: none; + height: 12px; + width: 12px; + border-radius: 50%; + background: #000; + border: 2px solid #fff; + cursor: pointer; + } + + .jcolor-sliders-final-color { + padding: 6px; + user-select: all; + margin-top: 10px; + text-align: center; + } + + .jcolor-sliders-final-color > div:nth-child(2) { + width: 71px; + text-transform: uppercase; + } + + .jcolor .jtabs .jtabs-headers-container .jtabs-controls { + display: none !important; + } + + .jcolor .jtabs .jtabs-headers-container { + display: flex !important; + justify-content: center; + padding: 4px; + } + + .jcolor .jtabs-headers > div:not(.jtabs-border) { + padding: 2px !important; + padding-left: 15px !important; + padding-right: 15px !important; + font-size: 0.8em; + } + .jcontextmenu { + position: fixed; + z-index: 10000; + background: #fff; + color: #555; + font-size: 11px; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); + -moz-box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); + box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); + border: 1px solid #c6c6c6; + padding: 0px; + padding-top: 4px; + padding-bottom: 4px; + margin: 0px; + outline: none; + display: none; + } + + .jcontextmenu.jcontextmenu-focus { + display: inline-block; + } + + .jcontextmenu > div { + box-sizing: border-box; + display: flex; + padding: 8px 8px 8px 32px; + width: 250px; + position: relative; + cursor: default; + font-size: 11px; + font-family: sans-serif; + text-align: left; + -webkit-box-align: center; + align-items: center; + } + + .jcontextmenu > div::before { + content: attr(data-icon); + font-family: "Material Icons" !important; + font-size: 15px; + position: absolute; + left: 9px; + line-height: 24px; + } + + .jcontextmenu.symbols > div::before { + font-family: "Material Symbols Outlined" !important; + } + + .jcontextmenu > div.header { + display: none; + } + + .jcontextmenu > div a { + color: #555; + text-decoration: none; + flex: 1; + cursor: pointer; + } + + .jcontextmenu > div span { + margin-right: 10px; + } + + .jcontextmenu .jcontextmenu-disabled a { + color: #ccc; + } + + .jcontextmenu .jcontextmenu-disabled::before { + color: #ccc; + } + + .jcontextmenu > div:hover { + background: #ebebeb; + } + + .jcontextmenu hr { + border: 1px solid #e9e9e9; + border-bottom: 0; + margin-top: 5px; + margin-bottom: 5px; + } + + .jcontextmenu > hr:hover { + background: transparent; + } + + .jcontextmenu .jcontextmenu { + top: 4px; + left: 99%; + opacity: 0; + position: absolute; + } + + .jcontextmenu > div:hover > .jcontextmenu { + display: block; + opacity: 1; + -webkit-transform: translate(0, 0) scale(1); + transform: translate(0, 0) scale(1); + pointer-events: auto; + } + + @media only screen and (max-width: 420px) { + .jcontextmenu { + top: initial !important; + left: 0px !important; + bottom: 0px !important; + width: 100vw; + height: 260px; + overflow: scroll; + animation: slide-bottom-in 0.4s forwards; + padding-top: 0px; + } + .jcontextmenu div { + width: 100%; + text-align: center; + border-bottom: 1px solid #ccc; + padding: 15px; + } + .jcontextmenu > div.header { + background-color: lightgray; + padding: 5px; + top: 0px; + position: sticky; + z-index: 2; + } + .jcontextmenu > div.header > a.title { + text-align: left; + } + + .jcontextmenu > div.header > a.close { + text-align: right; + } + .jcontextmenu a { + font-size: 1.4em; + text-transform: uppercase; + } + .jcontextmenu span { + display: none; + } + .jcontextmenu span { + display: none; + } + .jcontextmenu hr { + display: none; + } + } + + .jdropdown { + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + box-sizing: border-box; + background: #fff; + -webkit-tap-highlight-color: transparent; + display: inline-block; + } + + .jdropdown-backdrop { + position: fixed; + top: 0px; + left: 0px; + min-width: 100%; + min-height: 100%; + background-color: rgba(0, 0, 0, 0.5); + border: 0px; + padding: 0px; + z-index: 8000; + display: none; + } + + .jdropdown[disabled] { + opacity: 0.5; + pointer-events: none; + } + + .jdropdown-focus { + position: relative; + } + + .jdropdown-focus .jdropdown-container { + transform: translate3d(0, 0, 0); + } + + .jdropdown-default.jdropdown-focus .jdropdown-header { + outline: auto 5px -webkit-focus-ring-color; + } + + .jdropdown-default.jdropdown-focus .jdropdown-header.jdropdown-add { + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27 fill=%27gray%27 width=%2724px%27 height=%2724px%27%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3Cpath d=%27M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z%27/%3E%3C/svg%3E"); + } + + .jdropdown-container-header { + padding: 0px; + margin: 0px; + position: relative; + box-sizing: border-box; + } + + .jdropdown-header { + width: 100%; + appearance: none; + background-repeat: no-repeat; + background-position: top 50% right 5px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2724%27 height=%2724%27 viewBox=%270 0 24 24%27%3E%3Cpath fill=%27none%27 d=%27M0 0h24v24H0V0z%27/%3E%3Cpath d=%27M7 10l5 5 5-5H7z%27 fill=%27gray%27/%3E%3C/svg%3E"); + text-overflow: ellipsis; + cursor: pointer; + box-sizing: border-box; + -webkit-appearance: none; + -moz-appearance: none; + padding-right: 30px !important; + } + + .jdropdown-insert-button { + font-size: 1.4em; + text-transform: uppercase; + position: absolute; + right: 30px; + top: 4px; + display: none; + } + + .jdropdown-container { + min-width: inherit; + transform: translate3d(-10000px, 0, 0); + position: absolute; + z-index: 9001; + } + + .jdropdown-close { + display: none; + font-size: 1em; + color: var(--active-color); + text-transform: uppercase; + text-align: right; + padding: 12px; + font-weight: bold; + } + + .jdropdown-content { + min-width: inherit; + margin: 0px; + box-sizing: border-box; + } + + .jdropdown-content:empty { + } + + .jdropdown-item { + white-space: nowrap; + text-align: left; + text-overflow: ellipsis; + overflow-x: hidden; + color: #000; + display: flex; + align-items: center; + } + + .jdropdown-description { + text-overflow: ellipsis; + overflow: hidden; + line-height: 1.5em; + } + + .jdropdown-image { + margin-right: 10px; + width: 32px; + height: 32px; + border-radius: 20px; + } + + .jdropdown-image-small { + width: 24px; + height: 24px; + } + + .jdropdown-icon { + margin-right: 10px; + font-size: 30px; + margin-left: -5px; + } + + .jdropdown-icon-small { + font-size: 24px; + margin-left: 0px; + } + + .jdropdown-title { + font-size: 0.7em; + text-overflow: ellipsis; + overflow-x: hidden; + display: block; + } + + /** Default visual **/ + + .jdropdown-default .jdropdown-header { + border: 1px solid #ccc; + padding: 5px; + padding-left: 10px; + padding-right: 16px; + } + + .jdropdown-default .jdropdown-container { + background-color: #fff; + } + + .jdropdown-default.jdropdown-focus.jdropdown-insert .jdropdown-header { + padding-right: 50px; + } + + .jdropdown-default.jdropdown-focus.jdropdown-insert .jdropdown-insert-button { + display: block; + } + + .jdropdown-default .jdropdown-content { + min-width: inherit; + border: 1px solid #8fb1e3; + margin: 0px; + background-color: #fff; + box-sizing: border-box; + min-height: 10px; + max-height: 215px; + overflow-y: auto; + } + + .jdropdown-default .jdropdown-item { + padding: 4px; + padding-left: 8px; + padding-right: 40px; + } + + .jdropdown-default .jdropdown-item:hover { + background-color: #1f93ff; + color: #fff; + } + + .jdropdown-default .jdropdown-cursor { + background-color: #eee; + } + + .jdropdown-default .jdropdown-selected { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIiAvPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiIGZpbGw9IndoaXRlIiAvPjwvc3ZnPgo=); + background-repeat: no-repeat; + background-position: top 50% right 5px; + background-color: #1f93ff; + color: #fff; + } + + .jdropdown-default .jdropdown-group { + margin-top: 5px; + } + + .jdropdown-default .jdropdown-group .jdropdown-item { + padding-left: 16px; + } + + .jdropdown-default .jdropdown-group-name { + padding-left: 8px; + font-weight: bold; + text-align: left; + } + + .jdropdown-default .jdropdown-reset_ { + content: "x"; + position: absolute; + top: 0; + right: 0; + margin: 5px; + margin-right: 10px; + font-size: 12px; + width: 12px; + cursor: pointer; + text-shadow: 0px 0px 5px #fff; + display: none; + line-height: 1.8em; + } + + .jdropdown-default.jdropdown-focus .jdropdown-reset_ { + display: block; + } + + /** Default render for mobile **/ + + .jdropdown-picker.jdropdown-focus .jdropdown-backdrop { + display: block; + } + + .jdropdown-picker .jdropdown-header { + outline: none; + } + + .jdropdown-picker .jdropdown-container { + position: fixed; + bottom: 0px; + left: 0px; + border-bottom: 1px solid #e6e6e8; + width: 100%; + background-color: #fff; + box-sizing: border-box; + } + + .jdropdown-picker .jdropdown-close { + -webkit-box-shadow: 0px -1px 5px 0px rgba(0, 0, 0, 0.39); + -moz-box-shadow: 0px -1px 5px 0px rgba(0, 0, 0, 0.39); + box-shadow: 0px -1px 5px 0px rgba(0, 0, 0, 0.39); + background-color: #fff; + display: block; + } + + .jdropdown-picker .jdropdown-content { + overflow-y: scroll; + height: 280px; + background-color: #fafafa; + border-top: 1px solid #e6e6e8; + } + + .jdropdown-picker .jdropdown-group-name { + font-size: 1em; + text-transform: uppercase; + padding-top: 10px; + padding-bottom: 10px; + display: block; + border-bottom: 1px solid #e6e6e8; + padding-left: 20px; + padding-right: 20px; + text-align: center; + font-weight: bold; + } + + .jdropdown-picker .jdropdown-item { + font-size: 1em; + text-transform: uppercase; + padding-top: 10px; + padding-bottom: 10px; + border-bottom: 1px solid #e6e6e8; + padding-left: 20px; + padding-right: 20px; + } + + .jdropdown-picker .jdropdown-selected { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIiAvPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiIGZpbGw9IndoaXRlIiAvPjwvc3ZnPgo=); + background-repeat: no-repeat; + background-position: top 50% right 15px; + background-color: #1f93ff; + color: #fff; + } + + .jdropdown-picker .jdropdown-cursor { + background-color: #1f93ff; + color: #fff; + } + + /** Default render for mobile searchbar **/ + + .jdropdown-searchbar.jdropdown-focus { + position: fixed; + top: 0px !important; + left: 0px !important; + width: 100% !important; + height: 100% !important; + background-color: #fafafa; + padding: 0px; + z-index: 9001; + overflow-y: scroll; + will-change: scroll-position; + -webkit-overflow-scrolling: touch; + } + + .jdropdown-searchbar.jdropdown-focus .jdropdown-container-header { + position: fixed; + top: 0px; + left: 0px; + z-index: 9002; + padding: 6px; + background-color: #fff; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + width: 100%; + } + + .jdropdown-searchbar.jdropdown-focus .jdropdown-header { + border: 0px !important; + background-position-x: 0% !important; + background-position-y: 40% !important; + background-repeat: no-repeat; + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTUuNSAxNGgtLjc5bC0uMjgtLjI3QzE1LjQxIDEyLjU5IDE2IDExLjExIDE2IDkuNSAxNiA1LjkxIDEzLjA5IDMgOS41IDNTMyA1LjkxIDMgOS41IDUuOTEgMTYgOS41IDE2YzEuNjEgMCAzLjA5LS41OSA0LjIzLTEuNTdsLjI3LjI4di43OWw1IDQuOTlMMjAuNDkgMTlsLTQuOTktNXptLTYgMEM3LjAxIDE0IDUgMTEuOTkgNSA5LjVTNy4wMSA1IDkuNSA1IDE0IDcuMDEgMTQgOS41IDExLjk5IDE0IDkuNSAxNHoiIGZpbGw9IiNlNmU2ZTgiLz48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PC9zdmc+); + padding-left: 30px !important; + padding-right: 60px !important; + } + + .jdropdown-searchbar.jdropdown-focus .jdropdown-close { + display: block; + } + + .jdropdown-searchbar .jdropdown-header { + outline: none; + } + + .jdropdown-searchbar .jdropdown-container { + margin-top: 40px; + width: 100%; + } + + .jdropdown-searchbar .jdropdown-close { + position: fixed; + top: 0px; + right: 0px; + } + + .jdropdown-searchbar .jdropdown-content { + margin-top: 10px; + } + + .jdropdown-searchbar .jdropdown-group { + margin-top: 10px; + margin-bottom: 15px; + background-color: #fff; + } + + .jdropdown-searchbar .jdropdown-group-name { + border-top: 1px solid #e6e6e8; + border-bottom: 1px solid #e6e6e8; + padding: 10px; + padding-left: 12px; + font-weight: bold; + } + + .jdropdown-searchbar .jdropdown-group-arrow { + float: right; + width: 24px; + height: 24px; + background-repeat: no-repeat; + } + + .jdropdown-searchbar .jdropdown-group-arrow-down { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNNy40MSA4LjU5TDEyIDEzLjE3bDQuNTktNC41OEwxOCAxMGwtNiA2LTYtNiAxLjQxLTEuNDF6Ii8+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyNHYyNEgwVjB6Ii8+PC9zdmc+); + } + + .jdropdown-searchbar .jdropdown-group-arrow-up { + background-image: url(data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTcuNDEgMTUuNDFMMTIgMTAuODNsNC41OSA0LjU4TDE4IDE0bC02LTYtNiA2eiIvPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48L3N2Zz4=); + } + + .jdropdown-searchbar .jdropdown-item { + padding-top: 10px; + padding-bottom: 10px; + border-bottom: 1px solid #e6e6e8; + padding-left: 15px; + padding-right: 40px; + background-color: #fff; + font-size: 0.9em; + } + + .jdropdown-searchbar .jdropdown-description { + text-overflow: ellipsis; + overflow: hidden; + max-width: calc(100% - 20px); + } + + .jdropdown-searchbar .jdropdown-content > .jdropdown-item:first-child { + border-top: 1px solid #e6e6e8; + } + + .jdropdown-searchbar .jdropdown-selected { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTkgMTYuMTdMNC44MyAxMmwtMS40MiAxLjQxTDkgMTkgMjEgN2wtMS40MS0xLjQxeiIgZmlsbD0iIzAwN2FmZiIvPjwvc3ZnPg==); + background-repeat: no-repeat; + background-position: top 50% right 15px; + } + + /** List render **/ + + .jdropdown-list { + } + + .jdropdown-list .jdropdown-container { + display: block; + } + + .jdropdown-list .jdropdown-header { + display: none; + } + + .jdropdown-list .jdropdown-group { + background-color: #fff; + } + + .jdropdown-list .jdropdown-group-name { + border-bottom: 1px solid #e6e6e8; + padding-top: 10px; + padding-bottom: 10px; + font-weight: bold; + } + + .jdropdown-list .jdropdown-item { + padding-top: 10px; + padding-bottom: 10px; + border-bottom: 1px solid #e6e6e8; + padding-left: 10px; + padding-right: 40px; + background-color: #fff; + } + + .jdropdown-list .jdropdown-selected { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTkgMTYuMTdMNC44MyAxMmwtMS40MiAxLjQxTDkgMTkgMjEgN2wtMS40MS0xLjQxeiIgZmlsbD0iIzAwN2FmZiIvPjwvc3ZnPg==); + background-repeat: no-repeat; + background-position: top 50% right 10px; + } + + @media only screen and (max-width: 800px) { + .jdropdown-list { + width: 100% !important; + border: 0px; + padding: 0px; + } + + .jdropdown-list .jdropdown-container { + min-width: 100%; + } + + .jdropdown-searchbar.jdropdown-focus .jdropdown-description { + text-transform: uppercase; + } + } + + .app .jdropdown-item { + text-transform: uppercase; + } + + .jdropdown-create-container { + margin: 10px; + border: 1px solid #ccc; + border-radius: 2px; + padding: 6px; + } + + .jdropdown-color { + background-color: #fff; + border: 1px solid transparent; + border-radius: 12px; + width: 12px; + height: 12px; + margin-right: 6px; + } + + .jdropdown-item[data-disabled] { + opacity: 0.5; + pointer-events: none; + } + .jeditor-container { + border: 1px solid #ccc; + box-sizing: border-box; + } + + .jeditor-container.with-margin { + background-color: #f2f2f2; + max-width: 1200px; + } + + .jeditor-dragging { + border: 1px dashed #000; + } + + .jeditor { + outline: none; + word-break: break-word; + } + + .jeditor-container.with-margin .jeditor { + background-color: #fff; + margin: 80px; + min-height: 800px; + padding: 80px; + max-width: 800px; + } + + .jeditor[data-placeholder]:empty:before { + content: attr(data-placeholder); + color: lightgray; + } + + /** Snippet **/ + + .jsnippet { + margin-top: 15px; + cursor: pointer; + border: 1px solid #ccc; + position: relative; + } + + .jsnippet:focus { + outline: none; + } + + .jsnippet img { + width: 100%; + } + + .jsnippet .jsnippet-title { + padding: 15px; + font-size: 1.4em; + } + + .jsnippet .jsnippet-description { + padding-left: 15px; + padding-right: 15px; + font-size: 1em; + } + + .jsnippet .jsnippet-host { + padding: 15px; + text-transform: uppercase; + font-size: 0.8em; + color: #777; + text-align: right; + } + + .jsnippet .jsnippet-url { + display: none; + } + + .jeditor .jsnippet:after { + content: "close"; + font-family: "Material icons"; + font-size: 24px; + width: 24px; + height: 24px; + line-height: 24px; + cursor: pointer; + text-shadow: 0px 0px 2px #fff; + position: absolute; + top: 12px; + right: 12px; + } + + .jsnippet * { + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + user-select: none; + + -webkit-user-drag: none; + -khtml-user-drag: none; + -moz-user-drag: none; + -o-user-drag: none; + } + + .jeditor img { + border: 2px solid transparent; + box-sizing: border-box; + } + + .jeditor img.resizing { + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + user-select: none; + + -webkit-user-drag: none; + -khtml-user-drag: none; + -moz-user-drag: none; + -o-user-drag: none; + } + + .jeditor img:focus { + border: 2px solid #0096fd; + outline: #0096fd; + } + + .jeditor .pdf { + background-image: url("data:image/svg+xml,%3Csvg version=%271.1%27 id=%27Layer_1%27 xmlns=%27http://www.w3.org/2000/svg%27 xmlns:xlink=%27http://www.w3.org/1999/xlink%27 x=%270px%27 y=%270px%27 viewBox=%270 0 512 512%27 style=%27enable-background:new 0 0 512 512;%27 xml:space=%27preserve%27%3E%3Cpath style=%27fill:%23C30B15;%27 d=%27M511.344,274.266C511.77,268.231,512,262.143,512,256C512,114.615,397.385,0,256,0S0,114.615,0,256 c0,117.769,79.53,216.949,187.809,246.801L511.344,274.266z%27/%3E%3Cpath style=%27fill:%2385080E;%27 d=%27M511.344,274.266L314.991,77.913L119.096,434.087l68.714,68.714C209.522,508.787,232.385,512,256,512 C391.243,512,501.976,407.125,511.344,274.266z%27/%3E%3Cpolygon style=%27fill:%23FFFFFF;%27 points=%27278.328,333.913 255.711,77.913 119.096,77.913 119.096,311.652 %27/%3E%3Cpolygon style=%27fill:%23E8E6E6;%27 points=%27392.904,311.652 392.904,155.826 337.252,133.565 314.991,77.913 255.711,77.913 256.067,333.913 %27/%3E%3Cpolygon style=%27fill:%23FFFFFF;%27 points=%27314.991,155.826 314.991,77.913 392.904,155.826 %27/%3E%3Crect x=%27119.096%27 y=%27311.652%27 style=%27fill:%23FC0F1A;%27 width=%27273.809%27 height=%27122.435%27/%3E%3Cg%3E%3Cpath style=%27fill:%23FFFFFF;%27 d=%27M204.871,346.387c13.547,0,21.341,6.659,21.341,18.465c0,12.412-7.795,19.601-21.341,19.601h-9.611 v14.909h-13.471v-52.975L204.871,346.387L204.871,346.387z M195.26,373.858h8.93c5.904,0,9.308-2.952,9.308-8.552 c0-5.525-3.406-8.324-9.308-8.324h-8.93V373.858z%27/%3E%3Cpath style=%27fill:%23FFFFFF;%27 d=%27M257.928,346.387c16.649,0,28.152,10.746,28.152,26.487c0,15.666-11.655,26.488-28.683,26.488 h-22.25v-52.975H257.928z M248.619,388.615h9.611c8.249,0,14.151-6.357,14.151-15.665c0-9.384-6.205-15.817-14.757-15.817h-9.006 V388.615z%27/%3E%3Cpath style=%27fill:%23FFFFFF;%27 d=%27M308.563,356.982v12.26h23.763v10.596h-23.763v19.525h-13.471v-52.975h39.277v10.595h-25.806 V356.982z%27/%3E%3C/g%3E%3C/svg%3E%0A"); + background-repeat: no-repeat; + background-size: cover; + width: 60px; + height: 60px; + } + + .jeditor-toolbar { + width: fit-content; + max-width: 100%; + box-sizing: border-box; + margin: 10px; + } + + .toolbar-on-top .jeditor-toolbar { + width: initial; + margin: 0px; + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1); + display: block; + } + + .toolbar-on-top .jeditor { + padding: 15px; + } + + .toolbar-on-top .jtoolbar .material-icons { + font-size: 24px; + transform: initial; + margin: 4px; + } + + .toolbar-on-top .jtoolbar .jpicker-header { + font-size: 1em; + margin-top: 4px; + margin-bottom: 4px; + } + + .jeditor table { + border-collapse: collapse; + } + + .jeditor table td { + border: 1px solid #bbb; + height: 2em; + } + + .jeditor table td:focus { + border: 1px solid blue; + } + + .jeditor .line-break { + border-top: 1px dashed #ccc; + display: flex; + justify-content: center; + pointer-events: none; + } + + .jeditor .line-break:before { + content: "New page"; + background-color: #fff; + color: #ccc; + margin: -1em; + padding: 6px; + position: absolute; + } + .jfloating { + position: fixed; + bottom: 0px; + right: 0px; + margin-right: 5px; + + -webkit-box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + border: 1px solid #ccc; + background-color: #fff; + box-sizing: border-box; + padding-top: 50px !important; + z-index: 9002; + border-radius: 8px; + } + + .jfloating.jfloating-big { + width: 510px !important; + height: 472px !important; + } + + .jfloating.jfloating-small { + width: 300px !important; + height: 320px !important; + } + + .jfloating.jfloating-large { + width: 600px !important; + height: 600px !important; + } + + .jfloating:before { + position: absolute; + top: 0; + left: 0; + width: 100%; + content: attr(title); + padding: 15px; + box-sizing: border-box; + font-size: 1.2em; + box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2); + background-color: #fff; + border-radius: 8px 8px 0px 0px; + background-color: #404040; + font-size: 0.93rem; + font-weight: 600; + color: white; + letter-spacing: 0.5px; + } + + .jfloating:after { + content: ""; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2724%27 height=%2724%27 viewBox=%270 0 24 24%27%3E%3Cpath fill=%27%23FFF%27 d=%27M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z%27/%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3C/svg%3E"); + position: absolute; + top: 0; + right: 0; + margin: 14px; + font-size: 24px; + width: 24px; + height: 24px; + cursor: pointer; + text-shadow: 0px 0px 5px #fff; + } + + .jfloating_content { + padding: 20px; + overflow-y: auto; + max-height: 100%; + box-sizing: border-box; + height: -webkit-fill-available; + } + + .jfloating.jfloating-minimized { + height: 50px !important; + } + + .jfloating.jfloating-minimized .jfloating_content { + display: none; + } + + .jmodal { + position: fixed; + top: 50%; + left: 50%; + width: 60%; + height: 60%; + -webkit-box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); + border: 1px solid #ccc; + background-color: #fff; + transform: translate(-50%, -50%); + box-sizing: border-box; + z-index: 9002; + border-radius: 4px; + display: flex; + flex-direction: column; + } + + .jmodal_title { + padding: 20px; + height: 70px; + box-sizing: border-box; + font-size: 1.4em; + background-color: #fff; + border-radius: 8px 8px 0px 0px; + pointer-events: none; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + align-items: center; + border-bottom: 1px solid #eee; + } + + .jmodal_title > div { + font-size: 1.4em; + } + + .jmodal_title[data-icon]:before { + content: attr(data-icon); + font-family: "Material Icons" !important; + width: 24px; + height: 24px; + font-size: 24px; + margin-right: 10px; + line-height: 24px; + } + + .jmodal_content { + padding: 20px; + overflow-y: auto; + height: 100%; + box-sizing: border-box; + scrollbar-width: thin; + scrollbar-color: #333 transparent; + } + + .jmodal_title:empty { + display: none; + } + + .jmodal_title:empty + .jmodal_content { + height: 100%; + } + + .jmodal_content::-webkit-scrollbar { + height: 12px; + } + + .jmodal_content::-webkit-scrollbar { + width: 12px; + } + + .jmodal_content::-webkit-scrollbar-track { + border: 1px solid #fff; + background: #eee; + } + + .jmodal_content::-webkit-scrollbar-thumb { + border: 1px solid #fff; + background: #888; + } + + .jmodal:after { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2724%27 height=%2724%27 viewBox=%270 0 24 24%27%3E%3Cpath d=%27M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z%27/%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3C/svg%3E"); + position: absolute; + top: 0; + right: 0; + margin: 25px; + font-size: 24px; + width: 24px; + height: 24px; + cursor: pointer; + text-shadow: 0px 0px 5px #fff; + } + + .jmodal_fullscreen { + width: 100% !important; + height: 100% !important; + top: 0px; + left: 0px; + transform: none; + border: 0px; + border-radius: 0px; + } + + .jmodal_backdrop { + position: fixed; + top: 0px; + left: 0px; + min-width: 100%; + min-height: 100%; + background-color: rgba(0, 0, 0, 0.2); + border: 0px; + padding: 0px; + z-index: 8000; + display: none; + + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + supported by Chrome and Opera */ + } + + .jmodal_content .jcalendar .jcalendar-content, + .jmodal_content .jdropdown-container { + position: fixed; + } + + .jnotification { + position: fixed; + z-index: 10000; + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding: 10px; + bottom: 0px; + } + + .jnotification-container { + -webkit-box-shadow: 0px 2px 15px -5px rgba(0, 0, 0, 0.7); + box-shadow: 0px 2px 15px -5px rgba(0, 0, 0, 0.7); + padding: 12px; + border-radius: 8px; + + background-color: #000; + background: rgba(92, 92, 92, 1); + background: linear-gradient( + 0deg, + rgba(92, 92, 92, 1) 0%, + rgba(77, 77, 77, 1) 100% + ); + color: #fff; + width: 320px; + margin: 30px; + padding: 20px; + } + + .jnotification-close { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2720%27 height=%2720%27 viewBox=%270 0 24 24%27 fill=%27white%27%3E%3Cpath d=%27M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z%27/%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3C/svg%3E"); + font-size: 20px; + width: 20px; + height: 20px; + cursor: pointer; + } + + .jnotification-title { + font-weight: bold; + } + + .jnotification-header { + display: flex; + padding-bottom: 5px; + } + + .jnotification-header:empty { + display: none; + } + + .jnotification-image { + margin-right: 5px; + } + + .jnotification-image:empty { + display: none; + } + + .jnotification-image img { + width: 24px; + } + + .jnotification-name { + text-transform: uppercase; + font-size: 0.9em; + flex: 1; + letter-spacing: 0.1em; + } + + .jnotification-error .jnotification-container { + background: rgb(182, 38, 6); + background: linear-gradient( + 0deg, + rgba(170, 41, 13, 1) 0%, + rgba(149, 11, 11, 1) 100% + ); + } + + @media (max-width: 800px) { + .jnotification { + top: calc(0px + var(--safe-area-top)); + width: 100%; + } + .jnotification-container { + background: rgba(255, 255, 255, 0.95); + border: 1px solid #eee; + color: #444; + margin: 0px; + width: initial; + } + .jnotification-error .jnotification-container { + background: rgba(255, 255, 255, 0.95); + color: #790909; + } + .jnotification-close { + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2720%27 height=%2720%27 viewBox=%270 0 24 24%27 fill=%27black%27%3E%3Cpath d=%27M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z%27/%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3C/svg%3E"); + } + } + + .jnotification-header { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + } + .jpicker { + cursor: pointer; + white-space: nowrap; + display: inline-flex; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + outline: none; + position: relative; + } + + .jpicker-header { + background-repeat: no-repeat; + background-position: top 50% right 5px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2724%27 height=%2724%27 viewBox=%270 0 24 24%27%3E%3Cpath fill=%27none%27 d=%27M0 0h24v24H0V0z%27/%3E%3Cpath d=%27M7 10l5 5 5-5H7z%27 fill=%27gray%27/%3E%3C/svg%3E"); + text-overflow: ellipsis; + cursor: pointer; + box-sizing: border-box; + text-align: left; + outline: none; + + line-height: 24px; + padding: 2px; + padding-left: 12px; + padding-right: 35px; + outline: none; + border-radius: 4px; + } + + .jpicker-header:hover { + background-color: #f2f2f2; + } + + .jpicker-content { + position: absolute; + top: 0; + display: none; + box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), + 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.2); + border-radius: 4px; + background-color: #fff; + padding: 4px; + z-index: 50; + text-align: left; + max-height: 250px; + scrollbar-width: thin; + scrollbar-color: #333 transparent; + } + + .jpicker-content::-webkit-scrollbar { + width: 8px; + } + + .jpicker-content::-webkit-scrollbar-track { + background: #eee; + } + + .jpicker-content::-webkit-scrollbar-thumb { + background: #888; + } + + .jpicker-content > div { + padding: 6px; + padding-left: 15px; + padding-right: 15px; + } + + .jpicker-focus > .jpicker-content { + display: block; + } + + .jpicker-content > div:hover { + background-color: #efefef; + } + + .jpicker-content > div:empty { + opacity: 0; + } + + .jpicker-header > i, + .jpicker-header > div { + display: block; + } + + .jpicker-focus > .jpicker-content.jpicker-columns { + display: flex !important ; + justify-content: center; + flex-wrap: wrap; + } + + .jpicker-focus .jpicker-content.jpicker-grid { + display: inline-grid; + } + + .jprogressbar { + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + box-sizing: border-box; + background: #fff; + -webkit-tap-highlight-color: transparent; + display: inline-block; + box-sizing: border-box; + cursor: pointer; + border: 1px solid #ccc; + position: relative; + } + + .jprogressbar::before { + content: attr(data-value); + position: absolute; + margin: 5px; + margin-left: 10px; + } + + .jprogressbar-header::placeholder { + color: #000; + } + + .jprogressbar::focus { + outline: auto 5px -webkit-focus-ring-color; + } + + .jprogressbar > div { + background-color: #eee; + background-color: red; + box-sizing: border-box; + height: 31px; + } + .jrating { + display: flex; + } + .jrating > div { + width: 24px; + height: 24px; + line-height: 24px; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2724%27 height=%2724%27 viewBox=%270 0 24 24%27%3E%3Cpath d=%27M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z%27 fill=%27gray%27/%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3C/svg%3E"); + } + + .jrating .jrating-over { + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2724%27 height=%2724%27 viewBox=%270 0 24 24%27 fill=%27black%27%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3Cpath d=%27M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z%27/%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3C/svg%3E"); + opacity: 0.7; + } + + .jrating .jrating-selected { + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2724%27 height=%2724%27 viewBox=%270 0 24 24%27 fill=%27red%27%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3Cpath d=%27M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z%27/%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3C/svg%3E"); + } + + .jsearch { + position: relative; + display: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .jsearch_container { + position: absolute; + box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.302), + 0 2px 6px 2px rgba(60, 64, 67, 0.149); + border: none; + -webkit-border-radius: 4px; + border-radius: 4px; + width: 280px; + padding: 8px 0; + z-index: 1; + + -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + -webkit-transition: opacity 0.218s; + transition: opacity 0.218s; + background: #fff; + border: 1px solid rgba(0, 0, 0, 0.2); + cursor: pointer; + margin: 0; + min-width: 300px; + outline: none; + width: auto; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .jsearch_container:empty:after { + content: attr(data-placeholder); + } + + .jsearch_container > div { + color: #333; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: flex; + padding: 5px 10px; + user-select: none; + -webkit-align-items: center; + align-items: center; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .jsearch_container > div:hover { + background-color: #e8eaed; + } + + .jsearch_container > div > img { + width: 32px; + height: 32px; + user-select: none; + border-radius: 16px; + margin-right: 2px; + } + + .jsearch_container > div > div { + overflow: hidden; + text-overflow: ellipsis; + margin-left: 2px; + max-width: 300px; + white-space: nowrap; + user-select: none; + } + + .jsearch_container .selected { + background-color: #e8eaed; + } + .jslider { + outline: none; + } + + .jslider-focus { + width: 100% !important; + height: 100% !important; + } + + .jslider-focus img { + display: none; + } + + .jslider img { + width: 100px; + } + + .jslider-left::before { + position: fixed; + left: 15px; + top: 50%; + content: "arrow_back_ios"; + color: #fff; + width: 30px; + height: 30px; + font-family: "Material Icons"; + font-size: 30px; + /* before it was 0px 0px 0px #000 */ + text-shadow: 0px 0px 6px rgb(56, 56, 56); + text-align: center; + cursor: pointer; + } + + .jslider-right::after { + position: fixed; + right: 15px; + top: 50%; + content: "arrow_forward_ios"; + color: #fff; + width: 30px; + height: 30px; + font-family: "Material Icons"; + font-size: 30px; + /* before it was 0px 0px 0px #000 */ + text-shadow: 0px 0px 6px rgb(56, 56, 56); + text-align: center; + cursor: pointer; + } + + .jslider-close { + width: 24px; + height: 24px; + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2724%27 height=%2724%27 viewBox=%270 0 24 24%27 fill=%27white%27%3E%3Cpath d=%27M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z%27/%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3C/svg%3E"); + position: fixed; + top: 15px; + right: 15px; + cursor: pointer; + z-index: 3000; + + display: block !important; + } + + .jslider-counter { + height: 24px; + background-color: transparent; + position: fixed; + left: 50%; + transform: translateX(-50%); + bottom: 15px; + cursor: pointer; + z-index: 3000; + + display: flex; + display: -webkit-flex; + -webkit-justify-content: center; + -webkit-align-items: center; + -webkit-flex-direction: row; + justify-content: center; + align-items: center; + flex-direction: row; + } + + .jslider-caption { + position: fixed; + max-width: 90vw; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + top: 15px; + left: 15px; + z-index: 3000; + color: #fff; + font-size: 1rem; + + display: block !important; + } + + .jslider-counter div { + width: 10px; + height: 10px; + background: #fff; + border-radius: 50%; + margin: 0px 5px; + + display: block !important; + } + + .jslider-counter .jslider-counter-focus { + background-color: cornflowerblue; + pointer-events: none; + } + + .jslider-focus { + position: fixed; + left: 0; + top: 0; + width: 100%; + min-height: 100%; + max-height: 100%; + z-index: 2000; + margin: 0px; + box-sizing: border-box; + + background-color: rgba(0, 0, 0, 0.8); + -webkit-transition-duration: 0.05s; + transition-duration: 0.05s; + display: flex; + -ms-flex-align: center; + -webkit-align-items: center; + -webkit-box-align: center; + + align-items: center; + } + + .jslider-focus img { + width: 50vw; + height: auto; + box-sizing: border-box; + margin: 0 auto; + vertical-align: middle; + display: none; + } + + .jslider-focus img.jslider-vertical { + width: auto; + /* before it was 50vh */ + height: 80vh; + } + + @media only screen and (max-width: 576px) { + .jslider-focus img.jslider-vertical { + width: 99vw !important; + height: auto !important; + } + + .jslider-focus img { + width: 100vw !important; + height: auto !important; + } + } + + .jslider-grid { + display: -ms-grid; + display: grid; + grid-gap: 1px; + position: relative; + } + + .jslider-grid[data-number="2"] { + -ms-grid-columns: 1fr 50%; + grid-template-columns: 1fr 50%; + } + + .jslider-grid[data-number="3"] { + -ms-grid-columns: 1fr 33%; + grid-template-columns: 1fr 33%; + } + + .jslider-grid[data-number="4"] { + -ms-grid-columns: 1fr 25%; + grid-template-columns: 1fr 25%; + } + + .jslider-grid img { + display: none; + width: 100%; + height: 100%; + object-fit: cover; + } + + .jslider-grid[data-total]:after { + content: attr(data-total) "+"; + font-size: 1.5em; + position: absolute; + color: #fff; + right: 15px; + bottom: 6px; + } + + .jslider-grid img:first-child { + -ms-grid-column: 1; + -ms-grid-row: 1; + grid-column: 1; + grid-row: 1; + display: block; + } + + .jslider-grid[data-number="2"] img:nth-child(2) { + -ms-grid-column: 2; + -ms-grid-row: 1; + grid-column: 2; + grid-row: 1; + display: block; + } + + .jslider-grid[data-number="3"] img:first-child { + -ms-grid-column: 1 / 2; + -ms-grid-row: 1 / 4; + grid-column: 1 / 2; + grid-row: 1 / 4; + } + + .jslider-grid[data-number="3"] img:nth-child(2) { + -ms-grid-column: 2; + -ms-grid-row: 1; + grid-column: 2; + grid-row: 1; + display: block; + } + + .jslider-grid[data-number="3"] img:nth-child(3) { + -ms-grid-column: 2; + -ms-grid-row: 2; + grid-column: 2; + grid-row: 2; + display: block; + } + + .jslider-grid[data-number="4"] img:first-child { + -ms-grid-column: 1 / 2; + -ms-grid-row: 1 / 4; + grid-column: 1 / 2; + grid-row: 1 / 4; + } + + .jslider-grid[data-number="4"] img:nth-child(2) { + -ms-grid-column: 2; + -ms-grid-row: 1; + grid-column: 2; + grid-row: 1; + display: block; + } + + .jslider-grid[data-number="4"] img:nth-child(3) { + -ms-grid-column: 2; + -ms-grid-row: 2; + grid-column: 2; + grid-row: 2; + display: block; + } + + .jslider-grid[data-number="4"] img:nth-child(4) { + -ms-grid-column: 2; + -ms-grid-row: 3; + grid-column: 2; + grid-row: 3; + display: block; + } + + .jtabs { + max-width: 100vw; + position: relative; + } + + .jtabs .jtabs-headers-container { + display: flex; + align-items: center; + } + + .jtabs .jtabs-headers { + display: flex; + align-items: center; + overflow: hidden; + position: relative; + } + + .jtabs .jtabs-headers > div:not(.jtabs-border) { + padding: 8px; + padding-left: 20px; + padding-right: 20px; + margin-left: 1px; + margin-right: 1px; + background-color: #f1f1f1; + cursor: pointer; + white-space: nowrap; + text-align: center; + } + + .jtabs .jtabs-headers > div.jtabs-selected { + background-color: #e8e8e8; + color: #000; + } + + .jtabs .jtabs-headers > div > div { + color: #555; + width: 100%; + overflow: hidden; + } + + .jtabs .jtabs-headers i { + display: block; + margin: auto; + } + + .jtabs .jtabs-content { + box-sizing: border-box; + } + + .jtabs .jtabs-content > div { + display: none; + box-sizing: border-box; + } + + .jtabs .jtabs-content > div.jtabs-selected { + display: block; + } + + .jtabs .jtabs-border { + position: absolute; + height: 2px; + background-color: #888; + transform-origin: left; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + transition-property: color, left, transform; + display: none; + } + + .jtabs-animation .jtabs-border { + display: initial; + } + + .jtabs .jtabs-controls { + margin: 3px; + margin-left: 10px; + display: flex; + min-width: 82px; + } + + .jtabs .jtabs-controls > div { + cursor: pointer; + background-position: center; + background-repeat: no-repeat; + width: 24px; + height: 24px; + line-height: 24px; + } + + .jtabs .jtabs-prev { + margin-left: 10px; + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27 fill=%27gray%27 width=%2718px%27 height=%2718px%27%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3Cpath d=%27M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z%27/%3E%3C/svg%3E"); + } + + .jtabs .jtabs-prev.disabled { + margin-left: 10px; + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27 fill=%27lightgray%27 width=%2718px%27 height=%2718px%27%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3Cpath d=%27M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z%27/%3E%3C/svg%3E"); + } + + .jtabs .jtabs-next { + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27 fill=%27gray%27 width=%2718px%27 height=%2718px%27%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3Cpath d=%27M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z%27/%3E%3C/svg%3E"); + } + + .jtabs .jtabs-next.disabled { + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27 fill=%27lightgray%27 width=%2718px%27 height=%2718px%27%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3Cpath d=%27M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z%27/%3E%3C/svg%3E"); + } + + .jtabs .jtabs-add { + background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 height=%2724%27 viewBox=%270 0 24 24%27 width=%2724%27%3E%3Cpath d=%27M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z%27 fill=%27%23bbbbbb%27/%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3C/svg%3E"); + } + + /** Modern skin **/ + + .jtabs.jtabs-modern .jtabs-headers > div:not(.jtabs-border) { + padding: 4px; + padding-left: 10px; + padding-right: 10px; + background-color: #fff; + } + + .jtabs.jtabs-modern .jtabs-headers > .jtabs-selected { + color: #000; + } + + .jtabs.jtabs-modern .jtabs-headers > .jtabs-selected .material-icons { + color: #000; + } + + .jtabs.jtabs-modern .jtabs-headers { + background: #eeeeef !important; + padding: 2px; + border-radius: 4px; + } + + .jtabs.jtabs-modern .jtabs-headers .jtabs-border { + border-color: #eeeeef !important; + } + + .jtabs.jtabs-modern .jtabs-border { + background-color: rgba(194, 197, 188, 0.884); + } + .jtags { + display: flex; + flex-wrap: wrap; + -ms-flex-direction: row; + -webkit-flex-direction: row; + flex-direction: row; + -ms-flex-pack: flex-start; + -webkit-justify-content: space-between; + justify-content: flex-start; + padding: 1px; + border: 1px solid #ccc; + position: relative; + } + + .jtags.jtags-empty:not(.jtags-focus)::before { + position: absolute; + margin: 3px; + color: #ccc; + content: attr(data-placeholder); + top: 0px; + margin-left: 6px; + } + + .jtags > div { + padding: 5px; + padding-left: 10px; + padding-right: 22px; + position: relative; + border-radius: 1px; + margin: 2px; + display: block; + outline: none; + } + + .jtags > div:empty:before { + content: " "; + white-space: pre; + } + + .jtags > div::after { + content: "x"; + position: absolute; + top: 6px; + right: 4px; + width: 12px; + height: 12px; + cursor: pointer; + font-size: 11px; + display: none; + } + + .jtags_label { + background-color: #eeeeee !important; + } + + .jtags_label::after { + display: inline-block !important; + } + + .jtags_error::after { + color: #fff !important; + } + + .jtags_error { + background-color: #d93025 !important; + color: #fff; + } + + .jtoolbar-container { + border-radius: 2px; + box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), + 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.2); + display: inline-flex !important; + } + + .jtoolbar { + cursor: pointer; + white-space: nowrap; + display: flex; + padding: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + font-size: 13px; + } + + .jtoolbar-disabled { + pointer-events: none; + opacity: 0.4; + } + + .jtoolbar-mobile { + display: flex; + position: fixed; + bottom: 0; + margin: 0; + left: 0; + width: 100%; + background: #f7f7f8; + z-index: 1; + box-sizing: border-box; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.1); + border-radius: 0px; + } + + .jtoolbar > div { + display: inline-flex; + align-items: center; + box-sizing: border-box; + vertical-align: middle; + justify-content: space-evenly; + } + + .jtoolbar-mobile > div { + display: flex; + width: 100%; + } + + .jtoolbar .jtoolbar-item { + text-align: center; + margin: auto; + padding: 2px; + padding-left: 4px; + padding-right: 4px; + } + + .jtoolbar-mobile .jtoolbar-item { + position: relative; + flex: 1; + } + + .jtoolbar .jtoolbar-divisor { + width: 2px; + height: 18px; + padding: 0px; + margin-left: 4px; + margin-right: 4px; + background-color: #ddd; + } + + .jtoolbar .jtoolbar-label { + padding-left: 8px; + padding-right: 8px; + } + + .jtoolbar-mobile a { + text-decoration: none; + display: inline-block; + } + + .jtoolbar-mobile i { + display: inline-flex !important; + color: #929292; + } + + .jtoolbar-mobile span { + font-size: 0.7em; + display: block; + color: #929292; + } + + .jtoolbar-mobile .jtoolbar-selected a, + .jtoolbar-mobile .jtoolbar-selected i, + .jtoolbar-mobile .jtoolbar-selected span { + color: var(--active-color) !important; + background-color: transparent; + } + + .jtoolbar-item { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .jtoolbar-item i { + display: block; + color: #333; + } + + .jtoolbar-item:hover { + background-color: #f2f2f2; + } + + .jtoolbar .jpicker { + padding-left: 0px; + padding-right: 0px; + } + + .jtoolbar .jpicker-header { + height: 24px; + line-height: 24px; + padding: 0px; + padding-right: 20px; + padding-left: 8px; + background-position: top 50% right 0px; + display: flex; + align-items: center; + font-size: 0.9em; + } + + .jtoolbar .jpicker-content > div { + padding: 6px; + } + + .jtoolbar-active { + background-color: #eee; + } + + .jtoolbar .fa { + width: 18px; + height: 18px; + display: block; + line-height: 18px; + font-size: 14px; + } + + .jtoolbar .material-icons { + font-size: 18px; + width: 24px; + height: 24px; + display: block; + line-height: 24px; + transform: rotate(0.03deg); + text-align: center; + } + + .jtoolbar .jtoolbar-arrow { + background-repeat: no-repeat; + background-position: center; + background-image: url("data:image/svg+xml,%0A%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27 fill=%27black%27 width=%2718px%27 height=%2718px%27%3E%3Cpath d=%27M0 0h24v24H0z%27 fill=%27none%27/%3E%3Cpath d=%27M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z%27/%3E%3C/svg%3E"); + width: 24px; + height: 16px; + margin-left: 4px; + border-left: 1px solid #f2f2f2; + } + + .jtoolbar-floating { + position: absolute; + display: none; + box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), + 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.2); + border-radius: 4px; + background-color: #fff; + padding: 4px; + z-index: 50; + text-align: left; + margin-right: 20px; + } + + .jtoolbar-floating .jtoolbar-divisor { + display: none; + } + + .jtoolbar-arrow-selected .jtoolbar-floating { + display: flex; + flex-wrap: wrap; + } +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index b98d4fad470..b27705c956b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2382,6 +2382,7 @@ en: add_warning: "This is an official warning." toggle_whisper: "Toggle Whisper" toggle_unlisted: "Toggle Unlisted" + insert_table: "Insert Table" posting_not_on_topic: "Which topic do you want to reply to?" saved_local_draft_tip: "saved locally" similar_topics: "Your topic is similar to…" @@ -4645,6 +4646,64 @@ en: patternMismatch: "Please match the requested format." badInput: "Please enter a valid input." + table_builder: + title: "Table Builder" + modal: + title: "Table Builder" + create: "Build Table" + help: + title: "Using the Spreadsheet Editor" + enter_key: "Enter" + tab_key: "Tab" + new_row: "at the end of a row to insert a new row." + new_col: "at the end of a column to insert a new column." + options: "Right-click on cells to access more options in a dropdown menu." + confirm_close: "Are you sure you want to close the table bulder? Any unsaved changes will be lost." + edit: + btn_edit: "Edit Table" + modal: + title: "Edit Table" + cancel: "cancel" + create: "Save" + reason: "why are you editing?" + trigger_reason: "Add reason for edit" + default_edit_reason: "Update Table with Table Editor" + default_header: + col_1: "Column 1" + col_2: "Column 2" + col_3: "Column 3" + col_4: "Column 4" + spreadsheet: + no_records_found: "No records found" + show: "Show" + entries: "entries" + about: "About" + prompts: + delete_selected_rows: "Are you sure you want to delete the selected rows?" + delete_selected_cols: "Are you sure you want to delete the selected columns?" + will_destroy_merged_cells: "This action will destroy any existing merged cells. Are you sure?" + will_clear_search_results: "This action will destroy any existing merged cells. Are you sure?" + conflicts_with_merged_cells: "There is a conflict with another merged cell" + invalid_merge_props: "Invalid merged properties" + cells_already_merged: "Cell already merged" + no_cells_selected: "No cells selected" + context_menu: + row: + before: "Insert a new row before" + after: "Insert a new row after" + delete: "Delete selected rows" + col: + before: "Insert a new column before" + after: "Insert a new column after" + delete: "Delete selected columns" + rename: "Rename this column" + order: + ascending: "Order ascending" + descending: "Order descending" + copy: "Copy..." + paste: "Paste..." + save: "Save as..." + # This section is exported to the javascript for i18n in the admin section admin_js: # This is a text input placeholder, keep the translation short diff --git a/public/javascripts/jspreadsheet/jspreadsheet.js b/public/javascripts/jspreadsheet/jspreadsheet.js new file mode 100644 index 00000000000..33ed309cd3b --- /dev/null +++ b/public/javascripts/jspreadsheet/jspreadsheet.js @@ -0,0 +1,16905 @@ +/** + * Jspreadsheet v4.11.1 + * + * Website: https://bossanova.uk/jspreadsheet/ + * Description: Create amazing web based spreadsheets. + * + * This software is distribute under MIT License + */ + +var formula = (function () { + // Based on sutoiku work (https://github.com/sutoiku) + var error = (function () { + var exports = {}; + + exports.nil = new Error("#NULL!"); + exports.div0 = new Error("#DIV/0!"); + exports.value = new Error("#VALUE!"); + exports.ref = new Error("#REF!"); + exports.name = new Error("#NAME?"); + exports.num = new Error("#NUM!"); + exports.na = new Error("#N/A"); + exports.error = new Error("#ERROR!"); + exports.data = new Error("#GETTING_DATA"); + + return exports; + })(); + + var utils = (function () { + var exports = {}; + + exports.flattenShallow = function (array) { + if (!array || !array.reduce) { + return array; + } + + return array.reduce(function (a, b) { + var aIsArray = Array.isArray(a); + var bIsArray = Array.isArray(b); + + if (aIsArray && bIsArray) { + return a.concat(b); + } + if (aIsArray) { + a.push(b); + + return a; + } + if (bIsArray) { + return [a].concat(b); + } + + return [a, b]; + }); + }; + + exports.isFlat = function (array) { + if (!array) { + return false; + } + + for (var i = 0; i < array.length; ++i) { + if (Array.isArray(array[i])) { + return false; + } + } + + return true; + }; + + exports.flatten = function () { + var result = exports.argsToArray.apply(null, arguments); + + while (!exports.isFlat(result)) { + result = exports.flattenShallow(result); + } + + return result; + }; + + exports.argsToArray = function (args) { + var result = []; + + exports.arrayEach(args, function (value) { + result.push(value); + }); + + return result; + }; + + exports.numbers = function () { + var possibleNumbers = this.flatten.apply(null, arguments); + return possibleNumbers.filter(function (el) { + return typeof el === "number"; + }); + }; + + exports.cleanFloat = function (number) { + var power = 1e14; + return Math.round(number * power) / power; + }; + + exports.parseBool = function (bool) { + if (typeof bool === "boolean") { + return bool; + } + + if (bool instanceof Error) { + return bool; + } + + if (typeof bool === "number") { + return bool !== 0; + } + + if (typeof bool === "string") { + var up = bool.toUpperCase(); + if (up === "TRUE") { + return true; + } + + if (up === "FALSE") { + return false; + } + } + + if (bool instanceof Date && !isNaN(bool)) { + return true; + } + + return error.value; + }; + + exports.parseNumber = function (string) { + if (string === undefined || string === "") { + return error.value; + } + if (!isNaN(string)) { + return parseFloat(string); + } + + return error.value; + }; + + exports.parseNumberArray = function (arr) { + var len; + + if (!arr || (len = arr.length) === 0) { + return error.value; + } + + var parsed; + + while (len--) { + parsed = exports.parseNumber(arr[len]); + if (parsed === error.value) { + return parsed; + } + arr[len] = parsed; + } + + return arr; + }; + + exports.parseMatrix = function (matrix) { + var n; + + if (!matrix || (n = matrix.length) === 0) { + return error.value; + } + var pnarr; + + for (var i = 0; i < matrix.length; i++) { + pnarr = exports.parseNumberArray(matrix[i]); + matrix[i] = pnarr; + + if (pnarr instanceof Error) { + return pnarr; + } + } + + return matrix; + }; + + var d1900 = new Date(Date.UTC(1900, 0, 1)); + exports.parseDate = function (date) { + if (!isNaN(date)) { + if (date instanceof Date) { + return new Date(date); + } + var d = parseInt(date, 10); + if (d < 0) { + return error.num; + } + if (d <= 60) { + return new Date(d1900.getTime() + (d - 1) * 86400000); + } + return new Date(d1900.getTime() + (d - 2) * 86400000); + } + if (typeof date === "string") { + date = new Date(date); + if (!isNaN(date)) { + return date; + } + } + return error.value; + }; + + exports.parseDateArray = function (arr) { + var len = arr.length; + var parsed; + while (len--) { + parsed = this.parseDate(arr[len]); + if (parsed === error.value) { + return parsed; + } + arr[len] = parsed; + } + return arr; + }; + + exports.anyIsError = function () { + var n = arguments.length; + while (n--) { + if (arguments[n] instanceof Error) { + return true; + } + } + return false; + }; + + exports.arrayValuesToNumbers = function (arr) { + var n = arr.length; + var el; + while (n--) { + el = arr[n]; + if (typeof el === "number") { + continue; + } + if (el === true) { + arr[n] = 1; + continue; + } + if (el === false) { + arr[n] = 0; + continue; + } + if (typeof el === "string") { + var number = this.parseNumber(el); + if (number instanceof Error) { + arr[n] = 0; + } else { + arr[n] = number; + } + } + } + return arr; + }; + + exports.rest = function (array, idx) { + idx = idx || 1; + if (!array || typeof array.slice !== "function") { + return array; + } + return array.slice(idx); + }; + + exports.initial = function (array, idx) { + idx = idx || 1; + if (!array || typeof array.slice !== "function") { + return array; + } + return array.slice(0, array.length - idx); + }; + + exports.arrayEach = function (array, iteratee) { + var index = -1, + length = array.length; + + while (++index < length) { + if (iteratee(array[index], index, array) === false) { + break; + } + } + + return array; + }; + + exports.transpose = function (matrix) { + if (!matrix) { + return error.value; + } + + return matrix[0].map(function (col, i) { + return matrix.map(function (row) { + return row[i]; + }); + }); + }; + + return exports; + })(); + + var met = {}; + + met.datetime = (function () { + var exports = {}; + + var d1900 = new Date(1900, 0, 1); + var WEEK_STARTS = [ + undefined, + 0, + 1, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + 1, + 2, + 3, + 4, + 5, + 6, + 0, + ]; + var WEEK_TYPES = [ + [], + [1, 2, 3, 4, 5, 6, 7], + [7, 1, 2, 3, 4, 5, 6], + [6, 0, 1, 2, 3, 4, 5], + [], + [], + [], + [], + [], + [], + [], + [7, 1, 2, 3, 4, 5, 6], + [6, 7, 1, 2, 3, 4, 5], + [5, 6, 7, 1, 2, 3, 4], + [4, 5, 6, 7, 1, 2, 3], + [3, 4, 5, 6, 7, 1, 2], + [2, 3, 4, 5, 6, 7, 1], + [1, 2, 3, 4, 5, 6, 7], + ]; + var WEEKEND_TYPES = [ + [], + [6, 0], + [0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + undefined, + undefined, + undefined, + [0, 0], + [1, 1], + [2, 2], + [3, 3], + [4, 4], + [5, 5], + [6, 6], + ]; + + exports.DATE = function (year, month, day) { + year = utils.parseNumber(year); + month = utils.parseNumber(month); + day = utils.parseNumber(day); + if (utils.anyIsError(year, month, day)) { + return error.value; + } + if (year < 0 || month < 0 || day < 0) { + return error.num; + } + var date = new Date(year, month - 1, day); + return date; + }; + + exports.DATEVALUE = function (date_text) { + if (typeof date_text !== "string") { + return error.value; + } + var date = Date.parse(date_text); + if (isNaN(date)) { + return error.value; + } + if (date <= -2203891200000) { + return (date - d1900) / 86400000 + 1; + } + return (date - d1900) / 86400000 + 2; + }; + + exports.DAY = function (serial_number) { + var date = utils.parseDate(serial_number); + if (date instanceof Error) { + return date; + } + return date.getDate(); + }; + + exports.DAYS = function (end_date, start_date) { + end_date = utils.parseDate(end_date); + start_date = utils.parseDate(start_date); + if (end_date instanceof Error) { + return end_date; + } + if (start_date instanceof Error) { + return start_date; + } + return serial(end_date) - serial(start_date); + }; + + exports.DAYS360 = function (start_date, end_date, method) {}; + + exports.EDATE = function (start_date, months) { + start_date = utils.parseDate(start_date); + if (start_date instanceof Error) { + return start_date; + } + if (isNaN(months)) { + return error.value; + } + months = parseInt(months, 10); + start_date.setMonth(start_date.getMonth() + months); + return serial(start_date); + }; + + exports.EOMONTH = function (start_date, months) { + start_date = utils.parseDate(start_date); + if (start_date instanceof Error) { + return start_date; + } + if (isNaN(months)) { + return error.value; + } + months = parseInt(months, 10); + return serial( + new Date( + start_date.getFullYear(), + start_date.getMonth() + months + 1, + 0 + ) + ); + }; + + exports.HOUR = function (serial_number) { + serial_number = utils.parseDate(serial_number); + if (serial_number instanceof Error) { + return serial_number; + } + return serial_number.getHours(); + }; + + exports.INTERVAL = function (second) { + if (typeof second !== "number" && typeof second !== "string") { + return error.value; + } else { + second = parseInt(second, 10); + } + + var year = Math.floor(second / 946080000); + second = second % 946080000; + var month = Math.floor(second / 2592000); + second = second % 2592000; + var day = Math.floor(second / 86400); + second = second % 86400; + + var hour = Math.floor(second / 3600); + second = second % 3600; + var min = Math.floor(second / 60); + second = second % 60; + var sec = second; + + year = year > 0 ? year + "Y" : ""; + month = month > 0 ? month + "M" : ""; + day = day > 0 ? day + "D" : ""; + hour = hour > 0 ? hour + "H" : ""; + min = min > 0 ? min + "M" : ""; + sec = sec > 0 ? sec + "S" : ""; + + return "P" + year + month + day + "T" + hour + min + sec; + }; + + exports.ISOWEEKNUM = function (date) { + date = utils.parseDate(date); + if (date instanceof Error) { + return date; + } + + date.setHours(0, 0, 0); + date.setDate(date.getDate() + 4 - (date.getDay() || 7)); + var yearStart = new Date(date.getFullYear(), 0, 1); + return Math.ceil(((date - yearStart) / 86400000 + 1) / 7); + }; + + exports.MINUTE = function (serial_number) { + serial_number = utils.parseDate(serial_number); + if (serial_number instanceof Error) { + return serial_number; + } + return serial_number.getMinutes(); + }; + + exports.MONTH = function (serial_number) { + serial_number = utils.parseDate(serial_number); + if (serial_number instanceof Error) { + return serial_number; + } + return serial_number.getMonth() + 1; + }; + + exports.NETWORKDAYS = function (start_date, end_date, holidays) {}; + + exports.NETWORKDAYS.INTL = function ( + start_date, + end_date, + weekend, + holidays + ) {}; + + exports.NOW = function () { + return new Date(); + }; + + exports.SECOND = function (serial_number) { + serial_number = utils.parseDate(serial_number); + if (serial_number instanceof Error) { + return serial_number; + } + return serial_number.getSeconds(); + }; + + exports.TIME = function (hour, minute, second) { + hour = utils.parseNumber(hour); + minute = utils.parseNumber(minute); + second = utils.parseNumber(second); + if (utils.anyIsError(hour, minute, second)) { + return error.value; + } + if (hour < 0 || minute < 0 || second < 0) { + return error.num; + } + return (3600 * hour + 60 * minute + second) / 86400; + }; + + exports.TIMEVALUE = function (time_text) { + time_text = utils.parseDate(time_text); + if (time_text instanceof Error) { + return time_text; + } + return ( + (3600 * time_text.getHours() + + 60 * time_text.getMinutes() + + time_text.getSeconds()) / + 86400 + ); + }; + + exports.TODAY = function () { + return new Date(); + }; + + exports.WEEKDAY = function (serial_number, return_type) { + serial_number = utils.parseDate(serial_number); + if (serial_number instanceof Error) { + return serial_number; + } + if (return_type === undefined) { + return_type = 1; + } + var day = serial_number.getDay(); + return WEEK_TYPES[return_type][day]; + }; + + exports.WEEKNUM = function (serial_number, return_type) {}; + + exports.WORKDAY = function (start_date, days, holidays) {}; + + exports.WORKDAY.INTL = function (start_date, days, weekend, holidays) {}; + + exports.YEAR = function (serial_number) { + serial_number = utils.parseDate(serial_number); + if (serial_number instanceof Error) { + return serial_number; + } + return serial_number.getFullYear(); + }; + + function isLeapYear(year) { + return new Date(year, 1, 29).getMonth() === 1; + } + + exports.YEARFRAC = function (start_date, end_date, basis) {}; + + function serial(date) { + var addOn = date > -2203891200000 ? 2 : 1; + return (date - d1900) / 86400000 + addOn; + } + + return exports; + })(); + + met.database = (function () { + var exports = {}; + + function compact(array) { + if (!array) { + return array; + } + var result = []; + for (var i = 0; i < array.length; ++i) { + if (!array[i]) { + continue; + } + result.push(array[i]); + } + return result; + } + + exports.FINDFIELD = function (database, title) { + var index = null; + for (var i = 0; i < database.length; i++) { + if (database[i][0] === title) { + index = i; + break; + } + } + + // Return error if the input field title is incorrect + if (index == null) { + return error.value; + } + return index; + }; + + function findResultIndex(database, criterias) { + var matches = {}; + for (var i = 1; i < database[0].length; ++i) { + matches[i] = true; + } + var maxCriteriaLength = criterias[0].length; + for (i = 1; i < criterias.length; ++i) { + if (criterias[i].length > maxCriteriaLength) { + maxCriteriaLength = criterias[i].length; + } + } + + for (var k = 1; k < database.length; ++k) { + for (var l = 1; l < database[k].length; ++l) { + var currentCriteriaResult = false; + var hasMatchingCriteria = false; + for (var j = 0; j < criterias.length; ++j) { + var criteria = criterias[j]; + if (criteria.length < maxCriteriaLength) { + continue; + } + + var criteriaField = criteria[0]; + if (database[k][0] !== criteriaField) { + continue; + } + hasMatchingCriteria = true; + for (var p = 1; p < criteria.length; ++p) { + currentCriteriaResult = + currentCriteriaResult || eval(database[k][l] + criteria[p]); // jshint + // ignore:line + } + } + if (hasMatchingCriteria) { + matches[l] = matches[l] && currentCriteriaResult; + } + } + } + + var result = []; + for (var n = 0; n < database[0].length; ++n) { + if (matches[n]) { + result.push(n - 1); + } + } + return result; + } + + // Database functions + exports.DAVERAGE = function (database, field, criteria) { + // Return error if field is not a number and not a string + if (isNaN(field) && typeof field !== "string") { + return error.value; + } + var resultIndexes = findResultIndex(database, criteria); + var targetFields = []; + if (typeof field === "string") { + var index = exports.FINDFIELD(database, field); + targetFields = utils.rest(database[index]); + } else { + targetFields = utils.rest(database[field]); + } + var sum = 0; + for (var i = 0; i < resultIndexes.length; i++) { + sum += targetFields[resultIndexes[i]]; + } + return resultIndexes.length === 0 + ? error.div0 + : sum / resultIndexes.length; + }; + + exports.DCOUNT = function (database, field, criteria) {}; + + exports.DCOUNTA = function (database, field, criteria) {}; + + exports.DGET = function (database, field, criteria) { + // Return error if field is not a number and not a string + if (isNaN(field) && typeof field !== "string") { + return error.value; + } + var resultIndexes = findResultIndex(database, criteria); + var targetFields = []; + if (typeof field === "string") { + var index = exports.FINDFIELD(database, field); + targetFields = utils.rest(database[index]); + } else { + targetFields = utils.rest(database[field]); + } + // Return error if no record meets the criteria + if (resultIndexes.length === 0) { + return error.value; + } + // Returns the #NUM! error value because more than one record meets the + // criteria + if (resultIndexes.length > 1) { + return error.num; + } + + return targetFields[resultIndexes[0]]; + }; + + exports.DMAX = function (database, field, criteria) { + // Return error if field is not a number and not a string + if (isNaN(field) && typeof field !== "string") { + return error.value; + } + var resultIndexes = findResultIndex(database, criteria); + var targetFields = []; + if (typeof field === "string") { + var index = exports.FINDFIELD(database, field); + targetFields = utils.rest(database[index]); + } else { + targetFields = utils.rest(database[field]); + } + var maxValue = targetFields[resultIndexes[0]]; + for (var i = 1; i < resultIndexes.length; i++) { + if (maxValue < targetFields[resultIndexes[i]]) { + maxValue = targetFields[resultIndexes[i]]; + } + } + return maxValue; + }; + + exports.DMIN = function (database, field, criteria) { + // Return error if field is not a number and not a string + if (isNaN(field) && typeof field !== "string") { + return error.value; + } + var resultIndexes = findResultIndex(database, criteria); + var targetFields = []; + if (typeof field === "string") { + var index = exports.FINDFIELD(database, field); + targetFields = utils.rest(database[index]); + } else { + targetFields = utils.rest(database[field]); + } + var minValue = targetFields[resultIndexes[0]]; + for (var i = 1; i < resultIndexes.length; i++) { + if (minValue > targetFields[resultIndexes[i]]) { + minValue = targetFields[resultIndexes[i]]; + } + } + return minValue; + }; + + exports.DPRODUCT = function (database, field, criteria) { + // Return error if field is not a number and not a string + if (isNaN(field) && typeof field !== "string") { + return error.value; + } + var resultIndexes = findResultIndex(database, criteria); + var targetFields = []; + if (typeof field === "string") { + var index = exports.FINDFIELD(database, field); + targetFields = utils.rest(database[index]); + } else { + targetFields = utils.rest(database[field]); + } + var targetValues = []; + for (var i = 0; i < resultIndexes.length; i++) { + targetValues[i] = targetFields[resultIndexes[i]]; + } + targetValues = compact(targetValues); + var result = 1; + for (i = 0; i < targetValues.length; i++) { + result *= targetValues[i]; + } + return result; + }; + + exports.DSTDEV = function (database, field, criteria) {}; + + exports.DSTDEVP = function (database, field, criteria) {}; + + exports.DSUM = function (database, field, criteria) {}; + + exports.DVAR = function (database, field, criteria) {}; + + exports.DVARP = function (database, field, criteria) {}; + + exports.MATCH = function (lookupValue, lookupArray, matchType) { + if (!lookupValue && !lookupArray) { + return error.na; + } + if (arguments.length === 2) { + matchType = 1; + } + if (!(lookupArray instanceof Array)) { + return error.na; + } + if (matchType !== -1 && matchType !== 0 && matchType !== 1) { + return error.na; + } + + var index; + var indexValue; + + for (var idx = 0; idx < lookupArray.length; idx++) { + if (matchType === 1) { + if (lookupArray[idx] === lookupValue) { + return idx + 1; + } else if (lookupArray[idx] < lookupValue) { + if (!indexValue) { + index = idx + 1; + indexValue = lookupArray[idx]; + } else if (lookupArray[idx] > indexValue) { + index = idx + 1; + indexValue = lookupArray[idx]; + } + } + } else if (matchType === 0) { + if (typeof lookupValue === "string") { + lookupValue = lookupValue.replace(/\?/g, "."); + if ( + lookupArray[idx].toLowerCase().match(lookupValue.toLowerCase()) + ) { + return idx + 1; + } + } else { + if (lookupArray[idx] === lookupValue) { + return idx + 1; + } + } + } else if (matchType === -1) { + if (lookupArray[idx] === lookupValue) { + return idx + 1; + } else if (lookupArray[idx] > lookupValue) { + if (!indexValue) { + index = idx + 1; + indexValue = lookupArray[idx]; + } else if (lookupArray[idx] < indexValue) { + index = idx + 1; + indexValue = lookupArray[idx]; + } + } + } + } + + return index ? index : error.na; + }; + + return exports; + })(); + + met.engineering = (function () { + var exports = {}; + + function isValidBinaryNumber(number) { + return /^[01]{1,10}$/.test(number); + } + + exports.BESSELI = function (x, n) {}; + + exports.BESSELJ = function (x, n) {}; + + exports.BESSELK = function (x, n) {}; + + exports.BESSELY = function (x, n) {}; + + exports.BIN2DEC = function (number) { + // Return error if number is not binary or contains more than 10 + // characters (10 digits) + if (!isValidBinaryNumber(number)) { + return error.num; + } + + // Convert binary number to decimal + var result = parseInt(number, 2); + + // Handle negative numbers + var stringified = number.toString(); + if (stringified.length === 10 && stringified.substring(0, 1) === "1") { + return parseInt(stringified.substring(1), 2) - 512; + } else { + return result; + } + }; + + exports.BIN2HEX = function (number, places) { + // Return error if number is not binary or contains more than 10 + // characters (10 digits) + if (!isValidBinaryNumber(number)) { + return error.num; + } + + // Ignore places and return a 10-character hexadecimal number if number + // is negative + var stringified = number.toString(); + if (stringified.length === 10 && stringified.substring(0, 1) === "1") { + return (1099511627264 + parseInt(stringified.substring(1), 2)).toString( + 16 + ); + } + + // Convert binary number to hexadecimal + var result = parseInt(number, 2).toString(16); + + // Return hexadecimal number using the minimum number of characters + // necessary if places is undefined + if (places === undefined) { + return result; + } else { + // Return error if places is nonnumeric + if (isNaN(places)) { + return error.value; + } + + // Return error if places is negative + if (places < 0) { + return error.num; + } + + // Truncate places in case it is not an integer + places = Math.floor(places); + + // Pad return value with leading 0s (zeros) if necessary (using + // Underscore.string) + return places >= result.length + ? REPT("0", places - result.length) + result + : error.num; + } + }; + + exports.BIN2OCT = function (number, places) { + // Return error if number is not binary or contains more than 10 + // characters (10 digits) + if (!isValidBinaryNumber(number)) { + return error.num; + } + + // Ignore places and return a 10-character octal number if number is + // negative + var stringified = number.toString(); + if (stringified.length === 10 && stringified.substring(0, 1) === "1") { + return (1073741312 + parseInt(stringified.substring(1), 2)).toString(8); + } + + // Convert binary number to octal + var result = parseInt(number, 2).toString(8); + + // Return octal number using the minimum number of characters necessary + // if places is undefined + if (places === undefined) { + return result; + } else { + // Return error if places is nonnumeric + if (isNaN(places)) { + return error.value; + } + + // Return error if places is negative + if (places < 0) { + return error.num; + } + + // Truncate places in case it is not an integer + places = Math.floor(places); + + // Pad return value with leading 0s (zeros) if necessary (using + // Underscore.string) + return places >= result.length + ? REPT("0", places - result.length) + result + : error.num; + } + }; + + exports.BITAND = function (number1, number2) { + // Return error if either number is a non-numeric value + number1 = utils.parseNumber(number1); + number2 = utils.parseNumber(number2); + if (utils.anyIsError(number1, number2)) { + return error.value; + } + + // Return error if either number is less than 0 + if (number1 < 0 || number2 < 0) { + return error.num; + } + + // Return error if either number is a non-integer + if (Math.floor(number1) !== number1 || Math.floor(number2) !== number2) { + return error.num; + } + + // Return error if either number is greater than (2^48)-1 + if (number1 > 281474976710655 || number2 > 281474976710655) { + return error.num; + } + + // Return bitwise AND of two numbers + return number1 & number2; + }; + + exports.BITLSHIFT = function (number, shift) { + number = utils.parseNumber(number); + shift = utils.parseNumber(shift); + if (utils.anyIsError(number, shift)) { + return error.value; + } + + // Return error if number is less than 0 + if (number < 0) { + return error.num; + } + + // Return error if number is a non-integer + if (Math.floor(number) !== number) { + return error.num; + } + + // Return error if number is greater than (2^48)-1 + if (number > 281474976710655) { + return error.num; + } + + // Return error if the absolute value of shift is greater than 53 + if (Math.abs(shift) > 53) { + return error.num; + } + + // Return number shifted by shift bits to the left or to the right if + // shift is negative + return shift >= 0 ? number << shift : number >> -shift; + }; + + exports.BITOR = function (number1, number2) { + number1 = utils.parseNumber(number1); + number2 = utils.parseNumber(number2); + if (utils.anyIsError(number1, number2)) { + return error.value; + } + + // Return error if either number is less than 0 + if (number1 < 0 || number2 < 0) { + return error.num; + } + + // Return error if either number is a non-integer + if (Math.floor(number1) !== number1 || Math.floor(number2) !== number2) { + return error.num; + } + + // Return error if either number is greater than (2^48)-1 + if (number1 > 281474976710655 || number2 > 281474976710655) { + return error.num; + } + + // Return bitwise OR of two numbers + return number1 | number2; + }; + + exports.BITRSHIFT = function (number, shift) { + number = utils.parseNumber(number); + shift = utils.parseNumber(shift); + if (utils.anyIsError(number, shift)) { + return error.value; + } + + // Return error if number is less than 0 + if (number < 0) { + return error.num; + } + + // Return error if number is a non-integer + if (Math.floor(number) !== number) { + return error.num; + } + + // Return error if number is greater than (2^48)-1 + if (number > 281474976710655) { + return error.num; + } + + // Return error if the absolute value of shift is greater than 53 + if (Math.abs(shift) > 53) { + return error.num; + } + + // Return number shifted by shift bits to the right or to the left if + // shift is negative + return shift >= 0 ? number >> shift : number << -shift; + }; + + exports.BITXOR = function (number1, number2) { + number1 = utils.parseNumber(number1); + number2 = utils.parseNumber(number2); + if (utils.anyIsError(number1, number2)) { + return error.value; + } + + // Return error if either number is less than 0 + if (number1 < 0 || number2 < 0) { + return error.num; + } + + // Return error if either number is a non-integer + if (Math.floor(number1) !== number1 || Math.floor(number2) !== number2) { + return error.num; + } + + // Return error if either number is greater than (2^48)-1 + if (number1 > 281474976710655 || number2 > 281474976710655) { + return error.num; + } + + // Return bitwise XOR of two numbers + return number1 ^ number2; + }; + + exports.COMPLEX = function (real, imaginary, suffix) { + real = utils.parseNumber(real); + imaginary = utils.parseNumber(imaginary); + if (utils.anyIsError(real, imaginary)) { + return real; + } + + // Set suffix + suffix = suffix === undefined ? "i" : suffix; + + // Return error if suffix is neither "i" nor "j" + if (suffix !== "i" && suffix !== "j") { + return error.value; + } + + // Return complex number + if (real === 0 && imaginary === 0) { + return 0; + } else if (real === 0) { + return imaginary === 1 ? suffix : imaginary.toString() + suffix; + } else if (imaginary === 0) { + return real.toString(); + } else { + var sign = imaginary > 0 ? "+" : ""; + return ( + real.toString() + + sign + + (imaginary === 1 ? suffix : imaginary.toString() + suffix) + ); + } + }; + + exports.CONVERT = function (number, from_unit, to_unit) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + + // List of units supported by CONVERT and units defined by the + // International System of Units + // [Name, Symbol, Alternate symbols, Quantity, ISU, CONVERT, Conversion + // ratio] + var units = [ + [ + "a.u. of action", + "?", + null, + "action", + false, + false, + 1.05457168181818e-34, + ], + [ + "a.u. of charge", + "e", + null, + "electric_charge", + false, + false, + 1.60217653141414e-19, + ], + [ + "a.u. of energy", + "Eh", + null, + "energy", + false, + false, + 4.35974417757576e-18, + ], + [ + "a.u. of length", + "a?", + null, + "length", + false, + false, + 5.29177210818182e-11, + ], + [ + "a.u. of mass", + "m?", + null, + "mass", + false, + false, + 9.10938261616162e-31, + ], + [ + "a.u. of time", + "?/Eh", + null, + "time", + false, + false, + 2.41888432650516e-17, + ], + ["admiralty knot", "admkn", null, "speed", false, true, 0.514773333], + ["ampere", "A", null, "electric_current", true, false, 1], + [ + "ampere per meter", + "A/m", + null, + "magnetic_field_intensity", + true, + false, + 1, + ], + ["ångström", "Å", ["ang"], "length", false, true, 1e-10], + ["are", "ar", null, "area", false, true, 100], + [ + "astronomical unit", + "ua", + null, + "length", + false, + false, + 1.49597870691667e-11, + ], + ["bar", "bar", null, "pressure", false, false, 100000], + ["barn", "b", null, "area", false, false, 1e-28], + ["becquerel", "Bq", null, "radioactivity", true, false, 1], + ["bit", "bit", ["b"], "information", false, true, 1], + ["btu", "BTU", ["btu"], "energy", false, true, 1055.05585262], + ["byte", "byte", null, "information", false, true, 8], + ["candela", "cd", null, "luminous_intensity", true, false, 1], + [ + "candela per square metre", + "cd/m?", + null, + "luminance", + true, + false, + 1, + ], + ["coulomb", "C", null, "electric_charge", true, false, 1], + ["cubic ångström", "ang3", ["ang^3"], "volume", false, true, 1e-30], + ["cubic foot", "ft3", ["ft^3"], "volume", false, true, 0.028316846592], + ["cubic inch", "in3", ["in^3"], "volume", false, true, 0.000016387064], + [ + "cubic light-year", + "ly3", + ["ly^3"], + "volume", + false, + true, + 8.46786664623715e-47, + ], + ["cubic metre", "m?", null, "volume", true, true, 1], + [ + "cubic mile", + "mi3", + ["mi^3"], + "volume", + false, + true, + 4168181825.44058, + ], + [ + "cubic nautical mile", + "Nmi3", + ["Nmi^3"], + "volume", + false, + true, + 6352182208, + ], + [ + "cubic Pica", + "Pica3", + ["Picapt3", "Pica^3", "Picapt^3"], + "volume", + false, + true, + 7.58660370370369e-8, + ], + ["cubic yard", "yd3", ["yd^3"], "volume", false, true, 0.764554857984], + ["cup", "cup", null, "volume", false, true, 0.0002365882365], + ["dalton", "Da", ["u"], "mass", false, false, 1.66053886282828e-27], + ["day", "d", ["day"], "time", false, true, 86400], + ["degree", "°", null, "angle", false, false, 0.0174532925199433], + [ + "degrees Rankine", + "Rank", + null, + "temperature", + false, + true, + 0.555555555555556, + ], + ["dyne", "dyn", ["dy"], "force", false, true, 0.00001], + ["electronvolt", "eV", ["ev"], "energy", false, true, 1.60217656514141], + ["ell", "ell", null, "length", false, true, 1.143], + ["erg", "erg", ["e"], "energy", false, true, 1e-7], + ["farad", "F", null, "electric_capacitance", true, false, 1], + ["fluid ounce", "oz", null, "volume", false, true, 0.0000295735295625], + ["foot", "ft", null, "length", false, true, 0.3048], + ["foot-pound", "flb", null, "energy", false, true, 1.3558179483314], + ["gal", "Gal", null, "acceleration", false, false, 0.01], + ["gallon", "gal", null, "volume", false, true, 0.003785411784], + ["gauss", "G", ["ga"], "magnetic_flux_density", false, true, 1], + ["grain", "grain", null, "mass", false, true, 0.0000647989], + ["gram", "g", null, "mass", false, true, 0.001], + ["gray", "Gy", null, "absorbed_dose", true, false, 1], + [ + "gross registered ton", + "GRT", + ["regton"], + "volume", + false, + true, + 2.8316846592, + ], + ["hectare", "ha", null, "area", false, true, 10000], + ["henry", "H", null, "inductance", true, false, 1], + ["hertz", "Hz", null, "frequency", true, false, 1], + ["horsepower", "HP", ["h"], "power", false, true, 745.69987158227], + [ + "horsepower-hour", + "HPh", + ["hh", "hph"], + "energy", + false, + true, + 2684519.538, + ], + ["hour", "h", ["hr"], "time", false, true, 3600], + [ + "imperial gallon (U.K.)", + "uk_gal", + null, + "volume", + false, + true, + 0.00454609, + ], + [ + "imperial hundredweight", + "lcwt", + ["uk_cwt", "hweight"], + "mass", + false, + true, + 50.802345, + ], + [ + "imperial quart (U.K)", + "uk_qt", + null, + "volume", + false, + true, + 0.0011365225, + ], + [ + "imperial ton", + "brton", + ["uk_ton", "LTON"], + "mass", + false, + true, + 1016.046909, + ], + ["inch", "in", null, "length", false, true, 0.0254], + [ + "international acre", + "uk_acre", + null, + "area", + false, + true, + 4046.8564224, + ], + ["IT calorie", "cal", null, "energy", false, true, 4.1868], + ["joule", "J", null, "energy", true, true, 1], + ["katal", "kat", null, "catalytic_activity", true, false, 1], + ["kelvin", "K", ["kel"], "temperature", true, true, 1], + ["kilogram", "kg", null, "mass", true, true, 1], + ["knot", "kn", null, "speed", false, true, 0.514444444444444], + ["light-year", "ly", null, "length", false, true, 9460730472580800], + ["litre", "L", ["l", "lt"], "volume", false, true, 0.001], + ["lumen", "lm", null, "luminous_flux", true, false, 1], + ["lux", "lx", null, "illuminance", true, false, 1], + ["maxwell", "Mx", null, "magnetic_flux", false, false, 1e-18], + ["measurement ton", "MTON", null, "volume", false, true, 1.13267386368], + [ + "meter per hour", + "m/h", + ["m/hr"], + "speed", + false, + true, + 0.00027777777777778, + ], + ["meter per second", "m/s", ["m/sec"], "speed", true, true, 1], + [ + "meter per second squared", + "m?s??", + null, + "acceleration", + true, + false, + 1, + ], + ["parsec", "pc", ["parsec"], "length", false, true, 30856775814671900], + [ + "meter squared per second", + "m?/s", + null, + "kinematic_viscosity", + true, + false, + 1, + ], + ["metre", "m", null, "length", true, true, 1], + ["miles per hour", "mph", null, "speed", false, true, 0.44704], + [ + "millimetre of mercury", + "mmHg", + null, + "pressure", + false, + false, + 133.322, + ], + ["minute", "?", null, "angle", false, false, 0.000290888208665722], + ["minute", "min", ["mn"], "time", false, true, 60], + ["modern teaspoon", "tspm", null, "volume", false, true, 0.000005], + ["mole", "mol", null, "amount_of_substance", true, false, 1], + ["morgen", "Morgen", null, "area", false, true, 2500], + [ + "n.u. of action", + "?", + null, + "action", + false, + false, + 1.05457168181818e-34, + ], + [ + "n.u. of mass", + "m?", + null, + "mass", + false, + false, + 9.10938261616162e-31, + ], + ["n.u. of speed", "c?", null, "speed", false, false, 299792458], + [ + "n.u. of time", + "?/(me?c??)", + null, + "time", + false, + false, + 1.28808866778687e-21, + ], + ["nautical mile", "M", ["Nmi"], "length", false, true, 1852], + ["newton", "N", null, "force", true, true, 1], + [ + "œrsted", + "Oe ", + null, + "magnetic_field_intensity", + false, + false, + 79.5774715459477, + ], + ["ohm", "Ω", null, "electric_resistance", true, false, 1], + ["ounce mass", "ozm", null, "mass", false, true, 0.028349523125], + ["pascal", "Pa", null, "pressure", true, false, 1], + ["pascal second", "Pa?s", null, "dynamic_viscosity", true, false, 1], + ["pferdestärke", "PS", null, "power", false, true, 735.49875], + ["phot", "ph", null, "illuminance", false, false, 0.0001], + [ + "pica (1/6 inch)", + "pica", + null, + "length", + false, + true, + 0.00035277777777778, + ], + [ + "pica (1/72 inch)", + "Pica", + ["Picapt"], + "length", + false, + true, + 0.00423333333333333, + ], + ["poise", "P", null, "dynamic_viscosity", false, false, 0.1], + ["pond", "pond", null, "force", false, true, 0.00980665], + ["pound force", "lbf", null, "force", false, true, 4.4482216152605], + ["pound mass", "lbm", null, "mass", false, true, 0.45359237], + ["quart", "qt", null, "volume", false, true, 0.000946352946], + ["radian", "rad", null, "angle", true, false, 1], + ["second", "?", null, "angle", false, false, 0.00000484813681109536], + ["second", "s", ["sec"], "time", true, true, 1], + [ + "short hundredweight", + "cwt", + ["shweight"], + "mass", + false, + true, + 45.359237, + ], + ["siemens", "S", null, "electrical_conductance", true, false, 1], + ["sievert", "Sv", null, "equivalent_dose", true, false, 1], + ["slug", "sg", null, "mass", false, true, 14.59390294], + ["square ångström", "ang2", ["ang^2"], "area", false, true, 1e-20], + ["square foot", "ft2", ["ft^2"], "area", false, true, 0.09290304], + ["square inch", "in2", ["in^2"], "area", false, true, 0.00064516], + [ + "square light-year", + "ly2", + ["ly^2"], + "area", + false, + true, + 8.95054210748189e31, + ], + ["square meter", "m?", null, "area", true, true, 1], + ["square mile", "mi2", ["mi^2"], "area", false, true, 2589988.110336], + [ + "square nautical mile", + "Nmi2", + ["Nmi^2"], + "area", + false, + true, + 3429904, + ], + [ + "square Pica", + "Pica2", + ["Picapt2", "Pica^2", "Picapt^2"], + "area", + false, + true, + 0.00001792111111111, + ], + ["square yard", "yd2", ["yd^2"], "area", false, true, 0.83612736], + ["statute mile", "mi", null, "length", false, true, 1609.344], + ["steradian", "sr", null, "solid_angle", true, false, 1], + ["stilb", "sb", null, "luminance", false, false, 0.0001], + ["stokes", "St", null, "kinematic_viscosity", false, false, 0.0001], + ["stone", "stone", null, "mass", false, true, 6.35029318], + ["tablespoon", "tbs", null, "volume", false, true, 0.0000147868], + ["teaspoon", "tsp", null, "volume", false, true, 0.00000492892], + ["tesla", "T", null, "magnetic_flux_density", true, true, 1], + ["thermodynamic calorie", "c", null, "energy", false, true, 4.184], + ["ton", "ton", null, "mass", false, true, 907.18474], + ["tonne", "t", null, "mass", false, false, 1000], + ["U.K. pint", "uk_pt", null, "volume", false, true, 0.00056826125], + ["U.S. bushel", "bushel", null, "volume", false, true, 0.03523907], + ["U.S. oil barrel", "barrel", null, "volume", false, true, 0.158987295], + ["U.S. pint", "pt", ["us_pt"], "volume", false, true, 0.000473176473], + [ + "U.S. survey mile", + "survey_mi", + null, + "length", + false, + true, + 1609.347219, + ], + [ + "U.S. survey/statute acre", + "us_acre", + null, + "area", + false, + true, + 4046.87261, + ], + ["volt", "V", null, "voltage", true, false, 1], + ["watt", "W", null, "power", true, true, 1], + ["watt-hour", "Wh", ["wh"], "energy", false, true, 3600], + ["weber", "Wb", null, "magnetic_flux", true, false, 1], + ["yard", "yd", null, "length", false, true, 0.9144], + ["year", "yr", null, "time", false, true, 31557600], + ]; + + // Binary prefixes + // [Name, Prefix power of 2 value, Previx value, Abbreviation, Derived + // from] + var binary_prefixes = { + Yi: ["yobi", 80, 1208925819614629174706176, "Yi", "yotta"], + Zi: ["zebi", 70, 1180591620717411303424, "Zi", "zetta"], + Ei: ["exbi", 60, 1152921504606846976, "Ei", "exa"], + Pi: ["pebi", 50, 1125899906842624, "Pi", "peta"], + Ti: ["tebi", 40, 1099511627776, "Ti", "tera"], + Gi: ["gibi", 30, 1073741824, "Gi", "giga"], + Mi: ["mebi", 20, 1048576, "Mi", "mega"], + ki: ["kibi", 10, 1024, "ki", "kilo"], + }; + + // Unit prefixes + // [Name, Multiplier, Abbreviation] + var unit_prefixes = { + Y: ["yotta", 1e24, "Y"], + Z: ["zetta", 1e21, "Z"], + E: ["exa", 1e18, "E"], + P: ["peta", 1e15, "P"], + T: ["tera", 1e12, "T"], + G: ["giga", 1e9, "G"], + M: ["mega", 1e6, "M"], + k: ["kilo", 1e3, "k"], + h: ["hecto", 1e2, "h"], + e: ["dekao", 1e1, "e"], + d: ["deci", 1e-1, "d"], + c: ["centi", 1e-2, "c"], + m: ["milli", 1e-3, "m"], + u: ["micro", 1e-6, "u"], + n: ["nano", 1e-9, "n"], + p: ["pico", 1e-12, "p"], + f: ["femto", 1e-15, "f"], + a: ["atto", 1e-18, "a"], + z: ["zepto", 1e-21, "z"], + y: ["yocto", 1e-24, "y"], + }; + + // Initialize units and multipliers + var from = null; + var to = null; + var base_from_unit = from_unit; + var base_to_unit = to_unit; + var from_multiplier = 1; + var to_multiplier = 1; + var alt; + + // Lookup from and to units + for (var i = 0; i < units.length; i++) { + alt = units[i][2] === null ? [] : units[i][2]; + if ( + units[i][1] === base_from_unit || + alt.indexOf(base_from_unit) >= 0 + ) { + from = units[i]; + } + if (units[i][1] === base_to_unit || alt.indexOf(base_to_unit) >= 0) { + to = units[i]; + } + } + + // Lookup from prefix + if (from === null) { + var from_binary_prefix = binary_prefixes[from_unit.substring(0, 2)]; + var from_unit_prefix = unit_prefixes[from_unit.substring(0, 1)]; + + // Handle dekao unit prefix (only unit prefix with two characters) + if (from_unit.substring(0, 2) === "da") { + from_unit_prefix = ["dekao", 1e1, "da"]; + } + + // Handle binary prefixes first (so that 'Yi' is processed before + // 'Y') + if (from_binary_prefix) { + from_multiplier = from_binary_prefix[2]; + base_from_unit = from_unit.substring(2); + } else if (from_unit_prefix) { + from_multiplier = from_unit_prefix[1]; + base_from_unit = from_unit.substring(from_unit_prefix[2].length); + } + + // Lookup from unit + for (var j = 0; j < units.length; j++) { + alt = units[j][2] === null ? [] : units[j][2]; + if ( + units[j][1] === base_from_unit || + alt.indexOf(base_from_unit) >= 0 + ) { + from = units[j]; + } + } + } + + // Lookup to prefix + if (to === null) { + var to_binary_prefix = binary_prefixes[to_unit.substring(0, 2)]; + var to_unit_prefix = unit_prefixes[to_unit.substring(0, 1)]; + + // Handle dekao unit prefix (only unit prefix with two characters) + if (to_unit.substring(0, 2) === "da") { + to_unit_prefix = ["dekao", 1e1, "da"]; + } + + // Handle binary prefixes first (so that 'Yi' is processed before + // 'Y') + if (to_binary_prefix) { + to_multiplier = to_binary_prefix[2]; + base_to_unit = to_unit.substring(2); + } else if (to_unit_prefix) { + to_multiplier = to_unit_prefix[1]; + base_to_unit = to_unit.substring(to_unit_prefix[2].length); + } + + // Lookup to unit + for (var k = 0; k < units.length; k++) { + alt = units[k][2] === null ? [] : units[k][2]; + if (units[k][1] === base_to_unit || alt.indexOf(base_to_unit) >= 0) { + to = units[k]; + } + } + } + + // Return error if a unit does not exist + if (from === null || to === null) { + return error.na; + } + + // Return error if units represent different quantities + if (from[3] !== to[3]) { + return error.na; + } + + // Return converted number + return (number * from[6] * from_multiplier) / (to[6] * to_multiplier); + }; + + exports.DEC2BIN = function (number, places) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + + // Return error if number is not decimal, is lower than -512, or is + // greater than 511 + if (!/^-?[0-9]{1,3}$/.test(number) || number < -512 || number > 511) { + return error.num; + } + + // Ignore places and return a 10-character binary number if number is + // negative + if (number < 0) { + return ( + "1" + + REPT("0", 9 - (512 + number).toString(2).length) + + (512 + number).toString(2) + ); + } + + // Convert decimal number to binary + var result = parseInt(number, 10).toString(2); + + // Return binary number using the minimum number of characters necessary + // if places is undefined + if (typeof places === "undefined") { + return result; + } else { + // Return error if places is nonnumeric + if (isNaN(places)) { + return error.value; + } + + // Return error if places is negative + if (places < 0) { + return error.num; + } + + // Truncate places in case it is not an integer + places = Math.floor(places); + + // Pad return value with leading 0s (zeros) if necessary (using + // Underscore.string) + return places >= result.length + ? REPT("0", places - result.length) + result + : error.num; + } + }; + + exports.DEC2HEX = function (number, places) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + + // Return error if number is not decimal, is lower than -549755813888, + // or is greater than 549755813887 + if ( + !/^-?[0-9]{1,12}$/.test(number) || + number < -549755813888 || + number > 549755813887 + ) { + return error.num; + } + + // Ignore places and return a 10-character hexadecimal number if number + // is negative + if (number < 0) { + return (1099511627776 + number).toString(16); + } + + // Convert decimal number to hexadecimal + var result = parseInt(number, 10).toString(16); + + // Return hexadecimal number using the minimum number of characters + // necessary if places is undefined + if (typeof places === "undefined") { + return result; + } else { + // Return error if places is nonnumeric + if (isNaN(places)) { + return error.value; + } + + // Return error if places is negative + if (places < 0) { + return error.num; + } + + // Truncate places in case it is not an integer + places = Math.floor(places); + + // Pad return value with leading 0s (zeros) if necessary (using + // Underscore.string) + return places >= result.length + ? REPT("0", places - result.length) + result + : error.num; + } + }; + + exports.DEC2OCT = function (number, places) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + + // Return error if number is not decimal, is lower than -549755813888, + // or is greater than 549755813887 + if ( + !/^-?[0-9]{1,9}$/.test(number) || + number < -536870912 || + number > 536870911 + ) { + return error.num; + } + + // Ignore places and return a 10-character octal number if number is + // negative + if (number < 0) { + return (1073741824 + number).toString(8); + } + + // Convert decimal number to octal + var result = parseInt(number, 10).toString(8); + + // Return octal number using the minimum number of characters necessary + // if places is undefined + if (typeof places === "undefined") { + return result; + } else { + // Return error if places is nonnumeric + if (isNaN(places)) { + return error.value; + } + + // Return error if places is negative + if (places < 0) { + return error.num; + } + + // Truncate places in case it is not an integer + places = Math.floor(places); + + // Pad return value with leading 0s (zeros) if necessary (using + // Underscore.string) + return places >= result.length + ? REPT("0", places - result.length) + result + : error.num; + } + }; + + exports.DELTA = function (number1, number2) { + // Set number2 to zero if undefined + number2 = number2 === undefined ? 0 : number2; + number1 = utils.parseNumber(number1); + number2 = utils.parseNumber(number2); + if (utils.anyIsError(number1, number2)) { + return error.value; + } + + // Return delta + return number1 === number2 ? 1 : 0; + }; + + exports.ERF = function (lower_bound, upper_bound) {}; + + exports.ERF.PRECISE = function () {}; + + exports.ERFC = function (x) {}; + + exports.ERFC.PRECISE = function () {}; + + exports.GESTEP = function (number, step) { + step = step || 0; + number = utils.parseNumber(number); + if (utils.anyIsError(step, number)) { + return number; + } + + // Return delta + return number >= step ? 1 : 0; + }; + + exports.HEX2BIN = function (number, places) { + // Return error if number is not hexadecimal or contains more than ten + // characters (10 digits) + if (!/^[0-9A-Fa-f]{1,10}$/.test(number)) { + return error.num; + } + + // Check if number is negative + var negative = + number.length === 10 && number.substring(0, 1).toLowerCase() === "f" + ? true + : false; + + // Convert hexadecimal number to decimal + var decimal = negative + ? parseInt(number, 16) - 1099511627776 + : parseInt(number, 16); + + // Return error if number is lower than -512 or greater than 511 + if (decimal < -512 || decimal > 511) { + return error.num; + } + + // Ignore places and return a 10-character binary number if number is + // negative + if (negative) { + return ( + "1" + + REPT("0", 9 - (512 + decimal).toString(2).length) + + (512 + decimal).toString(2) + ); + } + + // Convert decimal number to binary + var result = decimal.toString(2); + + // Return binary number using the minimum number of characters necessary + // if places is undefined + if (places === undefined) { + return result; + } else { + // Return error if places is nonnumeric + if (isNaN(places)) { + return error.value; + } + + // Return error if places is negative + if (places < 0) { + return error.num; + } + + // Truncate places in case it is not an integer + places = Math.floor(places); + + // Pad return value with leading 0s (zeros) if necessary (using + // Underscore.string) + return places >= result.length + ? REPT("0", places - result.length) + result + : error.num; + } + }; + + exports.HEX2DEC = function (number) { + // Return error if number is not hexadecimal or contains more than ten + // characters (10 digits) + if (!/^[0-9A-Fa-f]{1,10}$/.test(number)) { + return error.num; + } + + // Convert hexadecimal number to decimal + var decimal = parseInt(number, 16); + + // Return decimal number + return decimal >= 549755813888 ? decimal - 1099511627776 : decimal; + }; + + exports.HEX2OCT = function (number, places) { + // Return error if number is not hexadecimal or contains more than ten + // characters (10 digits) + if (!/^[0-9A-Fa-f]{1,10}$/.test(number)) { + return error.num; + } + + // Convert hexadecimal number to decimal + var decimal = parseInt(number, 16); + + // Return error if number is positive and greater than 0x1fffffff + // (536870911) + if (decimal > 536870911 && decimal < 1098974756864) { + return error.num; + } + + // Ignore places and return a 10-character octal number if number is + // negative + if (decimal >= 1098974756864) { + return (decimal - 1098437885952).toString(8); + } + + // Convert decimal number to octal + var result = decimal.toString(8); + + // Return octal number using the minimum number of characters necessary + // if places is undefined + if (places === undefined) { + return result; + } else { + // Return error if places is nonnumeric + if (isNaN(places)) { + return error.value; + } + + // Return error if places is negative + if (places < 0) { + return error.num; + } + + // Truncate places in case it is not an integer + places = Math.floor(places); + + // Pad return value with leading 0s (zeros) if necessary (using + // Underscore.string) + return places >= result.length + ? REPT("0", places - result.length) + result + : error.num; + } + }; + + exports.IMABS = function (inumber) { + // Lookup real and imaginary coefficients using exports.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + // Return error if either coefficient is not a number + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Return absolute value of complex number + return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); + }; + + exports.IMAGINARY = function (inumber) { + if (inumber === undefined || inumber === true || inumber === false) { + return error.value; + } + + // Return 0 if inumber is equal to 0 + if (inumber === 0 || inumber === "0") { + return 0; + } + + // Handle special cases + if (["i", "j"].indexOf(inumber) >= 0) { + return 1; + } + + // Normalize imaginary coefficient + inumber = inumber + .replace("+i", "+1i") + .replace("-i", "-1i") + .replace("+j", "+1j") + .replace("-j", "-1j"); + + // Lookup sign + var plus = inumber.indexOf("+"); + var minus = inumber.indexOf("-"); + if (plus === 0) { + plus = inumber.indexOf("+", 1); + } + + if (minus === 0) { + minus = inumber.indexOf("-", 1); + } + + // Lookup imaginary unit + var last = inumber.substring(inumber.length - 1, inumber.length); + var unit = last === "i" || last === "j"; + + if (plus >= 0 || minus >= 0) { + // Return error if imaginary unit is neither i nor j + if (!unit) { + return error.num; + } + + // Return imaginary coefficient of complex number + if (plus >= 0) { + return isNaN(inumber.substring(0, plus)) || + isNaN(inumber.substring(plus + 1, inumber.length - 1)) + ? error.num + : Number(inumber.substring(plus + 1, inumber.length - 1)); + } else { + return isNaN(inumber.substring(0, minus)) || + isNaN(inumber.substring(minus + 1, inumber.length - 1)) + ? error.num + : -Number(inumber.substring(minus + 1, inumber.length - 1)); + } + } else { + if (unit) { + return isNaN(inumber.substring(0, inumber.length - 1)) + ? error.num + : inumber.substring(0, inumber.length - 1); + } else { + return isNaN(inumber) ? error.num : 0; + } + } + }; + + exports.IMARGUMENT = function (inumber) { + // Lookup real and imaginary coefficients using exports.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + // Return error if either coefficient is not a number + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Return error if inumber is equal to zero + if (x === 0 && y === 0) { + return error.div0; + } + + // Return PI/2 if x is equal to zero and y is positive + if (x === 0 && y > 0) { + return Math.PI / 2; + } + + // Return -PI/2 if x is equal to zero and y is negative + if (x === 0 && y < 0) { + return -Math.PI / 2; + } + + // Return zero if x is negative and y is equal to zero + if (y === 0 && x > 0) { + return 0; + } + + // Return zero if x is negative and y is equal to zero + if (y === 0 && x < 0) { + return -Math.PI; + } + + // Return argument of complex number + if (x > 0) { + return Math.atan(y / x); + } else if (x < 0 && y >= 0) { + return Math.atan(y / x) + Math.PI; + } else { + return Math.atan(y / x) - Math.PI; + } + }; + + exports.IMCONJUGATE = function (inumber) { + // Lookup real and imaginary coefficients using exports.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Lookup imaginary unit + var unit = inumber.substring(inumber.length - 1); + unit = unit === "i" || unit === "j" ? unit : "i"; + + // Return conjugate of complex number + return y !== 0 ? exports.COMPLEX(x, -y, unit) : inumber; + }; + + exports.IMCOS = function (inumber) { + // Lookup real and imaginary coefficients using exports.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Lookup imaginary unit + var unit = inumber.substring(inumber.length - 1); + unit = unit === "i" || unit === "j" ? unit : "i"; + + // Return cosine of complex number + return exports.COMPLEX( + (Math.cos(x) * (Math.exp(y) + Math.exp(-y))) / 2, + (-Math.sin(x) * (Math.exp(y) - Math.exp(-y))) / 2, + unit + ); + }; + + exports.IMCOSH = function (inumber) { + // Lookup real and imaginary coefficients using exports.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Lookup imaginary unit + var unit = inumber.substring(inumber.length - 1); + unit = unit === "i" || unit === "j" ? unit : "i"; + + // Return hyperbolic cosine of complex number + return exports.COMPLEX( + (Math.cos(y) * (Math.exp(x) + Math.exp(-x))) / 2, + (Math.sin(y) * (Math.exp(x) - Math.exp(-x))) / 2, + unit + ); + }; + + exports.IMCOT = function (inumber) { + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Return cotangent of complex number + return exports.IMDIV(exports.IMCOS(inumber), exports.IMSIN(inumber)); + }; + + exports.IMDIV = function (inumber1, inumber2) { + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var a = exports.IMREAL(inumber1); + var b = exports.IMAGINARY(inumber1); + var c = exports.IMREAL(inumber2); + var d = exports.IMAGINARY(inumber2); + + if (utils.anyIsError(a, b, c, d)) { + return error.value; + } + + // Lookup imaginary unit + var unit1 = inumber1.substring(inumber1.length - 1); + var unit2 = inumber2.substring(inumber2.length - 1); + var unit = "i"; + if (unit1 === "j") { + unit = "j"; + } else if (unit2 === "j") { + unit = "j"; + } + + // Return error if inumber2 is null + if (c === 0 && d === 0) { + return error.num; + } + + // Return exponential of complex number + var den = c * c + d * d; + return exports.COMPLEX( + (a * c + b * d) / den, + (b * c - a * d) / den, + unit + ); + }; + + exports.IMEXP = function (inumber) { + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Lookup imaginary unit + var unit = inumber.substring(inumber.length - 1); + unit = unit === "i" || unit === "j" ? unit : "i"; + + // Return exponential of complex number + var e = Math.exp(x); + return exports.COMPLEX(e * Math.cos(y), e * Math.sin(y), unit); + }; + + exports.IMLN = function (inumber) { + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Lookup imaginary unit + var unit = inumber.substring(inumber.length - 1); + unit = unit === "i" || unit === "j" ? unit : "i"; + + // Return exponential of complex number + return exports.COMPLEX( + Math.log(Math.sqrt(x * x + y * y)), + Math.atan(y / x), + unit + ); + }; + + exports.IMLOG10 = function (inumber) { + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Lookup imaginary unit + var unit = inumber.substring(inumber.length - 1); + unit = unit === "i" || unit === "j" ? unit : "i"; + + // Return exponential of complex number + return exports.COMPLEX( + Math.log(Math.sqrt(x * x + y * y)) / Math.log(10), + Math.atan(y / x) / Math.log(10), + unit + ); + }; + + exports.IMLOG2 = function (inumber) { + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Lookup imaginary unit + var unit = inumber.substring(inumber.length - 1); + unit = unit === "i" || unit === "j" ? unit : "i"; + + // Return exponential of complex number + return exports.COMPLEX( + Math.log(Math.sqrt(x * x + y * y)) / Math.log(2), + Math.atan(y / x) / Math.log(2), + unit + ); + }; + + exports.IMPOWER = function (inumber, number) { + number = utils.parseNumber(number); + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + if (utils.anyIsError(number, x, y)) { + return error.value; + } + + // Lookup imaginary unit + var unit = inumber.substring(inumber.length - 1); + unit = unit === "i" || unit === "j" ? unit : "i"; + + // Calculate power of modulus + var p = Math.pow(exports.IMABS(inumber), number); + + // Calculate argument + var t = exports.IMARGUMENT(inumber); + + // Return exponential of complex number + return exports.COMPLEX( + p * Math.cos(number * t), + p * Math.sin(number * t), + unit + ); + }; + + exports.IMPRODUCT = function () { + // Initialize result + var result = arguments[0]; + + // Loop on all numbers + for (var i = 1; i < arguments.length; i++) { + // Lookup coefficients of two complex numbers + var a = exports.IMREAL(result); + var b = exports.IMAGINARY(result); + var c = exports.IMREAL(arguments[i]); + var d = exports.IMAGINARY(arguments[i]); + + if (utils.anyIsError(a, b, c, d)) { + return error.value; + } + + // Complute product of two complex numbers + result = exports.COMPLEX(a * c - b * d, a * d + b * c); + } + + // Return product of complex numbers + return result; + }; + + exports.IMREAL = function (inumber) { + if (inumber === undefined || inumber === true || inumber === false) { + return error.value; + } + + // Return 0 if inumber is equal to 0 + if (inumber === 0 || inumber === "0") { + return 0; + } + + // Handle special cases + if ( + [ + "i", + "+i", + "1i", + "+1i", + "-i", + "-1i", + "j", + "+j", + "1j", + "+1j", + "-j", + "-1j", + ].indexOf(inumber) >= 0 + ) { + return 0; + } + + // Lookup sign + var plus = inumber.indexOf("+"); + var minus = inumber.indexOf("-"); + if (plus === 0) { + plus = inumber.indexOf("+", 1); + } + if (minus === 0) { + minus = inumber.indexOf("-", 1); + } + + // Lookup imaginary unit + var last = inumber.substring(inumber.length - 1, inumber.length); + var unit = last === "i" || last === "j"; + + if (plus >= 0 || minus >= 0) { + // Return error if imaginary unit is neither i nor j + if (!unit) { + return error.num; + } + + // Return real coefficient of complex number + if (plus >= 0) { + return isNaN(inumber.substring(0, plus)) || + isNaN(inumber.substring(plus + 1, inumber.length - 1)) + ? error.num + : Number(inumber.substring(0, plus)); + } else { + return isNaN(inumber.substring(0, minus)) || + isNaN(inumber.substring(minus + 1, inumber.length - 1)) + ? error.num + : Number(inumber.substring(0, minus)); + } + } else { + if (unit) { + return isNaN(inumber.substring(0, inumber.length - 1)) + ? error.num + : 0; + } else { + return isNaN(inumber) ? error.num : inumber; + } + } + }; + + exports.IMSEC = function (inumber) { + // Return error if inumber is a logical value + if (inumber === true || inumber === false) { + return error.value; + } + + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Return secant of complex number + return exports.IMDIV("1", exports.IMCOS(inumber)); + }; + + exports.IMSECH = function (inumber) { + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Return hyperbolic secant of complex number + return exports.IMDIV("1", exports.IMCOSH(inumber)); + }; + + exports.IMSIN = function (inumber) { + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Lookup imaginary unit + var unit = inumber.substring(inumber.length - 1); + unit = unit === "i" || unit === "j" ? unit : "i"; + + // Return sine of complex number + return exports.COMPLEX( + (Math.sin(x) * (Math.exp(y) + Math.exp(-y))) / 2, + (Math.cos(x) * (Math.exp(y) - Math.exp(-y))) / 2, + unit + ); + }; + + exports.IMSINH = function (inumber) { + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Lookup imaginary unit + var unit = inumber.substring(inumber.length - 1); + unit = unit === "i" || unit === "j" ? unit : "i"; + + // Return hyperbolic sine of complex number + return exports.COMPLEX( + (Math.cos(y) * (Math.exp(x) - Math.exp(-x))) / 2, + (Math.sin(y) * (Math.exp(x) + Math.exp(-x))) / 2, + unit + ); + }; + + exports.IMSQRT = function (inumber) { + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Lookup imaginary unit + var unit = inumber.substring(inumber.length - 1); + unit = unit === "i" || unit === "j" ? unit : "i"; + + // Calculate power of modulus + var s = Math.sqrt(exports.IMABS(inumber)); + + // Calculate argument + var t = exports.IMARGUMENT(inumber); + + // Return exponential of complex number + return exports.COMPLEX(s * Math.cos(t / 2), s * Math.sin(t / 2), unit); + }; + + exports.IMCSC = function (inumber) { + // Return error if inumber is a logical value + if (inumber === true || inumber === false) { + return error.value; + } + + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + // Return error if either coefficient is not a number + if (utils.anyIsError(x, y)) { + return error.num; + } + + // Return cosecant of complex number + return exports.IMDIV("1", exports.IMSIN(inumber)); + }; + + exports.IMCSCH = function (inumber) { + // Return error if inumber is a logical value + if (inumber === true || inumber === false) { + return error.value; + } + + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + // Return error if either coefficient is not a number + if (utils.anyIsError(x, y)) { + return error.num; + } + + // Return hyperbolic cosecant of complex number + return exports.IMDIV("1", exports.IMSINH(inumber)); + }; + + exports.IMSUB = function (inumber1, inumber2) { + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var a = this.IMREAL(inumber1); + var b = this.IMAGINARY(inumber1); + var c = this.IMREAL(inumber2); + var d = this.IMAGINARY(inumber2); + + if (utils.anyIsError(a, b, c, d)) { + return error.value; + } + + // Lookup imaginary unit + var unit1 = inumber1.substring(inumber1.length - 1); + var unit2 = inumber2.substring(inumber2.length - 1); + var unit = "i"; + if (unit1 === "j") { + unit = "j"; + } else if (unit2 === "j") { + unit = "j"; + } + + // Return _ of two complex numbers + return this.COMPLEX(a - c, b - d, unit); + }; + + exports.IMSUM = function () { + var args = utils.flatten(arguments); + + // Initialize result + var result = args[0]; + + // Loop on all numbers + for (var i = 1; i < args.length; i++) { + // Lookup coefficients of two complex numbers + var a = this.IMREAL(result); + var b = this.IMAGINARY(result); + var c = this.IMREAL(args[i]); + var d = this.IMAGINARY(args[i]); + + if (utils.anyIsError(a, b, c, d)) { + return error.value; + } + + // Complute product of two complex numbers + result = this.COMPLEX(a + c, b + d); + } + + // Return sum of complex numbers + return result; + }; + + exports.IMTAN = function (inumber) { + // Return error if inumber is a logical value + if (inumber === true || inumber === false) { + return error.value; + } + + // Lookup real and imaginary coefficients using Formula.js + // [http://formulajs.org] + var x = exports.IMREAL(inumber); + var y = exports.IMAGINARY(inumber); + + if (utils.anyIsError(x, y)) { + return error.value; + } + + // Return tangent of complex number + return this.IMDIV(this.IMSIN(inumber), this.IMCOS(inumber)); + }; + + exports.OCT2BIN = function (number, places) { + // Return error if number is not hexadecimal or contains more than ten + // characters (10 digits) + if (!/^[0-7]{1,10}$/.test(number)) { + return error.num; + } + + // Check if number is negative + var negative = + number.length === 10 && number.substring(0, 1) === "7" ? true : false; + + // Convert octal number to decimal + var decimal = negative + ? parseInt(number, 8) - 1073741824 + : parseInt(number, 8); + + // Return error if number is lower than -512 or greater than 511 + if (decimal < -512 || decimal > 511) { + return error.num; + } + + // Ignore places and return a 10-character binary number if number is + // negative + if (negative) { + return ( + "1" + + REPT("0", 9 - (512 + decimal).toString(2).length) + + (512 + decimal).toString(2) + ); + } + + // Convert decimal number to binary + var result = decimal.toString(2); + + // Return binary number using the minimum number of characters necessary + // if places is undefined + if (typeof places === "undefined") { + return result; + } else { + // Return error if places is nonnumeric + if (isNaN(places)) { + return error.value; + } + + // Return error if places is negative + if (places < 0) { + return error.num; + } + + // Truncate places in case it is not an integer + places = Math.floor(places); + + // Pad return value with leading 0s (zeros) if necessary (using + // Underscore.string) + return places >= result.length + ? REPT("0", places - result.length) + result + : error.num; + } + }; + + exports.OCT2DEC = function (number) { + // Return error if number is not octal or contains more than ten + // characters (10 digits) + if (!/^[0-7]{1,10}$/.test(number)) { + return error.num; + } + + // Convert octal number to decimal + var decimal = parseInt(number, 8); + + // Return decimal number + return decimal >= 536870912 ? decimal - 1073741824 : decimal; + }; + + exports.OCT2HEX = function (number, places) { + // Return error if number is not octal or contains more than ten + // characters (10 digits) + if (!/^[0-7]{1,10}$/.test(number)) { + return error.num; + } + + // Convert octal number to decimal + var decimal = parseInt(number, 8); + + // Ignore places and return a 10-character octal number if number is + // negative + if (decimal >= 536870912) { + return "ff" + (decimal + 3221225472).toString(16); + } + + // Convert decimal number to hexadecimal + var result = decimal.toString(16); + + // Return hexadecimal number using the minimum number of characters + // necessary if places is undefined + if (places === undefined) { + return result; + } else { + // Return error if places is nonnumeric + if (isNaN(places)) { + return error.value; + } + + // Return error if places is negative + if (places < 0) { + return error.num; + } + + // Truncate places in case it is not an integer + places = Math.floor(places); + + // Pad return value with leading 0s (zeros) if necessary (using + // Underscore.string) + return places >= result.length + ? REPT("0", places - result.length) + result + : error.num; + } + }; + + return exports; + })(); + + met.financial = (function () { + var exports = {}; + + function validDate(d) { + return d && d.getTime && !isNaN(d.getTime()); + } + + function ensureDate(d) { + return d instanceof Date ? d : new Date(d); + } + + exports.ACCRINT = function ( + issue, + first, + settlement, + rate, + par, + frequency, + basis + ) { + // Return error if either date is invalid + issue = ensureDate(issue); + first = ensureDate(first); + settlement = ensureDate(settlement); + if (!validDate(issue) || !validDate(first) || !validDate(settlement)) { + return "#VALUE!"; + } + + // Return error if either rate or par are lower than or equal to zero + if (rate <= 0 || par <= 0) { + return "#NUM!"; + } + + // Return error if frequency is neither 1, 2, or 4 + if ([1, 2, 4].indexOf(frequency) === -1) { + return "#NUM!"; + } + + // Return error if basis is neither 0, 1, 2, 3, or 4 + if ([0, 1, 2, 3, 4].indexOf(basis) === -1) { + return "#NUM!"; + } + + // Return error if settlement is before or equal to issue + if (settlement <= issue) { + return "#NUM!"; + } + + // Set default values + par = par || 0; + basis = basis || 0; + + // Compute accrued interest + return par * rate * YEARFRAC(issue, settlement, basis); + }; + + exports.ACCRINTM = null; + + exports.AMORDEGRC = null; + + exports.AMORLINC = null; + + exports.COUPDAYBS = null; + + exports.COUPDAYS = null; + + exports.COUPDAYSNC = null; + + exports.COUPNCD = null; + + exports.COUPNUM = null; + + exports.COUPPCD = null; + + exports.CUMIPMT = function (rate, periods, value, start, end, type) { + // Credits: algorithm inspired by Apache OpenOffice + // Credits: Hannes Stiebitzhofer for the translations of function and + // variable names + // Requires exports.FV() and exports.PMT() from exports.js + // [http://stoic.com/exports/] + + rate = utils.parseNumber(rate); + periods = utils.parseNumber(periods); + value = utils.parseNumber(value); + if (utils.anyIsError(rate, periods, value)) { + return error.value; + } + + // Return error if either rate, periods, or value are lower than or + // equal to zero + if (rate <= 0 || periods <= 0 || value <= 0) { + return error.num; + } + + // Return error if start < 1, end < 1, or start > end + if (start < 1 || end < 1 || start > end) { + return error.num; + } + + // Return error if type is neither 0 nor 1 + if (type !== 0 && type !== 1) { + return error.num; + } + + // Compute cumulative interest + var payment = exports.PMT(rate, periods, value, 0, type); + var interest = 0; + + if (start === 1) { + if (type === 0) { + interest = -value; + start++; + } + } + + for (var i = start; i <= end; i++) { + if (type === 1) { + interest += exports.FV(rate, i - 2, payment, value, 1) - payment; + } else { + interest += exports.FV(rate, i - 1, payment, value, 0); + } + } + interest *= rate; + + // Return cumulative interest + return interest; + }; + + exports.CUMPRINC = function (rate, periods, value, start, end, type) { + // Credits: algorithm inspired by Apache OpenOffice + // Credits: Hannes Stiebitzhofer for the translations of function and + // variable names + + rate = utils.parseNumber(rate); + periods = utils.parseNumber(periods); + value = utils.parseNumber(value); + if (utils.anyIsError(rate, periods, value)) { + return error.value; + } + + // Return error if either rate, periods, or value are lower than or + // equal to zero + if (rate <= 0 || periods <= 0 || value <= 0) { + return error.num; + } + + // Return error if start < 1, end < 1, or start > end + if (start < 1 || end < 1 || start > end) { + return error.num; + } + + // Return error if type is neither 0 nor 1 + if (type !== 0 && type !== 1) { + return error.num; + } + + // Compute cumulative principal + var payment = exports.PMT(rate, periods, value, 0, type); + var principal = 0; + if (start === 1) { + if (type === 0) { + principal = payment + value * rate; + } else { + principal = payment; + } + start++; + } + for (var i = start; i <= end; i++) { + if (type > 0) { + principal += + payment - + (exports.FV(rate, i - 2, payment, value, 1) - payment) * rate; + } else { + principal += + payment - exports.FV(rate, i - 1, payment, value, 0) * rate; + } + } + + // Return cumulative principal + return principal; + }; + + exports.DB = function (cost, salvage, life, period, month) { + // Initialize month + month = month === undefined ? 12 : month; + + cost = utils.parseNumber(cost); + salvage = utils.parseNumber(salvage); + life = utils.parseNumber(life); + period = utils.parseNumber(period); + month = utils.parseNumber(month); + if (utils.anyIsError(cost, salvage, life, period, month)) { + return error.value; + } + + // Return error if any of the parameters is negative + if (cost < 0 || salvage < 0 || life < 0 || period < 0) { + return error.num; + } + + // Return error if month is not an integer between 1 and 12 + if ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].indexOf(month) === -1) { + return error.num; + } + + // Return error if period is greater than life + if (period > life) { + return error.num; + } + + // Return 0 (zero) if salvage is greater than or equal to cost + if (salvage >= cost) { + return 0; + } + + // Rate is rounded to three decimals places + var rate = (1 - Math.pow(salvage / cost, 1 / life)).toFixed(3); + + // Compute initial depreciation + var initial = (cost * rate * month) / 12; + + // Compute total depreciation + var total = initial; + var current = 0; + var ceiling = period === life ? life - 1 : period; + for (var i = 2; i <= ceiling; i++) { + current = (cost - total) * rate; + total += current; + } + + // Depreciation for the first and last periods are special cases + if (period === 1) { + // First period + return initial; + } else if (period === life) { + // Last period + return (cost - total) * rate; + } else { + return current; + } + }; + + exports.DDB = function (cost, salvage, life, period, factor) { + // Initialize factor + factor = factor === undefined ? 2 : factor; + + cost = utils.parseNumber(cost); + salvage = utils.parseNumber(salvage); + life = utils.parseNumber(life); + period = utils.parseNumber(period); + factor = utils.parseNumber(factor); + if (utils.anyIsError(cost, salvage, life, period, factor)) { + return error.value; + } + + // Return error if any of the parameters is negative or if factor is + // null + if (cost < 0 || salvage < 0 || life < 0 || period < 0 || factor <= 0) { + return error.num; + } + + // Return error if period is greater than life + if (period > life) { + return error.num; + } + + // Return 0 (zero) if salvage is greater than or equal to cost + if (salvage >= cost) { + return 0; + } + + // Compute depreciation + var total = 0; + var current = 0; + for (var i = 1; i <= period; i++) { + current = Math.min( + (cost - total) * (factor / life), + cost - salvage - total + ); + total += current; + } + + // Return depreciation + return current; + }; + + exports.DISC = null; + + exports.DOLLARDE = function (dollar, fraction) { + // Credits: algorithm inspired by Apache OpenOffice + + dollar = utils.parseNumber(dollar); + fraction = utils.parseNumber(fraction); + if (utils.anyIsError(dollar, fraction)) { + return error.value; + } + + // Return error if fraction is negative + if (fraction < 0) { + return error.num; + } + + // Return error if fraction is greater than or equal to 0 and less than + // 1 + if (fraction >= 0 && fraction < 1) { + return error.div0; + } + + // Truncate fraction if it is not an integer + fraction = parseInt(fraction, 10); + + // Compute integer part + var result = parseInt(dollar, 10); + + // Add decimal part + result += + ((dollar % 1) * + Math.pow(10, Math.ceil(Math.log(fraction) / Math.LN10))) / + fraction; + + // Round result + var power = Math.pow(10, Math.ceil(Math.log(fraction) / Math.LN2) + 1); + result = Math.round(result * power) / power; + + // Return converted dollar price + return result; + }; + + exports.DOLLARFR = function (dollar, fraction) { + // Credits: algorithm inspired by Apache OpenOffice + + dollar = utils.parseNumber(dollar); + fraction = utils.parseNumber(fraction); + if (utils.anyIsError(dollar, fraction)) { + return error.value; + } + + // Return error if fraction is negative + if (fraction < 0) { + return error.num; + } + + // Return error if fraction is greater than or equal to 0 and less than + // 1 + if (fraction >= 0 && fraction < 1) { + return error.div0; + } + + // Truncate fraction if it is not an integer + fraction = parseInt(fraction, 10); + + // Compute integer part + var result = parseInt(dollar, 10); + + // Add decimal part + result += + (dollar % 1) * + Math.pow(10, -Math.ceil(Math.log(fraction) / Math.LN10)) * + fraction; + + // Return converted dollar price + return result; + }; + + exports.DURATION = null; + + exports.EFFECT = function (rate, periods) { + rate = utils.parseNumber(rate); + periods = utils.parseNumber(periods); + if (utils.anyIsError(rate, periods)) { + return error.value; + } + + // Return error if rate <=0 or periods < 1 + if (rate <= 0 || periods < 1) { + return error.num; + } + + // Truncate periods if it is not an integer + periods = parseInt(periods, 10); + + // Return effective annual interest rate + return Math.pow(1 + rate / periods, periods) - 1; + }; + + exports.FV = function (rate, periods, payment, value, type) { + // Credits: algorithm inspired by Apache OpenOffice + + value = value || 0; + type = type || 0; + + rate = utils.parseNumber(rate); + periods = utils.parseNumber(periods); + payment = utils.parseNumber(payment); + value = utils.parseNumber(value); + type = utils.parseNumber(type); + if (utils.anyIsError(rate, periods, payment, value, type)) { + return error.value; + } + + // Return future value + var result; + if (rate === 0) { + result = value + payment * periods; + } else { + var term = Math.pow(1 + rate, periods); + if (type === 1) { + result = value * term + (payment * (1 + rate) * (term - 1)) / rate; + } else { + result = value * term + (payment * (term - 1)) / rate; + } + } + return -result; + }; + + exports.FVSCHEDULE = function (principal, schedule) { + principal = utils.parseNumber(principal); + schedule = utils.parseNumberArray(utils.flatten(schedule)); + if (utils.anyIsError(principal, schedule)) { + return error.value; + } + + var n = schedule.length; + var future = principal; + + // Apply all interests in schedule + for (var i = 0; i < n; i++) { + // Apply scheduled interest + future *= 1 + schedule[i]; + } + + // Return future value + return future; + }; + + exports.INTRATE = null; + + exports.IPMT = function (rate, period, periods, present, future, type) { + // Credits: algorithm inspired by Apache OpenOffice + + future = future || 0; + type = type || 0; + + rate = utils.parseNumber(rate); + period = utils.parseNumber(period); + periods = utils.parseNumber(periods); + present = utils.parseNumber(present); + future = utils.parseNumber(future); + type = utils.parseNumber(type); + if (utils.anyIsError(rate, period, periods, present, future, type)) { + return error.value; + } + + // Compute payment + var payment = exports.PMT(rate, periods, present, future, type); + + // Compute interest + var interest; + if (period === 1) { + if (type === 1) { + interest = 0; + } else { + interest = -present; + } + } else { + if (type === 1) { + interest = + exports.FV(rate, period - 2, payment, present, 1) - payment; + } else { + interest = exports.FV(rate, period - 1, payment, present, 0); + } + } + + // Return interest + return interest * rate; + }; + + exports.IRR = function (values, guess) { + // Credits: algorithm inspired by Apache OpenOffice + + guess = guess || 0; + + values = utils.parseNumberArray(utils.flatten(values)); + guess = utils.parseNumber(guess); + if (utils.anyIsError(values, guess)) { + return error.value; + } + + // Calculates the resulting amount + var irrResult = function (values, dates, rate) { + var r = rate + 1; + var result = values[0]; + for (var i = 1; i < values.length; i++) { + result += values[i] / Math.pow(r, (dates[i] - dates[0]) / 365); + } + return result; + }; + + // Calculates the first derivation + var irrResultDeriv = function (values, dates, rate) { + var r = rate + 1; + var result = 0; + for (var i = 1; i < values.length; i++) { + var frac = (dates[i] - dates[0]) / 365; + result -= (frac * values[i]) / Math.pow(r, frac + 1); + } + return result; + }; + + // Initialize dates and check that values contains at least one positive + // value and one negative value + var dates = []; + var positive = false; + var negative = false; + for (var i = 0; i < values.length; i++) { + dates[i] = i === 0 ? 0 : dates[i - 1] + 365; + if (values[i] > 0) { + positive = true; + } + if (values[i] < 0) { + negative = true; + } + } + + // Return error if values does not contain at least one positive value + // and one negative value + if (!positive || !negative) { + return error.num; + } + + // Initialize guess and resultRate + guess = guess === undefined ? 0.1 : guess; + var resultRate = guess; + + // Set maximum epsilon for end of iteration + var epsMax = 1e-10; + + // Implement Newton's method + var newRate, epsRate, resultValue; + var contLoop = true; + do { + resultValue = irrResult(values, dates, resultRate); + newRate = + resultRate - resultValue / irrResultDeriv(values, dates, resultRate); + epsRate = Math.abs(newRate - resultRate); + resultRate = newRate; + contLoop = epsRate > epsMax && Math.abs(resultValue) > epsMax; + } while (contLoop); + + // Return internal rate of return + return resultRate; + }; + + exports.ISPMT = function (rate, period, periods, value) { + rate = utils.parseNumber(rate); + period = utils.parseNumber(period); + periods = utils.parseNumber(periods); + value = utils.parseNumber(value); + if (utils.anyIsError(rate, period, periods, value)) { + return error.value; + } + + // Return interest + return value * rate * (period / periods - 1); + }; + + exports.MDURATION = null; + + exports.MIRR = function (values, finance_rate, reinvest_rate) { + values = utils.parseNumberArray(utils.flatten(values)); + finance_rate = utils.parseNumber(finance_rate); + reinvest_rate = utils.parseNumber(reinvest_rate); + if (utils.anyIsError(values, finance_rate, reinvest_rate)) { + return error.value; + } + + // Initialize number of values + var n = values.length; + + // Lookup payments (negative values) and incomes (positive values) + var payments = []; + var incomes = []; + for (var i = 0; i < n; i++) { + if (values[i] < 0) { + payments.push(values[i]); + } else { + incomes.push(values[i]); + } + } + + // Return modified internal rate of return + var num = + -exports.NPV(reinvest_rate, incomes) * + Math.pow(1 + reinvest_rate, n - 1); + var den = exports.NPV(finance_rate, payments) * (1 + finance_rate); + return Math.pow(num / den, 1 / (n - 1)) - 1; + }; + + exports.NOMINAL = function (rate, periods) { + rate = utils.parseNumber(rate); + periods = utils.parseNumber(periods); + if (utils.anyIsError(rate, periods)) { + return error.value; + } + + // Return error if rate <=0 or periods < 1 + if (rate <= 0 || periods < 1) { + return error.num; + } + + // Truncate periods if it is not an integer + periods = parseInt(periods, 10); + + // Return nominal annual interest rate + return (Math.pow(rate + 1, 1 / periods) - 1) * periods; + }; + + exports.NPER = function (rate, payment, present, future, type) { + type = type === undefined ? 0 : type; + future = future === undefined ? 0 : future; + + rate = utils.parseNumber(rate); + payment = utils.parseNumber(payment); + present = utils.parseNumber(present); + future = utils.parseNumber(future); + type = utils.parseNumber(type); + if (utils.anyIsError(rate, payment, present, future, type)) { + return error.value; + } + + // Return number of periods + var num = payment * (1 + rate * type) - future * rate; + var den = present * rate + payment * (1 + rate * type); + return Math.log(num / den) / Math.log(1 + rate); + }; + + exports.NPV = function () { + var args = utils.parseNumberArray(utils.flatten(arguments)); + if (args instanceof Error) { + return args; + } + + // Lookup rate + var rate = args[0]; + + // Initialize net present value + var value = 0; + + // Loop on all values + for (var j = 1; j < args.length; j++) { + value += args[j] / Math.pow(1 + rate, j); + } + + // Return net present value + return value; + }; + + exports.ODDFPRICE = null; + + exports.ODDFYIELD = null; + + exports.ODDLPRICE = null; + + exports.ODDLYIELD = null; + + exports.PDURATION = function (rate, present, future) { + rate = utils.parseNumber(rate); + present = utils.parseNumber(present); + future = utils.parseNumber(future); + if (utils.anyIsError(rate, present, future)) { + return error.value; + } + + // Return error if rate <=0 + if (rate <= 0) { + return error.num; + } + + // Return number of periods + return (Math.log(future) - Math.log(present)) / Math.log(1 + rate); + }; + + exports.PMT = function (rate, periods, present, future, type) { + // Credits: algorithm inspired by Apache OpenOffice + + future = future || 0; + type = type || 0; + + rate = utils.parseNumber(rate); + periods = utils.parseNumber(periods); + present = utils.parseNumber(present); + future = utils.parseNumber(future); + type = utils.parseNumber(type); + if (utils.anyIsError(rate, periods, present, future, type)) { + return error.value; + } + + // Return payment + var result; + if (rate === 0) { + result = (present + future) / periods; + } else { + var term = Math.pow(1 + rate, periods); + if (type === 1) { + result = + ((future * rate) / (term - 1) + (present * rate) / (1 - 1 / term)) / + (1 + rate); + } else { + result = + (future * rate) / (term - 1) + (present * rate) / (1 - 1 / term); + } + } + return -result; + }; + + exports.PPMT = function (rate, period, periods, present, future, type) { + future = future || 0; + type = type || 0; + + rate = utils.parseNumber(rate); + periods = utils.parseNumber(periods); + present = utils.parseNumber(present); + future = utils.parseNumber(future); + type = utils.parseNumber(type); + if (utils.anyIsError(rate, periods, present, future, type)) { + return error.value; + } + + return ( + exports.PMT(rate, periods, present, future, type) - + exports.IPMT(rate, period, periods, present, future, type) + ); + }; + + exports.PRICE = null; + + exports.PRICEDISC = null; + + exports.PRICEMAT = null; + + exports.PV = function (rate, periods, payment, future, type) { + future = future || 0; + type = type || 0; + + rate = utils.parseNumber(rate); + periods = utils.parseNumber(periods); + payment = utils.parseNumber(payment); + future = utils.parseNumber(future); + type = utils.parseNumber(type); + if (utils.anyIsError(rate, periods, payment, future, type)) { + return error.value; + } + + // Return present value + if (rate === 0) { + return -payment * periods - future; + } else { + return ( + (((1 - Math.pow(1 + rate, periods)) / rate) * + payment * + (1 + rate * type) - + future) / + Math.pow(1 + rate, periods) + ); + } + }; + + exports.RATE = function (periods, payment, present, future, type, guess) { + // Credits: rabugento + + guess = guess === undefined ? 0.01 : guess; + future = future === undefined ? 0 : future; + type = type === undefined ? 0 : type; + + periods = utils.parseNumber(periods); + payment = utils.parseNumber(payment); + present = utils.parseNumber(present); + future = utils.parseNumber(future); + type = utils.parseNumber(type); + guess = utils.parseNumber(guess); + if (utils.anyIsError(periods, payment, present, future, type, guess)) { + return error.value; + } + + // Set maximum epsilon for end of iteration + var epsMax = 1e-6; + + // Set maximum number of iterations + var iterMax = 100; + var iter = 0; + var close = false; + var rate = guess; + + while (iter < iterMax && !close) { + var t1 = Math.pow(rate + 1, periods); + var t2 = Math.pow(rate + 1, periods - 1); + + var f1 = + future + + t1 * present + + (payment * (t1 - 1) * (rate * type + 1)) / rate; + var f2 = + periods * t2 * present - + (payment * (t1 - 1) * (rate * type + 1)) / Math.pow(rate, 2); + var f3 = + (periods * payment * t2 * (rate * type + 1)) / rate + + (payment * (t1 - 1) * type) / rate; + + var newRate = rate - f1 / (f2 + f3); + + if (Math.abs(newRate - rate) < epsMax) close = true; + iter++; + rate = newRate; + } + + if (!close) return Number.NaN + rate; + return rate; + }; + + // TODO + exports.RECEIVED = null; + + exports.RRI = function (periods, present, future) { + periods = utils.parseNumber(periods); + present = utils.parseNumber(present); + future = utils.parseNumber(future); + if (utils.anyIsError(periods, present, future)) { + return error.value; + } + + // Return error if periods or present is equal to 0 (zero) + if (periods === 0 || present === 0) { + return error.num; + } + + // Return equivalent interest rate + return Math.pow(future / present, 1 / periods) - 1; + }; + + exports.SLN = function (cost, salvage, life) { + cost = utils.parseNumber(cost); + salvage = utils.parseNumber(salvage); + life = utils.parseNumber(life); + if (utils.anyIsError(cost, salvage, life)) { + return error.value; + } + + // Return error if life equal to 0 (zero) + if (life === 0) { + return error.num; + } + + // Return straight-line depreciation + return (cost - salvage) / life; + }; + + exports.SYD = function (cost, salvage, life, period) { + // Return error if any of the parameters is not a number + cost = utils.parseNumber(cost); + salvage = utils.parseNumber(salvage); + life = utils.parseNumber(life); + period = utils.parseNumber(period); + if (utils.anyIsError(cost, salvage, life, period)) { + return error.value; + } + + // Return error if life equal to 0 (zero) + if (life === 0) { + return error.num; + } + + // Return error if period is lower than 1 or greater than life + if (period < 1 || period > life) { + return error.num; + } + + // Truncate period if it is not an integer + period = parseInt(period, 10); + + // Return straight-line depreciation + return ((cost - salvage) * (life - period + 1) * 2) / (life * (life + 1)); + }; + + exports.TBILLEQ = function (settlement, maturity, discount) { + settlement = utils.parseDate(settlement); + maturity = utils.parseDate(maturity); + discount = utils.parseNumber(discount); + if (utils.anyIsError(settlement, maturity, discount)) { + return error.value; + } + + // Return error if discount is lower than or equal to zero + if (discount <= 0) { + return error.num; + } + + // Return error if settlement is greater than maturity + if (settlement > maturity) { + return error.num; + } + + // Return error if maturity is more than one year after settlement + if (maturity - settlement > 365 * 24 * 60 * 60 * 1000) { + return error.num; + } + + // Return bond-equivalent yield + return ( + (365 * discount) / + (360 - discount * DAYS360(settlement, maturity, false)) + ); + }; + + exports.TBILLPRICE = function (settlement, maturity, discount) { + settlement = utils.parseDate(settlement); + maturity = utils.parseDate(maturity); + discount = utils.parseNumber(discount); + if (utils.anyIsError(settlement, maturity, discount)) { + return error.value; + } + + // Return error if discount is lower than or equal to zero + if (discount <= 0) { + return error.num; + } + + // Return error if settlement is greater than maturity + if (settlement > maturity) { + return error.num; + } + + // Return error if maturity is more than one year after settlement + if (maturity - settlement > 365 * 24 * 60 * 60 * 1000) { + return error.num; + } + + // Return bond-equivalent yield + return ( + 100 * (1 - (discount * DAYS360(settlement, maturity, false)) / 360) + ); + }; + + exports.TBILLYIELD = function (settlement, maturity, price) { + settlement = utils.parseDate(settlement); + maturity = utils.parseDate(maturity); + price = utils.parseNumber(price); + if (utils.anyIsError(settlement, maturity, price)) { + return error.value; + } + + // Return error if price is lower than or equal to zero + if (price <= 0) { + return error.num; + } + + // Return error if settlement is greater than maturity + if (settlement > maturity) { + return error.num; + } + + // Return error if maturity is more than one year after settlement + if (maturity - settlement > 365 * 24 * 60 * 60 * 1000) { + return error.num; + } + + // Return bond-equivalent yield + return ( + ((100 - price) * 360) / (price * DAYS360(settlement, maturity, false)) + ); + }; + + exports.VDB = null; + + exports.XIRR = function (values, dates, guess) { + // Credits: algorithm inspired by Apache OpenOffice + + values = utils.parseNumberArray(utils.flatten(values)); + dates = utils.parseDateArray(utils.flatten(dates)); + guess = utils.parseNumber(guess); + if (utils.anyIsError(values, dates, guess)) { + return error.value; + } + + // Calculates the resulting amount + var irrResult = function (values, dates, rate) { + var r = rate + 1; + var result = values[0]; + for (var i = 1; i < values.length; i++) { + result += values[i] / Math.pow(r, DAYS(dates[i], dates[0]) / 365); + } + return result; + }; + + // Calculates the first derivation + var irrResultDeriv = function (values, dates, rate) { + var r = rate + 1; + var result = 0; + for (var i = 1; i < values.length; i++) { + var frac = DAYS(dates[i], dates[0]) / 365; + result -= (frac * values[i]) / Math.pow(r, frac + 1); + } + return result; + }; + + // Check that values contains at least one positive value and one + // negative value + var positive = false; + var negative = false; + for (var i = 0; i < values.length; i++) { + if (values[i] > 0) { + positive = true; + } + if (values[i] < 0) { + negative = true; + } + } + + // Return error if values does not contain at least one positive value + // and one negative value + if (!positive || !negative) { + return error.num; + } + + // Initialize guess and resultRate + guess = guess || 0.1; + var resultRate = guess; + + // Set maximum epsilon for end of iteration + var epsMax = 1e-10; + + // Implement Newton's method + var newRate, epsRate, resultValue; + var contLoop = true; + do { + resultValue = irrResult(values, dates, resultRate); + newRate = + resultRate - resultValue / irrResultDeriv(values, dates, resultRate); + epsRate = Math.abs(newRate - resultRate); + resultRate = newRate; + contLoop = epsRate > epsMax && Math.abs(resultValue) > epsMax; + } while (contLoop); + + // Return internal rate of return + return resultRate; + }; + + exports.XNPV = function (rate, values, dates) { + rate = utils.parseNumber(rate); + values = utils.parseNumberArray(utils.flatten(values)); + dates = utils.parseDateArray(utils.flatten(dates)); + if (utils.anyIsError(rate, values, dates)) { + return error.value; + } + + var result = 0; + for (var i = 0; i < values.length; i++) { + result += + values[i] / Math.pow(1 + rate, DAYS(dates[i], dates[0]) / 365); + } + return result; + }; + + exports.YIELD = null; + + exports.YIELDDISC = null; + + exports.YIELDMAT = null; + + return exports; + })(); + + met.information = (function () { + var exports = {}; + exports.CELL = null; + + exports.ERROR = {}; + exports.ERROR.TYPE = function (error_val) { + switch (error_val) { + case error.nil: + return 1; + case error.div0: + return 2; + case error.value: + return 3; + case error.ref: + return 4; + case error.name: + return 5; + case error.num: + return 6; + case error.na: + return 7; + case error.data: + return 8; + } + return error.na; + }; + + exports.INFO = null; + + exports.ISBLANK = function (value) { + return value === null; + }; + + exports.ISBINARY = function (number) { + return /^[01]{1,10}$/.test(number); + }; + + exports.ISERR = function (value) { + return ( + [ + error.value, + error.ref, + error.div0, + error.num, + error.name, + error.nil, + ].indexOf(value) >= 0 || + (typeof value === "number" && (isNaN(value) || !isFinite(value))) + ); + }; + + exports.ISERROR = function (value) { + return exports.ISERR(value) || value === error.na; + }; + + exports.ISEVEN = function (number) { + return Math.floor(Math.abs(number)) & 1 ? false : true; + }; + + // TODO + exports.ISFORMULA = null; + + exports.ISLOGICAL = function (value) { + return value === true || value === false; + }; + + exports.ISNA = function (value) { + return value === error.na; + }; + + exports.ISNONTEXT = function (value) { + return typeof value !== "string"; + }; + + exports.ISNUMBER = function (value) { + return typeof value === "number" && !isNaN(value) && isFinite(value); + }; + + exports.ISODD = function (number) { + return Math.floor(Math.abs(number)) & 1 ? true : false; + }; + + exports.ISREF = null; + + exports.ISTEXT = function (value) { + return typeof value === "string"; + }; + + exports.N = function (value) { + if (this.ISNUMBER(value)) { + return value; + } + if (value instanceof Date) { + return value.getTime(); + } + if (value === true) { + return 1; + } + if (value === false) { + return 0; + } + if (this.ISERROR(value)) { + return value; + } + return 0; + }; + + exports.NA = function () { + return error.na; + }; + + exports.SHEET = null; + + exports.SHEETS = null; + + exports.TYPE = function (value) { + if (this.ISNUMBER(value)) { + return 1; + } + if (this.ISTEXT(value)) { + return 2; + } + if (this.ISLOGICAL(value)) { + return 4; + } + if (this.ISERROR(value)) { + return 16; + } + if (Array.isArray(value)) { + return 64; + } + }; + + return exports; + })(); + + met.logical = (function () { + var exports = {}; + + exports.AND = function () { + var args = utils.flatten(arguments); + var result = true; + for (var i = 0; i < args.length; i++) { + if (!args[i]) { + result = false; + } + } + return result; + }; + + exports.CHOOSE = function () { + if (arguments.length < 2) { + return error.na; + } + + var index = arguments[0]; + if (index < 1 || index > 254) { + return error.value; + } + + if (arguments.length < index + 1) { + return error.value; + } + + return arguments[index]; + }; + + exports.FALSE = function () { + return false; + }; + + exports.IF = function (test, then_value, otherwise_value) { + return test ? then_value : otherwise_value; + }; + + exports.IFERROR = function (value, valueIfError) { + if (ISERROR(value)) { + return valueIfError; + } + return value; + }; + + exports.IFNA = function (value, value_if_na) { + return value === error.na ? value_if_na : value; + }; + + exports.NOT = function (logical) { + return !logical; + }; + + exports.OR = function () { + var args = utils.flatten(arguments); + var result = false; + for (var i = 0; i < args.length; i++) { + if (args[i]) { + result = true; + } + } + return result; + }; + + exports.TRUE = function () { + return true; + }; + + exports.XOR = function () { + var args = utils.flatten(arguments); + var result = 0; + for (var i = 0; i < args.length; i++) { + if (args[i]) { + result++; + } + } + return Math.floor(Math.abs(result)) & 1 ? true : false; + }; + + exports.SWITCH = function () { + var result; + if (arguments.length > 0) { + var targetValue = arguments[0]; + var argc = arguments.length - 1; + var switchCount = Math.floor(argc / 2); + var switchSatisfied = false; + var defaultClause = + argc % 2 === 0 ? null : arguments[arguments.length - 1]; + + if (switchCount) { + for (var index = 0; index < switchCount; index++) { + if (targetValue === arguments[index * 2 + 1]) { + result = arguments[index * 2 + 2]; + switchSatisfied = true; + break; + } + } + } + + if (!switchSatisfied && defaultClause) { + result = defaultClause; + } + } + + return result; + }; + + return exports; + })(); + + met.math = (function () { + var exports = {}; + + exports.ABS = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.abs(utils.parseNumber(number)); + }; + + exports.ACOS = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.acos(number); + }; + + exports.ACOSH = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.log(number + Math.sqrt(number * number - 1)); + }; + + exports.ACOT = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.atan(1 / number); + }; + + exports.ACOTH = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return 0.5 * Math.log((number + 1) / (number - 1)); + }; + + exports.AGGREGATE = null; + + exports.ARABIC = function (text) { + // Credits: Rafa? Kukawski + if ( + !/^M*(?:D?C{0,3}|C[MD])(?:L?X{0,3}|X[CL])(?:V?I{0,3}|I[XV])$/.test(text) + ) { + return error.value; + } + var r = 0; + text.replace(/[MDLV]|C[MD]?|X[CL]?|I[XV]?/g, function (i) { + r += { + M: 1000, + CM: 900, + D: 500, + CD: 400, + C: 100, + XC: 90, + L: 50, + XL: 40, + X: 10, + IX: 9, + V: 5, + IV: 4, + I: 1, + }[i]; + }); + return r; + }; + + exports.ASIN = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.asin(number); + }; + + exports.ASINH = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.log(number + Math.sqrt(number * number + 1)); + }; + + exports.ATAN = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.atan(number); + }; + + exports.ATAN2 = function (number_x, number_y) { + number_x = utils.parseNumber(number_x); + number_y = utils.parseNumber(number_y); + if (utils.anyIsError(number_x, number_y)) { + return error.value; + } + return Math.atan2(number_x, number_y); + }; + + exports.ATANH = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.log((1 + number) / (1 - number)) / 2; + }; + + exports.BASE = function (number, radix, min_length) { + min_length = min_length || 0; + + number = utils.parseNumber(number); + radix = utils.parseNumber(radix); + min_length = utils.parseNumber(min_length); + if (utils.anyIsError(number, radix, min_length)) { + return error.value; + } + min_length = min_length === undefined ? 0 : min_length; + var result = number.toString(radix); + return ( + new Array(Math.max(min_length + 1 - result.length, 0)).join("0") + + result + ); + }; + + exports.CEILING = function (number, significance, mode) { + significance = significance === undefined ? 1 : significance; + mode = mode === undefined ? 0 : mode; + + number = utils.parseNumber(number); + significance = utils.parseNumber(significance); + mode = utils.parseNumber(mode); + if (utils.anyIsError(number, significance, mode)) { + return error.value; + } + if (significance === 0) { + return 0; + } + + significance = Math.abs(significance); + if (number >= 0) { + return Math.ceil(number / significance) * significance; + } else { + if (mode === 0) { + return ( + -1 * Math.floor(Math.abs(number) / significance) * significance + ); + } else { + return -1 * Math.ceil(Math.abs(number) / significance) * significance; + } + } + }; + + exports.CEILING.MATH = exports.CEILING; + + exports.CEILING.PRECISE = exports.CEILING; + + exports.COMBIN = function (number, number_chosen) { + number = utils.parseNumber(number); + number_chosen = utils.parseNumber(number_chosen); + if (utils.anyIsError(number, number_chosen)) { + return error.value; + } + return ( + exports.FACT(number) / + (exports.FACT(number_chosen) * exports.FACT(number - number_chosen)) + ); + }; + + exports.COMBINA = function (number, number_chosen) { + number = utils.parseNumber(number); + number_chosen = utils.parseNumber(number_chosen); + if (utils.anyIsError(number, number_chosen)) { + return error.value; + } + return number === 0 && number_chosen === 0 + ? 1 + : exports.COMBIN(number + number_chosen - 1, number - 1); + }; + + exports.COS = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.cos(number); + }; + + exports.COSH = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return (Math.exp(number) + Math.exp(-number)) / 2; + }; + + exports.COT = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return 1 / Math.tan(number); + }; + + exports.COTH = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + var e2 = Math.exp(2 * number); + return (e2 + 1) / (e2 - 1); + }; + + exports.CSC = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return 1 / Math.sin(number); + }; + + exports.CSCH = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return 2 / (Math.exp(number) - Math.exp(-number)); + }; + + exports.DECIMAL = function (number, radix) { + if (arguments.length < 1) { + return error.value; + } + + return parseInt(number, radix); + }; + + exports.DEGREES = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return (number * 180) / Math.PI; + }; + + exports.EVEN = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return exports.CEILING(number, -2, -1); + }; + + exports.EXP = Math.exp; + + var MEMOIZED_FACT = []; + exports.FACT = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + var n = Math.floor(number); + if (n === 0 || n === 1) { + return 1; + } else if (MEMOIZED_FACT[n] > 0) { + return MEMOIZED_FACT[n]; + } else { + MEMOIZED_FACT[n] = exports.FACT(n - 1) * n; + return MEMOIZED_FACT[n]; + } + }; + + exports.FACTDOUBLE = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + var n = Math.floor(number); + if (n <= 0) { + return 1; + } else { + return n * exports.FACTDOUBLE(n - 2); + } + }; + + exports.FLOOR = function (number, significance, mode) { + significance = significance === undefined ? 1 : significance; + mode = mode === undefined ? 0 : mode; + + number = utils.parseNumber(number); + significance = utils.parseNumber(significance); + mode = utils.parseNumber(mode); + if (utils.anyIsError(number, significance, mode)) { + return error.value; + } + if (significance === 0) { + return 0; + } + + significance = Math.abs(significance); + if (number >= 0) { + return Math.floor(number / significance) * significance; + } else { + if (mode === 0) { + return -1 * Math.ceil(Math.abs(number) / significance) * significance; + } else { + return ( + -1 * Math.floor(Math.abs(number) / significance) * significance + ); + } + } + }; + + exports.FLOOR.MATH = exports.FLOOR; + + exports.GCD = null; + + exports.INT = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.floor(number); + }; + + exports.LCM = function () { + // Credits: Jonas Raoni Soares Silva + var o = utils.parseNumberArray(utils.flatten(arguments)); + if (o instanceof Error) { + return o; + } + for (var i, j, n, d, r = 1; (n = o.pop()) !== undefined; ) { + while (n > 1) { + if (n % 2) { + for (i = 3, j = Math.floor(Math.sqrt(n)); i <= j && n % i; i += 2) { + // empty + } + d = i <= j ? i : n; + } else { + d = 2; + } + for ( + n /= d, r *= d, i = o.length; + i; + o[--i] % d === 0 && (o[i] /= d) === 1 && o.splice(i, 1) + ) { + // empty + } + } + } + return r; + }; + + exports.LN = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.log(number); + }; + + exports.LOG = function (number, base) { + number = utils.parseNumber(number); + base = base === undefined ? 10 : utils.parseNumber(base); + + if (utils.anyIsError(number, base)) { + return error.value; + } + + return Math.log(number) / Math.log(base); + }; + + exports.LOG10 = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.log(number) / Math.log(10); + }; + + exports.MDETERM = null; + + exports.MINVERSE = null; + + exports.MMULT = null; + + exports.MOD = function (dividend, divisor) { + dividend = utils.parseNumber(dividend); + divisor = utils.parseNumber(divisor); + if (utils.anyIsError(dividend, divisor)) { + return error.value; + } + if (divisor === 0) { + return error.div0; + } + var modulus = Math.abs(dividend % divisor); + return divisor > 0 ? modulus : -modulus; + }; + + exports.MROUND = function (number, multiple) { + number = utils.parseNumber(number); + multiple = utils.parseNumber(multiple); + if (utils.anyIsError(number, multiple)) { + return error.value; + } + if (number * multiple < 0) { + return error.num; + } + + return Math.round(number / multiple) * multiple; + }; + + exports.MULTINOMIAL = function () { + var args = utils.parseNumberArray(utils.flatten(arguments)); + if (args instanceof Error) { + return args; + } + var sum = 0; + var divisor = 1; + for (var i = 0; i < args.length; i++) { + sum += args[i]; + divisor *= exports.FACT(args[i]); + } + return exports.FACT(sum) / divisor; + }; + + exports.MUNIT = null; + + exports.ODD = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + var temp = Math.ceil(Math.abs(number)); + temp = temp & 1 ? temp : temp + 1; + return number > 0 ? temp : -temp; + }; + + exports.PI = function () { + return Math.PI; + }; + + exports.POWER = function (number, power) { + number = utils.parseNumber(number); + power = utils.parseNumber(power); + if (utils.anyIsError(number, power)) { + return error.value; + } + var result = Math.pow(number, power); + if (isNaN(result)) { + return error.num; + } + + return result; + }; + + exports.PRODUCT = function () { + var args = utils.parseNumberArray(utils.flatten(arguments)); + if (args instanceof Error) { + return args; + } + var result = 1; + for (var i = 0; i < args.length; i++) { + result *= args[i]; + } + return result; + }; + + exports.QUOTIENT = function (numerator, denominator) { + numerator = utils.parseNumber(numerator); + denominator = utils.parseNumber(denominator); + if (utils.anyIsError(numerator, denominator)) { + return error.value; + } + return parseInt(numerator / denominator, 10); + }; + + exports.RADIANS = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return (number * Math.PI) / 180; + }; + + exports.RAND = function () { + return Math.random(); + }; + + exports.RANDBETWEEN = function (bottom, top) { + bottom = utils.parseNumber(bottom); + top = utils.parseNumber(top); + if (utils.anyIsError(bottom, top)) { + return error.value; + } + // Creative Commons Attribution 3.0 License + // Copyright (c) 2012 eqcode + return bottom + Math.ceil((top - bottom + 1) * Math.random()) - 1; + }; + + exports.ROMAN = null; + + exports.ROUND = function (number, digits) { + number = utils.parseNumber(number); + digits = utils.parseNumber(digits); + if (utils.anyIsError(number, digits)) { + return error.value; + } + return Math.round(number * Math.pow(10, digits)) / Math.pow(10, digits); + }; + + exports.ROUNDDOWN = function (number, digits) { + number = utils.parseNumber(number); + digits = utils.parseNumber(digits); + if (utils.anyIsError(number, digits)) { + return error.value; + } + var sign = number > 0 ? 1 : -1; + return ( + (sign * Math.floor(Math.abs(number) * Math.pow(10, digits))) / + Math.pow(10, digits) + ); + }; + + exports.ROUNDUP = function (number, digits) { + number = utils.parseNumber(number); + digits = utils.parseNumber(digits); + if (utils.anyIsError(number, digits)) { + return error.value; + } + var sign = number > 0 ? 1 : -1; + return ( + (sign * Math.ceil(Math.abs(number) * Math.pow(10, digits))) / + Math.pow(10, digits) + ); + }; + + exports.SEC = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return 1 / Math.cos(number); + }; + + exports.SECH = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return 2 / (Math.exp(number) + Math.exp(-number)); + }; + + exports.SERIESSUM = function (x, n, m, coefficients) { + x = utils.parseNumber(x); + n = utils.parseNumber(n); + m = utils.parseNumber(m); + coefficients = utils.parseNumberArray(coefficients); + if (utils.anyIsError(x, n, m, coefficients)) { + return error.value; + } + var result = coefficients[0] * Math.pow(x, n); + for (var i = 1; i < coefficients.length; i++) { + result += coefficients[i] * Math.pow(x, n + i * m); + } + return result; + }; + + exports.SIGN = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + if (number < 0) { + return -1; + } else if (number === 0) { + return 0; + } else { + return 1; + } + }; + + exports.SIN = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.sin(number); + }; + + exports.SINH = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return (Math.exp(number) - Math.exp(-number)) / 2; + }; + + exports.SQRT = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + if (number < 0) { + return error.num; + } + return Math.sqrt(number); + }; + + exports.SQRTPI = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.sqrt(number * Math.PI); + }; + + exports.SUBTOTAL = null; + + exports.ADD = function (num1, num2) { + if (arguments.length !== 2) { + return error.na; + } + + num1 = utils.parseNumber(num1); + num2 = utils.parseNumber(num2); + if (utils.anyIsError(num1, num2)) { + return error.value; + } + + return num1 + num2; + }; + + exports.MINUS = function (num1, num2) { + if (arguments.length !== 2) { + return error.na; + } + + num1 = utils.parseNumber(num1); + num2 = utils.parseNumber(num2); + if (utils.anyIsError(num1, num2)) { + return error.value; + } + + return num1 - num2; + }; + + exports.DIVIDE = function (dividend, divisor) { + if (arguments.length !== 2) { + return error.na; + } + + dividend = utils.parseNumber(dividend); + divisor = utils.parseNumber(divisor); + if (utils.anyIsError(dividend, divisor)) { + return error.value; + } + + if (divisor === 0) { + return error.div0; + } + + return dividend / divisor; + }; + + exports.MULTIPLY = function (factor1, factor2) { + if (arguments.length !== 2) { + return error.na; + } + + factor1 = utils.parseNumber(factor1); + factor2 = utils.parseNumber(factor2); + if (utils.anyIsError(factor1, factor2)) { + return error.value; + } + + return factor1 * factor2; + }; + + exports.GTE = function (num1, num2) { + if (arguments.length !== 2) { + return error.na; + } + + num1 = utils.parseNumber(num1); + num2 = utils.parseNumber(num2); + if (utils.anyIsError(num1, num2)) { + return error.error; + } + + return num1 >= num2; + }; + + exports.LT = function (num1, num2) { + if (arguments.length !== 2) { + return error.na; + } + + num1 = utils.parseNumber(num1); + num2 = utils.parseNumber(num2); + if (utils.anyIsError(num1, num2)) { + return error.error; + } + + return num1 < num2; + }; + + exports.LTE = function (num1, num2) { + if (arguments.length !== 2) { + return error.na; + } + + num1 = utils.parseNumber(num1); + num2 = utils.parseNumber(num2); + if (utils.anyIsError(num1, num2)) { + return error.error; + } + + return num1 <= num2; + }; + + exports.EQ = function (value1, value2) { + if (arguments.length !== 2) { + return error.na; + } + + return value1 === value2; + }; + + exports.NE = function (value1, value2) { + if (arguments.length !== 2) { + return error.na; + } + + return value1 !== value2; + }; + + exports.POW = function (base, exponent) { + if (arguments.length !== 2) { + return error.na; + } + + base = utils.parseNumber(base); + exponent = utils.parseNumber(exponent); + if (utils.anyIsError(base, exponent)) { + return error.error; + } + + return exports.POWER(base, exponent); + }; + + exports.SUM = function () { + var result = 0; + var argsKeys = Object.keys(arguments); + for (var i = 0; i < argsKeys.length; ++i) { + var elt = arguments[argsKeys[i]]; + if (typeof elt === "number") { + result += elt; + } else if (typeof elt === "string") { + var parsed = parseFloat(elt); + !isNaN(parsed) && (result += parsed); + } else if (Array.isArray(elt)) { + result += exports.SUM.apply(null, elt); + } + } + return result; + }; + + exports.SUMIF = function () { + var args = utils.argsToArray(arguments); + var criteria = args.pop(); + var range = utils.parseNumberArray(utils.flatten(args)); + if (range instanceof Error) { + return range; + } + var result = 0; + for (var i = 0; i < range.length; i++) { + result += eval(range[i] + criteria) ? range[i] : 0; // jshint ignore:line + } + return result; + }; + + exports.SUMIFS = function () { + var args = utils.argsToArray(arguments); + var range = utils.parseNumberArray(utils.flatten(args.shift())); + if (range instanceof Error) { + return range; + } + var criteria = args; + + var n_range_elements = range.length; + var n_criterias = criteria.length; + + var result = 0; + + for (var i = 0; i < n_range_elements; i++) { + var el = range[i]; + var condition = ""; + for (var c = 0; c < n_criterias; c += 2) { + if (isNaN(criteria[c][i])) { + condition += '"' + criteria[c][i] + '"' + criteria[c + 1]; + } else { + condition += criteria[c][i] + criteria[c + 1]; + } + if (c !== n_criterias - 1) { + condition += " && "; + } + } + condition = condition.slice(0, -4); + if (eval(condition)) { + // jshint ignore:line + result += el; + } + } + return result; + }; + + exports.SUMPRODUCT = null; + + exports.SUMSQ = function () { + var numbers = utils.parseNumberArray(utils.flatten(arguments)); + if (numbers instanceof Error) { + return numbers; + } + var result = 0; + var length = numbers.length; + for (var i = 0; i < length; i++) { + result += ISNUMBER(numbers[i]) ? numbers[i] * numbers[i] : 0; + } + return result; + }; + + exports.SUMX2MY2 = function (array_x, array_y) { + array_x = utils.parseNumberArray(utils.flatten(array_x)); + array_y = utils.parseNumberArray(utils.flatten(array_y)); + if (utils.anyIsError(array_x, array_y)) { + return error.value; + } + var result = 0; + for (var i = 0; i < array_x.length; i++) { + result += array_x[i] * array_x[i] - array_y[i] * array_y[i]; + } + return result; + }; + + exports.SUMX2PY2 = function (array_x, array_y) { + array_x = utils.parseNumberArray(utils.flatten(array_x)); + array_y = utils.parseNumberArray(utils.flatten(array_y)); + if (utils.anyIsError(array_x, array_y)) { + return error.value; + } + var result = 0; + array_x = utils.parseNumberArray(utils.flatten(array_x)); + array_y = utils.parseNumberArray(utils.flatten(array_y)); + for (var i = 0; i < array_x.length; i++) { + result += array_x[i] * array_x[i] + array_y[i] * array_y[i]; + } + return result; + }; + + exports.SUMXMY2 = function (array_x, array_y) { + array_x = utils.parseNumberArray(utils.flatten(array_x)); + array_y = utils.parseNumberArray(utils.flatten(array_y)); + if (utils.anyIsError(array_x, array_y)) { + return error.value; + } + var result = 0; + array_x = utils.flatten(array_x); + array_y = utils.flatten(array_y); + for (var i = 0; i < array_x.length; i++) { + result += Math.pow(array_x[i] - array_y[i], 2); + } + return result; + }; + + exports.TAN = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return Math.tan(number); + }; + + exports.TANH = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + var e2 = Math.exp(2 * number); + return (e2 - 1) / (e2 + 1); + }; + + exports.TRUNC = function (number, digits) { + digits = digits === undefined ? 0 : digits; + number = utils.parseNumber(number); + digits = utils.parseNumber(digits); + if (utils.anyIsError(number, digits)) { + return error.value; + } + var sign = number > 0 ? 1 : -1; + return ( + (sign * Math.floor(Math.abs(number) * Math.pow(10, digits))) / + Math.pow(10, digits) + ); + }; + + return exports; + })(); + + met.misc = (function () { + var exports = {}; + + exports.UNIQUE = function () { + var result = []; + for (var i = 0; i < arguments.length; ++i) { + var hasElement = false; + var element = arguments[i]; + + // Check if we've already seen this element. + for (var j = 0; j < result.length; ++j) { + hasElement = result[j] === element; + if (hasElement) { + break; + } + } + + // If we did not find it, add it to the result. + if (!hasElement) { + result.push(element); + } + } + return result; + }; + + exports.FLATTEN = utils.flatten; + + exports.ARGS2ARRAY = function () { + return Array.prototype.slice.call(arguments, 0); + }; + + exports.REFERENCE = function (context, reference) { + try { + var path = reference.split("."); + var result = context; + for (var i = 0; i < path.length; ++i) { + var step = path[i]; + if (step[step.length - 1] === "]") { + var opening = step.indexOf("["); + var index = step.substring(opening + 1, step.length - 1); + result = result[step.substring(0, opening)][index]; + } else { + result = result[step]; + } + } + return result; + } catch (error) {} + }; + + exports.JOIN = function (array, separator) { + return array.join(separator); + }; + + exports.NUMBERS = function () { + var possibleNumbers = utils.flatten(arguments); + return possibleNumbers.filter(function (el) { + return typeof el === "number"; + }); + }; + + exports.NUMERAL = null; + + return exports; + })(); + + met.text = (function () { + var exports = {}; + + exports.ASC = null; + + exports.BAHTTEXT = null; + + exports.CHAR = function (number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return String.fromCharCode(number); + }; + + exports.CLEAN = function (text) { + text = text || ""; + var re = /[\0-\x1F]/g; + return text.replace(re, ""); + }; + + exports.CODE = function (text) { + text = text || ""; + return text.charCodeAt(0); + }; + + exports.CONCATENATE = function () { + var args = utils.flatten(arguments); + + var trueFound = 0; + while ((trueFound = args.indexOf(true)) > -1) { + args[trueFound] = "TRUE"; + } + + var falseFound = 0; + while ((falseFound = args.indexOf(false)) > -1) { + args[falseFound] = "FALSE"; + } + + return args.join(""); + }; + + exports.DBCS = null; + + exports.DOLLAR = null; + + exports.EXACT = function (text1, text2) { + return text1 === text2; + }; + + exports.FIND = function (find_text, within_text, position) { + position = position === undefined ? 0 : position; + return within_text + ? within_text.indexOf(find_text, position - 1) + 1 + : null; + }; + + exports.FIXED = null; + + exports.HTML2TEXT = function (value) { + var result = ""; + + if (value) { + if (value instanceof Array) { + value.forEach(function (line) { + if (result !== "") { + result += "\n"; + } + result += line.replace(/<(?:.|\n)*?>/gm, ""); + }); + } else { + result = value.replace(/<(?:.|\n)*?>/gm, ""); + } + } + + return result; + }; + + exports.LEFT = function (text, number) { + number = number === undefined ? 1 : number; + number = utils.parseNumber(number); + if (number instanceof Error || typeof text !== "string") { + return error.value; + } + return text ? text.substring(0, number) : null; + }; + + exports.LEN = function (text) { + if (arguments.length === 0) { + return error.error; + } + + if (typeof text === "string") { + return text ? text.length : 0; + } + + if (text.length) { + return text.length; + } + + if (text == null) { + return 0; + } + + return error.value; + }; + + exports.LOWER = function (text) { + if (typeof text !== "string") { + return error.value; + } + return text ? text.toLowerCase() : text; + }; + + exports.MID = function (text, start, number) { + start = utils.parseNumber(start); + number = utils.parseNumber(number); + if (utils.anyIsError(start, number) || typeof text !== "string") { + return number; + } + + var begin = start - 1; + var end = begin + number; + + return text.substring(begin, end); + }; + + exports.NUMBERVALUE = null; + + exports.PRONETIC = null; + + exports.PROPER = function (text) { + if (text === undefined || text.length === 0) { + return error.value; + } + if (text === true) { + text = "TRUE"; + } + if (text === false) { + text = "FALSE"; + } + if (isNaN(text) && typeof text === "number") { + return error.value; + } + if (typeof text === "number") { + text = "" + text; + } + + return text.replace(/\w\S*/g, function (txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + }; + + exports.REGEXEXTRACT = function (text, regular_expression) { + var match = text.match(new RegExp(regular_expression)); + return match ? match[match.length > 1 ? match.length - 1 : 0] : null; + }; + + exports.REGEXMATCH = function (text, regular_expression, full) { + var match = text.match(new RegExp(regular_expression)); + return full ? match : !!match; + }; + + exports.REGEXREPLACE = function (text, regular_expression, replacement) { + return text.replace(new RegExp(regular_expression), replacement); + }; + + exports.REPLACE = function (text, position, length, new_text) { + position = utils.parseNumber(position); + length = utils.parseNumber(length); + if ( + utils.anyIsError(position, length) || + typeof text !== "string" || + typeof new_text !== "string" + ) { + return error.value; + } + return ( + text.substr(0, position - 1) + + new_text + + text.substr(position - 1 + length) + ); + }; + + exports.REPT = function (text, number) { + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return new Array(number + 1).join(text); + }; + + exports.RIGHT = function (text, number) { + number = number === undefined ? 1 : number; + number = utils.parseNumber(number); + if (number instanceof Error) { + return number; + } + return text ? text.substring(text.length - number) : null; + }; + + exports.SEARCH = function (find_text, within_text, position) { + var foundAt; + if (typeof find_text !== "string" || typeof within_text !== "string") { + return error.value; + } + position = position === undefined ? 0 : position; + foundAt = + within_text + .toLowerCase() + .indexOf(find_text.toLowerCase(), position - 1) + 1; + return foundAt === 0 ? error.value : foundAt; + }; + + exports.SPLIT = function (text, separator) { + return text.split(separator); + }; + + exports.SUBSTITUTE = function (text, old_text, new_text, occurrence) { + if (!text || !old_text || !new_text) { + return text; + } else if (occurrence === undefined) { + return text.replace(new RegExp(old_text, "g"), new_text); + } else { + var index = 0; + var i = 0; + while (text.indexOf(old_text, index) > 0) { + index = text.indexOf(old_text, index + 1); + i++; + if (i === occurrence) { + return ( + text.substring(0, index) + + new_text + + text.substring(index + old_text.length) + ); + } + } + } + }; + + exports.T = function (value) { + return typeof value === "string" ? value : ""; + }; + + exports.TEXT = null; + + exports.TRIM = function (text) { + if (typeof text !== "string") { + return error.value; + } + return text.replace(/ +/g, " ").trim(); + }; + + exports.UNICHAR = exports.CHAR; + + exports.UNICODE = exports.CODE; + + exports.UPPER = function (text) { + if (typeof text !== "string") { + return error.value; + } + return text.toUpperCase(); + }; + + exports.VALUE = null; + + return exports; + })(); + + met.stats = (function () { + var exports = {}; + + var SQRT2PI = 2.5066282746310002; + + exports.AVEDEV = null; + + exports.AVERAGE = function () { + var range = utils.numbers(utils.flatten(arguments)); + var n = range.length; + var sum = 0; + var count = 0; + for (var i = 0; i < n; i++) { + sum += range[i]; + count += 1; + } + return sum / count; + }; + + exports.AVERAGEA = function () { + var range = utils.flatten(arguments); + var n = range.length; + var sum = 0; + var count = 0; + for (var i = 0; i < n; i++) { + var el = range[i]; + if (typeof el === "number") { + sum += el; + } + if (el === true) { + sum++; + } + if (el !== null) { + count++; + } + } + return sum / count; + }; + + exports.AVERAGEIF = function (range, criteria, average_range) { + average_range = average_range || range; + range = utils.flatten(range); + average_range = utils.parseNumberArray(utils.flatten(average_range)); + if (average_range instanceof Error) { + return average_range; + } + var average_count = 0; + var result = 0; + for (var i = 0; i < range.length; i++) { + if (eval(range[i] + criteria)) { + // jshint ignore:line + result += average_range[i]; + average_count++; + } + } + return result / average_count; + }; + + exports.AVERAGEIFS = null; + + exports.COUNT = function () { + return utils.numbers(utils.flatten(arguments)).length; + }; + + exports.COUNTA = function () { + var range = utils.flatten(arguments); + return range.length - exports.COUNTBLANK(range); + }; + + exports.COUNTIN = function (range, value) { + var result = 0; + for (var i = 0; i < range.length; i++) { + if (range[i] === value) { + result++; + } + } + return result; + }; + + exports.COUNTBLANK = function () { + var range = utils.flatten(arguments); + var blanks = 0; + var element; + for (var i = 0; i < range.length; i++) { + element = range[i]; + if (element === null || element === "") { + blanks++; + } + } + return blanks; + }; + + exports.COUNTIF = function () { + var args = utils.argsToArray(arguments); + var criteria = args.pop(); + var range = utils.flatten(args); + if (!/[<>=!]/.test(criteria)) { + criteria = '=="' + criteria + '"'; + } + var matches = 0; + for (var i = 0; i < range.length; i++) { + if (typeof range[i] !== "string") { + if (eval(range[i] + criteria)) { + // jshint ignore:line + matches++; + } + } else { + if (eval('"' + range[i] + '"' + criteria)) { + // jshint ignore:line + matches++; + } + } + } + return matches; + }; + + exports.COUNTIFS = function () { + var args = utils.argsToArray(arguments); + var results = new Array(utils.flatten(args[0]).length); + for (var i = 0; i < results.length; i++) { + results[i] = true; + } + for (i = 0; i < args.length; i += 2) { + var range = utils.flatten(args[i]); + var criteria = args[i + 1]; + if (!/[<>=!]/.test(criteria)) { + criteria = '=="' + criteria + '"'; + } + for (var j = 0; j < range.length; j++) { + if (typeof range[j] !== "string") { + results[j] = results[j] && eval(range[j] + criteria); // jshint ignore:line + } else { + results[j] = results[j] && eval('"' + range[j] + '"' + criteria); // jshint ignore:line + } + } + } + var result = 0; + for (i = 0; i < results.length; i++) { + if (results[i]) { + result++; + } + } + return result; + }; + + exports.COUNTUNIQUE = function () { + return UNIQUE.apply(null, utils.flatten(arguments)).length; + }; + + exports.FISHER = function (x) { + x = utils.parseNumber(x); + if (x instanceof Error) { + return x; + } + return Math.log((1 + x) / (1 - x)) / 2; + }; + + exports.FISHERINV = function (y) { + y = utils.parseNumber(y); + if (y instanceof Error) { + return y; + } + var e2y = Math.exp(2 * y); + return (e2y - 1) / (e2y + 1); + }; + + exports.FREQUENCY = function (data, bins) { + data = utils.parseNumberArray(utils.flatten(data)); + bins = utils.parseNumberArray(utils.flatten(bins)); + if (utils.anyIsError(data, bins)) { + return error.value; + } + var n = data.length; + var b = bins.length; + var r = []; + for (var i = 0; i <= b; i++) { + r[i] = 0; + for (var j = 0; j < n; j++) { + if (i === 0) { + if (data[j] <= bins[0]) { + r[0] += 1; + } + } else if (i < b) { + if (data[j] > bins[i - 1] && data[j] <= bins[i]) { + r[i] += 1; + } + } else if (i === b) { + if (data[j] > bins[b - 1]) { + r[b] += 1; + } + } + } + } + return r; + }; + + exports.LARGE = function (range, k) { + range = utils.parseNumberArray(utils.flatten(range)); + k = utils.parseNumber(k); + if (utils.anyIsError(range, k)) { + return range; + } + return range.sort(function (a, b) { + return b - a; + })[k - 1]; + }; + + exports.MAX = function () { + var range = utils.numbers(utils.flatten(arguments)); + return range.length === 0 ? 0 : Math.max.apply(Math, range); + }; + + exports.MAXA = function () { + var range = utils.arrayValuesToNumbers(utils.flatten(arguments)); + return range.length === 0 ? 0 : Math.max.apply(Math, range); + }; + + exports.MIN = function () { + var range = utils.numbers(utils.flatten(arguments)); + return range.length === 0 ? 0 : Math.min.apply(Math, range); + }; + + exports.MINA = function () { + var range = utils.arrayValuesToNumbers(utils.flatten(arguments)); + return range.length === 0 ? 0 : Math.min.apply(Math, range); + }; + + exports.MODE = {}; + + exports.MODE.MULT = function () { + // Credits: Roönaän + var range = utils.parseNumberArray(utils.flatten(arguments)); + if (range instanceof Error) { + return range; + } + var n = range.length; + var count = {}; + var maxItems = []; + var max = 0; + var currentItem; + + for (var i = 0; i < n; i++) { + currentItem = range[i]; + count[currentItem] = count[currentItem] ? count[currentItem] + 1 : 1; + if (count[currentItem] > max) { + max = count[currentItem]; + maxItems = []; + } + if (count[currentItem] === max) { + maxItems[maxItems.length] = currentItem; + } + } + return maxItems; + }; + + exports.MODE.SNGL = function () { + var range = utils.parseNumberArray(utils.flatten(arguments)); + if (range instanceof Error) { + return range; + } + return exports.MODE.MULT(range).sort(function (a, b) { + return a - b; + })[0]; + }; + + exports.PERCENTILE = {}; + + exports.PERCENTILE.EXC = function (array, k) { + array = utils.parseNumberArray(utils.flatten(array)); + k = utils.parseNumber(k); + if (utils.anyIsError(array, k)) { + return error.value; + } + array = array.sort(function (a, b) { + { + return a - b; + } + }); + var n = array.length; + if (k < 1 / (n + 1) || k > 1 - 1 / (n + 1)) { + return error.num; + } + var l = k * (n + 1) - 1; + var fl = Math.floor(l); + return utils.cleanFloat( + l === fl ? array[l] : array[fl] + (l - fl) * (array[fl + 1] - array[fl]) + ); + }; + + exports.PERCENTILE.INC = function (array, k) { + array = utils.parseNumberArray(utils.flatten(array)); + k = utils.parseNumber(k); + if (utils.anyIsError(array, k)) { + return error.value; + } + array = array.sort(function (a, b) { + return a - b; + }); + var n = array.length; + var l = k * (n - 1); + var fl = Math.floor(l); + return utils.cleanFloat( + l === fl ? array[l] : array[fl] + (l - fl) * (array[fl + 1] - array[fl]) + ); + }; + + exports.PERCENTRANK = {}; + + exports.PERCENTRANK.EXC = function (array, x, significance) { + significance = significance === undefined ? 3 : significance; + array = utils.parseNumberArray(utils.flatten(array)); + x = utils.parseNumber(x); + significance = utils.parseNumber(significance); + if (utils.anyIsError(array, x, significance)) { + return error.value; + } + array = array.sort(function (a, b) { + return a - b; + }); + var uniques = UNIQUE.apply(null, array); + var n = array.length; + var m = uniques.length; + var power = Math.pow(10, significance); + var result = 0; + var match = false; + var i = 0; + while (!match && i < m) { + if (x === uniques[i]) { + result = (array.indexOf(uniques[i]) + 1) / (n + 1); + match = true; + } else if (x >= uniques[i] && (x < uniques[i + 1] || i === m - 1)) { + result = + (array.indexOf(uniques[i]) + + 1 + + (x - uniques[i]) / (uniques[i + 1] - uniques[i])) / + (n + 1); + match = true; + } + i++; + } + return Math.floor(result * power) / power; + }; + + exports.PERCENTRANK.INC = function (array, x, significance) { + significance = significance === undefined ? 3 : significance; + array = utils.parseNumberArray(utils.flatten(array)); + x = utils.parseNumber(x); + significance = utils.parseNumber(significance); + if (utils.anyIsError(array, x, significance)) { + return error.value; + } + array = array.sort(function (a, b) { + return a - b; + }); + var uniques = UNIQUE.apply(null, array); + var n = array.length; + var m = uniques.length; + var power = Math.pow(10, significance); + var result = 0; + var match = false; + var i = 0; + while (!match && i < m) { + if (x === uniques[i]) { + result = array.indexOf(uniques[i]) / (n - 1); + match = true; + } else if (x >= uniques[i] && (x < uniques[i + 1] || i === m - 1)) { + result = + (array.indexOf(uniques[i]) + + (x - uniques[i]) / (uniques[i + 1] - uniques[i])) / + (n - 1); + match = true; + } + i++; + } + return Math.floor(result * power) / power; + }; + + exports.PERMUT = function (number, number_chosen) { + number = utils.parseNumber(number); + number_chosen = utils.parseNumber(number_chosen); + if (utils.anyIsError(number, number_chosen)) { + return error.value; + } + return FACT(number) / FACT(number - number_chosen); + }; + + exports.PERMUTATIONA = function (number, number_chosen) { + number = utils.parseNumber(number); + number_chosen = utils.parseNumber(number_chosen); + if (utils.anyIsError(number, number_chosen)) { + return error.value; + } + return Math.pow(number, number_chosen); + }; + + exports.PHI = function (x) { + x = utils.parseNumber(x); + if (x instanceof Error) { + return error.value; + } + return Math.exp(-0.5 * x * x) / SQRT2PI; + }; + + exports.PROB = function (range, probability, lower, upper) { + if (lower === undefined) { + return 0; + } + upper = upper === undefined ? lower : upper; + + range = utils.parseNumberArray(utils.flatten(range)); + probability = utils.parseNumberArray(utils.flatten(probability)); + lower = utils.parseNumber(lower); + upper = utils.parseNumber(upper); + if (utils.anyIsError(range, probability, lower, upper)) { + return error.value; + } + + if (lower === upper) { + return range.indexOf(lower) >= 0 + ? probability[range.indexOf(lower)] + : 0; + } + + var sorted = range.sort(function (a, b) { + return a - b; + }); + var n = sorted.length; + var result = 0; + for (var i = 0; i < n; i++) { + if (sorted[i] >= lower && sorted[i] <= upper) { + result += probability[range.indexOf(sorted[i])]; + } + } + return result; + }; + + exports.QUARTILE = {}; + + exports.QUARTILE.EXC = function (range, quart) { + range = utils.parseNumberArray(utils.flatten(range)); + quart = utils.parseNumber(quart); + if (utils.anyIsError(range, quart)) { + return error.value; + } + switch (quart) { + case 1: + return exports.PERCENTILE.EXC(range, 0.25); + case 2: + return exports.PERCENTILE.EXC(range, 0.5); + case 3: + return exports.PERCENTILE.EXC(range, 0.75); + default: + return error.num; + } + }; + + exports.QUARTILE.INC = function (range, quart) { + range = utils.parseNumberArray(utils.flatten(range)); + quart = utils.parseNumber(quart); + if (utils.anyIsError(range, quart)) { + return error.value; + } + switch (quart) { + case 1: + return exports.PERCENTILE.INC(range, 0.25); + case 2: + return exports.PERCENTILE.INC(range, 0.5); + case 3: + return exports.PERCENTILE.INC(range, 0.75); + default: + return error.num; + } + }; + + exports.RANK = {}; + + exports.RANK.AVG = function (number, range, order) { + number = utils.parseNumber(number); + range = utils.parseNumberArray(utils.flatten(range)); + if (utils.anyIsError(number, range)) { + return error.value; + } + range = utils.flatten(range); + order = order || false; + var sort = order + ? function (a, b) { + return a - b; + } + : function (a, b) { + return b - a; + }; + range = range.sort(sort); + + var length = range.length; + var count = 0; + for (var i = 0; i < length; i++) { + if (range[i] === number) { + count++; + } + } + + return count > 1 + ? (2 * range.indexOf(number) + count + 1) / 2 + : range.indexOf(number) + 1; + }; + + exports.RANK.EQ = function (number, range, order) { + number = utils.parseNumber(number); + range = utils.parseNumberArray(utils.flatten(range)); + if (utils.anyIsError(number, range)) { + return error.value; + } + order = order || false; + var sort = order + ? function (a, b) { + return a - b; + } + : function (a, b) { + return b - a; + }; + range = range.sort(sort); + return range.indexOf(number) + 1; + }; + + exports.RSQ = function (data_x, data_y) { + // no need to flatten here, PEARSON will take care of that + data_x = utils.parseNumberArray(utils.flatten(data_x)); + data_y = utils.parseNumberArray(utils.flatten(data_y)); + if (utils.anyIsError(data_x, data_y)) { + return error.value; + } + return Math.pow(exports.PEARSON(data_x, data_y), 2); + }; + + exports.SMALL = function (range, k) { + range = utils.parseNumberArray(utils.flatten(range)); + k = utils.parseNumber(k); + if (utils.anyIsError(range, k)) { + return range; + } + return range.sort(function (a, b) { + return a - b; + })[k - 1]; + }; + + exports.STANDARDIZE = function (x, mean, sd) { + x = utils.parseNumber(x); + mean = utils.parseNumber(mean); + sd = utils.parseNumber(sd); + if (utils.anyIsError(x, mean, sd)) { + return error.value; + } + return (x - mean) / sd; + }; + + exports.STDEV = {}; + + exports.STDEV.P = function () { + var v = exports.VAR.P.apply(this, arguments); + return Math.sqrt(v); + }; + + exports.STDEV.S = function () { + var v = exports.VAR.S.apply(this, arguments); + return Math.sqrt(v); + }; + + exports.STDEVA = function () { + var v = exports.VARA.apply(this, arguments); + return Math.sqrt(v); + }; + + exports.STDEVPA = function () { + var v = exports.VARPA.apply(this, arguments); + return Math.sqrt(v); + }; + + exports.VAR = {}; + + exports.VAR.P = function () { + var range = utils.numbers(utils.flatten(arguments)); + var n = range.length; + var sigma = 0; + var mean = exports.AVERAGE(range); + for (var i = 0; i < n; i++) { + sigma += Math.pow(range[i] - mean, 2); + } + return sigma / n; + }; + + exports.VAR.S = function () { + var range = utils.numbers(utils.flatten(arguments)); + var n = range.length; + var sigma = 0; + var mean = exports.AVERAGE(range); + for (var i = 0; i < n; i++) { + sigma += Math.pow(range[i] - mean, 2); + } + return sigma / (n - 1); + }; + + exports.VARA = function () { + var range = utils.flatten(arguments); + var n = range.length; + var sigma = 0; + var count = 0; + var mean = exports.AVERAGEA(range); + for (var i = 0; i < n; i++) { + var el = range[i]; + if (typeof el === "number") { + sigma += Math.pow(el - mean, 2); + } else if (el === true) { + sigma += Math.pow(1 - mean, 2); + } else { + sigma += Math.pow(0 - mean, 2); + } + + if (el !== null) { + count++; + } + } + return sigma / (count - 1); + }; + + exports.VARPA = function () { + var range = utils.flatten(arguments); + var n = range.length; + var sigma = 0; + var count = 0; + var mean = exports.AVERAGEA(range); + for (var i = 0; i < n; i++) { + var el = range[i]; + if (typeof el === "number") { + sigma += Math.pow(el - mean, 2); + } else if (el === true) { + sigma += Math.pow(1 - mean, 2); + } else { + sigma += Math.pow(0 - mean, 2); + } + + if (el !== null) { + count++; + } + } + return sigma / count; + }; + + exports.WEIBULL = {}; + + exports.WEIBULL.DIST = function (x, alpha, beta, cumulative) { + x = utils.parseNumber(x); + alpha = utils.parseNumber(alpha); + beta = utils.parseNumber(beta); + if (utils.anyIsError(x, alpha, beta)) { + return error.value; + } + return cumulative + ? 1 - Math.exp(-Math.pow(x / beta, alpha)) + : (Math.pow(x, alpha - 1) * + Math.exp(-Math.pow(x / beta, alpha)) * + alpha) / + Math.pow(beta, alpha); + }; + + exports.Z = {}; + + exports.Z.TEST = function (range, x, sd) { + range = utils.parseNumberArray(utils.flatten(range)); + x = utils.parseNumber(x); + if (utils.anyIsError(range, x)) { + return error.value; + } + + sd = sd || exports.STDEV.S(range); + var n = range.length; + return ( + 1 - + exports.NORM.S.DIST( + (exports.AVERAGE(range) - x) / (sd / Math.sqrt(n)), + true + ) + ); + }; + + return exports; + })(); + + met.utils = (function () { + var exports = {}; + + exports.PROGRESS = function (p, c) { + var color = c ? c : "red"; + var value = p ? p : "0"; + + return ( + '
' + ); + }; + + exports.RATING = function (v) { + var html = '
'; + for (var i = 0; i < 5; i++) { + if (i < v) { + html += '
'; + } else { + html += "
"; + } + } + html += "
"; + return html; + }; + + return exports; + })(); + + for (var i = 0; i < Object.keys(met).length; i++) { + var methods = met[Object.keys(met)[i]]; + var keys = Object.keys(methods); + for (var j = 0; j < keys.length; j++) { + if (!methods[keys[j]]) { + window[keys[j]] = function () { + return keys[j] + "Not implemented"; + }; + } else if ( + typeof methods[keys[j]] == "function" || + typeof methods[keys[j]] == "object" + ) { + window[keys[j]] = methods[keys[j]]; + window[keys[j]].toString = function () { + return "#ERROR"; + }; + + if (typeof methods[keys[j]] == "object") { + var tmp = Object.keys(methods[keys[j]]); + for (var z = 0; z < tmp.length; z++) { + window[keys[j]][tmp[z]].toString = function () { + return "#ERROR"; + }; + } + } + } else { + window[keys[j]] = function () { + return keys[j] + "Not implemented"; + }; + } + } + } + + /** + * Instance execution helpers + */ + var x = null; + var y = null; + var instance = null; + + window["TABLE"] = function () { + return instance; + }; + window["COLUMN"] = window["COL"] = function () { + return parseInt(x) + 1; + }; + window["ROW"] = function () { + return parseInt(y) + 1; + }; + window["CELL"] = function () { + return F.getColumnNameFromCoords(x, y); + }; + window["VALUE"] = function (col, row, processed) { + return instance.getValueFromCoords( + parseInt(col) - 1, + parseInt(row) - 1, + processed + ); + }; + window["THISROWCELL"] = function (col) { + return instance.getValueFromCoords(parseInt(col) - 1, parseInt(y)); + }; + + // Secure formula + var secureFormula = function (oldValue, runtime) { + var newValue = ""; + var inside = 0; + + var special = ["=", "!", ">", "<"]; + + for (var i = 0; i < oldValue.length; i++) { + if (oldValue[i] == '"') { + if (inside == 0) { + inside = 1; + } else { + inside = 0; + } + } + + if (inside == 1) { + newValue += oldValue[i]; + } else { + newValue += oldValue[i].toUpperCase(); + + if (runtime == true) { + if ( + i > 0 && + oldValue[i] == "=" && + special.indexOf(oldValue[i - 1]) == -1 && + special.indexOf(oldValue[i + 1]) == -1 + ) { + newValue += "="; + } + } + } + } + + // Adapt to JS + newValue = newValue.replace(/\^/g, "**"); + newValue = newValue.replace(/\<\>/g, "!="); + newValue = newValue.replace(/\&/g, "+"); + newValue = newValue.replace(/\$/g, ""); + + return newValue; + }; + + // Convert range tokens + var tokensUpdate = function (tokens, e) { + for (var index = 0; index < tokens.length; index++) { + var f = F.getTokensFromRange(tokens[index]); + e = e.replace(tokens[index], "[" + f.join(",") + "]"); + } + return e; + }; + + var F = function (expression, variables, i, j, obj) { + // Global helpers + instance = obj; + x = i; + y = j; + // String + var s = ""; + var keys = Object.keys(variables); + if (keys.length) { + for (var i = 0; i < keys.length; i++) { + if (keys[i].indexOf(".") == -1 && keys[i].indexOf("!") == -1) { + s += "var " + keys[i] + " = " + variables[keys[i]] + ";\n"; + } else { + s += keys[i] + " = " + variables[keys[i]] + ";\n"; + } + } + } + // Remove $ + expression = expression.replace(/\$/g, ""); + // Replace ! per dot + expression = expression.replace(/\!/g, "."); + // Adapt to JS + expression = secureFormula(expression, true); + // Update range + var tokens = expression.match( + /([A-Z]+[0-9]*\.)?(\$?[A-Z]+\$?[0-9]+):(\$?[A-Z]+\$?[0-9]+)?/g + ); + if (tokens && tokens.length) { + expression = tokensUpdate(tokens, expression); + } + + // Calculate + return new Function(s + "; return " + expression)(); + }; + + /** + * Get letter based on a number + * @param {number} i + * @return {string} + */ + var getColumnName = function (i) { + var letter = ""; + if (i > 701) { + letter += String.fromCharCode(64 + parseInt(i / 676)); + letter += String.fromCharCode(64 + parseInt((i % 676) / 26)); + } else if (i > 25) { + letter += String.fromCharCode(64 + parseInt(i / 26)); + } + letter += String.fromCharCode(65 + (i % 26)); + + return letter; + }; + + /** + * Get column name from coords + */ + F.getColumnNameFromCoords = function (x, y) { + return getColumnName(parseInt(x)) + (parseInt(y) + 1); + }; + + F.getCoordsFromColumnName = function (columnName) { + // Get the letters + var t = /^[a-zA-Z]+/.exec(columnName); + + if (t) { + // Base 26 calculation + var code = 0; + for (var i = 0; i < t[0].length; i++) { + code += + parseInt(t[0].charCodeAt(i) - 64) * Math.pow(26, t[0].length - 1 - i); + } + code--; + // Make sure jspreadsheet starts on zero + if (code < 0) { + code = 0; + } + + // Number + var number = parseInt(/[0-9]+$/.exec(columnName)) || null; + if (number > 0) { + number--; + } + + return [code, number]; + } + }; + + F.getRangeFromTokens = function (tokens) { + tokens = tokens.filter(function (v) { + return v != "#REF!"; + }); + + var d = ""; + var t = ""; + for (var i = 0; i < tokens.length; i++) { + if (tokens[i].indexOf(".") >= 0) { + d = "."; + } else if (tokens[i].indexOf("!") >= 0) { + d = "!"; + } + if (d) { + t = tokens[i].split(d); + tokens[i] = t[1]; + t = t[0] + d; + } + } + + tokens.sort(function (a, b) { + var t1 = Helpers.getCoordsFromColumnName(a); + var t2 = Helpers.getCoordsFromColumnName(b); + if (t1[1] > t2[1]) { + return 1; + } else if (t1[1] < t2[1]) { + return -1; + } else { + if (t1[0] > t2[0]) { + return 1; + } else if (t1[0] < t2[0]) { + return -1; + } else { + return 0; + } + } + }); + + if (!tokens.length) { + return "#REF!"; + } else { + return t + (tokens[0] + ":" + tokens[tokens.length - 1]); + } + }; + + F.getTokensFromRange = function (range) { + if (range.indexOf(".") > 0) { + var t = range.split("."); + range = t[1]; + t = t[0] + "."; + } else if (range.indexOf("!") > 0) { + var t = range.split("!"); + range = t[1]; + t = t[0] + "!"; + } else { + var t = ""; + } + + var range = range.split(":"); + var e1 = F.getCoordsFromColumnName(range[0]); + var e2 = F.getCoordsFromColumnName(range[1]); + + if (e1[0] <= e2[0]) { + var x1 = e1[0]; + var x2 = e2[0]; + } else { + var x1 = e2[0]; + var x2 = e1[0]; + } + + if (e1[1] === null && e2[1] == null) { + var y1 = null; + var y2 = null; + + var k = Object.keys(vars); + for (var i = 0; i < k.length; i++) { + var tmp = F.getCoordsFromColumnName(k[i]); + if (tmp[0] === e1[0]) { + if (y1 === null || tmp[1] < y1) { + y1 = tmp[1]; + } + } + if (tmp[0] === e2[0]) { + if (y2 === null || tmp[1] > y2) { + y2 = tmp[1]; + } + } + } + } else { + if (e1[1] <= e2[1]) { + var y1 = e1[1]; + var y2 = e2[1]; + } else { + var y1 = e2[1]; + var y2 = e1[1]; + } + } + + var f = []; + for (var j = y1; j <= y2; j++) { + var line = []; + for (var i = x1; i <= x2; i++) { + line.push(t + F.getColumnNameFromCoords(i, j)); + } + f.push(line); + } + + return f; + }; + + F.setFormula = function (o) { + var k = Object.keys(o); + for (var i = 0; i < k.length; i++) { + if (typeof o[k[i]] == "function") { + window[k[i]] = o[k[i]]; + } + } + }; + + return F; +})(); + +if (!jSuites && typeof require === "function") { + var jSuites = require("jsuites"); +} + +(function (global, factory) { + typeof exports === "object" && typeof module !== "undefined" + ? (module.exports = factory()) + : typeof define === "function" && define.amd + ? define(factory) + : (global.jspreadsheet = global.jexcel = factory()); +})(this, function () { + "use strict"; + + // Basic version information + var Version = (function () { + // Information + var info = { + title: "Jspreadsheet", + version: "4.11.1", + type: "CE", + host: "https://bossanova.uk/jspreadsheet", + license: "MIT", + print: function () { + return [ + this.title + " " + this.type + " " + this.version, + this.host, + this.license, + ].join("\r\n"); + }, + }; + + return function () { + return info; + }; + })(); + + /** + * The value is a formula + */ + var isFormula = function (value) { + var v = ("" + value)[0]; + return v == "=" || v == "#" ? true : false; + }; + + /** + * Get the mask in the jSuites.mask format + */ + var getMask = function (o) { + if (o.format || o.mask || o.locale) { + var opt = {}; + if (o.mask) { + opt.mask = o.mask; + } else if (o.format) { + opt.mask = o.format; + } else { + opt.locale = o.locale; + opt.options = o.options; + } + + if (o.decimal) { + if (!opt.options) { + opt.options = {}; + } + opt.options = { decimal: o.decimal }; + } + return opt; + } + + return null; + }; + + // Jspreadsheet core object + var jexcel = function (el, options) { + // Create jspreadsheet object + var obj = {}; + obj.options = {}; + + if (!(el instanceof Element || el instanceof HTMLDocument)) { + console.error("Jspreadsheet: el is not a valid DOM element"); + return false; + } else if (el.tagName == "TABLE") { + if ((options = jexcel.createFromTable(el, options))) { + var div = document.createElement("div"); + el.parentNode.insertBefore(div, el); + el.remove(); + el = div; + } else { + console.error("Jspreadsheet: el is not a valid DOM element"); + return false; + } + } + + // Loading default configuration + var defaults = { + // External data + url: null, + // Ajax options + method: "GET", + requestVariables: null, + // Data + data: null, + // Custom sorting handler + sorting: null, + // Copy behavior + copyCompatibility: false, + root: null, + // Rows and columns definitions + rows: [], + columns: [], + // Deprected legacy options + colHeaders: [], + colWidths: [], + colAlignments: [], + nestedHeaders: null, + // Column width that is used by default + defaultColWidth: 50, + defaultColAlign: "center", + // Rows height default + defaultRowHeight: null, + // Spare rows and columns + minSpareRows: 0, + minSpareCols: 0, + // Minimal table dimensions + minDimensions: [0, 0], + // Allow Export + allowExport: true, + // @type {boolean} - Include the header titles on download + includeHeadersOnDownload: false, + // @type {boolean} - Include the header titles on copy + includeHeadersOnCopy: false, + // Allow column sorting + columnSorting: true, + // Allow column dragging + columnDrag: false, + // Allow column resizing + columnResize: true, + // Allow row resizing + rowResize: false, + // Allow row dragging + rowDrag: true, + // Allow table edition + editable: true, + // Allow new rows + allowInsertRow: true, + // Allow new rows + allowManualInsertRow: true, + // Allow new columns + allowInsertColumn: true, + // Allow new rows + allowManualInsertColumn: true, + // Allow row delete + allowDeleteRow: true, + // Allow deleting of all rows + allowDeletingAllRows: false, + // Allow column delete + allowDeleteColumn: true, + // Allow rename column + allowRenameColumn: true, + // Allow comments + allowComments: false, + // Global wrap + wordWrap: false, + // Image options + imageOptions: null, + // CSV source + csv: null, + // Filename + csvFileName: "jspreadsheet", + // Consider first line as header + csvHeaders: true, + // Delimiters + csvDelimiter: ",", + // First row as header + parseTableFirstRowAsHeader: false, + parseTableAutoCellType: false, + // Disable corner selection + selectionCopy: true, + // Merged cells + mergeCells: {}, + // Create toolbar + toolbar: null, + // Allow search + search: false, + // Create pagination + pagination: false, + paginationOptions: null, + // Full screen + fullscreen: false, + // Lazy loading + lazyLoading: false, + loadingSpin: false, + // Table overflow + tableOverflow: false, + tableHeight: "300px", + tableWidth: null, + textOverflow: false, + // Meta + meta: null, + // Style + style: null, + classes: null, + // Execute formulas + parseFormulas: true, + autoIncrement: true, + autoCasting: true, + // Security + secureFormulas: true, + stripHTML: true, + stripHTMLOnCopy: false, + // Filters + filters: false, + footers: null, + // Event handles + onundo: null, + onredo: null, + onload: null, + onchange: null, + oncomments: null, + onbeforechange: null, + onafterchanges: null, + onbeforeinsertrow: null, + oninsertrow: null, + onbeforeinsertcolumn: null, + oninsertcolumn: null, + onbeforedeleterow: null, + ondeleterow: null, + onbeforedeletecolumn: null, + ondeletecolumn: null, + onmoverow: null, + onmovecolumn: null, + onresizerow: null, + onresizecolumn: null, + onsort: null, + onselection: null, + oncopy: null, + onpaste: null, + onbeforepaste: null, + onmerge: null, + onfocus: null, + onblur: null, + onchangeheader: null, + oncreateeditor: null, + oneditionstart: null, + oneditionend: null, + onchangestyle: null, + onchangemeta: null, + onchangepage: null, + onbeforesave: null, + onsave: null, + // Global event dispatcher + onevent: null, + // Persistance + persistance: false, + // Customize any cell behavior + updateTable: null, + // Detach the HTML table when calling updateTable + detachForUpdates: false, + freezeColumns: null, + // Texts + text: { + noRecordsFound: "No records found", + showingPage: "Showing page {0} of {1} entries", + show: "Show ", + search: "Search", + entries: " entries", + columnName: "Column name", + insertANewColumnBefore: "Insert a new column before", + insertANewColumnAfter: "Insert a new column after", + deleteSelectedColumns: "Delete selected columns", + renameThisColumn: "Rename this column", + orderAscending: "Order ascending", + orderDescending: "Order descending", + insertANewRowBefore: "Insert a new row before", + insertANewRowAfter: "Insert a new row after", + deleteSelectedRows: "Delete selected rows", + editComments: "Edit comments", + addComments: "Add comments", + comments: "Comments", + clearComments: "Clear comments", + copy: "Copy...", + paste: "Paste...", + saveAs: "Save as...", + about: "About", + areYouSureToDeleteTheSelectedRows: + "Are you sure to delete the selected rows?", + areYouSureToDeleteTheSelectedColumns: + "Are you sure to delete the selected columns?", + thisActionWillDestroyAnyExistingMergedCellsAreYouSure: + "This action will destroy any existing merged cells. Are you sure?", + thisActionWillClearYourSearchResultsAreYouSure: + "This action will clear your search results. Are you sure?", + thereIsAConflictWithAnotherMergedCell: + "There is a conflict with another merged cell", + invalidMergeProperties: "Invalid merged properties", + cellAlreadyMerged: "Cell already merged", + noCellsSelected: "No cells selected", + }, + // About message + about: true, + }; + + // Loading initial configuration from user + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + if (property === "text") { + obj.options[property] = defaults[property]; + for (var textKey in options[property]) { + if (options[property].hasOwnProperty(textKey)) { + obj.options[property][textKey] = options[property][textKey]; + } + } + } else { + obj.options[property] = options[property]; + } + } else { + obj.options[property] = defaults[property]; + } + } + + // Global elements + obj.el = el; + obj.corner = null; + obj.contextMenu = null; + obj.textarea = null; + obj.ads = null; + obj.content = null; + obj.table = null; + obj.thead = null; + obj.tbody = null; + obj.rows = []; + obj.results = null; + obj.searchInput = null; + obj.toolbar = null; + obj.pagination = null; + obj.pageNumber = null; + obj.headerContainer = null; + obj.colgroupContainer = null; + + // Containers + obj.headers = []; + obj.records = []; + obj.history = []; + obj.formula = []; + obj.colgroup = []; + obj.selection = []; + obj.highlighted = []; + obj.selectedCell = null; + obj.selectedContainer = null; + obj.style = []; + obj.data = null; + obj.filter = null; + obj.filters = []; + + // Internal controllers + obj.cursor = null; + obj.historyIndex = -1; + obj.ignoreEvents = false; + obj.ignoreHistory = false; + obj.edition = null; + obj.hashString = null; + obj.resizing = null; + obj.dragging = null; + + // Lazy loading + if ( + obj.options.lazyLoading == true && + obj.options.tableOverflow == false && + obj.options.fullscreen == false + ) { + console.error( + "Jspreadsheet: The lazyloading only works when tableOverflow = yes or fullscreen = yes" + ); + obj.options.lazyLoading = false; + } + + /** + * Activate/Disable fullscreen + * use programmatically : table.fullscreen(); or table.fullscreen(true); or table.fullscreen(false); + * @Param {boolean} activate + */ + obj.fullscreen = function (activate) { + // If activate not defined, get reverse options.fullscreen + if (activate == null) { + activate = !obj.options.fullscreen; + } + + // If change + if (obj.options.fullscreen != activate) { + obj.options.fullscreen = activate; + + // Test LazyLoading conflict + if (activate == true) { + el.classList.add("fullscreen"); + } else { + el.classList.remove("fullscreen"); + } + } + }; + + /** + * Trigger events + */ + obj.dispatch = function (event) { + // Dispatch events + if (!obj.ignoreEvents) { + // Call global event + if (typeof obj.options.onevent == "function") { + var ret = obj.options.onevent.apply(this, arguments); + } + // Call specific events + if (typeof obj.options[event] == "function") { + var ret = obj.options[event].apply( + this, + Array.prototype.slice.call(arguments, 1) + ); + } + } + + // Persistance + if (event == "onafterchanges" && obj.options.persistance) { + var url = + obj.options.persistance == true + ? obj.options.url + : obj.options.persistance; + var data = obj.prepareJson(arguments[2]); + obj.save(url, data); + } + + return ret; + }; + + /** + * Prepare the jspreadsheet table + * + * @Param config + */ + obj.prepareTable = function () { + // Loading initial data from remote sources + var results = []; + + // Number of columns + var size = obj.options.columns.length; + + if (obj.options.data && typeof obj.options.data[0] !== "undefined") { + // Data keys + var keys = Object.keys(obj.options.data[0]); + + if (keys.length > size) { + size = keys.length; + } + } + + // Minimal dimensions + if (obj.options.minDimensions[0] > size) { + size = obj.options.minDimensions[0]; + } + + // Requests + var multiple = []; + + // Preparations + for (var i = 0; i < size; i++) { + // Deprected options. You should use only columns + if (!obj.options.colHeaders[i]) { + obj.options.colHeaders[i] = ""; + } + if (!obj.options.colWidths[i]) { + obj.options.colWidths[i] = obj.options.defaultColWidth; + } + if (!obj.options.colAlignments[i]) { + obj.options.colAlignments[i] = obj.options.defaultColAlign; + } + + // Default column description + if (!obj.options.columns[i]) { + obj.options.columns[i] = { type: "text" }; + } else if (!obj.options.columns[i].type) { + obj.options.columns[i].type = "text"; + } + if (!obj.options.columns[i].name) { + obj.options.columns[i].name = keys && keys[i] ? keys[i] : i; + } + if (!obj.options.columns[i].source) { + obj.options.columns[i].source = []; + } + if (!obj.options.columns[i].options) { + obj.options.columns[i].options = []; + } + if (!obj.options.columns[i].editor) { + obj.options.columns[i].editor = null; + } + if (!obj.options.columns[i].allowEmpty) { + obj.options.columns[i].allowEmpty = false; + } + if (!obj.options.columns[i].title) { + obj.options.columns[i].title = obj.options.colHeaders[i] + ? obj.options.colHeaders[i] + : ""; + } + if (!obj.options.columns[i].width) { + obj.options.columns[i].width = obj.options.colWidths[i] + ? obj.options.colWidths[i] + : obj.options.defaultColWidth; + } + if (!obj.options.columns[i].align) { + obj.options.columns[i].align = obj.options.colAlignments[i] + ? obj.options.colAlignments[i] + : "center"; + } + + // Pre-load initial source for json autocomplete + if ( + obj.options.columns[i].type == "autocomplete" || + obj.options.columns[i].type == "dropdown" + ) { + // if remote content + if (obj.options.columns[i].url) { + multiple.push({ + url: obj.options.columns[i].url, + index: i, + method: "GET", + dataType: "json", + success: function (data) { + var source = []; + for (var i = 0; i < data.length; i++) { + obj.options.columns[this.index].source.push(data[i]); + } + }, + }); + } + } else if (obj.options.columns[i].type == "calendar") { + // Default format for date columns + if (!obj.options.columns[i].options.format) { + obj.options.columns[i].options.format = "DD/MM/YYYY"; + } + } + } + // Create the table when is ready + if (!multiple.length) { + obj.createTable(); + } else { + jSuites.ajax(multiple, function () { + obj.createTable(); + }); + } + }; + + obj.createTable = function () { + // Elements + obj.table = document.createElement("table"); + obj.thead = document.createElement("thead"); + obj.tbody = document.createElement("tbody"); + + // Create headers controllers + obj.headers = []; + obj.colgroup = []; + + // Create table container + obj.content = document.createElement("div"); + obj.content.classList.add("jexcel_content"); + obj.content.onscroll = function (e) { + obj.scrollControls(e); + }; + obj.content.onwheel = function (e) { + obj.wheelControls(e); + }; + + // Create toolbar object + obj.toolbar = document.createElement("div"); + obj.toolbar.classList.add("jexcel_toolbar"); + + // Search + var searchContainer = document.createElement("div"); + var searchText = document.createTextNode(obj.options.text.search + ": "); + obj.searchInput = document.createElement("input"); + obj.searchInput.classList.add("jexcel_search"); + searchContainer.appendChild(searchText); + searchContainer.appendChild(obj.searchInput); + obj.searchInput.onfocus = function () { + obj.resetSelection(); + }; + + // Pagination select option + var paginationUpdateContainer = document.createElement("div"); + + if ( + obj.options.pagination > 0 && + obj.options.paginationOptions && + obj.options.paginationOptions.length > 0 + ) { + obj.paginationDropdown = document.createElement("select"); + obj.paginationDropdown.classList.add("jexcel_pagination_dropdown"); + obj.paginationDropdown.onchange = function () { + obj.options.pagination = parseInt(this.value); + obj.page(0); + }; + + for (var i = 0; i < obj.options.paginationOptions.length; i++) { + var temp = document.createElement("option"); + temp.value = obj.options.paginationOptions[i]; + temp.innerHTML = obj.options.paginationOptions[i]; + obj.paginationDropdown.appendChild(temp); + } + + // Set initial pagination value + obj.paginationDropdown.value = obj.options.pagination; + + paginationUpdateContainer.appendChild( + document.createTextNode(obj.options.text.show) + ); + paginationUpdateContainer.appendChild(obj.paginationDropdown); + paginationUpdateContainer.appendChild( + document.createTextNode(obj.options.text.entries) + ); + } + + // Filter and pagination container + var filter = document.createElement("div"); + filter.classList.add("jexcel_filter"); + filter.appendChild(paginationUpdateContainer); + filter.appendChild(searchContainer); + + // Colsgroup + obj.colgroupContainer = document.createElement("colgroup"); + var tempCol = document.createElement("col"); + tempCol.setAttribute("width", "50"); + obj.colgroupContainer.appendChild(tempCol); + + // Nested + if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) { + // Flexible way to handle nestedheaders + if (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) { + for (var j = 0; j < obj.options.nestedHeaders.length; j++) { + obj.thead.appendChild( + obj.createNestedHeader(obj.options.nestedHeaders[j]) + ); + } + } else { + obj.thead.appendChild( + obj.createNestedHeader(obj.options.nestedHeaders) + ); + } + } + + // Row + obj.headerContainer = document.createElement("tr"); + var tempCol = document.createElement("td"); + tempCol.classList.add("jexcel_selectall"); + obj.headerContainer.appendChild(tempCol); + + for (var i = 0; i < obj.options.columns.length; i++) { + // Create header + obj.createCellHeader(i); + // Append cell to the container + obj.headerContainer.appendChild(obj.headers[i]); + obj.colgroupContainer.appendChild(obj.colgroup[i]); + } + + obj.thead.appendChild(obj.headerContainer); + + // Filters + if (obj.options.filters == true) { + obj.filter = document.createElement("tr"); + var td = document.createElement("td"); + obj.filter.appendChild(td); + + for (var i = 0; i < obj.options.columns.length; i++) { + var td = document.createElement("td"); + td.innerHTML = " "; + td.setAttribute("data-x", i); + td.className = "jexcel_column_filter"; + if (obj.options.columns[i].type == "hidden") { + td.style.display = "none"; + } + obj.filter.appendChild(td); + } + + obj.thead.appendChild(obj.filter); + } + + // Content table + obj.table = document.createElement("table"); + obj.table.classList.add("jexcel"); + obj.table.setAttribute("cellpadding", "0"); + obj.table.setAttribute("cellspacing", "0"); + obj.table.setAttribute("unselectable", "yes"); + //obj.table.setAttribute('onselectstart', 'return false'); + obj.table.appendChild(obj.colgroupContainer); + obj.table.appendChild(obj.thead); + obj.table.appendChild(obj.tbody); + + if (!obj.options.textOverflow) { + obj.table.classList.add("jexcel_overflow"); + } + + // Spreadsheet corner + obj.corner = document.createElement("div"); + obj.corner.className = "jexcel_corner"; + obj.corner.setAttribute("unselectable", "on"); + obj.corner.setAttribute("onselectstart", "return false"); + + if (obj.options.selectionCopy == false) { + obj.corner.style.display = "none"; + } + + // Textarea helper + obj.textarea = document.createElement("textarea"); + obj.textarea.className = "jexcel_textarea"; + obj.textarea.id = "jexcel_textarea"; + obj.textarea.tabIndex = "-1"; + + // Contextmenu container + obj.contextMenu = document.createElement("div"); + obj.contextMenu.className = "jexcel_contextmenu"; + + // Create element + jSuites.contextmenu(obj.contextMenu, { + onclick: function () { + obj.contextMenu.contextmenu.close(false); + }, + }); + + // Powered by Jspreadsheet + var ads = document.createElement("a"); + ads.setAttribute("href", "https://bossanova.uk/jspreadsheet/"); + obj.ads = document.createElement("div"); + obj.ads.className = "jexcel_about"; + try { + if ( + typeof sessionStorage !== "undefined" && + !sessionStorage.getItem("jexcel") + ) { + sessionStorage.setItem("jexcel", true); + var img = document.createElement("img"); + img.src = "//bossanova.uk/jspreadsheet/logo.png"; + ads.appendChild(img); + } + } catch (exception) {} + var span = document.createElement("span"); + span.innerHTML = "Jspreadsheet CE"; + ads.appendChild(span); + obj.ads.appendChild(ads); + + // Create table container TODO: frozen columns + var container = document.createElement("div"); + container.classList.add("jexcel_table"); + + // Pagination + obj.pagination = document.createElement("div"); + obj.pagination.classList.add("jexcel_pagination"); + var paginationInfo = document.createElement("div"); + var paginationPages = document.createElement("div"); + obj.pagination.appendChild(paginationInfo); + obj.pagination.appendChild(paginationPages); + + // Hide pagination if not in use + if (!obj.options.pagination) { + obj.pagination.style.display = "none"; + } + + // Append containers to the table + if (obj.options.search == true) { + el.appendChild(filter); + } + + // Elements + obj.content.appendChild(obj.table); + obj.content.appendChild(obj.corner); + obj.content.appendChild(obj.textarea); + + el.appendChild(obj.toolbar); + el.appendChild(obj.content); + el.appendChild(obj.pagination); + el.appendChild(obj.contextMenu); + el.appendChild(obj.ads); + el.classList.add("jexcel_container"); + + // Create toolbar + if (obj.options.toolbar && obj.options.toolbar.length) { + obj.createToolbar(); + } + + // Fullscreen + if (obj.options.fullscreen == true) { + el.classList.add("fullscreen"); + } else { + // Overflow + if (obj.options.tableOverflow == true) { + if (obj.options.tableHeight) { + obj.content.style["overflow-y"] = "auto"; + obj.content.style["box-shadow"] = + "rgb(221 221 221) 2px 2px 5px 0.1px"; + obj.content.style.maxHeight = obj.options.tableHeight; + } + if (obj.options.tableWidth) { + obj.content.style["overflow-x"] = "auto"; + obj.content.style.width = obj.options.tableWidth; + } + } + } + + // With toolbars + if (obj.options.tableOverflow != true && obj.options.toolbar) { + el.classList.add("with-toolbar"); + } + + // Actions + if (obj.options.columnDrag == true) { + obj.thead.classList.add("draggable"); + } + if (obj.options.columnResize == true) { + obj.thead.classList.add("resizable"); + } + if (obj.options.rowDrag == true) { + obj.tbody.classList.add("draggable"); + } + if (obj.options.rowResize == true) { + obj.tbody.classList.add("resizable"); + } + + // Load data + obj.setData(); + + // Style + if (obj.options.style) { + obj.setStyle(obj.options.style, null, null, 1, 1); + } + + // Classes + if (obj.options.classes) { + var k = Object.keys(obj.options.classes); + for (var i = 0; i < k.length; i++) { + var cell = jexcel.getIdFromColumnName(k[i], true); + obj.records[cell[1]][cell[0]].classList.add( + obj.options.classes[k[i]] + ); + } + } + }; + + /** + * Refresh the data + * + * @return void + */ + obj.refresh = function () { + if (obj.options.url) { + // Loading + if (obj.options.loadingSpin == true) { + jSuites.loading.show(); + } + + jSuites.ajax({ + url: obj.options.url, + method: obj.options.method, + data: obj.options.requestVariables, + dataType: "json", + success: function (result) { + // Data + obj.options.data = result.data ? result.data : result; + // Prepare table + obj.setData(); + // Hide spin + if (obj.options.loadingSpin == true) { + jSuites.loading.hide(); + } + }, + }); + } else { + obj.setData(); + } + }; + + /** + * Set data + * + * @param array data In case no data is sent, default is reloaded + * @return void + */ + obj.setData = function (data) { + // Update data + if (data) { + if (typeof data == "string") { + data = JSON.parse(data); + } + + obj.options.data = data; + } + + // Data + if (!obj.options.data) { + obj.options.data = []; + } + + // Prepare data + if (obj.options.data && obj.options.data[0]) { + if (!Array.isArray(obj.options.data[0])) { + var data = []; + for (var j = 0; j < obj.options.data.length; j++) { + var row = []; + for (var i = 0; i < obj.options.columns.length; i++) { + row[i] = obj.options.data[j][obj.options.columns[i].name]; + } + data.push(row); + } + + obj.options.data = data; + } + } + + // Adjust minimal dimensions + var j = 0; + var i = 0; + var size_i = obj.options.columns.length; + var size_j = obj.options.data.length; + var min_i = obj.options.minDimensions[0]; + var min_j = obj.options.minDimensions[1]; + var max_i = min_i > size_i ? min_i : size_i; + var max_j = min_j > size_j ? min_j : size_j; + + for (j = 0; j < max_j; j++) { + for (i = 0; i < max_i; i++) { + if (obj.options.data[j] == undefined) { + obj.options.data[j] = []; + } + + if (obj.options.data[j][i] == undefined) { + obj.options.data[j][i] = ""; + } + } + } + + // Reset containers + obj.rows = []; + obj.results = null; + obj.records = []; + obj.history = []; + + // Reset internal controllers + obj.historyIndex = -1; + + // Reset data + obj.tbody.innerHTML = ""; + + // Lazy loading + if (obj.options.lazyLoading == true) { + // Load only 100 records + var startNumber = 0; + var finalNumber = + obj.options.data.length < 100 ? obj.options.data.length : 100; + + if (obj.options.pagination) { + obj.options.pagination = false; + console.error( + "Jspreadsheet: Pagination will be disable due the lazyLoading" + ); + } + } else if (obj.options.pagination) { + // Pagination + if (!obj.pageNumber) { + obj.pageNumber = 0; + } + var quantityPerPage = obj.options.pagination; + startNumber = obj.options.pagination * obj.pageNumber; + finalNumber = + obj.options.pagination * obj.pageNumber + obj.options.pagination; + + if (obj.options.data.length < finalNumber) { + finalNumber = obj.options.data.length; + } + } else { + var startNumber = 0; + var finalNumber = obj.options.data.length; + } + + // Append nodes to the HTML + for (j = 0; j < obj.options.data.length; j++) { + // Create row + var tr = obj.createRow(j, obj.options.data[j]); + // Append line to the table + if (j >= startNumber && j < finalNumber) { + obj.tbody.appendChild(tr); + } + } + + if (obj.options.lazyLoading == true) { + // Do not create pagination with lazyloading activated + } else if (obj.options.pagination) { + obj.updatePagination(); + } + + // Merge cells + if (obj.options.mergeCells) { + var keys = Object.keys(obj.options.mergeCells); + for (var i = 0; i < keys.length; i++) { + var num = obj.options.mergeCells[keys[i]]; + obj.setMerge(keys[i], num[0], num[1], 1); + } + } + + // Updata table with custom configurations if applicable + obj.updateTable(); + + // Onload + obj.dispatch("onload", el, obj); + }; + + /** + * Get the whole table data + * + * @param bool get highlighted cells only + * @return array data + */ + obj.getData = function (highlighted, dataOnly) { + // Control vars + var dataset = []; + var px = 0; + var py = 0; + + // Data type + var dataType = + dataOnly == true || obj.options.copyCompatibility == false + ? true + : false; + + // Column and row length + var x = obj.options.columns.length; + var y = obj.options.data.length; + + // Go through the columns to get the data + for (var j = 0; j < y; j++) { + px = 0; + for (var i = 0; i < x; i++) { + // Cell selected or fullset + if ( + !highlighted || + obj.records[j][i].classList.contains("highlight") + ) { + // Get value + if (!dataset[py]) { + dataset[py] = []; + } + if (!dataType) { + dataset[py][px] = obj.records[j][i].innerHTML; + } else { + dataset[py][px] = obj.options.data[j][i]; + } + px++; + } + } + if (px > 0) { + py++; + } + } + + return dataset; + }; + + /** + * Get json data by row number + * + * @param integer row number + * @return object + */ + obj.getJsonRow = function (rowNumber) { + var rowData = obj.options.data[rowNumber]; + var x = obj.options.columns.length; + + var row = {}; + for (var i = 0; i < x; i++) { + if (!obj.options.columns[i].name) { + obj.options.columns[i].name = i; + } + row[obj.options.columns[i].name] = rowData[i]; + } + + return row; + }; + + /** + * Get the whole table data + * + * @param bool highlighted cells only + * @return string value + */ + obj.getJson = function (highlighted) { + // Control vars + var data = []; + + // Column and row length + var x = obj.options.columns.length; + var y = obj.options.data.length; + + // Go through the columns to get the data + for (var j = 0; j < y; j++) { + var row = null; + for (var i = 0; i < x; i++) { + if ( + !highlighted || + obj.records[j][i].classList.contains("highlight") + ) { + if (row == null) { + row = {}; + } + if (!obj.options.columns[i].name) { + obj.options.columns[i].name = i; + } + row[obj.options.columns[i].name] = obj.options.data[j][i]; + } + } + + if (row != null) { + data.push(row); + } + } + + return data; + }; + + /** + * Prepare JSON in the correct format + */ + obj.prepareJson = function (data) { + var rows = []; + for (var i = 0; i < data.length; i++) { + var x = data[i].x; + var y = data[i].y; + var k = obj.options.columns[x].name ? obj.options.columns[x].name : x; + + // Create row + if (!rows[y]) { + rows[y] = { + row: y, + data: {}, + }; + } + rows[y].data[k] = data[i].newValue; + } + + // Filter rows + return rows.filter(function (el) { + return el != null; + }); + }; + + /** + * Post json to a remote server + */ + obj.save = function (url, data) { + // Parse anything in the data before sending to the server + var ret = obj.dispatch("onbeforesave", el, obj, data); + if (ret) { + var data = ret; + } else { + if (ret === false) { + return false; + } + } + + // Remove update + jSuites.ajax({ + url: url, + method: "POST", + dataType: "json", + data: { data: JSON.stringify(data) }, + success: function (result) { + // Event + obj.dispatch("onsave", el, obj, data); + }, + }); + }; + + /** + * Get a row data by rowNumber + */ + obj.getRowData = function (rowNumber) { + return obj.options.data[rowNumber]; + }; + + /** + * Set a row data by rowNumber + */ + obj.setRowData = function (rowNumber, data) { + for (var i = 0; i < obj.headers.length; i++) { + // Update cell + var columnName = jexcel.getColumnNameFromId([i, rowNumber]); + // Set value + if (data[i] != null) { + obj.setValue(columnName, data[i]); + } + } + }; + + /** + * Get a column data by columnNumber + */ + obj.getColumnData = function (columnNumber) { + var dataset = []; + // Go through the rows to get the data + for (var j = 0; j < obj.options.data.length; j++) { + dataset.push(obj.options.data[j][columnNumber]); + } + return dataset; + }; + + /** + * Set a column data by colNumber + */ + obj.setColumnData = function (colNumber, data) { + for (var j = 0; j < obj.rows.length; j++) { + // Update cell + var columnName = jexcel.getColumnNameFromId([colNumber, j]); + // Set value + if (data[j] != null) { + obj.setValue(columnName, data[j]); + } + } + }; + + /** + * Create row + */ + obj.createRow = function (j, data) { + // Create container + if (!obj.records[j]) { + obj.records[j] = []; + } + // Default data + if (!data) { + var data = obj.options.data[j]; + } + // New line of data to be append in the table + obj.rows[j] = document.createElement("tr"); + obj.rows[j].setAttribute("data-y", j); + // Index + var index = null; + + // Set default row height + if (obj.options.defaultRowHeight) { + obj.rows[j].style.height = obj.options.defaultRowHeight + "px"; + } + + // Definitions + if (obj.options.rows[j]) { + if (obj.options.rows[j].height) { + obj.rows[j].style.height = obj.options.rows[j].height; + } + if (obj.options.rows[j].title) { + index = obj.options.rows[j].title; + } + } + if (!index) { + index = parseInt(j + 1); + } + // Row number label + var td = document.createElement("td"); + td.innerHTML = index; + td.setAttribute("data-y", j); + td.className = "jexcel_row"; + obj.rows[j].appendChild(td); + + // Data columns + for (var i = 0; i < obj.options.columns.length; i++) { + // New column of data to be append in the line + obj.records[j][i] = obj.createCell(i, j, data[i]); + // Add column to the row + obj.rows[j].appendChild(obj.records[j][i]); + } + + // Add row to the table body + return obj.rows[j]; + }; + + obj.parseValue = function (i, j, value, cell) { + if ( + ("" + value).substr(0, 1) == "=" && + obj.options.parseFormulas == true + ) { + value = obj.executeFormula(value, i, j); + } + + // Column options + var options = obj.options.columns[i]; + if (options && !isFormula(value)) { + // Mask options + var opt = null; + if ((opt = getMask(options))) { + if (value && value == Number(value)) { + value = Number(value); + } + // Process the decimals to match the mask + var masked = jSuites.mask.render(value, opt, true); + // Negative indication + if (cell) { + if (opt.mask) { + var t = opt.mask.split(";"); + if (t[1]) { + var t1 = t[1].match(new RegExp("\\[Red\\]", "gi")); + if (t1) { + if (value < 0) { + cell.classList.add("red"); + } else { + cell.classList.remove("red"); + } + } + var t2 = t[1].match(new RegExp("\\(", "gi")); + if (t2) { + if (value < 0) { + masked = "(" + masked + ")"; + } + } + } + } + } + + if (masked) { + value = masked; + } + } + } + + return value; + }; + + var validDate = function (date) { + date = "" + date; + if (date.substr(4, 1) == "-" && date.substr(7, 1) == "-") { + return true; + } else { + date = date.split("-"); + if ( + date[0].length == 4 && + date[0] == Number(date[0]) && + date[1].length == 2 && + date[1] == Number(date[1]) + ) { + return true; + } + } + return false; + }; + + /** + * Create cell + */ + obj.createCell = function (i, j, value) { + // Create cell and properties + var td = document.createElement("td"); + td.setAttribute("data-x", i); + td.setAttribute("data-y", j); + + // Security + if ( + ("" + value).substr(0, 1) == "=" && + obj.options.secureFormulas == true + ) { + var val = secureFormula(value); + if (val != value) { + // Update the data container + value = val; + } + } + + // Custom column + if (obj.options.columns[i].editor) { + if ( + obj.options.stripHTML === false || + obj.options.columns[i].stripHTML === false + ) { + td.innerHTML = value; + } else { + td.textContent = value; + } + if (typeof obj.options.columns[i].editor.createCell == "function") { + td = obj.options.columns[i].editor.createCell(td); + } + } else { + // Hidden column + if (obj.options.columns[i].type == "hidden") { + td.style.display = "none"; + td.textContent = value; + } else if ( + obj.options.columns[i].type == "checkbox" || + obj.options.columns[i].type == "radio" + ) { + // Create input + var element = document.createElement("input"); + element.type = obj.options.columns[i].type; + element.name = "c" + i; + element.checked = + value == 1 || value == true || value == "true" ? true : false; + element.onclick = function () { + obj.setValue(td, this.checked); + }; + + if ( + obj.options.columns[i].readOnly == true || + obj.options.editable == false + ) { + element.setAttribute("disabled", "disabled"); + } + + // Append to the table + td.appendChild(element); + // Make sure the values are correct + obj.options.data[j][i] = element.checked; + } else if (obj.options.columns[i].type == "calendar") { + // Try formatted date + var formatted = null; + if (!validDate(value)) { + var tmp = jSuites.calendar.extractDateFromString( + value, + obj.options.columns[i].options.format + ); + if (tmp) { + formatted = tmp; + } + } + // Create calendar cell + td.textContent = jSuites.calendar.getDateString( + formatted ? formatted : value, + obj.options.columns[i].options.format + ); + } else if ( + obj.options.columns[i].type == "dropdown" || + obj.options.columns[i].type == "autocomplete" + ) { + // Create dropdown cell + td.classList.add("jexcel_dropdown"); + td.textContent = obj.getDropDownValue(i, value); + } else if (obj.options.columns[i].type == "color") { + if (obj.options.columns[i].render == "square") { + var color = document.createElement("div"); + color.className = "color"; + color.style.backgroundColor = value; + td.appendChild(color); + } else { + td.style.color = value; + td.textContent = value; + } + } else if (obj.options.columns[i].type == "image") { + if (value && value.substr(0, 10) == "data:image") { + var img = document.createElement("img"); + img.src = value; + td.appendChild(img); + } + } else { + if (obj.options.columns[i].type == "html") { + td.innerHTML = stripScript(obj.parseValue(i, j, value, td)); + } else { + if ( + obj.options.stripHTML === false || + obj.options.columns[i].stripHTML === false + ) { + td.innerHTML = stripScript(obj.parseValue(i, j, value, td)); + } else { + td.textContent = obj.parseValue(i, j, value, td); + } + } + } + } + + // Readonly + if (obj.options.columns[i].readOnly == true) { + td.className = "readonly"; + } + + // Text align + var colAlign = obj.options.columns[i].align + ? obj.options.columns[i].align + : "center"; + td.style.textAlign = colAlign; + + // Wrap option + if ( + obj.options.columns[i].wordWrap != false && + (obj.options.wordWrap == true || + obj.options.columns[i].wordWrap == true || + td.innerHTML.length > 200) + ) { + td.style.whiteSpace = "pre-wrap"; + } + + // Overflow + if (i > 0) { + if (this.options.textOverflow == true) { + if (value || td.innerHTML) { + obj.records[j][i - 1].style.overflow = "hidden"; + } else { + if (i == obj.options.columns.length - 1) { + td.style.overflow = "hidden"; + } + } + } + } + return td; + }; + + obj.createCellHeader = function (colNumber) { + // Create col global control + var colWidth = obj.options.columns[colNumber].width + ? obj.options.columns[colNumber].width + : obj.options.defaultColWidth; + var colAlign = obj.options.columns[colNumber].align + ? obj.options.columns[colNumber].align + : obj.options.defaultColAlign; + + // Create header cell + obj.headers[colNumber] = document.createElement("td"); + if (obj.options.stripHTML) { + obj.headers[colNumber].textContent = obj.options.columns[colNumber] + .title + ? obj.options.columns[colNumber].title + : jexcel.getColumnName(colNumber); + } else { + obj.headers[colNumber].innerHTML = obj.options.columns[colNumber].title + ? obj.options.columns[colNumber].title + : jexcel.getColumnName(colNumber); + } + obj.headers[colNumber].setAttribute("data-x", colNumber); + obj.headers[colNumber].style.textAlign = colAlign; + if (obj.options.columns[colNumber].title) { + obj.headers[colNumber].setAttribute( + "title", + obj.options.columns[colNumber].title + ); + } + if (obj.options.columns[colNumber].id) { + obj.headers[colNumber].setAttribute( + "id", + obj.options.columns[colNumber].id + ); + } + + // Width control + obj.colgroup[colNumber] = document.createElement("col"); + obj.colgroup[colNumber].setAttribute("width", colWidth); + + // Hidden column + if (obj.options.columns[colNumber].type == "hidden") { + obj.headers[colNumber].style.display = "none"; + obj.colgroup[colNumber].style.display = "none"; + } + }; + + /** + * Update a nested header title + */ + obj.updateNestedHeader = function (x, y, title) { + if (obj.options.nestedHeaders[y][x].title) { + obj.options.nestedHeaders[y][x].title = title; + obj.options.nestedHeaders[y].element.children[x + 1].textContent = + title; + } + }; + + /** + * Create a nested header object + */ + obj.createNestedHeader = function (nestedInformation) { + var tr = document.createElement("tr"); + tr.classList.add("jexcel_nested"); + var td = document.createElement("td"); + tr.appendChild(td); + // Element + nestedInformation.element = tr; + + var headerIndex = 0; + for (var i = 0; i < nestedInformation.length; i++) { + // Default values + if (!nestedInformation[i].colspan) { + nestedInformation[i].colspan = 1; + } + if (!nestedInformation[i].align) { + nestedInformation[i].align = "center"; + } + if (!nestedInformation[i].title) { + nestedInformation[i].title = ""; + } + + // Number of columns + var numberOfColumns = nestedInformation[i].colspan; + + // Classes container + var column = []; + // Header classes for this cell + for (var x = 0; x < numberOfColumns; x++) { + if ( + obj.options.columns[headerIndex] && + obj.options.columns[headerIndex].type == "hidden" + ) { + numberOfColumns++; + } + column.push(headerIndex); + headerIndex++; + } + + // Created the nested cell + var td = document.createElement("td"); + td.setAttribute("data-column", column.join(",")); + td.setAttribute("colspan", nestedInformation[i].colspan); + td.setAttribute("align", nestedInformation[i].align); + td.textContent = nestedInformation[i].title; + tr.appendChild(td); + } + + return tr; + }; + + /** + * Create toolbar + */ + obj.createToolbar = function (toolbar) { + if (toolbar) { + obj.options.toolbar = toolbar; + } else { + var toolbar = obj.options.toolbar; + } + for (var i = 0; i < toolbar.length; i++) { + if (toolbar[i].type == "i") { + var toolbarItem = document.createElement("i"); + toolbarItem.classList.add("jexcel_toolbar_item"); + toolbarItem.classList.add("material-icons"); + toolbarItem.setAttribute("data-k", toolbar[i].k); + toolbarItem.setAttribute("data-v", toolbar[i].v); + toolbarItem.setAttribute("id", toolbar[i].id); + + // Tooltip + if (toolbar[i].tooltip) { + toolbarItem.setAttribute("title", toolbar[i].tooltip); + } + // Handle click + if (toolbar[i].onclick && typeof toolbar[i].onclick) { + toolbarItem.onclick = (function (a) { + var b = a; + return function () { + toolbar[b].onclick(el, obj, this); + }; + })(i); + } else { + toolbarItem.onclick = function () { + var k = this.getAttribute("data-k"); + var v = this.getAttribute("data-v"); + obj.setStyle(obj.highlighted, k, v); + }; + } + // Append element + toolbarItem.textContent = toolbar[i].content; + obj.toolbar.appendChild(toolbarItem); + } else if (toolbar[i].type == "select") { + var toolbarItem = document.createElement("select"); + toolbarItem.classList.add("jexcel_toolbar_item"); + toolbarItem.setAttribute("data-k", toolbar[i].k); + // Tooltip + if (toolbar[i].tooltip) { + toolbarItem.setAttribute("title", toolbar[i].tooltip); + } + // Handle onchange + if (toolbar[i].onchange && typeof toolbar[i].onchange) { + toolbarItem.onchange = toolbar[i].onchange; + } else { + toolbarItem.onchange = function () { + var k = this.getAttribute("data-k"); + obj.setStyle(obj.highlighted, k, this.value); + }; + } + // Add options to the dropdown + for (var j = 0; j < toolbar[i].v.length; j++) { + var toolbarDropdownOption = document.createElement("option"); + toolbarDropdownOption.value = toolbar[i].v[j]; + toolbarDropdownOption.textContent = toolbar[i].v[j]; + toolbarItem.appendChild(toolbarDropdownOption); + } + obj.toolbar.appendChild(toolbarItem); + } else if (toolbar[i].type == "color") { + var toolbarItem = document.createElement("i"); + toolbarItem.classList.add("jexcel_toolbar_item"); + toolbarItem.classList.add("material-icons"); + toolbarItem.setAttribute("data-k", toolbar[i].k); + toolbarItem.setAttribute("data-v", ""); + // Tooltip + if (toolbar[i].tooltip) { + toolbarItem.setAttribute("title", toolbar[i].tooltip); + } + obj.toolbar.appendChild(toolbarItem); + toolbarItem.textContent = toolbar[i].content; + jSuites.color(toolbarItem, { + onchange: function (o, v) { + var k = o.getAttribute("data-k"); + obj.setStyle(obj.highlighted, k, v); + }, + }); + } + } + }; + + /** + * Merge cells + * @param cellName + * @param colspan + * @param rowspan + * @param ignoreHistoryAndEvents + */ + obj.setMerge = function ( + cellName, + colspan, + rowspan, + ignoreHistoryAndEvents + ) { + var test = false; + + if (!cellName) { + if (!obj.highlighted.length) { + alert(obj.options.text.noCellsSelected); + return null; + } else { + var x1 = parseInt(obj.highlighted[0].getAttribute("data-x")); + var y1 = parseInt(obj.highlighted[0].getAttribute("data-y")); + var x2 = parseInt( + obj.highlighted[obj.highlighted.length - 1].getAttribute("data-x") + ); + var y2 = parseInt( + obj.highlighted[obj.highlighted.length - 1].getAttribute("data-y") + ); + var cellName = jexcel.getColumnNameFromId([x1, y1]); + var colspan = x2 - x1 + 1; + var rowspan = y2 - y1 + 1; + } + } + + var cell = jexcel.getIdFromColumnName(cellName, true); + + if (obj.options.mergeCells[cellName]) { + if (obj.records[cell[1]][cell[0]].getAttribute("data-merged")) { + test = obj.options.text.cellAlreadyMerged; + } + } else if ((!colspan || colspan < 2) && (!rowspan || rowspan < 2)) { + test = obj.options.text.invalidMergeProperties; + } else { + var cells = []; + for (var j = cell[1]; j < cell[1] + rowspan; j++) { + for (var i = cell[0]; i < cell[0] + colspan; i++) { + var columnName = jexcel.getColumnNameFromId([i, j]); + if (obj.records[j][i].getAttribute("data-merged")) { + test = obj.options.text.thereIsAConflictWithAnotherMergedCell; + } + } + } + } + + if (test) { + alert(test); + } else { + // Add property + if (colspan > 1) { + obj.records[cell[1]][cell[0]].setAttribute("colspan", colspan); + } else { + colspan = 1; + } + if (rowspan > 1) { + obj.records[cell[1]][cell[0]].setAttribute("rowspan", rowspan); + } else { + rowspan = 1; + } + // Keep links to the existing nodes + obj.options.mergeCells[cellName] = [colspan, rowspan, []]; + // Mark cell as merged + obj.records[cell[1]][cell[0]].setAttribute("data-merged", "true"); + // Overflow + obj.records[cell[1]][cell[0]].style.overflow = "hidden"; + // History data + var data = []; + // Adjust the nodes + for (var y = cell[1]; y < cell[1] + rowspan; y++) { + for (var x = cell[0]; x < cell[0] + colspan; x++) { + if (!(cell[0] == x && cell[1] == y)) { + data.push(obj.options.data[y][x]); + obj.updateCell(x, y, "", true); + obj.options.mergeCells[cellName][2].push(obj.records[y][x]); + obj.records[y][x].style.display = "none"; + obj.records[y][x] = obj.records[cell[1]][cell[0]]; + } + } + } + // In the initialization is not necessary keep the history + obj.updateSelection(obj.records[cell[1]][cell[0]]); + + if (!ignoreHistoryAndEvents) { + obj.setHistory({ + action: "setMerge", + column: cellName, + colspan: colspan, + rowspan: rowspan, + data: data, + }); + + obj.dispatch("onmerge", el, cellName, colspan, rowspan); + } + } + }; + + /** + * Merge cells + * @param cellName + * @param colspan + * @param rowspan + * @param ignoreHistoryAndEvents + */ + obj.getMerge = function (cellName) { + var data = {}; + if (cellName) { + if (obj.options.mergeCells[cellName]) { + data = [ + obj.options.mergeCells[cellName][0], + obj.options.mergeCells[cellName][1], + ]; + } else { + data = null; + } + } else { + if (obj.options.mergeCells) { + var mergedCells = obj.options.mergeCells; + var keys = Object.keys(obj.options.mergeCells); + for (var i = 0; i < keys.length; i++) { + data[keys[i]] = [ + obj.options.mergeCells[keys[i]][0], + obj.options.mergeCells[keys[i]][1], + ]; + } + } + } + + return data; + }; + + /** + * Remove merge by cellname + * @param cellName + */ + obj.removeMerge = function (cellName, data, keepOptions) { + if (obj.options.mergeCells[cellName]) { + var cell = jexcel.getIdFromColumnName(cellName, true); + obj.records[cell[1]][cell[0]].removeAttribute("colspan"); + obj.records[cell[1]][cell[0]].removeAttribute("rowspan"); + obj.records[cell[1]][cell[0]].removeAttribute("data-merged"); + var info = obj.options.mergeCells[cellName]; + + var index = 0; + for (var j = 0; j < info[1]; j++) { + for (var i = 0; i < info[0]; i++) { + if (j > 0 || i > 0) { + obj.records[cell[1] + j][cell[0] + i] = info[2][index]; + obj.records[cell[1] + j][cell[0] + i].style.display = ""; + // Recover data + if (data && data[index]) { + obj.updateCell(cell[0] + i, cell[1] + j, data[index]); + } + index++; + } + } + } + + // Update selection + obj.updateSelection( + obj.records[cell[1]][cell[0]], + obj.records[cell[1] + j - 1][cell[0] + i - 1] + ); + + if (!keepOptions) { + delete obj.options.mergeCells[cellName]; + } + } + }; + + /** + * Remove all merged cells + */ + obj.destroyMerged = function (keepOptions) { + // Remove any merged cells + if (obj.options.mergeCells) { + var mergedCells = obj.options.mergeCells; + var keys = Object.keys(obj.options.mergeCells); + for (var i = 0; i < keys.length; i++) { + obj.removeMerge(keys[i], null, keepOptions); + } + } + }; + + /** + * Is column merged + */ + obj.isColMerged = function (x, insertBefore) { + var cols = []; + // Remove any merged cells + if (obj.options.mergeCells) { + var keys = Object.keys(obj.options.mergeCells); + for (var i = 0; i < keys.length; i++) { + var info = jexcel.getIdFromColumnName(keys[i], true); + var colspan = obj.options.mergeCells[keys[i]][0]; + var x1 = info[0]; + var x2 = info[0] + (colspan > 1 ? colspan - 1 : 0); + + if (insertBefore == null) { + if (x1 <= x && x2 >= x) { + cols.push(keys[i]); + } + } else { + if (insertBefore) { + if (x1 < x && x2 >= x) { + cols.push(keys[i]); + } + } else { + if (x1 <= x && x2 > x) { + cols.push(keys[i]); + } + } + } + } + } + + return cols; + }; + + /** + * Is rows merged + */ + obj.isRowMerged = function (y, insertBefore) { + var rows = []; + // Remove any merged cells + if (obj.options.mergeCells) { + var keys = Object.keys(obj.options.mergeCells); + for (var i = 0; i < keys.length; i++) { + var info = jexcel.getIdFromColumnName(keys[i], true); + var rowspan = obj.options.mergeCells[keys[i]][1]; + var y1 = info[1]; + var y2 = info[1] + (rowspan > 1 ? rowspan - 1 : 0); + + if (insertBefore == null) { + if (y1 <= y && y2 >= y) { + rows.push(keys[i]); + } + } else { + if (insertBefore) { + if (y1 < y && y2 >= y) { + rows.push(keys[i]); + } + } else { + if (y1 <= y && y2 > y) { + rows.push(keys[i]); + } + } + } + } + } + + return rows; + }; + + /** + * Open the column filter + */ + obj.openFilter = function (columnId) { + if (!obj.options.filters) { + console.log("Jspreadsheet: filters not enabled."); + } else { + // Make sure is integer + columnId = parseInt(columnId); + // Reset selection + obj.resetSelection(); + // Load options + var optionsFiltered = []; + if (obj.options.columns[columnId].type == "checkbox") { + optionsFiltered.push({ id: "true", name: "True" }); + optionsFiltered.push({ id: "false", name: "False" }); + } else { + var options = []; + var hasBlanks = false; + for (var j = 0; j < obj.options.data.length; j++) { + var k = obj.options.data[j][columnId]; + var v = obj.records[j][columnId].innerHTML; + if (k && v) { + options[k] = v; + } else { + var hasBlanks = true; + } + } + var keys = Object.keys(options); + var optionsFiltered = []; + for (var j = 0; j < keys.length; j++) { + optionsFiltered.push({ id: keys[j], name: options[keys[j]] }); + } + // Has blank options + if (hasBlanks) { + optionsFiltered.push({ value: "", id: "", name: "(Blanks)" }); + } + } + + // Create dropdown + var div = document.createElement("div"); + obj.filter.children[columnId + 1].innerHTML = ""; + obj.filter.children[columnId + 1].appendChild(div); + obj.filter.children[columnId + 1].style.paddingLeft = "0px"; + obj.filter.children[columnId + 1].style.paddingRight = "0px"; + obj.filter.children[columnId + 1].style.overflow = "initial"; + + var opt = { + data: optionsFiltered, + multiple: true, + autocomplete: true, + opened: true, + value: + obj.filters[columnId] !== undefined ? obj.filters[columnId] : null, + width: "100%", + position: + obj.options.tableOverflow == true || obj.options.fullscreen == true + ? true + : false, + onclose: function (o) { + obj.resetFilters(); + obj.filters[columnId] = o.dropdown.getValue(true); + obj.filter.children[columnId + 1].innerHTML = o.dropdown.getText(); + obj.filter.children[columnId + 1].style.paddingLeft = ""; + obj.filter.children[columnId + 1].style.paddingRight = ""; + obj.filter.children[columnId + 1].style.overflow = ""; + obj.closeFilter(columnId); + obj.refreshSelection(); + }, + }; + + // Dynamic dropdown + jSuites.dropdown(div, opt); + } + }; + + obj.resetFilters = function () { + if (obj.options.filters) { + for (var i = 0; i < obj.filter.children.length; i++) { + obj.filter.children[i].innerHTML = " "; + obj.filters[i] = null; + } + } + + obj.results = null; + obj.updateResult(); + }; + + obj.closeFilter = function (columnId) { + if (!columnId) { + for (var i = 0; i < obj.filter.children.length; i++) { + if (obj.filters[i]) { + columnId = i; + } + } + } + + // Search filter + var search = function (query, x, y) { + for (var i = 0; i < query.length; i++) { + var value = "" + obj.options.data[y][x]; + var label = "" + obj.records[y][x].innerHTML; + if (query[i] == value || query[i] == label) { + return true; + } + } + return false; + }; + + var query = obj.filters[columnId]; + obj.results = []; + for (var j = 0; j < obj.options.data.length; j++) { + if (search(query, columnId, j)) { + obj.results.push(j); + } + } + if (!obj.results.length) { + obj.results = null; + } + + obj.updateResult(); + }; + + /** + * Open the editor + * + * @param object cell + * @return void + */ + obj.openEditor = function (cell, empty, e) { + // Get cell position + var y = cell.getAttribute("data-y"); + var x = cell.getAttribute("data-x"); + + // On edition start + obj.dispatch("oneditionstart", el, cell, x, y); + + // Overflow + if (x > 0) { + obj.records[y][x - 1].style.overflow = "hidden"; + } + + // Create editor + var createEditor = function (type) { + // Cell information + var info = cell.getBoundingClientRect(); + + // Create dropdown + var editor = document.createElement(type); + editor.style.width = info.width + "px"; + editor.style.height = info.height - 2 + "px"; + editor.style.minHeight = info.height - 2 + "px"; + + // Edit cell + cell.classList.add("editor"); + cell.innerHTML = ""; + cell.appendChild(editor); + + // On edition start + obj.dispatch("oncreateeditor", el, cell, x, y, editor); + + return editor; + }; + + // Readonly + if (cell.classList.contains("readonly") == true) { + // Do nothing + } else { + // Holder + obj.edition = [obj.records[y][x], obj.records[y][x].innerHTML, x, y]; + + // If there is a custom editor for it + if (obj.options.columns[x].editor) { + // Custom editors + obj.options.columns[x].editor.openEditor(cell, el, empty, e); + } else { + // Native functions + if (obj.options.columns[x].type == "hidden") { + // Do nothing + } else if ( + obj.options.columns[x].type == "checkbox" || + obj.options.columns[x].type == "radio" + ) { + // Get value + var value = cell.children[0].checked ? false : true; + // Toogle value + obj.setValue(cell, value); + // Do not keep edition open + obj.edition = null; + } else if ( + obj.options.columns[x].type == "dropdown" || + obj.options.columns[x].type == "autocomplete" + ) { + // Get current value + var value = obj.options.data[y][x]; + if (obj.options.columns[x].multiple && !Array.isArray(value)) { + value = value.split(";"); + } + + // Create dropdown + if (typeof obj.options.columns[x].filter == "function") { + var source = obj.options.columns[x].filter( + el, + cell, + x, + y, + obj.options.columns[x].source + ); + } else { + var source = obj.options.columns[x].source; + } + + // Do not change the original source + var data = []; + for (var j = 0; j < source.length; j++) { + data.push(source[j]); + } + + // Create editor + var editor = createEditor("div"); + var options = { + data: data, + multiple: obj.options.columns[x].multiple ? true : false, + autocomplete: + obj.options.columns[x].autocomplete || + obj.options.columns[x].type == "autocomplete" + ? true + : false, + opened: true, + value: value, + width: "100%", + height: editor.style.minHeight, + position: + obj.options.tableOverflow == true || + obj.options.fullscreen == true + ? true + : false, + onclose: function () { + obj.closeEditor(cell, true); + }, + }; + if ( + obj.options.columns[x].options && + obj.options.columns[x].options.type + ) { + options.type = obj.options.columns[x].options.type; + } + jSuites.dropdown(editor, options); + } else if ( + obj.options.columns[x].type == "calendar" || + obj.options.columns[x].type == "color" + ) { + // Value + var value = obj.options.data[y][x]; + // Create editor + var editor = createEditor("input"); + editor.value = value; + + if ( + obj.options.tableOverflow == true || + obj.options.fullscreen == true + ) { + obj.options.columns[x].options.position = true; + } + obj.options.columns[x].options.value = obj.options.data[y][x]; + obj.options.columns[x].options.opened = true; + obj.options.columns[x].options.onclose = function (el, value) { + obj.closeEditor(cell, true); + }; + // Current value + if (obj.options.columns[x].type == "color") { + jSuites.color(editor, obj.options.columns[x].options); + } else { + jSuites.calendar(editor, obj.options.columns[x].options); + } + // Focus on editor + editor.focus(); + } else if (obj.options.columns[x].type == "html") { + var value = obj.options.data[y][x]; + // Create editor + var editor = createEditor("div"); + editor.style.position = "relative"; + var div = document.createElement("div"); + div.classList.add("jexcel_richtext"); + editor.appendChild(div); + jSuites.editor(div, { + focus: true, + value: value, + }); + var rect = cell.getBoundingClientRect(); + var rectContent = div.getBoundingClientRect(); + if (window.innerHeight < rect.bottom + rectContent.height) { + div.style.top = rect.top - (rectContent.height + 2) + "px"; + } else { + div.style.top = rect.top + "px"; + } + } else if (obj.options.columns[x].type == "image") { + // Value + var img = cell.children[0]; + // Create editor + var editor = createEditor("div"); + editor.style.position = "relative"; + var div = document.createElement("div"); + div.classList.add("jclose"); + if (img && img.src) { + div.appendChild(img); + } + editor.appendChild(div); + jSuites.image(div, obj.options.imageOptions); + var rect = cell.getBoundingClientRect(); + var rectContent = div.getBoundingClientRect(); + if (window.innerHeight < rect.bottom + rectContent.height) { + div.style.top = rect.top - (rectContent.height + 2) + "px"; + } else { + div.style.top = rect.top + "px"; + } + } else { + // Value + var value = empty == true ? "" : obj.options.data[y][x]; + + // Basic editor + if ( + obj.options.columns[x].wordWrap != false && + (obj.options.wordWrap == true || + obj.options.columns[x].wordWrap == true) + ) { + var editor = createEditor("textarea"); + } else { + var editor = createEditor("input"); + } + + editor.focus(); + editor.value = value; + + // Column options + var options = obj.options.columns[x]; + // Format + var opt = null; + + // Apply format when is not a formula + if (!isFormula(value)) { + // Format + if ((opt = getMask(options))) { + // Masking + if (!options.disabledMaskOnEdition) { + if (options.mask) { + var m = options.mask.split(";"); + editor.setAttribute("data-mask", m[0]); + } else if (options.locale) { + editor.setAttribute("data-locale", options.locale); + } + } + // Input + opt.input = editor; + // Configuration + editor.mask = opt; + // Do not treat the decimals + jSuites.mask.render(value, opt, false); + } + } + + editor.onblur = function () { + obj.closeEditor(cell, true); + }; + editor.scrollLeft = editor.scrollWidth; + } + } + } + }; + + /** + * Close the editor and save the information + * + * @param object cell + * @param boolean save + * @return void + */ + obj.closeEditor = function (cell, save) { + var x = parseInt(cell.getAttribute("data-x")); + var y = parseInt(cell.getAttribute("data-y")); + + // Get cell properties + if (save == true) { + // If custom editor + if (obj.options.columns[x].editor) { + // Custom editor + var value = obj.options.columns[x].editor.closeEditor(cell, save); + } else { + // Native functions + if ( + obj.options.columns[x].type == "checkbox" || + obj.options.columns[x].type == "radio" || + obj.options.columns[x].type == "hidden" + ) { + // Do nothing + } else if ( + obj.options.columns[x].type == "dropdown" || + obj.options.columns[x].type == "autocomplete" + ) { + var value = cell.children[0].dropdown.close(true); + } else if (obj.options.columns[x].type == "calendar") { + var value = cell.children[0].calendar.close(true); + } else if (obj.options.columns[x].type == "color") { + var value = cell.children[0].color.close(true); + } else if (obj.options.columns[x].type == "html") { + var value = cell.children[0].children[0].editor.getData(); + } else if (obj.options.columns[x].type == "image") { + var img = cell.children[0].children[0].children[0]; + var value = img && img.tagName == "IMG" ? img.src : ""; + } else if (obj.options.columns[x].type == "numeric") { + var value = cell.children[0].value; + if (("" + value).substr(0, 1) != "=") { + if (value == "") { + value = obj.options.columns[x].allowEmpty ? "" : 0; + } + } + cell.children[0].onblur = null; + } else { + var value = cell.children[0].value; + cell.children[0].onblur = null; + + // Column options + var options = obj.options.columns[x]; + // Format + var opt = null; + if ((opt = getMask(options))) { + // Keep numeric in the raw data + if ( + value !== "" && + !isFormula(value) && + typeof value !== "number" + ) { + var t = jSuites.mask.extract(value, opt, true); + if (t && t.value !== "") { + value = t.value; + } + } + } + } + } + + // Ignore changes if the value is the same + if (obj.options.data[y][x] == value) { + cell.innerHTML = obj.edition[1]; + } else { + obj.setValue(cell, value); + } + } else { + if (obj.options.columns[x].editor) { + // Custom editor + obj.options.columns[x].editor.closeEditor(cell, save); + } else { + if ( + obj.options.columns[x].type == "dropdown" || + obj.options.columns[x].type == "autocomplete" + ) { + cell.children[0].dropdown.close(true); + } else if (obj.options.columns[x].type == "calendar") { + cell.children[0].calendar.close(true); + } else if (obj.options.columns[x].type == "color") { + cell.children[0].color.close(true); + } else { + cell.children[0].onblur = null; + } + } + + // Restore value + cell.innerHTML = obj.edition && obj.edition[1] ? obj.edition[1] : ""; + } + + // On edition end + obj.dispatch("oneditionend", el, cell, x, y, value, save); + + // Remove editor class + cell.classList.remove("editor"); + + // Finish edition + obj.edition = null; + }; + + /** + * Get the cell object + * + * @param object cell + * @return string value + */ + obj.getCell = function (cell) { + // Convert in case name is excel liked ex. A10, BB92 + cell = jexcel.getIdFromColumnName(cell, true); + var x = cell[0]; + var y = cell[1]; + + return obj.records[y][x]; + }; + + /** + * Get the column options + * @param x + * @param y + * @returns {{type: string}} + */ + obj.getColumnOptions = function (x, y) { + // Type + var options = obj.options.columns[x]; + + // Cell type + if (!options) { + options = { type: "text" }; + } + + return options; + }; + + /** + * Get the cell object from coords + * + * @param object cell + * @return string value + */ + obj.getCellFromCoords = function (x, y) { + return obj.records[y][x]; + }; + + /** + * Get label + * + * @param object cell + * @return string value + */ + obj.getLabel = function (cell) { + // Convert in case name is excel liked ex. A10, BB92 + cell = jexcel.getIdFromColumnName(cell, true); + var x = cell[0]; + var y = cell[1]; + + return obj.records[y][x].innerHTML; + }; + + /** + * Get labelfrom coords + * + * @param object cell + * @return string value + */ + obj.getLabelFromCoords = function (x, y) { + return obj.records[y][x].innerHTML; + }; + + /** + * Get the value from a cell + * + * @param object cell + * @return string value + */ + obj.getValue = function (cell, processedValue) { + if (typeof cell == "object") { + var x = cell.getAttribute("data-x"); + var y = cell.getAttribute("data-y"); + } else { + cell = jexcel.getIdFromColumnName(cell, true); + var x = cell[0]; + var y = cell[1]; + } + + var value = null; + + if (x != null && y != null) { + if ( + obj.records[y] && + obj.records[y][x] && + (processedValue || obj.options.copyCompatibility == true) + ) { + value = obj.records[y][x].innerHTML; + } else { + if (obj.options.data[y] && obj.options.data[y][x] != "undefined") { + value = obj.options.data[y][x]; + } + } + } + + return value; + }; + + /** + * Get the value from a coords + * + * @param int x + * @param int y + * @return string value + */ + obj.getValueFromCoords = function (x, y, processedValue) { + var value = null; + + if (x != null && y != null) { + if ( + (obj.records[y] && obj.records[y][x] && processedValue) || + obj.options.copyCompatibility == true + ) { + value = obj.records[y][x].innerHTML; + } else { + if (obj.options.data[y] && obj.options.data[y][x] != "undefined") { + value = obj.options.data[y][x]; + } + } + } + + return value; + }; + + /** + * Set a cell value + * + * @param mixed cell destination cell + * @param string value value + * @return void + */ + obj.setValue = function (cell, value, force) { + var records = []; + + if (typeof cell == "string") { + var columnId = jexcel.getIdFromColumnName(cell, true); + var x = columnId[0]; + var y = columnId[1]; + + // Update cell + records.push(obj.updateCell(x, y, value, force)); + + // Update all formulas in the chain + obj.updateFormulaChain(x, y, records); + } else { + var x = null; + var y = null; + if (cell && cell.getAttribute) { + var x = cell.getAttribute("data-x"); + var y = cell.getAttribute("data-y"); + } + + // Update cell + if (x != null && y != null) { + records.push(obj.updateCell(x, y, value, force)); + + // Update all formulas in the chain + obj.updateFormulaChain(x, y, records); + } else { + var keys = Object.keys(cell); + if (keys.length > 0) { + for (var i = 0; i < keys.length; i++) { + if (typeof cell[i] == "string") { + var columnId = jexcel.getIdFromColumnName(cell[i], true); + var x = columnId[0]; + var y = columnId[1]; + } else { + if (cell[i].x != null && cell[i].y != null) { + var x = cell[i].x; + var y = cell[i].y; + // Flexible setup + if (cell[i].newValue != null) { + value = cell[i].newValue; + } else if (cell[i].value != null) { + value = cell[i].value; + } + } else { + var x = cell[i].getAttribute("data-x"); + var y = cell[i].getAttribute("data-y"); + } + } + + // Update cell + if (x != null && y != null) { + records.push(obj.updateCell(x, y, value, force)); + + // Update all formulas in the chain + obj.updateFormulaChain(x, y, records); + } + } + } + } + } + + // Update history + obj.setHistory({ + action: "setValue", + records: records, + selection: obj.selectedCell, + }); + + // Update table with custom configurations if applicable + obj.updateTable(); + + // On after changes + obj.onafterchanges(el, records); + }; + + /** + * Set a cell value based on coordinates + * + * @param int x destination cell + * @param int y destination cell + * @param string value + * @return void + */ + obj.setValueFromCoords = function (x, y, value, force) { + var records = []; + records.push(obj.updateCell(x, y, value, force)); + + // Update all formulas in the chain + obj.updateFormulaChain(x, y, records); + + // Update history + obj.setHistory({ + action: "setValue", + records: records, + selection: obj.selectedCell, + }); + + // Update table with custom configurations if applicable + obj.updateTable(); + + // On after changes + obj.onafterchanges(el, records); + }; + + /** + * Toogle + */ + obj.setCheckRadioValue = function () { + var records = []; + var keys = Object.keys(obj.highlighted); + for (var i = 0; i < keys.length; i++) { + var x = obj.highlighted[i].getAttribute("data-x"); + var y = obj.highlighted[i].getAttribute("data-y"); + + if ( + obj.options.columns[x].type == "checkbox" || + obj.options.columns[x].type == "radio" + ) { + // Update cell + records.push(obj.updateCell(x, y, !obj.options.data[y][x])); + } + } + + if (records.length) { + // Update history + obj.setHistory({ + action: "setValue", + records: records, + selection: obj.selectedCell, + }); + + // On after changes + obj.onafterchanges(el, records); + } + }; + /** + * Strip tags + */ + var stripScript = function (a) { + var b = new Option(); + b.innerHTML = a; + var c = null; + for (a = b.getElementsByTagName("script"); (c = a[0]); ) + c.parentNode.removeChild(c); + return b.innerHTML; + }; + + /** + * Update cell content + * + * @param object cell + * @return void + */ + obj.updateCell = function (x, y, value, force) { + // Changing value depending on the column type + if (obj.records[y][x].classList.contains("readonly") == true && !force) { + // Do nothing + var record = { + x: x, + y: y, + col: x, + row: y, + }; + } else { + // Security + if ( + ("" + value).substr(0, 1) == "=" && + obj.options.secureFormulas == true + ) { + var val = secureFormula(value); + if (val != value) { + // Update the data container + value = val; + } + } + + // On change + var val = obj.dispatch( + "onbeforechange", + el, + obj.records[y][x], + x, + y, + value + ); + + // If you return something this will overwrite the value + if (val != undefined) { + value = val; + } + + if ( + obj.options.columns[x].editor && + typeof obj.options.columns[x].editor.updateCell == "function" + ) { + value = obj.options.columns[x].editor.updateCell( + obj.records[y][x], + value, + force + ); + } + + // History format + var record = { + x: x, + y: y, + col: x, + row: y, + newValue: value, + oldValue: obj.options.data[y][x], + }; + + if (obj.options.columns[x].editor) { + // Update data and cell + obj.options.data[y][x] = value; + } else { + // Native functions + if ( + obj.options.columns[x].type == "checkbox" || + obj.options.columns[x].type == "radio" + ) { + // Unchecked all options + if (obj.options.columns[x].type == "radio") { + for (var j = 0; j < obj.options.data.length; j++) { + obj.options.data[j][x] = false; + } + } + + // Update data and cell + obj.records[y][x].children[0].checked = + value == 1 || value == true || value == "true" || value == "TRUE" + ? true + : false; + obj.options.data[y][x] = obj.records[y][x].children[0].checked; + } else if ( + obj.options.columns[x].type == "dropdown" || + obj.options.columns[x].type == "autocomplete" + ) { + // Update data and cell + obj.options.data[y][x] = value; + obj.records[y][x].textContent = obj.getDropDownValue(x, value); + } else if (obj.options.columns[x].type == "calendar") { + // Try formatted date + var formatted = null; + if (!validDate(value)) { + var tmp = jSuites.calendar.extractDateFromString( + value, + obj.options.columns[x].options.format + ); + if (tmp) { + formatted = tmp; + } + } + // Update data and cell + obj.options.data[y][x] = value; + obj.records[y][x].textContent = jSuites.calendar.getDateString( + formatted ? formatted : value, + obj.options.columns[x].options.format + ); + } else if (obj.options.columns[x].type == "color") { + // Update color + obj.options.data[y][x] = value; + // Render + if (obj.options.columns[x].render == "square") { + var color = document.createElement("div"); + color.className = "color"; + color.style.backgroundColor = value; + obj.records[y][x].textContent = ""; + obj.records[y][x].appendChild(color); + } else { + obj.records[y][x].style.color = value; + obj.records[y][x].textContent = value; + } + } else if (obj.options.columns[x].type == "image") { + value = "" + value; + obj.options.data[y][x] = value; + obj.records[y][x].innerHTML = ""; + if (value && value.substr(0, 10) == "data:image") { + var img = document.createElement("img"); + img.src = value; + obj.records[y][x].appendChild(img); + } + } else { + // Update data and cell + obj.options.data[y][x] = value; + // Label + if (obj.options.columns[x].type == "html") { + obj.records[y][x].innerHTML = stripScript( + obj.parseValue(x, y, value) + ); + } else { + if ( + obj.options.stripHTML === false || + obj.options.columns[x].stripHTML === false + ) { + obj.records[y][x].innerHTML = stripScript( + obj.parseValue(x, y, value, obj.records[y][x]) + ); + } else { + obj.records[y][x].textContent = obj.parseValue( + x, + y, + value, + obj.records[y][x] + ); + } + } + // Handle big text inside a cell + if ( + obj.options.columns[x].wordWrap != false && + (obj.options.wordWrap == true || + obj.options.columns[x].wordWrap == true || + obj.records[y][x].innerHTML.length > 200) + ) { + obj.records[y][x].style.whiteSpace = "pre-wrap"; + } else { + obj.records[y][x].style.whiteSpace = ""; + } + } + } + + // Overflow + if (x > 0) { + if (value) { + obj.records[y][x - 1].style.overflow = "hidden"; + } else { + obj.records[y][x - 1].style.overflow = ""; + } + } + + // On change + obj.dispatch( + "onchange", + el, + obj.records[y] && obj.records[y][x] ? obj.records[y][x] : null, + x, + y, + value, + record.oldValue + ); + } + + return record; + }; + + /** + * Helper function to copy data using the corner icon + */ + obj.copyData = function (o, d) { + // Get data from all selected cells + var data = obj.getData(true, true); + + // Selected cells + var h = obj.selectedContainer; + + // Cells + var x1 = parseInt(o.getAttribute("data-x")); + var y1 = parseInt(o.getAttribute("data-y")); + var x2 = parseInt(d.getAttribute("data-x")); + var y2 = parseInt(d.getAttribute("data-y")); + + // Records + var records = []; + var breakControl = false; + + if (h[0] == x1) { + // Vertical copy + if (y1 < h[1]) { + var rowNumber = y1 - h[1]; + } else { + var rowNumber = 1; + } + var colNumber = 0; + } else { + if (x1 < h[0]) { + var colNumber = x1 - h[0]; + } else { + var colNumber = 1; + } + var rowNumber = 0; + } + + // Copy data procedure + var posx = 0; + var posy = 0; + + for (var j = y1; j <= y2; j++) { + // Skip hidden rows + if (obj.rows[j] && obj.rows[j].style.display == "none") { + continue; + } + + // Controls + if (data[posy] == undefined) { + posy = 0; + } + posx = 0; + + // Data columns + if (h[0] != x1) { + if (x1 < h[0]) { + var colNumber = x1 - h[0]; + } else { + var colNumber = 1; + } + } + // Data columns + for (var i = x1; i <= x2; i++) { + // Update non-readonly + if ( + obj.records[j][i] && + !obj.records[j][i].classList.contains("readonly") && + obj.records[j][i].style.display != "none" && + breakControl == false + ) { + // Stop if contains value + if (!obj.selection.length) { + if (obj.options.data[j][i] != "") { + breakControl = true; + continue; + } + } + + // Column + if (data[posy] == undefined) { + posx = 0; + } else if (data[posy][posx] == undefined) { + posx = 0; + } + + // Value + var value = data[posy][posx]; + + if (value && !data[1] && obj.options.autoIncrement == true) { + if ( + obj.options.columns[i].type == "text" || + obj.options.columns[i].type == "number" + ) { + if (("" + value).substr(0, 1) == "=") { + var tokens = value.match(/([A-Z]+[0-9]+)/g); + + if (tokens) { + var affectedTokens = []; + for (var index = 0; index < tokens.length; index++) { + var position = jexcel.getIdFromColumnName( + tokens[index], + 1 + ); + position[0] += colNumber; + position[1] += rowNumber; + if (position[1] < 0) { + position[1] = 0; + } + var token = jexcel.getColumnNameFromId([ + position[0], + position[1], + ]); + + if (token != tokens[index]) { + affectedTokens[tokens[index]] = token; + } + } + // Update formula + if (affectedTokens) { + value = obj.updateFormula(value, affectedTokens); + } + } + } else { + if (value == Number(value)) { + value = Number(value) + rowNumber; + } + } + } else if (obj.options.columns[i].type == "calendar") { + var date = new Date(value); + date.setDate(date.getDate() + rowNumber); + value = + date.getFullYear() + + "-" + + jexcel.doubleDigitFormat(parseInt(date.getMonth() + 1)) + + "-" + + jexcel.doubleDigitFormat(date.getDate()) + + " " + + "00:00:00"; + } + } + + records.push(obj.updateCell(i, j, value)); + + // Update all formulas in the chain + obj.updateFormulaChain(i, j, records); + } + posx++; + if (h[0] != x1) { + colNumber++; + } + } + posy++; + rowNumber++; + } + + // Update history + obj.setHistory({ + action: "setValue", + records: records, + selection: obj.selectedCell, + }); + + // Update table with custom configuration if applicable + obj.updateTable(); + + // On after changes + obj.onafterchanges(el, records); + }; + + /** + * Refresh current selection + */ + obj.refreshSelection = function () { + if (obj.selectedCell) { + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + } + }; + + /** + * Move coords to A1 in case overlaps with an excluded cell + */ + obj.conditionalSelectionUpdate = function (type, o, d) { + if (type == 1) { + if ( + obj.selectedCell && + ((o >= obj.selectedCell[1] && o <= obj.selectedCell[3]) || + (d >= obj.selectedCell[1] && d <= obj.selectedCell[3])) + ) { + obj.resetSelection(); + return; + } + } else { + if ( + obj.selectedCell && + ((o >= obj.selectedCell[0] && o <= obj.selectedCell[2]) || + (d >= obj.selectedCell[0] && d <= obj.selectedCell[2])) + ) { + obj.resetSelection(); + return; + } + } + }; + + /** + * Clear table selection + */ + obj.resetSelection = function (blur) { + // Remove style + if (!obj.highlighted.length) { + var previousStatus = 0; + } else { + var previousStatus = 1; + + for (var i = 0; i < obj.highlighted.length; i++) { + obj.highlighted[i].classList.remove("highlight"); + obj.highlighted[i].classList.remove("highlight-left"); + obj.highlighted[i].classList.remove("highlight-right"); + obj.highlighted[i].classList.remove("highlight-top"); + obj.highlighted[i].classList.remove("highlight-bottom"); + obj.highlighted[i].classList.remove("highlight-selected"); + + var px = parseInt(obj.highlighted[i].getAttribute("data-x")); + var py = parseInt(obj.highlighted[i].getAttribute("data-y")); + + // Check for merged cells + if (obj.highlighted[i].getAttribute("data-merged")) { + var colspan = parseInt(obj.highlighted[i].getAttribute("colspan")); + var rowspan = parseInt(obj.highlighted[i].getAttribute("rowspan")); + var ux = colspan > 0 ? px + (colspan - 1) : px; + var uy = rowspan > 0 ? py + (rowspan - 1) : py; + } else { + var ux = px; + var uy = py; + } + + // Remove selected from headers + for (var j = px; j <= ux; j++) { + if (obj.headers[j]) { + obj.headers[j].classList.remove("selected"); + } + } + + // Remove selected from rows + for (var j = py; j <= uy; j++) { + if (obj.rows[j]) { + obj.rows[j].classList.remove("selected"); + } + } + } + } + + // Reset highlighted cells + obj.highlighted = []; + + // Reset + obj.selectedCell = null; + + // Hide corner + obj.corner.style.top = "-2000px"; + obj.corner.style.left = "-2000px"; + + if (blur == true && previousStatus == 1) { + obj.dispatch("onblur", el); + } + + return previousStatus; + }; + + /** + * Update selection based on two cells + */ + obj.updateSelection = function (el1, el2, origin) { + var x1 = el1.getAttribute("data-x"); + var y1 = el1.getAttribute("data-y"); + if (el2) { + var x2 = el2.getAttribute("data-x"); + var y2 = el2.getAttribute("data-y"); + } else { + var x2 = x1; + var y2 = y1; + } + + obj.updateSelectionFromCoords(x1, y1, x2, y2, origin); + }; + + /** + * Update selection from coords + */ + obj.updateSelectionFromCoords = function (x1, y1, x2, y2, origin) { + // Reset Selection + var updated = null; + var previousState = obj.resetSelection(); + + // select column + if (y1 == null) { + y1 = 0; + y2 = obj.rows.length - 1; + } + + // Same element + if (x2 == null) { + x2 = x1; + } + if (y2 == null) { + y2 = y1; + } + + // Selection must be within the existing data + if (x1 >= obj.headers.length) { + x1 = obj.headers.length - 1; + } + if (y1 >= obj.rows.length) { + y1 = obj.rows.length - 1; + } + if (x2 >= obj.headers.length) { + x2 = obj.headers.length - 1; + } + if (y2 >= obj.rows.length) { + y2 = obj.rows.length - 1; + } + + // Keep selected cell + obj.selectedCell = [x1, y1, x2, y2]; + + // Select cells + if (x1 != null) { + // Add selected cell + if (obj.records[y1][x1]) { + obj.records[y1][x1].classList.add("highlight-selected"); + } + + // Origin & Destination + if (parseInt(x1) < parseInt(x2)) { + var px = parseInt(x1); + var ux = parseInt(x2); + } else { + var px = parseInt(x2); + var ux = parseInt(x1); + } + + if (parseInt(y1) < parseInt(y2)) { + var py = parseInt(y1); + var uy = parseInt(y2); + } else { + var py = parseInt(y2); + var uy = parseInt(y1); + } + + // Verify merged columns + for (var i = px; i <= ux; i++) { + for (var j = py; j <= uy; j++) { + if ( + obj.records[j][i] && + obj.records[j][i].getAttribute("data-merged") + ) { + var x = parseInt(obj.records[j][i].getAttribute("data-x")); + var y = parseInt(obj.records[j][i].getAttribute("data-y")); + var colspan = parseInt(obj.records[j][i].getAttribute("colspan")); + var rowspan = parseInt(obj.records[j][i].getAttribute("rowspan")); + + if (colspan > 1) { + if (x < px) { + px = x; + } + if (x + colspan > ux) { + ux = x + colspan - 1; + } + } + + if (rowspan) { + if (y < py) { + py = y; + } + if (y + rowspan > uy) { + uy = y + rowspan - 1; + } + } + } + } + } + + // Limits + var borderLeft = null; + var borderRight = null; + var borderTop = null; + var borderBottom = null; + + // Vertical limits + for (var j = py; j <= uy; j++) { + if (obj.rows[j].style.display != "none") { + if (borderTop == null) { + borderTop = j; + } + borderBottom = j; + } + } + + // Redefining styles + for (var i = px; i <= ux; i++) { + for (var j = py; j <= uy; j++) { + if ( + obj.rows[j].style.display != "none" && + obj.records[j][i].style.display != "none" + ) { + obj.records[j][i].classList.add("highlight"); + obj.highlighted.push(obj.records[j][i]); + } + } + + // Horizontal limits + if (obj.options.columns[i].type != "hidden") { + if (borderLeft == null) { + borderLeft = i; + } + borderRight = i; + } + } + + // Create borders + if (!borderLeft) { + borderLeft = 0; + } + if (!borderRight) { + borderRight = 0; + } + for (var i = borderLeft; i <= borderRight; i++) { + if (obj.options.columns[i].type != "hidden") { + // Top border + if (obj.records[borderTop] && obj.records[borderTop][i]) { + obj.records[borderTop][i].classList.add("highlight-top"); + } + // Bottom border + if (obj.records[borderBottom] && obj.records[borderBottom][i]) { + obj.records[borderBottom][i].classList.add("highlight-bottom"); + } + // Add selected from headers + obj.headers[i].classList.add("selected"); + } + } + + for (var j = borderTop; j <= borderBottom; j++) { + if (obj.rows[j] && obj.rows[j].style.display != "none") { + // Left border + obj.records[j][borderLeft].classList.add("highlight-left"); + // Right border + obj.records[j][borderRight].classList.add("highlight-right"); + // Add selected from rows + obj.rows[j].classList.add("selected"); + } + } + + obj.selectedContainer = [ + borderLeft, + borderTop, + borderRight, + borderBottom, + ]; + } + + // Handle events + if (previousState == 0) { + obj.dispatch("onfocus", el); + + obj.removeCopyingSelection(); + } + + obj.dispatch( + "onselection", + el, + borderLeft, + borderTop, + borderRight, + borderBottom, + origin + ); + + // Find corner cell + obj.updateCornerPosition(); + }; + + /** + * Remove copy selection + * + * @return void + */ + obj.removeCopySelection = function () { + // Remove current selection + for (var i = 0; i < obj.selection.length; i++) { + obj.selection[i].classList.remove("selection"); + obj.selection[i].classList.remove("selection-left"); + obj.selection[i].classList.remove("selection-right"); + obj.selection[i].classList.remove("selection-top"); + obj.selection[i].classList.remove("selection-bottom"); + } + + obj.selection = []; + }; + + /** + * Update copy selection + * + * @param int x, y + * @return void + */ + obj.updateCopySelection = function (x3, y3) { + // Remove selection + obj.removeCopySelection(); + + // Get elements first and last + var x1 = obj.selectedContainer[0]; + var y1 = obj.selectedContainer[1]; + var x2 = obj.selectedContainer[2]; + var y2 = obj.selectedContainer[3]; + + if (x3 != null && y3 != null) { + if (x3 - x2 > 0) { + var px = parseInt(x2) + 1; + var ux = parseInt(x3); + } else { + var px = parseInt(x3); + var ux = parseInt(x1) - 1; + } + + if (y3 - y2 > 0) { + var py = parseInt(y2) + 1; + var uy = parseInt(y3); + } else { + var py = parseInt(y3); + var uy = parseInt(y1) - 1; + } + + if (ux - px <= uy - py) { + var px = parseInt(x1); + var ux = parseInt(x2); + } else { + var py = parseInt(y1); + var uy = parseInt(y2); + } + + for (var j = py; j <= uy; j++) { + for (var i = px; i <= ux; i++) { + if ( + obj.records[j][i] && + obj.rows[j].style.display != "none" && + obj.records[j][i].style.display != "none" + ) { + obj.records[j][i].classList.add("selection"); + obj.records[py][i].classList.add("selection-top"); + obj.records[uy][i].classList.add("selection-bottom"); + obj.records[j][px].classList.add("selection-left"); + obj.records[j][ux].classList.add("selection-right"); + + // Persist selected elements + obj.selection.push(obj.records[j][i]); + } + } + } + } + }; + + /** + * Update corner position + * + * @return void + */ + obj.updateCornerPosition = function () { + // If any selected cells + if (!obj.highlighted.length) { + obj.corner.style.top = "-2000px"; + obj.corner.style.left = "-2000px"; + } else { + // Get last cell + var last = obj.highlighted[obj.highlighted.length - 1]; + var lastX = last.getAttribute("data-x"); + + var contentRect = obj.content.getBoundingClientRect(); + var x1 = contentRect.left; + var y1 = contentRect.top; + + var lastRect = last.getBoundingClientRect(); + var x2 = lastRect.left; + var y2 = lastRect.top; + var w2 = lastRect.width; + var h2 = lastRect.height; + + var x = x2 - x1 + obj.content.scrollLeft + w2 - 4; + var y = y2 - y1 + obj.content.scrollTop + h2 - 4; + + // Place the corner in the correct place + obj.corner.style.top = y + "px"; + obj.corner.style.left = x + "px"; + + if (obj.options.freezeColumns) { + var width = obj.getFreezeWidth(); + // Only check if the last column is not part of the merged cells + if (lastX > obj.options.freezeColumns - 1 && x2 - x1 + w2 < width) { + obj.corner.style.display = "none"; + } else { + if (obj.options.selectionCopy == true) { + obj.corner.style.display = ""; + } + } + } else { + if (obj.options.selectionCopy == true) { + obj.corner.style.display = ""; + } + } + } + }; + + /** + * Update scroll position based on the selection + */ + obj.updateScroll = function (direction) { + // Jspreadsheet Container information + var contentRect = obj.content.getBoundingClientRect(); + var x1 = contentRect.left; + var y1 = contentRect.top; + var w1 = contentRect.width; + var h1 = contentRect.height; + + // Direction Left or Up + var reference = obj.records[obj.selectedCell[3]][obj.selectedCell[2]]; + + // Reference + var referenceRect = reference.getBoundingClientRect(); + var x2 = referenceRect.left; + var y2 = referenceRect.top; + var w2 = referenceRect.width; + var h2 = referenceRect.height; + + // Direction + if (direction == 0 || direction == 1) { + var x = x2 - x1 + obj.content.scrollLeft; + var y = y2 - y1 + obj.content.scrollTop - 2; + } else { + var x = x2 - x1 + obj.content.scrollLeft + w2; + var y = y2 - y1 + obj.content.scrollTop + h2; + } + + // Top position check + if (y > obj.content.scrollTop + 30 && y < obj.content.scrollTop + h1) { + // In the viewport + } else { + // Out of viewport + if (y < obj.content.scrollTop + 30) { + obj.content.scrollTop = y - h2; + } else { + obj.content.scrollTop = y - (h1 - 2); + } + } + + // Freeze columns? + var freezed = obj.getFreezeWidth(); + + // Left position check - TODO: change that to the bottom border of the element + if ( + x > obj.content.scrollLeft + freezed && + x < obj.content.scrollLeft + w1 + ) { + // In the viewport + } else { + // Out of viewport + if (x < obj.content.scrollLeft + 30) { + obj.content.scrollLeft = x; + if (obj.content.scrollLeft < 50) { + obj.content.scrollLeft = 0; + } + } else if (x < obj.content.scrollLeft + freezed) { + obj.content.scrollLeft = x - freezed - 1; + } else { + obj.content.scrollLeft = x - (w1 - 20); + } + } + }; + + /** + * Get the column width + * + * @param int column column number (first column is: 0) + * @return int current width + */ + obj.getWidth = function (column) { + if (typeof column === "undefined") { + // Get all headers + var data = []; + for (var i = 0; i < obj.headers.length; i++) { + data.push(obj.options.columns[i].width); + } + } else { + // In case the column is an object + if (typeof column == "object") { + column = $(column).getAttribute("data-x"); + } + + data = obj.colgroup[column].getAttribute("width"); + } + + return data; + }; + + /** + * Set the column width + * + * @param int column number (first column is: 0) + * @param int new column width + * @param int old column width + */ + obj.setWidth = function (column, width, oldWidth) { + if (width) { + if (Array.isArray(column)) { + // Oldwidth + if (!oldWidth) { + var oldWidth = []; + } + // Set width + for (var i = 0; i < column.length; i++) { + if (!oldWidth[i]) { + oldWidth[i] = obj.colgroup[column[i]].getAttribute("width"); + } + var w = Array.isArray(width) && width[i] ? width[i] : width; + obj.colgroup[column[i]].setAttribute("width", w); + obj.options.columns[column[i]].width = w; + } + } else { + // Oldwidth + if (!oldWidth) { + oldWidth = obj.colgroup[column].getAttribute("width"); + } + // Set width + obj.colgroup[column].setAttribute("width", width); + obj.options.columns[column].width = width; + } + + // Keeping history of changes + obj.setHistory({ + action: "setWidth", + column: column, + oldValue: oldWidth, + newValue: width, + }); + + // On resize column + obj.dispatch("onresizecolumn", el, column, width, oldWidth); + + // Update corner position + obj.updateCornerPosition(); + } + }; + + /** + * Set the row height + * + * @param row - row number (first row is: 0) + * @param height - new row height + * @param oldHeight - old row height + */ + obj.setHeight = function (row, height, oldHeight) { + if (height > 0) { + // In case the column is an object + if (typeof row == "object") { + row = row.getAttribute("data-y"); + } + + // Oldwidth + if (!oldHeight) { + oldHeight = obj.rows[row].getAttribute("height"); + + if (!oldHeight) { + var rect = obj.rows[row].getBoundingClientRect(); + oldHeight = rect.height; + } + } + + // Integer + height = parseInt(height); + + // Set width + obj.rows[row].style.height = height + "px"; + + // Keep options updated + if (!obj.options.rows[row]) { + obj.options.rows[row] = {}; + } + obj.options.rows[row].height = height; + + // Keeping history of changes + obj.setHistory({ + action: "setHeight", + row: row, + oldValue: oldHeight, + newValue: height, + }); + + // On resize column + obj.dispatch("onresizerow", el, row, height, oldHeight); + + // Update corner position + obj.updateCornerPosition(); + } + }; + + /** + * Get the row height + * + * @param row - row number (first row is: 0) + * @return height - current row height + */ + obj.getHeight = function (row) { + if (typeof row === "undefined") { + // Get height of all rows + var data = []; + for (var j = 0; j < obj.rows.length; j++) { + var h = obj.rows[j].style.height; + if (h) { + data[j] = h; + } + } + } else { + // In case the row is an object + if (typeof row == "object") { + row = $(row).getAttribute("data-y"); + } + + var data = obj.rows[row].style.height; + } + + return data; + }; + + obj.setFooter = function (data) { + if (data) { + obj.options.footers = data; + } + + if (obj.options.footers) { + if (!obj.tfoot) { + obj.tfoot = document.createElement("tfoot"); + obj.table.appendChild(obj.tfoot); + } + + for (var j = 0; j < obj.options.footers.length; j++) { + if (obj.tfoot.children[j]) { + var tr = obj.tfoot.children[j]; + } else { + var tr = document.createElement("tr"); + var td = document.createElement("td"); + tr.appendChild(td); + obj.tfoot.appendChild(tr); + } + for (var i = 0; i < obj.headers.length; i++) { + if (!obj.options.footers[j][i]) { + obj.options.footers[j][i] = ""; + } + if (obj.tfoot.children[j].children[i + 1]) { + var td = obj.tfoot.children[j].children[i + 1]; + } else { + var td = document.createElement("td"); + tr.appendChild(td); + + // Text align + var colAlign = obj.options.columns[i].align + ? obj.options.columns[i].align + : "center"; + td.style.textAlign = colAlign; + } + td.textContent = obj.parseValue( + +obj.records.length + i, + j, + obj.options.footers[j][i] + ); + + // Hide/Show with hideColumn()/showColumn() + td.style.display = obj.colgroup[i].style.display; + } + } + } + }; + + /** + * Get the column title + * + * @param column - column number (first column is: 0) + * @param title - new column title + */ + obj.getHeader = function (column) { + return obj.headers[column].textContent; + }; + + /** + * Set the column title + * + * @param column - column number (first column is: 0) + * @param title - new column title + */ + obj.setHeader = function (column, newValue) { + if (obj.headers[column]) { + var oldValue = obj.headers[column].textContent; + + if (!newValue) { + newValue = prompt(obj.options.text.columnName, oldValue); + } + + if (newValue) { + obj.headers[column].textContent = newValue; + // Keep the title property + obj.headers[column].setAttribute("title", newValue); + // Update title + obj.options.columns[column].title = newValue; + } + + obj.setHistory({ + action: "setHeader", + column: column, + oldValue: oldValue, + newValue: newValue, + }); + + // On onchange header + obj.dispatch("onchangeheader", el, column, oldValue, newValue); + } + }; + + /** + * Get the headers + * + * @param asArray + * @return mixed + */ + obj.getHeaders = function (asArray) { + var title = []; + + for (var i = 0; i < obj.headers.length; i++) { + title.push(obj.getHeader(i)); + } + + return asArray ? title : title.join(obj.options.csvDelimiter); + }; + + /** + * Get meta information from cell(s) + * + * @return integer + */ + obj.getMeta = function (cell, key) { + if (!cell) { + return obj.options.meta; + } else { + if (key) { + return obj.options.meta[cell] && obj.options.meta[cell][key] + ? obj.options.meta[cell][key] + : null; + } else { + return obj.options.meta[cell] ? obj.options.meta[cell] : null; + } + } + }; + + /** + * Set meta information to cell(s) + * + * @return integer + */ + obj.setMeta = function (o, k, v) { + if (!obj.options.meta) { + obj.options.meta = {}; + } + + if (k && v) { + // Set data value + if (!obj.options.meta[o]) { + obj.options.meta[o] = {}; + } + obj.options.meta[o][k] = v; + } else { + // Apply that for all cells + var keys = Object.keys(o); + for (var i = 0; i < keys.length; i++) { + if (!obj.options.meta[keys[i]]) { + obj.options.meta[keys[i]] = {}; + } + + var prop = Object.keys(o[keys[i]]); + for (var j = 0; j < prop.length; j++) { + obj.options.meta[keys[i]][prop[j]] = o[keys[i]][prop[j]]; + } + } + } + + obj.dispatch("onchangemeta", el, o, k, v); + }; + + /** + * Update meta information + * + * @return integer + */ + obj.updateMeta = function (affectedCells) { + if (obj.options.meta) { + var newMeta = {}; + var keys = Object.keys(obj.options.meta); + for (var i = 0; i < keys.length; i++) { + if (affectedCells[keys[i]]) { + newMeta[affectedCells[keys[i]]] = obj.options.meta[keys[i]]; + } else { + newMeta[keys[i]] = obj.options.meta[keys[i]]; + } + } + // Update meta information + obj.options.meta = newMeta; + } + }; + + /** + * Get style information from cell(s) + * + * @return integer + */ + (obj.getStyle = function (cell, key) { + // Cell + if (!cell) { + // Control vars + var data = {}; + + // Column and row length + var x = obj.options.data[0].length; + var y = obj.options.data.length; + + // Go through the columns to get the data + for (var j = 0; j < y; j++) { + for (var i = 0; i < x; i++) { + // Value + var v = key + ? obj.records[j][i].style[key] + : obj.records[j][i].getAttribute("style"); + + // Any meta data for this column? + if (v) { + // Column name + var k = jexcel.getColumnNameFromId([i, j]); + // Value + data[k] = v; + } + } + } + + return data; + } else { + cell = jexcel.getIdFromColumnName(cell, true); + + return key + ? obj.records[cell[1]][cell[0]].style[key] + : obj.records[cell[1]][cell[0]].getAttribute("style"); + } + }), + (obj.resetStyle = function (o, ignoreHistoryAndEvents) { + var keys = Object.keys(o); + for (var i = 0; i < keys.length; i++) { + // Position + var cell = jexcel.getIdFromColumnName(keys[i], true); + if (obj.records[cell[1]] && obj.records[cell[1]][cell[0]]) { + obj.records[cell[1]][cell[0]].setAttribute("style", ""); + } + } + obj.setStyle(o, null, null, null, ignoreHistoryAndEvents); + }); + + /** + * Set meta information to cell(s) + * + * @return integer + */ + obj.setStyle = function (o, k, v, force, ignoreHistoryAndEvents) { + var newValue = {}; + var oldValue = {}; + + // Apply style + var applyStyle = function (cellId, key, value) { + // Position + var cell = jexcel.getIdFromColumnName(cellId, true); + + if ( + obj.records[cell[1]] && + obj.records[cell[1]][cell[0]] && + (obj.records[cell[1]][cell[0]].classList.contains("readonly") == + false || + force) + ) { + // Current value + var currentValue = obj.records[cell[1]][cell[0]].style[key]; + + // Change layout + if (currentValue == value && !force) { + value = ""; + obj.records[cell[1]][cell[0]].style[key] = ""; + } else { + obj.records[cell[1]][cell[0]].style[key] = value; + } + + // History + if (!oldValue[cellId]) { + oldValue[cellId] = []; + } + if (!newValue[cellId]) { + newValue[cellId] = []; + } + + oldValue[cellId].push([key + ":" + currentValue]); + newValue[cellId].push([key + ":" + value]); + } + }; + + if (k && v) { + // Get object from string + if (typeof o == "string") { + applyStyle(o, k, v); + } else { + // Avoid duplications + var oneApplication = []; + // Apply that for all cells + for (var i = 0; i < o.length; i++) { + var x = o[i].getAttribute("data-x"); + var y = o[i].getAttribute("data-y"); + var cellName = jexcel.getColumnNameFromId([x, y]); + // This happens when is a merged cell + if (!oneApplication[cellName]) { + applyStyle(cellName, k, v); + oneApplication[cellName] = true; + } + } + } + } else { + var keys = Object.keys(o); + for (var i = 0; i < keys.length; i++) { + var style = o[keys[i]]; + if (typeof style == "string") { + style = style.split(";"); + } + for (var j = 0; j < style.length; j++) { + if (typeof style[j] == "string") { + style[j] = style[j].split(":"); + } + // Apply value + if (style[j][0].trim()) { + applyStyle(keys[i], style[j][0].trim(), style[j][1]); + } + } + } + } + + var keys = Object.keys(oldValue); + for (var i = 0; i < keys.length; i++) { + oldValue[keys[i]] = oldValue[keys[i]].join(";"); + } + var keys = Object.keys(newValue); + for (var i = 0; i < keys.length; i++) { + newValue[keys[i]] = newValue[keys[i]].join(";"); + } + + if (!ignoreHistoryAndEvents) { + // Keeping history of changes + obj.setHistory({ + action: "setStyle", + oldValue: oldValue, + newValue: newValue, + }); + } + + obj.dispatch("onchangestyle", el, o, k, v); + }; + + /** + * Get cell comments, null cell for all + */ + obj.getComments = function (cell, withAuthor) { + if (cell) { + if (typeof cell == "string") { + var cell = jexcel.getIdFromColumnName(cell, true); + } + + if (withAuthor) { + return [ + obj.records[cell[1]][cell[0]].getAttribute("title"), + obj.records[cell[1]][cell[0]].getAttribute("author"), + ]; + } else { + return obj.records[cell[1]][cell[0]].getAttribute("title") || ""; + } + } else { + var data = {}; + for (var j = 0; j < obj.options.data.length; j++) { + for (var i = 0; i < obj.options.columns.length; i++) { + var comments = obj.records[j][i].getAttribute("title"); + if (comments) { + var cell = jexcel.getColumnNameFromId([i, j]); + data[cell] = comments; + } + } + } + return data; + } + }; + + /** + * Set cell comments + */ + obj.setComments = function (cellId, comments, author) { + if (typeof cellId == "string") { + var cell = jexcel.getIdFromColumnName(cellId, true); + } else { + var cell = cellId; + } + + // Keep old value + var title = obj.records[cell[1]][cell[0]].getAttribute("title"); + var author = obj.records[cell[1]][cell[0]].getAttribute("data-author"); + var oldValue = [title, author]; + + // Set new values + obj.records[cell[1]][cell[0]].setAttribute( + "title", + comments ? comments : "" + ); + obj.records[cell[1]][cell[0]].setAttribute( + "data-author", + author ? author : "" + ); + + // Remove class if there is no comment + if (comments) { + obj.records[cell[1]][cell[0]].classList.add("jexcel_comments"); + } else { + obj.records[cell[1]][cell[0]].classList.remove("jexcel_comments"); + } + + // Save history + obj.setHistory({ + action: "setComments", + column: cellId, + newValue: [comments, author], + oldValue: oldValue, + }); + // Set comments + obj.dispatch("oncomments", el, comments, title, cell, cell[0], cell[1]); + }; + + /** + * Get table config information + */ + obj.getConfig = function () { + var options = obj.options; + options.style = obj.getStyle(); + options.mergeCells = obj.getMerge(); + options.comments = obj.getComments(); + + return options; + }; + + /** + * Sort data and reload table + */ + obj.orderBy = function (column, order) { + if (column >= 0) { + // Merged cells + if (Object.keys(obj.options.mergeCells).length > 0) { + if ( + !confirm( + obj.options.text + .thisActionWillDestroyAnyExistingMergedCellsAreYouSure + ) + ) { + return false; + } else { + // Remove merged cells + obj.destroyMerged(); + } + } + + // Direction + if (order == null) { + order = obj.headers[column].classList.contains("arrow-down") ? 1 : 0; + } else { + order = order ? 1 : 0; + } + + // Test order + var temp = []; + if ( + obj.options.columns[column].type == "number" || + obj.options.columns[column].type == "numeric" || + obj.options.columns[column].type == "percentage" || + obj.options.columns[column].type == "autonumber" || + obj.options.columns[column].type == "color" + ) { + for (var j = 0; j < obj.options.data.length; j++) { + temp[j] = [j, Number(obj.options.data[j][column])]; + } + } else if ( + obj.options.columns[column].type == "calendar" || + obj.options.columns[column].type == "checkbox" || + obj.options.columns[column].type == "radio" + ) { + for (var j = 0; j < obj.options.data.length; j++) { + temp[j] = [j, obj.options.data[j][column]]; + } + } else { + for (var j = 0; j < obj.options.data.length; j++) { + temp[j] = [j, obj.records[j][column].textContent.toLowerCase()]; + } + } + + // Default sorting method + if (typeof obj.options.sorting !== "function") { + obj.options.sorting = function (direction) { + return function (a, b) { + var valueA = a[1]; + var valueB = b[1]; + + if (!direction) { + return valueA === "" && valueB !== "" + ? 1 + : valueA !== "" && valueB === "" + ? -1 + : valueA > valueB + ? 1 + : valueA < valueB + ? -1 + : 0; + } else { + return valueA === "" && valueB !== "" + ? 1 + : valueA !== "" && valueB === "" + ? -1 + : valueA > valueB + ? -1 + : valueA < valueB + ? 1 + : 0; + } + }; + }; + } + + temp = temp.sort(obj.options.sorting(order)); + + // Save history + var newValue = []; + for (var j = 0; j < temp.length; j++) { + newValue[j] = temp[j][0]; + } + + // Save history + obj.setHistory({ + action: "orderBy", + rows: newValue, + column: column, + order: order, + }); + + // Update order + obj.updateOrderArrow(column, order); + obj.updateOrder(newValue); + + // On sort event + obj.dispatch("onsort", el, column, order); + + return true; + } + }; + + /** + * Update order arrow + */ + obj.updateOrderArrow = function (column, order) { + // Remove order + for (var i = 0; i < obj.headers.length; i++) { + obj.headers[i].classList.remove("arrow-up"); + obj.headers[i].classList.remove("arrow-down"); + } + + // No order specified then toggle order + if (order) { + obj.headers[column].classList.add("arrow-up"); + } else { + obj.headers[column].classList.add("arrow-down"); + } + }; + + /** + * Update rows position + */ + obj.updateOrder = function (rows) { + // History + var data = []; + for (var j = 0; j < rows.length; j++) { + data[j] = obj.options.data[rows[j]]; + } + obj.options.data = data; + + var data = []; + for (var j = 0; j < rows.length; j++) { + data[j] = obj.records[rows[j]]; + } + obj.records = data; + + var data = []; + for (var j = 0; j < rows.length; j++) { + data[j] = obj.rows[rows[j]]; + } + obj.rows = data; + + // Update references + obj.updateTableReferences(); + + // Redo search + if (obj.results && obj.results.length) { + if (obj.searchInput.value) { + obj.search(obj.searchInput.value); + } else { + obj.closeFilter(); + } + } else { + // Create page + obj.results = null; + obj.pageNumber = 0; + + if (obj.options.pagination > 0) { + obj.page(0); + } else if (obj.options.lazyLoading == true) { + obj.loadPage(0); + } else { + for (var j = 0; j < obj.rows.length; j++) { + obj.tbody.appendChild(obj.rows[j]); + } + } + } + }; + + /** + * Move row + * + * @return void + */ + obj.moveRow = function (o, d, ignoreDom) { + if (Object.keys(obj.options.mergeCells).length > 0) { + if (o > d) { + var insertBefore = 1; + } else { + var insertBefore = 0; + } + + if ( + obj.isRowMerged(o).length || + obj.isRowMerged(d, insertBefore).length + ) { + if ( + !confirm( + obj.options.text + .thisActionWillDestroyAnyExistingMergedCellsAreYouSure + ) + ) { + return false; + } else { + obj.destroyMerged(); + } + } + } + + if (obj.options.search == true) { + if (obj.results && obj.results.length != obj.rows.length) { + if ( + confirm( + obj.options.text.thisActionWillClearYourSearchResultsAreYouSure + ) + ) { + obj.resetSearch(); + } else { + return false; + } + } + + obj.results = null; + } + + if (!ignoreDom) { + if ( + Array.prototype.indexOf.call(obj.tbody.children, obj.rows[d]) >= 0 + ) { + if (o > d) { + obj.tbody.insertBefore(obj.rows[o], obj.rows[d]); + } else { + obj.tbody.insertBefore(obj.rows[o], obj.rows[d].nextSibling); + } + } else { + obj.tbody.removeChild(obj.rows[o]); + } + } + + // Place references in the correct position + obj.rows.splice(d, 0, obj.rows.splice(o, 1)[0]); + obj.records.splice(d, 0, obj.records.splice(o, 1)[0]); + obj.options.data.splice(d, 0, obj.options.data.splice(o, 1)[0]); + + // Respect pagination + if ( + obj.options.pagination > 0 && + obj.tbody.children.length != obj.options.pagination + ) { + obj.page(obj.pageNumber); + } + + // Keeping history of changes + obj.setHistory({ + action: "moveRow", + oldValue: o, + newValue: d, + }); + + // Update table references + obj.updateTableReferences(); + + // Events + obj.dispatch("onmoverow", el, o, d); + }; + + /** + * Insert a new row + * + * @param mixed - number of blank lines to be insert or a single array with the data of the new row + * @param rowNumber + * @param insertBefore + * @return void + */ + obj.insertRow = function (mixed, rowNumber, insertBefore) { + // Configuration + if (obj.options.allowInsertRow == true) { + // Records + var records = []; + + // Data to be insert + var data = []; + + // The insert could be lead by number of rows or the array of data + if (mixed > 0) { + var numOfRows = mixed; + } else { + var numOfRows = 1; + + if (mixed) { + data = mixed; + } + } + + // Direction + var insertBefore = insertBefore ? true : false; + + // Current column number + var lastRow = obj.options.data.length - 1; + + if ( + rowNumber == undefined || + rowNumber >= parseInt(lastRow) || + rowNumber < 0 + ) { + rowNumber = lastRow; + } + + // Onbeforeinsertrow + if ( + obj.dispatch( + "onbeforeinsertrow", + el, + rowNumber, + numOfRows, + insertBefore + ) === false + ) { + return false; + } + + // Merged cells + if (Object.keys(obj.options.mergeCells).length > 0) { + if (obj.isRowMerged(rowNumber, insertBefore).length) { + if ( + !confirm( + obj.options.text + .thisActionWillDestroyAnyExistingMergedCellsAreYouSure + ) + ) { + return false; + } else { + obj.destroyMerged(); + } + } + } + + // Clear any search + if (obj.options.search == true) { + if (obj.results && obj.results.length != obj.rows.length) { + if ( + confirm( + obj.options.text.thisActionWillClearYourSearchResultsAreYouSure + ) + ) { + obj.resetSearch(); + } else { + return false; + } + } + + obj.results = null; + } + + // Insertbefore + var rowIndex = !insertBefore ? rowNumber + 1 : rowNumber; + + // Keep the current data + var currentRecords = obj.records.splice(rowIndex); + var currentData = obj.options.data.splice(rowIndex); + var currentRows = obj.rows.splice(rowIndex); + + // Adding lines + var rowRecords = []; + var rowData = []; + var rowNode = []; + + for (var row = rowIndex; row < numOfRows + rowIndex; row++) { + // Push data to the data container + obj.options.data[row] = []; + for (var col = 0; col < obj.options.columns.length; col++) { + obj.options.data[row][col] = data[col] ? data[col] : ""; + } + // Create row + var tr = obj.createRow(row, obj.options.data[row]); + // Append node + if (currentRows[0]) { + if ( + Array.prototype.indexOf.call( + obj.tbody.children, + currentRows[0] + ) >= 0 + ) { + obj.tbody.insertBefore(tr, currentRows[0]); + } + } else { + if ( + Array.prototype.indexOf.call( + obj.tbody.children, + obj.rows[rowNumber] + ) >= 0 + ) { + obj.tbody.appendChild(tr); + } + } + // Record History + rowRecords.push(obj.records[row]); + rowData.push(obj.options.data[row]); + rowNode.push(tr); + } + + // Copy the data back to the main data + Array.prototype.push.apply(obj.records, currentRecords); + Array.prototype.push.apply(obj.options.data, currentData); + Array.prototype.push.apply(obj.rows, currentRows); + + // Respect pagination + if (obj.options.pagination > 0) { + obj.page(obj.pageNumber); + } + + // Keep history + obj.setHistory({ + action: "insertRow", + rowNumber: rowNumber, + numOfRows: numOfRows, + insertBefore: insertBefore, + rowRecords: rowRecords, + rowData: rowData, + rowNode: rowNode, + }); + + // Remove table references + obj.updateTableReferences(); + + // Events + obj.dispatch( + "oninsertrow", + el, + rowNumber, + numOfRows, + rowRecords, + insertBefore + ); + } + }; + + /** + * Delete a row by number + * + * @param integer rowNumber - row number to be excluded + * @param integer numOfRows - number of lines + * @return void + */ + obj.deleteRow = function (rowNumber, numOfRows) { + // Global Configuration + if (obj.options.allowDeleteRow == true) { + if ( + obj.options.allowDeletingAllRows == true || + obj.options.data.length > 1 + ) { + // Delete row definitions + if (rowNumber == undefined) { + var number = obj.getSelectedRows(); + + if (!number[0]) { + rowNumber = obj.options.data.length - 1; + numOfRows = 1; + } else { + rowNumber = parseInt(number[0].getAttribute("data-y")); + numOfRows = number.length; + } + } + + // Last column + var lastRow = obj.options.data.length - 1; + + if (rowNumber == undefined || rowNumber > lastRow || rowNumber < 0) { + rowNumber = lastRow; + } + + if (!numOfRows) { + numOfRows = 1; + } + + // Do not delete more than the number of records + if (rowNumber + numOfRows >= obj.options.data.length) { + numOfRows = obj.options.data.length - rowNumber; + } + + // Onbeforedeleterow + if ( + obj.dispatch("onbeforedeleterow", el, rowNumber, numOfRows) === + false + ) { + return false; + } + + if (parseInt(rowNumber) > -1) { + // Merged cells + var mergeExists = false; + if (Object.keys(obj.options.mergeCells).length > 0) { + for (var row = rowNumber; row < rowNumber + numOfRows; row++) { + if (obj.isRowMerged(row, false).length) { + mergeExists = true; + } + } + } + if (mergeExists) { + if ( + !confirm( + obj.options.text + .thisActionWillDestroyAnyExistingMergedCellsAreYouSure + ) + ) { + return false; + } else { + obj.destroyMerged(); + } + } + + // Clear any search + if (obj.options.search == true) { + if (obj.results && obj.results.length != obj.rows.length) { + if ( + confirm( + obj.options.text + .thisActionWillClearYourSearchResultsAreYouSure + ) + ) { + obj.resetSearch(); + } else { + return false; + } + } + + obj.results = null; + } + + // If delete all rows, and set allowDeletingAllRows false, will stay one row + if ( + obj.options.allowDeletingAllRows == false && + lastRow + 1 === numOfRows + ) { + numOfRows--; + console.error( + "Jspreadsheet: It is not possible to delete the last row" + ); + } + + // Remove node + for (var row = rowNumber; row < rowNumber + numOfRows; row++) { + if ( + Array.prototype.indexOf.call( + obj.tbody.children, + obj.rows[row] + ) >= 0 + ) { + obj.rows[row].className = ""; + obj.rows[row].parentNode.removeChild(obj.rows[row]); + } + } + + // Remove data + var rowRecords = obj.records.splice(rowNumber, numOfRows); + var rowData = obj.options.data.splice(rowNumber, numOfRows); + var rowNode = obj.rows.splice(rowNumber, numOfRows); + + // Respect pagination + if ( + obj.options.pagination > 0 && + obj.tbody.children.length != obj.options.pagination + ) { + obj.page(obj.pageNumber); + } + + // Remove selection + obj.conditionalSelectionUpdate( + 1, + rowNumber, + rowNumber + numOfRows - 1 + ); + + // Keep history + obj.setHistory({ + action: "deleteRow", + rowNumber: rowNumber, + numOfRows: numOfRows, + insertBefore: 1, + rowRecords: rowRecords, + rowData: rowData, + rowNode: rowNode, + }); + + // Remove table references + obj.updateTableReferences(); + + // Events + obj.dispatch("ondeleterow", el, rowNumber, numOfRows, rowRecords); + } + } else { + console.error( + "Jspreadsheet: It is not possible to delete the last row" + ); + } + } + }; + + /** + * Move column + * + * @return void + */ + obj.moveColumn = function (o, d) { + if (Object.keys(obj.options.mergeCells).length > 0) { + if (o > d) { + var insertBefore = 1; + } else { + var insertBefore = 0; + } + + if ( + obj.isColMerged(o).length || + obj.isColMerged(d, insertBefore).length + ) { + if ( + !confirm( + obj.options.text + .thisActionWillDestroyAnyExistingMergedCellsAreYouSure + ) + ) { + return false; + } else { + obj.destroyMerged(); + } + } + } + + var o = parseInt(o); + var d = parseInt(d); + + if (o > d) { + obj.headerContainer.insertBefore(obj.headers[o], obj.headers[d]); + obj.colgroupContainer.insertBefore(obj.colgroup[o], obj.colgroup[d]); + + for (var j = 0; j < obj.rows.length; j++) { + obj.rows[j].insertBefore(obj.records[j][o], obj.records[j][d]); + } + } else { + obj.headerContainer.insertBefore( + obj.headers[o], + obj.headers[d].nextSibling + ); + obj.colgroupContainer.insertBefore( + obj.colgroup[o], + obj.colgroup[d].nextSibling + ); + + for (var j = 0; j < obj.rows.length; j++) { + obj.rows[j].insertBefore( + obj.records[j][o], + obj.records[j][d].nextSibling + ); + } + } + + obj.options.columns.splice(d, 0, obj.options.columns.splice(o, 1)[0]); + obj.headers.splice(d, 0, obj.headers.splice(o, 1)[0]); + obj.colgroup.splice(d, 0, obj.colgroup.splice(o, 1)[0]); + + for (var j = 0; j < obj.rows.length; j++) { + obj.options.data[j].splice(d, 0, obj.options.data[j].splice(o, 1)[0]); + obj.records[j].splice(d, 0, obj.records[j].splice(o, 1)[0]); + } + + // Update footers position + if (obj.options.footers) { + for (var j = 0; j < obj.options.footers.length; j++) { + obj.options.footers[j].splice( + d, + 0, + obj.options.footers[j].splice(o, 1)[0] + ); + } + } + + // Keeping history of changes + obj.setHistory({ + action: "moveColumn", + oldValue: o, + newValue: d, + }); + + // Update table references + obj.updateTableReferences(); + + // Events + obj.dispatch("onmovecolumn", el, o, d); + }; + + /** + * Insert a new column + * + * @param mixed - num of columns to be added or data to be added in one single column + * @param int columnNumber - number of columns to be created + * @param bool insertBefore + * @param object properties - column properties + * @return void + */ + obj.insertColumn = function ( + mixed, + columnNumber, + insertBefore, + properties + ) { + // Configuration + if (obj.options.allowInsertColumn == true) { + // Records + var records = []; + + // Data to be insert + var data = []; + + // The insert could be lead by number of rows or the array of data + if (mixed > 0) { + var numOfColumns = mixed; + } else { + var numOfColumns = 1; + + if (mixed) { + data = mixed; + } + } + + // Direction + var insertBefore = insertBefore ? true : false; + + // Current column number + var lastColumn = obj.options.columns.length - 1; + + // Confirm position + if ( + columnNumber == undefined || + columnNumber >= parseInt(lastColumn) || + columnNumber < 0 + ) { + columnNumber = lastColumn; + } + + // Onbeforeinsertcolumn + if ( + obj.dispatch( + "onbeforeinsertcolumn", + el, + columnNumber, + numOfColumns, + insertBefore + ) === false + ) { + return false; + } + + // Merged cells + if (Object.keys(obj.options.mergeCells).length > 0) { + if (obj.isColMerged(columnNumber, insertBefore).length) { + if ( + !confirm( + obj.options.text + .thisActionWillDestroyAnyExistingMergedCellsAreYouSure + ) + ) { + return false; + } else { + obj.destroyMerged(); + } + } + } + + // Create default properties + if (!properties) { + properties = []; + } + + for (var i = 0; i < numOfColumns; i++) { + if (!properties[i]) { + properties[i] = { + type: "text", + source: [], + options: [], + width: obj.options.defaultColWidth, + align: obj.options.defaultColAlign, + }; + } + } + + // Insert before + var columnIndex = !insertBefore ? columnNumber + 1 : columnNumber; + obj.options.columns = jexcel.injectArray( + obj.options.columns, + columnIndex, + properties + ); + + // Open space in the containers + var currentHeaders = obj.headers.splice(columnIndex); + var currentColgroup = obj.colgroup.splice(columnIndex); + + // History + var historyHeaders = []; + var historyColgroup = []; + var historyRecords = []; + var historyData = []; + var historyFooters = []; + + // Add new headers + for (var col = columnIndex; col < numOfColumns + columnIndex; col++) { + obj.createCellHeader(col); + obj.headerContainer.insertBefore( + obj.headers[col], + obj.headerContainer.children[col + 1] + ); + obj.colgroupContainer.insertBefore( + obj.colgroup[col], + obj.colgroupContainer.children[col + 1] + ); + + historyHeaders.push(obj.headers[col]); + historyColgroup.push(obj.colgroup[col]); + } + + // Add new footer cells + if (obj.options.footers) { + for (var j = 0; j < obj.options.footers.length; j++) { + historyFooters[j] = []; + for (var i = 0; i < numOfColumns; i++) { + historyFooters[j].push(""); + } + obj.options.footers[j].splice(columnIndex, 0, historyFooters[j]); + } + } + + // Adding visual columns + for (var row = 0; row < obj.options.data.length; row++) { + // Keep the current data + var currentData = obj.options.data[row].splice(columnIndex); + var currentRecord = obj.records[row].splice(columnIndex); + + // History + historyData[row] = []; + historyRecords[row] = []; + + for (var col = columnIndex; col < numOfColumns + columnIndex; col++) { + // New value + var value = data[row] ? data[row] : ""; + obj.options.data[row][col] = value; + // New cell + var td = obj.createCell(col, row, obj.options.data[row][col]); + obj.records[row][col] = td; + // Add cell to the row + if (obj.rows[row]) { + obj.rows[row].insertBefore(td, obj.rows[row].children[col + 1]); + } + + // Record History + historyData[row].push(value); + historyRecords[row].push(td); + } + + // Copy the data back to the main data + Array.prototype.push.apply(obj.options.data[row], currentData); + Array.prototype.push.apply(obj.records[row], currentRecord); + } + + Array.prototype.push.apply(obj.headers, currentHeaders); + Array.prototype.push.apply(obj.colgroup, currentColgroup); + + // Adjust nested headers + if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) { + // Flexible way to handle nestedheaders + if (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) { + for (var j = 0; j < obj.options.nestedHeaders.length; j++) { + var colspan = + parseInt( + obj.options.nestedHeaders[j][ + obj.options.nestedHeaders[j].length - 1 + ].colspan + ) + numOfColumns; + obj.options.nestedHeaders[j][ + obj.options.nestedHeaders[j].length - 1 + ].colspan = colspan; + obj.thead.children[j].children[ + obj.thead.children[j].children.length - 1 + ].setAttribute("colspan", colspan); + var o = + obj.thead.children[j].children[ + obj.thead.children[j].children.length - 1 + ].getAttribute("data-column"); + o = o.split(","); + for ( + var col = columnIndex; + col < numOfColumns + columnIndex; + col++ + ) { + o.push(col); + } + obj.thead.children[j].children[ + obj.thead.children[j].children.length - 1 + ].setAttribute("data-column", o); + } + } else { + var colspan = + parseInt(obj.options.nestedHeaders[0].colspan) + numOfColumns; + obj.options.nestedHeaders[0].colspan = colspan; + obj.thead.children[0].children[ + obj.thead.children[0].children.length - 1 + ].setAttribute("colspan", colspan); + } + } + + // Keep history + obj.setHistory({ + action: "insertColumn", + columnNumber: columnNumber, + numOfColumns: numOfColumns, + insertBefore: insertBefore, + columns: properties, + headers: historyHeaders, + colgroup: historyColgroup, + records: historyRecords, + footers: historyFooters, + data: historyData, + }); + + // Remove table references + obj.updateTableReferences(); + + // Events + obj.dispatch( + "oninsertcolumn", + el, + columnNumber, + numOfColumns, + historyRecords, + insertBefore + ); + } + }; + + /** + * Delete a column by number + * + * @param integer columnNumber - reference column to be excluded + * @param integer numOfColumns - number of columns to be excluded from the reference column + * @return void + */ + obj.deleteColumn = function (columnNumber, numOfColumns) { + // Global Configuration + if (obj.options.allowDeleteColumn == true) { + if (obj.headers.length > 1) { + // Delete column definitions + if (columnNumber == undefined) { + var number = obj.getSelectedColumns(true); + + if (!number.length) { + // Remove last column + columnNumber = obj.headers.length - 1; + numOfColumns = 1; + } else { + // Remove selected + columnNumber = parseInt(number[0]); + numOfColumns = parseInt(number.length); + } + } + + // Lasat column + var lastColumn = obj.options.data[0].length - 1; + + if ( + columnNumber == undefined || + columnNumber > lastColumn || + columnNumber < 0 + ) { + columnNumber = lastColumn; + } + + // Minimum of columns to be delete is 1 + if (!numOfColumns) { + numOfColumns = 1; + } + + // Can't delete more than the limit of the table + if (numOfColumns > obj.options.data[0].length - columnNumber) { + numOfColumns = obj.options.data[0].length - columnNumber; + } + + // onbeforedeletecolumn + if ( + obj.dispatch( + "onbeforedeletecolumn", + el, + columnNumber, + numOfColumns + ) === false + ) { + return false; + } + + // Can't remove the last column + if (parseInt(columnNumber) > -1) { + // Merged cells + var mergeExists = false; + if (Object.keys(obj.options.mergeCells).length > 0) { + for ( + var col = columnNumber; + col < columnNumber + numOfColumns; + col++ + ) { + if (obj.isColMerged(col, false).length) { + mergeExists = true; + } + } + } + if (mergeExists) { + if ( + !confirm( + obj.options.text + .thisActionWillDestroyAnyExistingMergedCellsAreYouSure + ) + ) { + return false; + } else { + obj.destroyMerged(); + } + } + + // Delete the column properties + var columns = obj.options.columns.splice( + columnNumber, + numOfColumns + ); + + for ( + var col = columnNumber; + col < columnNumber + numOfColumns; + col++ + ) { + obj.colgroup[col].className = ""; + obj.headers[col].className = ""; + obj.colgroup[col].parentNode.removeChild(obj.colgroup[col]); + obj.headers[col].parentNode.removeChild(obj.headers[col]); + } + + var historyHeaders = obj.headers.splice(columnNumber, numOfColumns); + var historyColgroup = obj.colgroup.splice( + columnNumber, + numOfColumns + ); + var historyRecords = []; + var historyData = []; + var historyFooters = []; + + for (var row = 0; row < obj.options.data.length; row++) { + for ( + var col = columnNumber; + col < columnNumber + numOfColumns; + col++ + ) { + obj.records[row][col].className = ""; + obj.records[row][col].parentNode.removeChild( + obj.records[row][col] + ); + } + } + + // Delete headers + for (var row = 0; row < obj.options.data.length; row++) { + // History + historyData[row] = obj.options.data[row].splice( + columnNumber, + numOfColumns + ); + historyRecords[row] = obj.records[row].splice( + columnNumber, + numOfColumns + ); + } + + // Delete footers + if (obj.options.footers) { + for (var row = 0; row < obj.options.footers.length; row++) { + historyFooters[row] = obj.options.footers[row].splice( + columnNumber, + numOfColumns + ); + } + } + + // Remove selection + obj.conditionalSelectionUpdate( + 0, + columnNumber, + columnNumber + numOfColumns - 1 + ); + + // Adjust nested headers + if ( + obj.options.nestedHeaders && + obj.options.nestedHeaders.length > 0 + ) { + // Flexible way to handle nestedheaders + if ( + obj.options.nestedHeaders[0] && + obj.options.nestedHeaders[0][0] + ) { + for (var j = 0; j < obj.options.nestedHeaders.length; j++) { + var colspan = + parseInt( + obj.options.nestedHeaders[j][ + obj.options.nestedHeaders[j].length - 1 + ].colspan + ) - numOfColumns; + obj.options.nestedHeaders[j][ + obj.options.nestedHeaders[j].length - 1 + ].colspan = colspan; + obj.thead.children[j].children[ + obj.thead.children[j].children.length - 1 + ].setAttribute("colspan", colspan); + } + } else { + var colspan = + parseInt(obj.options.nestedHeaders[0].colspan) - numOfColumns; + obj.options.nestedHeaders[0].colspan = colspan; + obj.thead.children[0].children[ + obj.thead.children[0].children.length - 1 + ].setAttribute("colspan", colspan); + } + } + + // Keeping history of changes + obj.setHistory({ + action: "deleteColumn", + columnNumber: columnNumber, + numOfColumns: numOfColumns, + insertBefore: 1, + columns: columns, + headers: historyHeaders, + colgroup: historyColgroup, + records: historyRecords, + footers: historyFooters, + data: historyData, + }); + + // Update table references + obj.updateTableReferences(); + + // Delete + obj.dispatch( + "ondeletecolumn", + el, + columnNumber, + numOfColumns, + historyRecords + ); + } + } else { + console.error( + "Jspreadsheet: It is not possible to delete the last column" + ); + } + } + }; + + /** + * Get selected rows numbers + * + * @return array + */ + (obj.getSelectedRows = function (asIds) { + var rows = []; + // Get all selected rows + for (var j = 0; j < obj.rows.length; j++) { + if (obj.rows[j].classList.contains("selected")) { + if (asIds) { + rows.push(j); + } else { + rows.push(obj.rows[j]); + } + } + } + + return rows; + }), + /** + * Get selected column numbers + * + * @return array + */ + (obj.getSelectedColumns = function () { + var cols = []; + // Get all selected cols + for (var i = 0; i < obj.headers.length; i++) { + if (obj.headers[i].classList.contains("selected")) { + cols.push(i); + } + } + + return cols; + }); + + /** + * Get highlighted + * + * @return array + */ + obj.getHighlighted = function () { + return obj.highlighted; + }; + + /** + * Update cell references + * + * @return void + */ + obj.updateTableReferences = function () { + // Update headers + for (var i = 0; i < obj.headers.length; i++) { + var x = obj.headers[i].getAttribute("data-x"); + + if (x != i) { + // Update coords + obj.headers[i].setAttribute("data-x", i); + // Title + if (!obj.headers[i].getAttribute("title")) { + obj.headers[i].innerHTML = jexcel.getColumnName(i); + } + } + } + + // Update all rows + for (var j = 0; j < obj.rows.length; j++) { + if (obj.rows[j]) { + var y = obj.rows[j].getAttribute("data-y"); + + if (y != j) { + // Update coords + obj.rows[j].setAttribute("data-y", j); + obj.rows[j].children[0].setAttribute("data-y", j); + // Row number + obj.rows[j].children[0].innerHTML = j + 1; + } + } + } + + // Regular cells affected by this change + var affectedTokens = []; + var mergeCellUpdates = []; + + // Update cell + var updatePosition = function (x, y, i, j) { + if (x != i) { + obj.records[j][i].setAttribute("data-x", i); + } + if (y != j) { + obj.records[j][i].setAttribute("data-y", j); + } + + // Other updates + if (x != i || y != j) { + var columnIdFrom = jexcel.getColumnNameFromId([x, y]); + var columnIdTo = jexcel.getColumnNameFromId([i, j]); + affectedTokens[columnIdFrom] = columnIdTo; + } + }; + + for (var j = 0; j < obj.records.length; j++) { + for (var i = 0; i < obj.records[0].length; i++) { + if (obj.records[j][i]) { + // Current values + var x = obj.records[j][i].getAttribute("data-x"); + var y = obj.records[j][i].getAttribute("data-y"); + + // Update column + if (obj.records[j][i].getAttribute("data-merged")) { + var columnIdFrom = jexcel.getColumnNameFromId([x, y]); + var columnIdTo = jexcel.getColumnNameFromId([i, j]); + if (mergeCellUpdates[columnIdFrom] == null) { + if (columnIdFrom == columnIdTo) { + mergeCellUpdates[columnIdFrom] = false; + } else { + var totalX = parseInt(i - x); + var totalY = parseInt(j - y); + mergeCellUpdates[columnIdFrom] = [columnIdTo, totalX, totalY]; + } + } + } else { + updatePosition(x, y, i, j); + } + } + } + } + + // Update merged if applicable + var keys = Object.keys(mergeCellUpdates); + if (keys.length) { + for (var i = 0; i < keys.length; i++) { + if (mergeCellUpdates[keys[i]]) { + var info = jexcel.getIdFromColumnName(keys[i], true); + var x = info[0]; + var y = info[1]; + updatePosition( + x, + y, + x + mergeCellUpdates[keys[i]][1], + y + mergeCellUpdates[keys[i]][2] + ); + + var columnIdFrom = keys[i]; + var columnIdTo = mergeCellUpdates[keys[i]][0]; + for ( + var j = 0; + j < obj.options.mergeCells[columnIdFrom][2].length; + j++ + ) { + var x = parseInt( + obj.options.mergeCells[columnIdFrom][2][j].getAttribute( + "data-x" + ) + ); + var y = parseInt( + obj.options.mergeCells[columnIdFrom][2][j].getAttribute( + "data-y" + ) + ); + obj.options.mergeCells[columnIdFrom][2][j].setAttribute( + "data-x", + x + mergeCellUpdates[keys[i]][1] + ); + obj.options.mergeCells[columnIdFrom][2][j].setAttribute( + "data-y", + y + mergeCellUpdates[keys[i]][2] + ); + } + + obj.options.mergeCells[columnIdTo] = + obj.options.mergeCells[columnIdFrom]; + delete obj.options.mergeCells[columnIdFrom]; + } + } + } + + // Update formulas + obj.updateFormulas(affectedTokens); + + // Update meta data + obj.updateMeta(affectedTokens); + + // Refresh selection + obj.refreshSelection(); + + // Update table with custom configuration if applicable + obj.updateTable(); + }; + + /** + * Custom settings for the cells + */ + obj.updateTable = function () { + // Check for spare + if (obj.options.minSpareRows > 0) { + var numBlankRows = 0; + for (var j = obj.rows.length - 1; j >= 0; j--) { + var test = false; + for (var i = 0; i < obj.headers.length; i++) { + if (obj.options.data[j][i]) { + test = true; + } + } + if (test) { + break; + } else { + numBlankRows++; + } + } + + if (obj.options.minSpareRows - numBlankRows > 0) { + obj.insertRow(obj.options.minSpareRows - numBlankRows); + } + } + + if (obj.options.minSpareCols > 0) { + var numBlankCols = 0; + for (var i = obj.headers.length - 1; i >= 0; i--) { + var test = false; + for (var j = 0; j < obj.rows.length; j++) { + if (obj.options.data[j][i]) { + test = true; + } + } + if (test) { + break; + } else { + numBlankCols++; + } + } + + if (obj.options.minSpareCols - numBlankCols > 0) { + obj.insertColumn(obj.options.minSpareCols - numBlankCols); + } + } + + // Customizations by the developer + if (typeof obj.options.updateTable == "function") { + if (obj.options.detachForUpdates) { + el.removeChild(obj.content); + } + + for (var j = 0; j < obj.rows.length; j++) { + for (var i = 0; i < obj.headers.length; i++) { + obj.options.updateTable( + el, + obj.records[j][i], + i, + j, + obj.options.data[j][i], + obj.records[j][i].textContent, + jexcel.getColumnNameFromId([i, j]) + ); + } + } + + if (obj.options.detachForUpdates) { + el.insertBefore(obj.content, obj.pagination); + } + } + + // Update footers + if (obj.options.footers) { + obj.setFooter(); + } + + // Update corner position + setTimeout(function () { + obj.updateCornerPosition(); + }, 0); + }; + + /** + * Readonly + */ + obj.isReadOnly = function (cell) { + if ((cell = obj.getCell(cell))) { + return cell.classList.contains("readonly") ? true : false; + } + }; + + /** + * Readonly + */ + obj.setReadOnly = function (cell, state) { + if ((cell = obj.getCell(cell))) { + if (state) { + cell.classList.add("readonly"); + } else { + cell.classList.remove("readonly"); + } + } + }; + + /** + * Show row + */ + obj.showRow = function (rowNumber) { + obj.rows[rowNumber].style.display = ""; + }; + + /** + * Hide row + */ + obj.hideRow = function (rowNumber) { + obj.rows[rowNumber].style.display = "none"; + }; + + /** + * Show column + */ + obj.showColumn = function (colNumber) { + obj.headers[colNumber].style.display = ""; + obj.colgroup[colNumber].style.display = ""; + if (obj.filter && obj.filter.children.length > colNumber + 1) { + obj.filter.children[colNumber + 1].style.display = ""; + } + for (var j = 0; j < obj.options.data.length; j++) { + obj.records[j][colNumber].style.display = ""; + } + + // Update footers + if (obj.options.footers) { + obj.setFooter(); + } + + obj.resetSelection(); + }; + + /** + * Hide column + */ + obj.hideColumn = function (colNumber) { + obj.headers[colNumber].style.display = "none"; + obj.colgroup[colNumber].style.display = "none"; + if (obj.filter && obj.filter.children.length > colNumber + 1) { + obj.filter.children[colNumber + 1].style.display = "none"; + } + for (var j = 0; j < obj.options.data.length; j++) { + obj.records[j][colNumber].style.display = "none"; + } + + // Update footers + if (obj.options.footers) { + obj.setFooter(); + } + + obj.resetSelection(); + }; + + /** + * Show index column + */ + obj.showIndex = function () { + obj.table.classList.remove("jexcel_hidden_index"); + }; + + /** + * Hide index column + */ + obj.hideIndex = function () { + obj.table.classList.add("jexcel_hidden_index"); + }; + + /** + * Update all related cells in the chain + */ + var chainLoopProtection = []; + + obj.updateFormulaChain = function (x, y, records) { + var cellId = jexcel.getColumnNameFromId([x, y]); + if (obj.formula[cellId] && obj.formula[cellId].length > 0) { + if (chainLoopProtection[cellId]) { + obj.records[y][x].innerHTML = "#ERROR"; + obj.formula[cellId] = ""; + } else { + // Protection + chainLoopProtection[cellId] = true; + + for (var i = 0; i < obj.formula[cellId].length; i++) { + var cell = jexcel.getIdFromColumnName(obj.formula[cellId][i], true); + // Update cell + var value = "" + obj.options.data[cell[1]][cell[0]]; + if (value.substr(0, 1) == "=") { + records.push(obj.updateCell(cell[0], cell[1], value, true)); + } else { + // No longer a formula, remove from the chain + Object.keys(obj.formula)[i] = null; + } + obj.updateFormulaChain(cell[0], cell[1], records); + } + } + } + + chainLoopProtection = []; + }; + + /** + * Update formulas + */ + obj.updateFormulas = function (referencesToUpdate) { + // Update formulas + for (var j = 0; j < obj.options.data.length; j++) { + for (var i = 0; i < obj.options.data[0].length; i++) { + var value = "" + obj.options.data[j][i]; + // Is formula + if (value.substr(0, 1) == "=") { + // Replace tokens + var newFormula = obj.updateFormula(value, referencesToUpdate); + if (newFormula != value) { + obj.options.data[j][i] = newFormula; + } + } + } + } + + // Update formula chain + var formula = []; + var keys = Object.keys(obj.formula); + for (var j = 0; j < keys.length; j++) { + // Current key and values + var key = keys[j]; + var value = obj.formula[key]; + // Update key + if (referencesToUpdate[key]) { + key = referencesToUpdate[key]; + } + // Update values + formula[key] = []; + for (var i = 0; i < value.length; i++) { + var letter = value[i]; + if (referencesToUpdate[letter]) { + letter = referencesToUpdate[letter]; + } + formula[key].push(letter); + } + } + obj.formula = formula; + }; + + /** + * Update formula + */ + obj.updateFormula = function (formula, referencesToUpdate) { + var testLetter = /[A-Z]/; + var testNumber = /[0-9]/; + + var newFormula = ""; + var letter = null; + var number = null; + var token = ""; + + for (var index = 0; index < formula.length; index++) { + if (testLetter.exec(formula[index])) { + letter = 1; + number = 0; + token += formula[index]; + } else if (testNumber.exec(formula[index])) { + number = letter ? 1 : 0; + token += formula[index]; + } else { + if (letter && number) { + token = referencesToUpdate[token] + ? referencesToUpdate[token] + : token; + } + newFormula += token; + newFormula += formula[index]; + letter = 0; + number = 0; + token = ""; + } + } + + if (token) { + if (letter && number) { + token = referencesToUpdate[token] ? referencesToUpdate[token] : token; + } + newFormula += token; + } + + return newFormula; + }; + + /** + * Secure formula + */ + var secureFormula = function (oldValue) { + var newValue = ""; + var inside = 0; + + for (var i = 0; i < oldValue.length; i++) { + if (oldValue[i] == '"') { + if (inside == 0) { + inside = 1; + } else { + inside = 0; + } + } + + if (inside == 1) { + newValue += oldValue[i]; + } else { + newValue += oldValue[i].toUpperCase(); + } + } + + return newValue; + }; + + /** + * Parse formulas + */ + obj.executeFormula = function (expression, x, y) { + var formulaResults = []; + var formulaLoopProtection = []; + + // Execute formula with loop protection + var execute = function (expression, x, y) { + // Parent column identification + var parentId = jexcel.getColumnNameFromId([x, y]); + + // Code protection + if (formulaLoopProtection[parentId]) { + console.error("Reference loop detected"); + return "#ERROR"; + } + + formulaLoopProtection[parentId] = true; + + // Convert range tokens + var tokensUpdate = function (tokens) { + for (var index = 0; index < tokens.length; index++) { + var f = []; + var token = tokens[index].split(":"); + var e1 = jexcel.getIdFromColumnName(token[0], true); + var e2 = jexcel.getIdFromColumnName(token[1], true); + + if (e1[0] <= e2[0]) { + var x1 = e1[0]; + var x2 = e2[0]; + } else { + var x1 = e2[0]; + var x2 = e1[0]; + } + + if (e1[1] <= e2[1]) { + var y1 = e1[1]; + var y2 = e2[1]; + } else { + var y1 = e2[1]; + var y2 = e1[1]; + } + + for (var j = y1; j <= y2; j++) { + for (var i = x1; i <= x2; i++) { + f.push(jexcel.getColumnNameFromId([i, j])); + } + } + + expression = expression.replace(tokens[index], f.join(",")); + } + }; + + // Range with $ remove $ + expression = expression.replace(/\$?([A-Z]+)\$?([0-9]+)/g, "$1$2"); + + var tokens = expression.match(/([A-Z]+[0-9]+)\:([A-Z]+[0-9]+)/g); + if (tokens && tokens.length) { + tokensUpdate(tokens); + } + + // Get tokens + var tokens = expression.match(/([A-Z]+[0-9]+)/g); + + // Direct self-reference protection + if (tokens && tokens.indexOf(parentId) > -1) { + console.error("Self Reference detected"); + return "#ERROR"; + } else { + // Expressions to be used in the parsing + var formulaExpressions = {}; + + if (tokens) { + for (var i = 0; i < tokens.length; i++) { + // Keep chain + if (!obj.formula[tokens[i]]) { + obj.formula[tokens[i]] = []; + } + // Is already in the register + if (obj.formula[tokens[i]].indexOf(parentId) < 0) { + obj.formula[tokens[i]].push(parentId); + } + + // Do not calculate again + if (eval("typeof(" + tokens[i] + ') == "undefined"')) { + // Coords + var position = jexcel.getIdFromColumnName(tokens[i], 1); + // Get value + if ( + typeof obj.options.data[position[1]] != "undefined" && + typeof obj.options.data[position[1]][position[0]] != + "undefined" + ) { + var value = obj.options.data[position[1]][position[0]]; + } else { + var value = ""; + } + // Get column data + if (("" + value).substr(0, 1) == "=") { + if (formulaResults[tokens[i]]) { + value = formulaResults[tokens[i]]; + } else { + value = execute(value, position[0], position[1]); + formulaResults[tokens[i]] = value; + } + } + // Type! + if (("" + value).trim() == "") { + // Null + formulaExpressions[tokens[i]] = null; + } else { + if ( + value == Number(value) && + obj.options.autoCasting == true + ) { + // Number + formulaExpressions[tokens[i]] = Number(value); + } else { + // Trying any formatted number + var number = obj.parseNumber(value, position[0]); + if (obj.options.autoCasting == true && number) { + formulaExpressions[tokens[i]] = number; + } else { + formulaExpressions[tokens[i]] = '"' + value + '"'; + } + } + } + } + } + } + + // Convert formula to javascript + try { + var res = jexcel.formula( + expression.substr(1), + formulaExpressions, + x, + y, + obj + ); + } catch (e) { + var res = "#ERROR"; + console.log(e); + } + + return res; + } + }; + + return execute(expression, x, y); + }; + + /** + * Trying to extract a number from a string + */ + obj.parseNumber = function (value, columnNumber) { + // Decimal point + var decimal = + columnNumber && obj.options.columns[columnNumber].decimal + ? obj.options.columns[columnNumber].decimal + : "."; + + // Parse both parts of the number + var number = "" + value; + number = number.split(decimal); + number[0] = number[0].match(/[+-]?[0-9]/g); + if (number[0]) { + number[0] = number[0].join(""); + } + if (number[1]) { + number[1] = number[1].match(/[0-9]*/g).join(""); + } + + // Is a valid number + if (number[0] && Number.isInteger(Number(number[0]))) { + if (!number[1]) { + var value = Number(number[0] + ".00"); + } else { + var value = Number(number[0] + "." + number[1]); + } + } else { + var value = null; + } + + return value; + }; + + /** + * Get row number + */ + obj.row = function (cell) {}; + + /** + * Get col number + */ + obj.col = function (cell) {}; + + obj.up = function (shiftKey, ctrlKey) { + if (shiftKey) { + if (obj.selectedCell[3] > 0) { + obj.up.visible(1, ctrlKey ? 0 : 1); + } + } else { + if (obj.selectedCell[1] > 0) { + obj.up.visible(0, ctrlKey ? 0 : 1); + } + obj.selectedCell[2] = obj.selectedCell[0]; + obj.selectedCell[3] = obj.selectedCell[1]; + } + + // Update selection + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + + // Change page + if (obj.options.lazyLoading == true) { + if (obj.selectedCell[1] == 0 || obj.selectedCell[3] == 0) { + obj.loadPage(0); + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + } else { + if (obj.loadValidation()) { + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + } else { + var item = parseInt(obj.tbody.firstChild.getAttribute("data-y")); + if (obj.selectedCell[1] - item < 30) { + obj.loadUp(); + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + } + } + } + } else if (obj.options.pagination > 0) { + var pageNumber = obj.whichPage(obj.selectedCell[3]); + if (pageNumber != obj.pageNumber) { + obj.page(pageNumber); + } + } + + obj.updateScroll(1); + }; + + obj.up.visible = function (group, direction) { + if (group == 0) { + var x = parseInt(obj.selectedCell[0]); + var y = parseInt(obj.selectedCell[1]); + } else { + var x = parseInt(obj.selectedCell[2]); + var y = parseInt(obj.selectedCell[3]); + } + + if (direction == 0) { + for (var j = 0; j < y; j++) { + if ( + obj.records[j][x].style.display != "none" && + obj.rows[j].style.display != "none" + ) { + y = j; + break; + } + } + } else { + y = obj.up.get(x, y); + } + + if (group == 0) { + obj.selectedCell[0] = x; + obj.selectedCell[1] = y; + } else { + obj.selectedCell[2] = x; + obj.selectedCell[3] = y; + } + }; + + obj.up.get = function (x, y) { + var x = parseInt(x); + var y = parseInt(y); + for (var j = y - 1; j >= 0; j--) { + if ( + obj.records[j][x].style.display != "none" && + obj.rows[j].style.display != "none" + ) { + if (obj.records[j][x].getAttribute("data-merged")) { + if (obj.records[j][x] == obj.records[y][x]) { + continue; + } + } + y = j; + break; + } + } + + return y; + }; + + obj.down = function (shiftKey, ctrlKey) { + if (shiftKey) { + if (obj.selectedCell[3] < obj.records.length - 1) { + obj.down.visible(1, ctrlKey ? 0 : 1); + } + } else { + if (obj.selectedCell[1] < obj.records.length - 1) { + obj.down.visible(0, ctrlKey ? 0 : 1); + } + obj.selectedCell[2] = obj.selectedCell[0]; + obj.selectedCell[3] = obj.selectedCell[1]; + } + + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + + // Change page + if (obj.options.lazyLoading == true) { + if ( + obj.selectedCell[1] == obj.records.length - 1 || + obj.selectedCell[3] == obj.records.length - 1 + ) { + obj.loadPage(-1); + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + } else { + if (obj.loadValidation()) { + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + } else { + var item = parseInt(obj.tbody.lastChild.getAttribute("data-y")); + if (item - obj.selectedCell[3] < 30) { + obj.loadDown(); + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + } + } + } + } else if (obj.options.pagination > 0) { + var pageNumber = obj.whichPage(obj.selectedCell[3]); + if (pageNumber != obj.pageNumber) { + obj.page(pageNumber); + } + } + + obj.updateScroll(3); + }; + + obj.down.visible = function (group, direction) { + if (group == 0) { + var x = parseInt(obj.selectedCell[0]); + var y = parseInt(obj.selectedCell[1]); + } else { + var x = parseInt(obj.selectedCell[2]); + var y = parseInt(obj.selectedCell[3]); + } + + if (direction == 0) { + for (var j = obj.rows.length - 1; j > y; j--) { + if ( + obj.records[j][x].style.display != "none" && + obj.rows[j].style.display != "none" + ) { + y = j; + break; + } + } + } else { + y = obj.down.get(x, y); + } + + if (group == 0) { + obj.selectedCell[0] = x; + obj.selectedCell[1] = y; + } else { + obj.selectedCell[2] = x; + obj.selectedCell[3] = y; + } + }; + + obj.down.get = function (x, y) { + var x = parseInt(x); + var y = parseInt(y); + for (var j = y + 1; j < obj.rows.length; j++) { + if ( + obj.records[j][x].style.display != "none" && + obj.rows[j].style.display != "none" + ) { + if (obj.records[j][x].getAttribute("data-merged")) { + if (obj.records[j][x] == obj.records[y][x]) { + continue; + } + } + y = j; + break; + } + } + + return y; + }; + + obj.right = function (shiftKey, ctrlKey) { + if (shiftKey) { + if (obj.selectedCell[2] < obj.headers.length - 1) { + obj.right.visible(1, ctrlKey ? 0 : 1); + } + } else { + if (obj.selectedCell[0] < obj.headers.length - 1) { + obj.right.visible(0, ctrlKey ? 0 : 1); + } + obj.selectedCell[2] = obj.selectedCell[0]; + obj.selectedCell[3] = obj.selectedCell[1]; + } + + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + obj.updateScroll(2); + }; + + obj.right.visible = function (group, direction) { + if (group == 0) { + var x = parseInt(obj.selectedCell[0]); + var y = parseInt(obj.selectedCell[1]); + } else { + var x = parseInt(obj.selectedCell[2]); + var y = parseInt(obj.selectedCell[3]); + } + + if (direction == 0) { + for (var i = obj.headers.length - 1; i > x; i--) { + if (obj.records[y][i].style.display != "none") { + x = i; + break; + } + } + } else { + x = obj.right.get(x, y); + } + + if (group == 0) { + obj.selectedCell[0] = x; + obj.selectedCell[1] = y; + } else { + obj.selectedCell[2] = x; + obj.selectedCell[3] = y; + } + }; + + obj.right.get = function (x, y) { + var x = parseInt(x); + var y = parseInt(y); + + for (var i = x + 1; i < obj.headers.length; i++) { + if (obj.records[y][i].style.display != "none") { + if (obj.records[y][i].getAttribute("data-merged")) { + if (obj.records[y][i] == obj.records[y][x]) { + continue; + } + } + x = i; + break; + } + } + + return x; + }; + + obj.left = function (shiftKey, ctrlKey) { + if (shiftKey) { + if (obj.selectedCell[2] > 0) { + obj.left.visible(1, ctrlKey ? 0 : 1); + } + } else { + if (obj.selectedCell[0] > 0) { + obj.left.visible(0, ctrlKey ? 0 : 1); + } + obj.selectedCell[2] = obj.selectedCell[0]; + obj.selectedCell[3] = obj.selectedCell[1]; + } + + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + obj.updateScroll(0); + }; + + obj.left.visible = function (group, direction) { + if (group == 0) { + var x = parseInt(obj.selectedCell[0]); + var y = parseInt(obj.selectedCell[1]); + } else { + var x = parseInt(obj.selectedCell[2]); + var y = parseInt(obj.selectedCell[3]); + } + + if (direction == 0) { + for (var i = 0; i < x; i++) { + if (obj.records[y][i].style.display != "none") { + x = i; + break; + } + } + } else { + x = obj.left.get(x, y); + } + + if (group == 0) { + obj.selectedCell[0] = x; + obj.selectedCell[1] = y; + } else { + obj.selectedCell[2] = x; + obj.selectedCell[3] = y; + } + }; + + obj.left.get = function (x, y) { + var x = parseInt(x); + var y = parseInt(y); + for (var i = x - 1; i >= 0; i--) { + if (obj.records[y][i].style.display != "none") { + if (obj.records[y][i].getAttribute("data-merged")) { + if (obj.records[y][i] == obj.records[y][x]) { + continue; + } + } + x = i; + break; + } + } + + return x; + }; + + obj.first = function (shiftKey, ctrlKey) { + if (shiftKey) { + if (ctrlKey) { + obj.selectedCell[3] = 0; + } else { + obj.left.visible(1, 0); + } + } else { + if (ctrlKey) { + obj.selectedCell[1] = 0; + } else { + obj.left.visible(0, 0); + } + obj.selectedCell[2] = obj.selectedCell[0]; + obj.selectedCell[3] = obj.selectedCell[1]; + } + + // Change page + if ( + obj.options.lazyLoading == true && + (obj.selectedCell[1] == 0 || obj.selectedCell[3] == 0) + ) { + obj.loadPage(0); + } else if (obj.options.pagination > 0) { + var pageNumber = obj.whichPage(obj.selectedCell[3]); + if (pageNumber != obj.pageNumber) { + obj.page(pageNumber); + } + } + + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + obj.updateScroll(1); + }; + + obj.last = function (shiftKey, ctrlKey) { + if (shiftKey) { + if (ctrlKey) { + obj.selectedCell[3] = obj.records.length - 1; + } else { + obj.right.visible(1, 0); + } + } else { + if (ctrlKey) { + obj.selectedCell[1] = obj.records.length - 1; + } else { + obj.right.visible(0, 0); + } + obj.selectedCell[2] = obj.selectedCell[0]; + obj.selectedCell[3] = obj.selectedCell[1]; + } + + // Change page + if ( + obj.options.lazyLoading == true && + (obj.selectedCell[1] == obj.records.length - 1 || + obj.selectedCell[3] == obj.records.length - 1) + ) { + obj.loadPage(-1); + } else if (obj.options.pagination > 0) { + var pageNumber = obj.whichPage(obj.selectedCell[3]); + if (pageNumber != obj.pageNumber) { + obj.page(pageNumber); + } + } + + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + obj.updateScroll(3); + }; + + obj.selectAll = function () { + if (!obj.selectedCell) { + obj.selectedCell = []; + } + + obj.selectedCell[0] = 0; + obj.selectedCell[1] = 0; + obj.selectedCell[2] = obj.headers.length - 1; + obj.selectedCell[3] = obj.records.length - 1; + + obj.updateSelectionFromCoords( + obj.selectedCell[0], + obj.selectedCell[1], + obj.selectedCell[2], + obj.selectedCell[3] + ); + }; + + /** + * Go to a page in a lazyLoading + */ + obj.loadPage = function (pageNumber) { + // Search + if ( + (obj.options.search == true || obj.options.filters == true) && + obj.results + ) { + var results = obj.results; + } else { + var results = obj.rows; + } + + // Per page + var quantityPerPage = 100; + + // pageNumber + if (pageNumber == null || pageNumber == -1) { + // Last page + pageNumber = Math.ceil(results.length / quantityPerPage) - 1; + } + + var startRow = pageNumber * quantityPerPage; + var finalRow = pageNumber * quantityPerPage + quantityPerPage; + if (finalRow > results.length) { + finalRow = results.length; + } + startRow = finalRow - 100; + if (startRow < 0) { + startRow = 0; + } + + // Appeding items + for (var j = startRow; j < finalRow; j++) { + if ( + (obj.options.search == true || obj.options.filters == true) && + obj.results + ) { + obj.tbody.appendChild(obj.rows[results[j]]); + } else { + obj.tbody.appendChild(obj.rows[j]); + } + + if (obj.tbody.children.length > quantityPerPage) { + obj.tbody.removeChild(obj.tbody.firstChild); + } + } + }; + + obj.loadUp = function () { + // Search + if ( + (obj.options.search == true || obj.options.filters == true) && + obj.results + ) { + var results = obj.results; + } else { + var results = obj.rows; + } + var test = 0; + if (results.length > 100) { + // Get the first element in the page + var item = parseInt(obj.tbody.firstChild.getAttribute("data-y")); + if ( + (obj.options.search == true || obj.options.filters == true) && + obj.results + ) { + item = results.indexOf(item); + } + if (item > 0) { + for (var j = 0; j < 30; j++) { + item = item - 1; + if (item > -1) { + if ( + (obj.options.search == true || obj.options.filters == true) && + obj.results + ) { + obj.tbody.insertBefore( + obj.rows[results[item]], + obj.tbody.firstChild + ); + } else { + obj.tbody.insertBefore(obj.rows[item], obj.tbody.firstChild); + } + if (obj.tbody.children.length > 100) { + obj.tbody.removeChild(obj.tbody.lastChild); + test = 1; + } + } + } + } + } + return test; + }; + + obj.loadDown = function () { + // Search + if ( + (obj.options.search == true || obj.options.filters == true) && + obj.results + ) { + var results = obj.results; + } else { + var results = obj.rows; + } + var test = 0; + if (results.length > 100) { + // Get the last element in the page + var item = parseInt(obj.tbody.lastChild.getAttribute("data-y")); + if ( + (obj.options.search == true || obj.options.filters == true) && + obj.results + ) { + item = results.indexOf(item); + } + if (item < obj.rows.length - 1) { + for (var j = 0; j <= 30; j++) { + if (item < results.length) { + if ( + (obj.options.search == true || obj.options.filters == true) && + obj.results + ) { + obj.tbody.appendChild(obj.rows[results[item]]); + } else { + obj.tbody.appendChild(obj.rows[item]); + } + if (obj.tbody.children.length > 100) { + obj.tbody.removeChild(obj.tbody.firstChild); + test = 1; + } + } + item = item + 1; + } + } + } + + return test; + }; + + obj.loadValidation = function () { + if (obj.selectedCell) { + var currentPage = + parseInt(obj.tbody.firstChild.getAttribute("data-y")) / 100; + var selectedPage = parseInt(obj.selectedCell[3] / 100); + var totalPages = parseInt(obj.rows.length / 100); + + if (currentPage != selectedPage && selectedPage <= totalPages) { + if ( + !Array.prototype.indexOf.call( + obj.tbody.children, + obj.rows[obj.selectedCell[3]] + ) + ) { + obj.loadPage(selectedPage); + return true; + } + } + } + + return false; + }; + + /** + * Reset search + */ + obj.resetSearch = function () { + obj.searchInput.value = ""; + obj.search(""); + obj.results = null; + }; + + /** + * Search + */ + obj.search = function (query) { + // Query + if (query) { + var query = query.toLowerCase(); + } + + // Reset any filter + if (obj.options.filters) { + obj.resetFilters(); + } + + // Reset selection + obj.resetSelection(); + + // Total of results + obj.pageNumber = 0; + obj.results = []; + + if (query) { + // Search filter + var search = function (item, query, index) { + for (var i = 0; i < item.length; i++) { + if ( + ("" + item[i]).toLowerCase().search(query) >= 0 || + ("" + obj.records[index][i].innerHTML) + .toLowerCase() + .search(query) >= 0 + ) { + return true; + } + } + return false; + }; + + // Result + var addToResult = function (k) { + if (obj.results.indexOf(k) == -1) { + obj.results.push(k); + } + }; + + // Filter + var data = obj.options.data.filter(function (v, k) { + if (search(v, query, k)) { + // Merged rows found + var rows = obj.isRowMerged(k); + if (rows.length) { + for (var i = 0; i < rows.length; i++) { + var row = jexcel.getIdFromColumnName(rows[i], true); + for (var j = 0; j < obj.options.mergeCells[rows[i]][1]; j++) { + addToResult(row[1] + j); + } + } + } else { + // Normal row found + addToResult(k); + } + return true; + } else { + return false; + } + }); + } else { + obj.results = null; + } + + return obj.updateResult(); + }; + + obj.updateResult = function () { + var total = 0; + var index = 0; + + // Page 1 + if (obj.options.lazyLoading == true) { + total = 100; + } else if (obj.options.pagination > 0) { + total = obj.options.pagination; + } else { + if (obj.results) { + total = obj.results.length; + } else { + total = obj.rows.length; + } + } + + // Reset current nodes + while (obj.tbody.firstChild) { + obj.tbody.removeChild(obj.tbody.firstChild); + } + + // Hide all records from the table + for (var j = 0; j < obj.rows.length; j++) { + if (!obj.results || obj.results.indexOf(j) > -1) { + if (index < total) { + obj.tbody.appendChild(obj.rows[j]); + index++; + } + obj.rows[j].style.display = ""; + } else { + obj.rows[j].style.display = "none"; + } + } + + // Update pagination + if (obj.options.pagination > 0) { + obj.updatePagination(); + } + + obj.updateCornerPosition(); + + return total; + }; + + /** + * Which page the cell is + */ + obj.whichPage = function (cell) { + // Search + if ( + (obj.options.search == true || obj.options.filters == true) && + obj.results + ) { + cell = obj.results.indexOf(cell); + } + + return ( + Math.ceil((parseInt(cell) + 1) / parseInt(obj.options.pagination)) - 1 + ); + }; + + /** + * Go to page + */ + obj.page = function (pageNumber) { + var oldPage = obj.pageNumber; + + // Search + if ( + (obj.options.search == true || obj.options.filters == true) && + obj.results + ) { + var results = obj.results; + } else { + var results = obj.rows; + } + + // Per page + var quantityPerPage = parseInt(obj.options.pagination); + + // pageNumber + if (pageNumber == null || pageNumber == -1) { + // Last page + pageNumber = Math.ceil(results.length / quantityPerPage) - 1; + } + + // Page number + obj.pageNumber = pageNumber; + + var startRow = pageNumber * quantityPerPage; + var finalRow = pageNumber * quantityPerPage + quantityPerPage; + if (finalRow > results.length) { + finalRow = results.length; + } + if (startRow < 0) { + startRow = 0; + } + + // Reset container + while (obj.tbody.firstChild) { + obj.tbody.removeChild(obj.tbody.firstChild); + } + + // Appeding items + for (var j = startRow; j < finalRow; j++) { + if ( + (obj.options.search == true || obj.options.filters == true) && + obj.results + ) { + obj.tbody.appendChild(obj.rows[results[j]]); + } else { + obj.tbody.appendChild(obj.rows[j]); + } + } + + if (obj.options.pagination > 0) { + obj.updatePagination(); + } + + // Update corner position + obj.updateCornerPosition(); + + // Events + obj.dispatch("onchangepage", el, pageNumber, oldPage); + }; + + /** + * Update the pagination + */ + obj.updatePagination = function () { + // Reset container + obj.pagination.children[0].innerHTML = ""; + obj.pagination.children[1].innerHTML = ""; + + // Start pagination + if (obj.options.pagination) { + // Searchable + if ( + (obj.options.search == true || obj.options.filters == true) && + obj.results + ) { + var results = obj.results.length; + } else { + var results = obj.rows.length; + } + + if (!results) { + // No records found + obj.pagination.children[0].innerHTML = + obj.options.text.noRecordsFound; + } else { + // Pagination container + var quantyOfPages = Math.ceil(results / obj.options.pagination); + + if (obj.pageNumber < 6) { + var startNumber = 1; + var finalNumber = quantyOfPages < 10 ? quantyOfPages : 10; + } else if (quantyOfPages - obj.pageNumber < 5) { + var startNumber = quantyOfPages - 9; + var finalNumber = quantyOfPages; + if (startNumber < 1) { + startNumber = 1; + } + } else { + var startNumber = obj.pageNumber - 4; + var finalNumber = obj.pageNumber + 5; + } + + // First + if (startNumber > 1) { + var paginationItem = document.createElement("div"); + paginationItem.className = "jexcel_page"; + paginationItem.innerHTML = "<"; + paginationItem.title = 1; + obj.pagination.children[1].appendChild(paginationItem); + } + + // Get page links + for (var i = startNumber; i <= finalNumber; i++) { + var paginationItem = document.createElement("div"); + paginationItem.className = "jexcel_page"; + paginationItem.innerHTML = i; + obj.pagination.children[1].appendChild(paginationItem); + + if (obj.pageNumber == i - 1) { + paginationItem.classList.add("jexcel_page_selected"); + } + } + + // Last + if (finalNumber < quantyOfPages) { + var paginationItem = document.createElement("div"); + paginationItem.className = "jexcel_page"; + paginationItem.innerHTML = ">"; + paginationItem.title = quantyOfPages; + obj.pagination.children[1].appendChild(paginationItem); + } + + // Text + var format = function (format) { + var args = Array.prototype.slice.call(arguments, 1); + return format.replace(/{(\d+)}/g, function (match, number) { + return typeof args[number] != "undefined" ? args[number] : match; + }); + }; + + obj.pagination.children[0].innerHTML = format( + obj.options.text.showingPage, + obj.pageNumber + 1, + quantyOfPages + ); + } + } + }; + + /** + * Download CSV table + * + * @return null + */ + obj.download = function (includeHeaders) { + if (obj.options.allowExport == false) { + console.error("Export not allowed"); + } else { + // Data + var data = ""; + + // Get data + data += obj.copy( + false, + obj.options.csvDelimiter, + true, + includeHeaders, + true + ); + + // Download element + var blob = new Blob(["\uFEFF" + data], { + type: "text/csv;charset=utf-8;", + }); + + // IE Compatibility + if (window.navigator && window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveOrOpenBlob( + blob, + obj.options.csvFileName + ".csv" + ); + } else { + // Download element + var pom = document.createElement("a"); + var url = URL.createObjectURL(blob); + pom.href = url; + pom.setAttribute("download", obj.options.csvFileName + ".csv"); + document.body.appendChild(pom); + pom.click(); + pom.parentNode.removeChild(pom); + } + } + }; + + /** + * Initializes a new history record for undo/redo + * + * @return null + */ + obj.setHistory = function (changes) { + if (obj.ignoreHistory != true) { + // Increment and get the current history index + var index = ++obj.historyIndex; + + // Slice the array to discard undone changes + obj.history = obj.history = obj.history.slice(0, index + 1); + + // Keep history + obj.history[index] = changes; + } + }; + + /** + * Copy method + * + * @param bool highlighted - Get only highlighted cells + * @param delimiter - \t default to keep compatibility with excel + * @return string value + */ + obj.copy = function ( + highlighted, + delimiter, + returnData, + includeHeaders, + download + ) { + if (!delimiter) { + delimiter = "\t"; + } + + var div = new RegExp(delimiter, "ig"); + + // Controls + var header = []; + var col = []; + var colLabel = []; + var row = []; + var rowLabel = []; + var x = obj.options.data[0].length; + var y = obj.options.data.length; + var tmp = ""; + var copyHeader = false; + var headers = ""; + var nestedHeaders = ""; + var numOfCols = 0; + var numOfRows = 0; + + // Partial copy + var copyX = 0; + var copyY = 0; + var isPartialCopy = true; + // Go through the columns to get the data + for (var j = 0; j < y; j++) { + for (var i = 0; i < x; i++) { + // If cell is highlighted + if ( + !highlighted || + obj.records[j][i].classList.contains("highlight") + ) { + if (copyX <= i) { + copyX = i; + } + if (copyY <= j) { + copyY = j; + } + } + } + } + if (x === copyX + 1 && y === copyY + 1) { + isPartialCopy = false; + } + + if ( + (download && obj.options.includeHeadersOnDownload == true) || + (!download && + obj.options.includeHeadersOnCopy == true && + !isPartialCopy) || + includeHeaders + ) { + // Nested headers + if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) { + // Flexible way to handle nestedheaders + if ( + !(obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) + ) { + tmp = [obj.options.nestedHeaders]; + } else { + tmp = obj.options.nestedHeaders; + } + + for (var j = 0; j < tmp.length; j++) { + var nested = []; + for (var i = 0; i < tmp[j].length; i++) { + var colspan = parseInt(tmp[j][i].colspan); + nested.push(tmp[j][i].title); + for (var c = 0; c < colspan - 1; c++) { + nested.push(""); + } + } + nestedHeaders += nested.join(delimiter) + "\r\n"; + } + } + + copyHeader = true; + } + + // Reset container + obj.style = []; + + // Go through the columns to get the data + for (var j = 0; j < y; j++) { + col = []; + colLabel = []; + + for (var i = 0; i < x; i++) { + // If cell is highlighted + if ( + !highlighted || + obj.records[j][i].classList.contains("highlight") + ) { + if (copyHeader == true) { + header.push(obj.headers[i].textContent); + } + // Values + var value = obj.options.data[j][i]; + if ( + value.match && + (value.match(div) || + value.match(/,/g) || + value.match(/\n/) || + value.match(/\"/)) + ) { + value = value.replace(new RegExp('"', "g"), '""'); + value = '"' + value + '"'; + } + col.push(value); + + // Labels + if ( + obj.options.columns[i].type == "checkbox" || + obj.options.columns[i].type == "radio" + ) { + var label = value; + } else { + if (obj.options.stripHTMLOnCopy == true) { + var label = obj.records[j][i].textContent; + } else { + var label = obj.records[j][i].innerHTML; + } + if ( + label.match && + (label.match(div) || + label.match(/,/g) || + label.match(/\n/) || + label.match(/\"/)) + ) { + // Scape double quotes + label = label.replace(new RegExp('"', "g"), '""'); + label = '"' + label + '"'; + } + } + colLabel.push(label); + + // Get style + tmp = obj.records[j][i].getAttribute("style"); + tmp = tmp.replace("display: none;", ""); + obj.style.push(tmp ? tmp : ""); + } + } + + if (col.length) { + if (copyHeader) { + numOfCols = col.length; + row.push(header.join(delimiter)); + } + row.push(col.join(delimiter)); + } + if (colLabel.length) { + numOfRows++; + if (copyHeader) { + rowLabel.push(header.join(delimiter)); + copyHeader = false; + } + rowLabel.push(colLabel.join(delimiter)); + } + } + + if (x == numOfCols && y == numOfRows) { + headers = nestedHeaders; + } + + // Final string + var str = headers + row.join("\r\n"); + var strLabel = headers + rowLabel.join("\r\n"); + + // Create a hidden textarea to copy the values + if (!returnData) { + if (obj.options.copyCompatibility == true) { + obj.textarea.value = strLabel; + } else { + obj.textarea.value = str; + } + obj.textarea.select(); + document.execCommand("copy"); + } + + // Keep data + if (obj.options.copyCompatibility == true) { + obj.data = strLabel; + } else { + obj.data = str; + } + // Keep non visible information + obj.hashString = obj.hash(obj.data); + + // Any exiting border should go + if (!returnData) { + obj.removeCopyingSelection(); + + // Border + if (obj.highlighted) { + for (var i = 0; i < obj.highlighted.length; i++) { + obj.highlighted[i].classList.add("copying"); + if (obj.highlighted[i].classList.contains("highlight-left")) { + obj.highlighted[i].classList.add("copying-left"); + } + if (obj.highlighted[i].classList.contains("highlight-right")) { + obj.highlighted[i].classList.add("copying-right"); + } + if (obj.highlighted[i].classList.contains("highlight-top")) { + obj.highlighted[i].classList.add("copying-top"); + } + if (obj.highlighted[i].classList.contains("highlight-bottom")) { + obj.highlighted[i].classList.add("copying-bottom"); + } + } + } + + // Paste event + obj.dispatch( + "oncopy", + el, + obj.options.copyCompatibility == true ? rowLabel : row, + obj.hashString + ); + } + + return obj.data; + }; + + /** + * Jspreadsheet paste method + * + * @param integer row number + * @return string value + */ + obj.paste = function (x, y, data) { + // Paste filter + var ret = obj.dispatch("onbeforepaste", el, data, x, y); + + if (ret === false) { + return false; + } else if (ret) { + var data = ret; + } + + // Controls + var hash = obj.hash(data); + var style = hash == obj.hashString ? obj.style : null; + + // Depending on the behavior + if (obj.options.copyCompatibility == true && hash == obj.hashString) { + var data = obj.data; + } + + // Split new line + var data = obj.parseCSV(data, "\t"); + + if (x != null && y != null && data) { + // Records + var i = 0; + var j = 0; + var records = []; + var newStyle = {}; + var oldStyle = {}; + var styleIndex = 0; + + // Index + var colIndex = parseInt(x); + var rowIndex = parseInt(y); + var row = null; + + // Go through the columns to get the data + while ((row = data[j])) { + i = 0; + colIndex = parseInt(x); + + while (row[i] != null) { + // Update and keep history + var record = obj.updateCell(colIndex, rowIndex, row[i]); + // Keep history + records.push(record); + // Update all formulas in the chain + obj.updateFormulaChain(colIndex, rowIndex, records); + // Style + if (style && style[styleIndex]) { + var columnName = jexcel.getColumnNameFromId([colIndex, rowIndex]); + newStyle[columnName] = style[styleIndex]; + oldStyle[columnName] = obj.getStyle(columnName); + obj.records[rowIndex][colIndex].setAttribute( + "style", + style[styleIndex] + ); + styleIndex++; + } + i++; + if (row[i] != null) { + if (colIndex >= obj.headers.length - 1) { + // If the pasted column is out of range, create it if possible + if (obj.options.allowInsertColumn == true) { + obj.insertColumn(); + // Otherwise skip the pasted data that overflows + } else { + break; + } + } + colIndex = obj.right.get(colIndex, rowIndex); + } + } + + j++; + if (data[j]) { + if (rowIndex >= obj.rows.length - 1) { + // If the pasted row is out of range, create it if possible + if (obj.options.allowInsertRow == true) { + obj.insertRow(); + // Otherwise skip the pasted data that overflows + } else { + break; + } + } + rowIndex = obj.down.get(x, rowIndex); + } + } + + // Select the new cells + obj.updateSelectionFromCoords(x, y, colIndex, rowIndex); + + // Update history + obj.setHistory({ + action: "setValue", + records: records, + selection: obj.selectedCell, + newStyle: newStyle, + oldStyle: oldStyle, + }); + + // Update table + obj.updateTable(); + + // Paste event + obj.dispatch("onpaste", el, data); + + // On after changes + obj.onafterchanges(el, records); + } + + obj.removeCopyingSelection(); + }; + + /** + * Remove copying border + */ + obj.removeCopyingSelection = function () { + var copying = document.querySelectorAll(".jexcel .copying"); + for (var i = 0; i < copying.length; i++) { + copying[i].classList.remove("copying"); + copying[i].classList.remove("copying-left"); + copying[i].classList.remove("copying-right"); + copying[i].classList.remove("copying-top"); + copying[i].classList.remove("copying-bottom"); + } + }; + + /** + * Process row + */ + obj.historyProcessRow = function (type, historyRecord) { + var rowIndex = !historyRecord.insertBefore + ? historyRecord.rowNumber + 1 + : +historyRecord.rowNumber; + + if (obj.options.search == true) { + if (obj.results && obj.results.length != obj.rows.length) { + obj.resetSearch(); + } + } + + // Remove row + if (type == 1) { + var numOfRows = historyRecord.numOfRows; + // Remove nodes + for (var j = rowIndex; j < numOfRows + rowIndex; j++) { + obj.rows[j].parentNode.removeChild(obj.rows[j]); + } + // Remove references + obj.records.splice(rowIndex, numOfRows); + obj.options.data.splice(rowIndex, numOfRows); + obj.rows.splice(rowIndex, numOfRows); + + obj.conditionalSelectionUpdate(1, rowIndex, numOfRows + rowIndex - 1); + } else { + // Insert data + obj.records = jexcel.injectArray( + obj.records, + rowIndex, + historyRecord.rowRecords + ); + obj.options.data = jexcel.injectArray( + obj.options.data, + rowIndex, + historyRecord.rowData + ); + obj.rows = jexcel.injectArray( + obj.rows, + rowIndex, + historyRecord.rowNode + ); + // Insert nodes + var index = 0; + for (var j = rowIndex; j < historyRecord.numOfRows + rowIndex; j++) { + obj.tbody.insertBefore( + historyRecord.rowNode[index], + obj.tbody.children[j] + ); + index++; + } + } + + // Respect pagination + if (obj.options.pagination > 0) { + obj.page(obj.pageNumber); + } + + obj.updateTableReferences(); + }; + + /** + * Process column + */ + obj.historyProcessColumn = function (type, historyRecord) { + var columnIndex = !historyRecord.insertBefore + ? historyRecord.columnNumber + 1 + : historyRecord.columnNumber; + + // Remove column + if (type == 1) { + var numOfColumns = historyRecord.numOfColumns; + + obj.options.columns.splice(columnIndex, numOfColumns); + for (var i = columnIndex; i < numOfColumns + columnIndex; i++) { + obj.headers[i].parentNode.removeChild(obj.headers[i]); + obj.colgroup[i].parentNode.removeChild(obj.colgroup[i]); + } + obj.headers.splice(columnIndex, numOfColumns); + obj.colgroup.splice(columnIndex, numOfColumns); + for (var j = 0; j < historyRecord.data.length; j++) { + for (var i = columnIndex; i < numOfColumns + columnIndex; i++) { + obj.records[j][i].parentNode.removeChild(obj.records[j][i]); + } + obj.records[j].splice(columnIndex, numOfColumns); + obj.options.data[j].splice(columnIndex, numOfColumns); + } + // Process footers + if (obj.options.footers) { + for (var j = 0; j < obj.options.footers.length; j++) { + obj.options.footers[j].splice(columnIndex, numOfColumns); + } + } + } else { + // Insert data + obj.options.columns = jexcel.injectArray( + obj.options.columns, + columnIndex, + historyRecord.columns + ); + obj.headers = jexcel.injectArray( + obj.headers, + columnIndex, + historyRecord.headers + ); + obj.colgroup = jexcel.injectArray( + obj.colgroup, + columnIndex, + historyRecord.colgroup + ); + + var index = 0; + for ( + var i = columnIndex; + i < historyRecord.numOfColumns + columnIndex; + i++ + ) { + obj.headerContainer.insertBefore( + historyRecord.headers[index], + obj.headerContainer.children[i + 1] + ); + obj.colgroupContainer.insertBefore( + historyRecord.colgroup[index], + obj.colgroupContainer.children[i + 1] + ); + index++; + } + + for (var j = 0; j < historyRecord.data.length; j++) { + obj.options.data[j] = jexcel.injectArray( + obj.options.data[j], + columnIndex, + historyRecord.data[j] + ); + obj.records[j] = jexcel.injectArray( + obj.records[j], + columnIndex, + historyRecord.records[j] + ); + var index = 0; + for ( + var i = columnIndex; + i < historyRecord.numOfColumns + columnIndex; + i++ + ) { + obj.rows[j].insertBefore( + historyRecord.records[j][index], + obj.rows[j].children[i + 1] + ); + index++; + } + } + // Process footers + if (obj.options.footers) { + for (var j = 0; j < obj.options.footers.length; j++) { + obj.options.footers[j] = jexcel.injectArray( + obj.options.footers[j], + columnIndex, + historyRecord.footers[j] + ); + } + } + } + + // Adjust nested headers + if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) { + // Flexible way to handle nestedheaders + if (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) { + for (var j = 0; j < obj.options.nestedHeaders.length; j++) { + if (type == 1) { + var colspan = + parseInt( + obj.options.nestedHeaders[j][ + obj.options.nestedHeaders[j].length - 1 + ].colspan + ) - historyRecord.numOfColumns; + } else { + var colspan = + parseInt( + obj.options.nestedHeaders[j][ + obj.options.nestedHeaders[j].length - 1 + ].colspan + ) + historyRecord.numOfColumns; + } + obj.options.nestedHeaders[j][ + obj.options.nestedHeaders[j].length - 1 + ].colspan = colspan; + obj.thead.children[j].children[ + obj.thead.children[j].children.length - 1 + ].setAttribute("colspan", colspan); + } + } else { + if (type == 1) { + var colspan = + parseInt(obj.options.nestedHeaders[0].colspan) - + historyRecord.numOfColumns; + } else { + var colspan = + parseInt(obj.options.nestedHeaders[0].colspan) + + historyRecord.numOfColumns; + } + obj.options.nestedHeaders[0].colspan = colspan; + obj.thead.children[0].children[ + obj.thead.children[0].children.length - 1 + ].setAttribute("colspan", colspan); + } + } + + obj.updateTableReferences(); + }; + + /** + * Undo last action + */ + obj.undo = function () { + // Ignore events and history + var ignoreEvents = obj.ignoreEvents ? true : false; + var ignoreHistory = obj.ignoreHistory ? true : false; + + obj.ignoreEvents = true; + obj.ignoreHistory = true; + + // Records + var records = []; + + // Update cells + if (obj.historyIndex >= 0) { + // History + var historyRecord = obj.history[obj.historyIndex--]; + + if (historyRecord.action == "insertRow") { + obj.historyProcessRow(1, historyRecord); + } else if (historyRecord.action == "deleteRow") { + obj.historyProcessRow(0, historyRecord); + } else if (historyRecord.action == "insertColumn") { + obj.historyProcessColumn(1, historyRecord); + } else if (historyRecord.action == "deleteColumn") { + obj.historyProcessColumn(0, historyRecord); + } else if (historyRecord.action == "moveRow") { + obj.moveRow(historyRecord.newValue, historyRecord.oldValue); + } else if (historyRecord.action == "moveColumn") { + obj.moveColumn(historyRecord.newValue, historyRecord.oldValue); + } else if (historyRecord.action == "setMerge") { + obj.removeMerge(historyRecord.column, historyRecord.data); + } else if (historyRecord.action == "setStyle") { + obj.setStyle(historyRecord.oldValue, null, null, 1); + } else if (historyRecord.action == "setWidth") { + obj.setWidth(historyRecord.column, historyRecord.oldValue); + } else if (historyRecord.action == "setHeight") { + obj.setHeight(historyRecord.row, historyRecord.oldValue); + } else if (historyRecord.action == "setHeader") { + obj.setHeader(historyRecord.column, historyRecord.oldValue); + } else if (historyRecord.action == "setComments") { + obj.setComments( + historyRecord.column, + historyRecord.oldValue[0], + historyRecord.oldValue[1] + ); + } else if (historyRecord.action == "orderBy") { + var rows = []; + for (var j = 0; j < historyRecord.rows.length; j++) { + rows[historyRecord.rows[j]] = j; + } + obj.updateOrderArrow( + historyRecord.column, + historyRecord.order ? 0 : 1 + ); + obj.updateOrder(rows); + } else if (historyRecord.action == "setValue") { + // Redo for changes in cells + for (var i = 0; i < historyRecord.records.length; i++) { + records.push({ + x: historyRecord.records[i].x, + y: historyRecord.records[i].y, + newValue: historyRecord.records[i].oldValue, + }); + + if (historyRecord.oldStyle) { + obj.resetStyle(historyRecord.oldStyle); + } + } + // Update records + obj.setValue(records); + + // Update selection + if (historyRecord.selection) { + obj.updateSelectionFromCoords( + historyRecord.selection[0], + historyRecord.selection[1], + historyRecord.selection[2], + historyRecord.selection[3] + ); + } + } + } + obj.ignoreEvents = ignoreEvents; + obj.ignoreHistory = ignoreHistory; + + // Events + obj.dispatch("onundo", el, historyRecord); + }; + + /** + * Redo previously undone action + */ + obj.redo = function () { + // Ignore events and history + var ignoreEvents = obj.ignoreEvents ? true : false; + var ignoreHistory = obj.ignoreHistory ? true : false; + + obj.ignoreEvents = true; + obj.ignoreHistory = true; + + // Records + var records = []; + + // Update cells + if (obj.historyIndex < obj.history.length - 1) { + // History + var historyRecord = obj.history[++obj.historyIndex]; + + if (historyRecord.action == "insertRow") { + obj.historyProcessRow(0, historyRecord); + } else if (historyRecord.action == "deleteRow") { + obj.historyProcessRow(1, historyRecord); + } else if (historyRecord.action == "insertColumn") { + obj.historyProcessColumn(0, historyRecord); + } else if (historyRecord.action == "deleteColumn") { + obj.historyProcessColumn(1, historyRecord); + } else if (historyRecord.action == "moveRow") { + obj.moveRow(historyRecord.oldValue, historyRecord.newValue); + } else if (historyRecord.action == "moveColumn") { + obj.moveColumn(historyRecord.oldValue, historyRecord.newValue); + } else if (historyRecord.action == "setMerge") { + obj.setMerge( + historyRecord.column, + historyRecord.colspan, + historyRecord.rowspan, + 1 + ); + } else if (historyRecord.action == "setStyle") { + obj.setStyle(historyRecord.newValue, null, null, 1); + } else if (historyRecord.action == "setWidth") { + obj.setWidth(historyRecord.column, historyRecord.newValue); + } else if (historyRecord.action == "setHeight") { + obj.setHeight(historyRecord.row, historyRecord.newValue); + } else if (historyRecord.action == "setHeader") { + obj.setHeader(historyRecord.column, historyRecord.newValue); + } else if (historyRecord.action == "setComments") { + obj.setComments( + historyRecord.column, + historyRecord.newValue[0], + historyRecord.newValue[1] + ); + } else if (historyRecord.action == "orderBy") { + obj.updateOrderArrow(historyRecord.column, historyRecord.order); + obj.updateOrder(historyRecord.rows); + } else if (historyRecord.action == "setValue") { + obj.setValue(historyRecord.records); + // Redo for changes in cells + for (var i = 0; i < historyRecord.records.length; i++) { + if (historyRecord.oldStyle) { + obj.resetStyle(historyRecord.newStyle); + } + } + // Update selection + if (historyRecord.selection) { + obj.updateSelectionFromCoords( + historyRecord.selection[0], + historyRecord.selection[1], + historyRecord.selection[2], + historyRecord.selection[3] + ); + } + } + } + obj.ignoreEvents = ignoreEvents; + obj.ignoreHistory = ignoreHistory; + + // Events + obj.dispatch("onredo", el, historyRecord); + }; + + /** + * Get dropdown value from key + */ + obj.getDropDownValue = function (column, key) { + var value = []; + + if (obj.options.columns[column] && obj.options.columns[column].source) { + // Create array from source + var combo = []; + var source = obj.options.columns[column].source; + + for (var i = 0; i < source.length; i++) { + if (typeof source[i] == "object") { + combo[source[i].id] = source[i].name; + } else { + combo[source[i]] = source[i]; + } + } + + // Guarantee single multiple compatibility + var keys = Array.isArray(key) ? key : ("" + key).split(";"); + + for (var i = 0; i < keys.length; i++) { + if (typeof keys[i] === "object") { + value.push(combo[keys[i].id]); + } else { + if (combo[keys[i]]) { + value.push(combo[keys[i]]); + } + } + } + } else { + console.error("Invalid column"); + } + + return value.length > 0 ? value.join("; ") : ""; + }; + + /** + * From stack overflow contributions + */ + obj.parseCSV = function (str, delimiter) { + // Remove last line break + str = str.replace(/\r?\n$|\r$|\n$/g, ""); + // Last caracter is the delimiter + if (str.charCodeAt(str.length - 1) == 9) { + str += "\0"; + } + // user-supplied delimeter or default comma + delimiter = delimiter || ","; + + var arr = []; + var quote = false; // true means we're inside a quoted field + // iterate over each character, keep track of current row and column (of the returned array) + for (var row = 0, col = 0, c = 0; c < str.length; c++) { + var cc = str[c], + nc = str[c + 1]; + arr[row] = arr[row] || []; + arr[row][col] = arr[row][col] || ""; + + // If the current character is a quotation mark, and we're inside a quoted field, and the next character is also a quotation mark, add a quotation mark to the current column and skip the next character + if (cc == '"' && quote && nc == '"') { + arr[row][col] += cc; + ++c; + continue; + } + + // If it's just one quotation mark, begin/end quoted field + if (cc == '"') { + quote = !quote; + continue; + } + + // If it's a comma and we're not in a quoted field, move on to the next column + if (cc == delimiter && !quote) { + ++col; + continue; + } + + // If it's a newline (CRLF) and we're not in a quoted field, skip the next character and move on to the next row and move to column 0 of that new row + if (cc == "\r" && nc == "\n" && !quote) { + ++row; + col = 0; + ++c; + continue; + } + + // If it's a newline (LF or CR) and we're not in a quoted field, move on to the next row and move to column 0 of that new row + if (cc == "\n" && !quote) { + ++row; + col = 0; + continue; + } + if (cc == "\r" && !quote) { + ++row; + col = 0; + continue; + } + + // Otherwise, append the current character to the current column + arr[row][col] += cc; + } + return arr; + }; + + obj.hash = function (str) { + var hash = 0, + i, + chr; + + if (str.length === 0) { + return hash; + } else { + for (i = 0; i < str.length; i++) { + chr = str.charCodeAt(i); + hash = (hash << 5) - hash + chr; + hash |= 0; + } + } + return hash; + }; + + obj.onafterchanges = function (el, records) { + // Events + obj.dispatch("onafterchanges", el, records); + }; + + obj.destroy = function () { + jexcel.destroy(el); + }; + + /** + * Initialization method + */ + obj.init = function () { + jexcel.current = obj; + + // Build handlers + if (typeof jexcel.build == "function") { + if (obj.options.root) { + jexcel.build(obj.options.root); + } else { + jexcel.build(document); + jexcel.build = null; + } + } + + // Event + el.setAttribute("tabindex", 1); + el.addEventListener("focus", function (e) { + if (jexcel.current && !obj.selectedCell) { + obj.updateSelectionFromCoords(0, 0, 0, 0); + obj.left(); + } + }); + + // Load the table data based on an CSV file + if (obj.options.csv) { + // Loading + if (obj.options.loadingSpin == true) { + jSuites.loading.show(); + } + + // Load CSV file + jSuites.ajax({ + url: obj.options.csv, + method: obj.options.method, + data: obj.options.requestVariables, + dataType: "text", + success: function (result) { + // Convert data + var newData = obj.parseCSV(result, obj.options.csvDelimiter); + + // Headers + if (obj.options.csvHeaders == true && newData.length > 0) { + var headers = newData.shift(); + for (var i = 0; i < headers.length; i++) { + if (!obj.options.columns[i]) { + obj.options.columns[i] = { + type: "text", + align: obj.options.defaultColAlign, + width: obj.options.defaultColWidth, + }; + } + // Precedence over pre-configurated titles + if (typeof obj.options.columns[i].title === "undefined") { + obj.options.columns[i].title = headers[i]; + } + } + } + // Data + obj.options.data = newData; + // Prepare table + obj.prepareTable(); + // Hide spin + if (obj.options.loadingSpin == true) { + jSuites.loading.hide(); + } + }, + }); + } else if (obj.options.url) { + // Loading + if (obj.options.loadingSpin == true) { + jSuites.loading.show(); + } + + jSuites.ajax({ + url: obj.options.url, + method: obj.options.method, + data: obj.options.requestVariables, + dataType: "json", + success: function (result) { + // Data + obj.options.data = result.data ? result.data : result; + // Prepare table + obj.prepareTable(); + // Hide spin + if (obj.options.loadingSpin == true) { + jSuites.loading.hide(); + } + }, + }); + } else { + // Prepare table + obj.prepareTable(); + } + }; + + // Context menu + if (options && options.contextMenu != null) { + obj.options.contextMenu = options.contextMenu; + } else { + obj.options.contextMenu = function (el, x, y, e) { + var items = []; + + if (y == null) { + // Insert a new column + if (obj.options.allowInsertColumn == true) { + items.push({ + title: obj.options.text.insertANewColumnBefore, + onclick: function () { + obj.insertColumn(1, parseInt(x), 1); + }, + }); + } + + if (obj.options.allowInsertColumn == true) { + items.push({ + title: obj.options.text.insertANewColumnAfter, + onclick: function () { + obj.insertColumn(1, parseInt(x), 0); + }, + }); + } + + // Delete a column + if (obj.options.allowDeleteColumn == true) { + items.push({ + title: obj.options.text.deleteSelectedColumns, + onclick: function () { + obj.deleteColumn( + obj.getSelectedColumns().length ? undefined : parseInt(x) + ); + }, + }); + } + + // Rename column + if (obj.options.allowRenameColumn == true) { + items.push({ + title: obj.options.text.renameThisColumn, + onclick: function () { + obj.setHeader(x); + }, + }); + } + + // Sorting + if (obj.options.columnSorting == true) { + // Line + items.push({ type: "line" }); + + items.push({ + title: obj.options.text.orderAscending, + onclick: function () { + obj.orderBy(x, 0); + }, + }); + items.push({ + title: obj.options.text.orderDescending, + onclick: function () { + obj.orderBy(x, 1); + }, + }); + } + } else { + // Insert new row + if (obj.options.allowInsertRow == true) { + items.push({ + title: obj.options.text.insertANewRowBefore, + onclick: function () { + obj.insertRow(1, parseInt(y), 1); + }, + }); + + items.push({ + title: obj.options.text.insertANewRowAfter, + onclick: function () { + obj.insertRow(1, parseInt(y)); + }, + }); + } + + if (obj.options.allowDeleteRow == true) { + items.push({ + title: obj.options.text.deleteSelectedRows, + onclick: function () { + obj.deleteRow( + obj.getSelectedRows().length ? undefined : parseInt(y) + ); + }, + }); + } + + if (x) { + if (obj.options.allowComments == true) { + items.push({ type: "line" }); + + var title = obj.records[y][x].getAttribute("title") || ""; + + items.push({ + title: title + ? obj.options.text.editComments + : obj.options.text.addComments, + onclick: function () { + var comment = prompt(obj.options.text.comments, title); + if (comment) { + obj.setComments([x, y], comment); + } + }, + }); + + if (title) { + items.push({ + title: obj.options.text.clearComments, + onclick: function () { + obj.setComments([x, y], ""); + }, + }); + } + } + } + } + + // Line + items.push({ type: "line" }); + + // Copy + items.push({ + title: obj.options.text.copy, + shortcut: "Ctrl + C", + onclick: function () { + obj.copy(true); + }, + }); + + // Paste + if (navigator && navigator.clipboard) { + items.push({ + title: obj.options.text.paste, + shortcut: "Ctrl + V", + onclick: function () { + if (obj.selectedCell) { + navigator.clipboard.readText().then(function (text) { + if (text) { + jexcel.current.paste( + obj.selectedCell[0], + obj.selectedCell[1], + text + ); + } + }); + } + }, + }); + } + + // Save + if (obj.options.allowExport) { + items.push({ + title: obj.options.text.saveAs, + shortcut: "Ctrl + S", + onclick: function () { + obj.download(); + }, + }); + } + + // About + if (obj.options.about) { + items.push({ + title: obj.options.text.about, + onclick: function () { + if (obj.options.about === true) { + alert(Version().print()); + } else { + alert(obj.options.about); + } + }, + }); + } + + return items; + }; + } + + obj.scrollControls = function (e) { + obj.wheelControls(); + + if ( + obj.options.freezeColumns > 0 && + obj.content.scrollLeft != scrollLeft + ) { + obj.updateFreezePosition(); + } + + // Close editor + if ( + obj.options.lazyLoading == true || + obj.options.tableOverflow == true + ) { + if (obj.edition && e.target.className.substr(0, 9) != "jdropdown") { + obj.closeEditor(obj.edition[0], true); + } + } + }; + + obj.wheelControls = function (e) { + if (obj.options.lazyLoading == true) { + if (jexcel.timeControlLoading == null) { + jexcel.timeControlLoading = setTimeout(function () { + if ( + obj.content.scrollTop + obj.content.clientHeight >= + obj.content.scrollHeight - 10 + ) { + if (obj.loadDown()) { + if ( + obj.content.scrollTop + obj.content.clientHeight > + obj.content.scrollHeight - 10 + ) { + obj.content.scrollTop = + obj.content.scrollTop - obj.content.clientHeight; + } + obj.updateCornerPosition(); + } + } else if (obj.content.scrollTop <= obj.content.clientHeight) { + if (obj.loadUp()) { + if (obj.content.scrollTop < 10) { + obj.content.scrollTop = + obj.content.scrollTop + obj.content.clientHeight; + } + obj.updateCornerPosition(); + } + } + + jexcel.timeControlLoading = null; + }, 100); + } + } + }; + + // Get width of all freezed cells together + obj.getFreezeWidth = function () { + var width = 0; + if (obj.options.freezeColumns > 0) { + for (var i = 0; i < obj.options.freezeColumns; i++) { + width += parseInt(obj.options.columns[i].width); + } + } + return width; + }; + + var scrollLeft = 0; + + obj.updateFreezePosition = function () { + scrollLeft = obj.content.scrollLeft; + var width = 0; + if (scrollLeft > 50) { + for (var i = 0; i < obj.options.freezeColumns; i++) { + if (i > 0) { + // Must check if the previous column is hidden or not to determin whether the width shoule be added or not! + if (obj.options.columns[i - 1].type !== "hidden") { + width += parseInt(obj.options.columns[i - 1].width); + } + } + obj.headers[i].classList.add("jexcel_freezed"); + obj.headers[i].style.left = width + "px"; + for (var j = 0; j < obj.rows.length; j++) { + if (obj.rows[j] && obj.records[j][i]) { + var shifted = + scrollLeft + + (i > 0 ? obj.records[j][i - 1].style.width : 0) - + 51 + + "px"; + obj.records[j][i].classList.add("jexcel_freezed"); + obj.records[j][i].style.left = shifted; + } + } + } + } else { + for (var i = 0; i < obj.options.freezeColumns; i++) { + obj.headers[i].classList.remove("jexcel_freezed"); + obj.headers[i].style.left = ""; + for (var j = 0; j < obj.rows.length; j++) { + if (obj.records[j][i]) { + obj.records[j][i].classList.remove("jexcel_freezed"); + obj.records[j][i].style.left = ""; + } + } + } + } + + // Place the corner in the correct place + obj.updateCornerPosition(); + }; + + el.addEventListener("DOMMouseScroll", obj.wheelControls); + el.addEventListener("mousewheel", obj.wheelControls); + + el.jexcel = obj; + el.jspreadsheet = obj; + + obj.init(); + + return obj; + }; + + // Define dictionary + jexcel.setDictionary = function (o) { + jSuites.setDictionary(o); + }; + + // Define extensions + jexcel.setExtensions = function (o) { + var k = Object.keys(o); + for (var i = 0; i < k.length; i++) { + if (typeof o[k[i]] === "function") { + jexcel[k[i]] = o[k[i]]; + if (jexcel.license && typeof o[k[i]].license == "function") { + o[k[i]].license(jexcel.license); + } + } + } + }; + + /** + * Formulas + */ + if (typeof formula !== "undefined") { + jexcel.formula = formula; + } + jexcel.version = Version; + + jexcel.current = null; + jexcel.timeControl = null; + jexcel.timeControlLoading = null; + + const destroyEvents = function (root) { + root.removeEventListener("mouseup", jexcel.mouseUpControls); + root.removeEventListener("mousedown", jexcel.mouseDownControls); + root.removeEventListener("mousemove", jexcel.mouseMoveControls); + root.removeEventListener("mouseover", jexcel.mouseOverControls); + root.removeEventListener("dblclick", jexcel.doubleClickControls); + root.removeEventListener("paste", jexcel.pasteControls); + root.removeEventListener("contextmenu", jexcel.contextMenuControls); + root.removeEventListener("touchstart", jexcel.touchStartControls); + root.removeEventListener("touchend", jexcel.touchEndControls); + root.removeEventListener("touchcancel", jexcel.touchEndControls); + document.removeEventListener("keydown", jexcel.keyDownControls); + }; + + jexcel.destroy = function (element, destroyEventHandlers) { + if (element.jexcel) { + var root = element.jexcel.options.root + ? element.jexcel.options.root + : document; + element.removeEventListener( + "DOMMouseScroll", + element.jexcel.scrollControls + ); + element.removeEventListener("mousewheel", element.jexcel.scrollControls); + element.jexcel = null; + element.innerHTML = ""; + + if (destroyEventHandlers) { + destroyEvents(root); + jexcel = null; + } + } + }; + + jexcel.build = function (root) { + destroyEvents(root); + root.addEventListener("mouseup", jexcel.mouseUpControls); + root.addEventListener("mousedown", jexcel.mouseDownControls); + root.addEventListener("mousemove", jexcel.mouseMoveControls); + root.addEventListener("mouseover", jexcel.mouseOverControls); + root.addEventListener("dblclick", jexcel.doubleClickControls); + root.addEventListener("paste", jexcel.pasteControls); + root.addEventListener("contextmenu", jexcel.contextMenuControls); + root.addEventListener("touchstart", jexcel.touchStartControls); + root.addEventListener("touchend", jexcel.touchEndControls); + root.addEventListener("touchcancel", jexcel.touchEndControls); + root.addEventListener("touchmove", jexcel.touchEndControls); + document.addEventListener("keydown", jexcel.keyDownControls); + }; + + /** + * Events + */ + jexcel.keyDownControls = function (e) { + if (jexcel.current) { + if (jexcel.current.edition) { + if (e.which == 27) { + // Escape + if (jexcel.current.edition) { + // Exit without saving + jexcel.current.closeEditor(jexcel.current.edition[0], false); + } + e.preventDefault(); + } else if (e.which == 13) { + // Enter + if ( + jexcel.current.options.columns[jexcel.current.edition[2]].type == + "calendar" + ) { + jexcel.current.closeEditor(jexcel.current.edition[0], true); + } else if ( + jexcel.current.options.columns[jexcel.current.edition[2]].type == + "dropdown" || + jexcel.current.options.columns[jexcel.current.edition[2]].type == + "autocomplete" + ) { + // Do nothing + } else { + // Alt enter -> do not close editor + if ( + (jexcel.current.options.wordWrap == true || + jexcel.current.options.columns[jexcel.current.edition[2]] + .wordWrap == true || + jexcel.current.options.data[jexcel.current.edition[3]][ + jexcel.current.edition[2] + ].length > 200) && + e.altKey + ) { + // Add new line to the editor + var editorTextarea = jexcel.current.edition[0].children[0]; + var editorValue = jexcel.current.edition[0].children[0].value; + var editorIndexOf = editorTextarea.selectionStart; + editorValue = + editorValue.slice(0, editorIndexOf) + + "\n" + + editorValue.slice(editorIndexOf); + editorTextarea.value = editorValue; + editorTextarea.focus(); + editorTextarea.selectionStart = editorIndexOf + 1; + editorTextarea.selectionEnd = editorIndexOf + 1; + } else { + jexcel.current.edition[0].children[0].blur(); + } + } + } else if (e.which == 9) { + // Tab + if ( + ["calendar", "html"].includes( + jexcel.current.options.columns[jexcel.current.edition[2]].type + ) + ) { + jexcel.current.closeEditor(jexcel.current.edition[0], true); + } else { + jexcel.current.edition[0].children[0].blur(); + } + } + } + + if (!jexcel.current.edition && jexcel.current.selectedCell) { + // Which key + if (e.which == 37) { + jexcel.current.left(e.shiftKey, e.ctrlKey); + e.preventDefault(); + } else if (e.which == 39) { + jexcel.current.right(e.shiftKey, e.ctrlKey); + e.preventDefault(); + } else if (e.which == 38) { + jexcel.current.up(e.shiftKey, e.ctrlKey); + e.preventDefault(); + } else if (e.which == 40) { + jexcel.current.down(e.shiftKey, e.ctrlKey); + e.preventDefault(); + } else if (e.which == 36) { + jexcel.current.first(e.shiftKey, e.ctrlKey); + e.preventDefault(); + } else if (e.which == 35) { + jexcel.current.last(e.shiftKey, e.ctrlKey); + e.preventDefault(); + } else if (e.which == 46) { + // Delete + if (jexcel.current.options.editable == true) { + if (jexcel.current.selectedRow) { + if (jexcel.current.options.allowDeleteRow == true) { + if ( + confirm( + jexcel.current.options.text + .areYouSureToDeleteTheSelectedRows + ) + ) { + jexcel.current.deleteRow(); + } + } + } else if (jexcel.current.selectedHeader) { + if (jexcel.current.options.allowDeleteColumn == true) { + if ( + confirm( + jexcel.current.options.text + .areYouSureToDeleteTheSelectedColumns + ) + ) { + jexcel.current.deleteColumn(); + } + } + } else { + // Change value + jexcel.current.setValue(jexcel.current.highlighted, ""); + } + } + } else if (e.which == 13) { + // Move cursor + if (e.shiftKey) { + jexcel.current.up(); + } else { + if (jexcel.current.options.allowInsertRow == true) { + if (jexcel.current.options.allowManualInsertRow == true) { + if ( + jexcel.current.selectedCell[1] == + jexcel.current.options.data.length - 1 + ) { + // New record in case selectedCell in the last row + jexcel.current.insertRow(); + } + } + } + + jexcel.current.down(); + } + e.preventDefault(); + } else if (e.which == 9) { + // Tab + if (e.shiftKey) { + jexcel.current.left(); + } else { + if (jexcel.current.options.allowInsertColumn == true) { + if (jexcel.current.options.allowManualInsertColumn == true) { + if ( + jexcel.current.selectedCell[0] == + jexcel.current.options.data[0].length - 1 + ) { + // New record in case selectedCell in the last column + jexcel.current.insertColumn(); + } + } + } + + jexcel.current.right(); + } + e.preventDefault(); + } else { + if ((e.ctrlKey || e.metaKey) && !e.shiftKey) { + if (e.which == 65) { + // Ctrl + A + jexcel.current.selectAll(); + e.preventDefault(); + } else if (e.which == 83) { + // Ctrl + S + jexcel.current.download(); + e.preventDefault(); + } else if (e.which == 89) { + // Ctrl + Y + jexcel.current.redo(); + e.preventDefault(); + } else if (e.which == 90) { + // Ctrl + Z + jexcel.current.undo(); + e.preventDefault(); + } else if (e.which == 67) { + // Ctrl + C + jexcel.current.copy(true); + e.preventDefault(); + } else if (e.which == 88) { + // Ctrl + X + if (jexcel.current.options.editable == true) { + jexcel.cutControls(); + } else { + jexcel.copyControls(); + } + e.preventDefault(); + } else if (e.which == 86) { + // Ctrl + V + jexcel.pasteControls(); + } + } else { + if (jexcel.current.selectedCell) { + if (jexcel.current.options.editable == true) { + var rowId = jexcel.current.selectedCell[1]; + var columnId = jexcel.current.selectedCell[0]; + + // If is not readonly + if ( + jexcel.current.options.columns[columnId].type != "readonly" + ) { + // Characters able to start a edition + if (e.keyCode == 32) { + // Space + e.preventDefault(); + if ( + jspreadsheet.current.options.columns[columnId].type == + "checkbox" || + jspreadsheet.current.options.columns[columnId].type == + "radio" + ) { + jspreadsheet.current.setCheckRadioValue(); + } else { + // Start edition + jspreadsheet.current.openEditor( + jspreadsheet.current.records[rowId][columnId], + true + ); + } + } else if (e.keyCode == 113) { + // Start edition with current content F2 + jexcel.current.openEditor( + jexcel.current.records[rowId][columnId], + false + ); + } else if ( + e.keyCode == 8 || + (e.keyCode >= 48 && e.keyCode <= 57) || + (e.keyCode >= 96 && e.keyCode <= 111) || + (e.keyCode >= 187 && e.keyCode <= 190) || + ((String.fromCharCode(e.keyCode) == e.key || + String.fromCharCode(e.keyCode).toLowerCase() == + e.key.toLowerCase()) && + jexcel.validLetter(String.fromCharCode(e.keyCode))) + ) { + // Start edition + jexcel.current.openEditor( + jexcel.current.records[rowId][columnId], + true + ); + // Prevent entries in the calendar + if ( + jexcel.current.options.columns[columnId].type == + "calendar" + ) { + e.preventDefault(); + } + } + } + } + } + } + } + } else { + if (e.target.classList.contains("jexcel_search")) { + if (jexcel.timeControl) { + clearTimeout(jexcel.timeControl); + } + + jexcel.timeControl = setTimeout(function () { + jexcel.current.search(e.target.value); + }, 200); + } + } + } + }; + + jexcel.isMouseAction = false; + + jexcel.mouseDownControls = function (e) { + e = e || window.event; + if (e.buttons) { + var mouseButton = e.buttons; + } else if (e.button) { + var mouseButton = e.button; + } else { + var mouseButton = e.which; + } + + // Get elements + var jexcelTable = jexcel.getElement(e.target); + + if (jexcelTable[0]) { + if (jexcel.current != jexcelTable[0].jexcel) { + if (jexcel.current) { + if (jexcel.current.edition) { + jexcel.current.closeEditor(jexcel.current.edition[0], true); + } + jexcel.current.resetSelection(); + } + jexcel.current = jexcelTable[0].jexcel; + } + } else { + if (jexcel.current) { + if (jexcel.current.edition) { + jexcel.current.closeEditor(jexcel.current.edition[0], true); + } + + jexcel.current.resetSelection(true); + jexcel.current = null; + } + } + + if (jexcel.current && mouseButton == 1) { + if (e.target.classList.contains("jexcel_selectall")) { + if (jexcel.current) { + jexcel.current.selectAll(); + } + } else if (e.target.classList.contains("jexcel_corner")) { + if (jexcel.current.options.editable == true) { + jexcel.current.selectedCorner = true; + } + } else { + // Header found + if (jexcelTable[1] == 1) { + var columnId = e.target.getAttribute("data-x"); + if (columnId) { + // Update cursor + var info = e.target.getBoundingClientRect(); + if ( + jexcel.current.options.columnResize == true && + info.width - e.offsetX < 6 + ) { + // Resize helper + jexcel.current.resizing = { + mousePosition: e.pageX, + column: columnId, + width: info.width, + }; + + // Border indication + jexcel.current.headers[columnId].classList.add("resizing"); + for (var j = 0; j < jexcel.current.records.length; j++) { + if (jexcel.current.records[j][columnId]) { + jexcel.current.records[j][columnId].classList.add("resizing"); + } + } + } else if ( + jexcel.current.options.columnDrag == true && + info.height - e.offsetY < 6 + ) { + if (jexcel.current.isColMerged(columnId).length) { + console.error( + "Jspreadsheet: This column is part of a merged cell." + ); + } else { + // Reset selection + jexcel.current.resetSelection(); + // Drag helper + jexcel.current.dragging = { + element: e.target, + column: columnId, + destination: columnId, + }; + // Border indication + jexcel.current.headers[columnId].classList.add("dragging"); + for (var j = 0; j < jexcel.current.records.length; j++) { + if (jexcel.current.records[j][columnId]) { + jexcel.current.records[j][columnId].classList.add( + "dragging" + ); + } + } + } + } else { + if (jexcel.current.selectedHeader && (e.shiftKey || e.ctrlKey)) { + var o = jexcel.current.selectedHeader; + var d = columnId; + } else { + // Press to rename + if ( + jexcel.current.selectedHeader == columnId && + jexcel.current.options.allowRenameColumn == true + ) { + jexcel.timeControl = setTimeout(function () { + jexcel.current.setHeader(columnId); + }, 800); + } + + // Keep track of which header was selected first + jexcel.current.selectedHeader = columnId; + + // Update selection single column + var o = columnId; + var d = columnId; + } + + // Update selection + jexcel.current.updateSelectionFromCoords( + o, + 0, + d, + jexcel.current.options.data.length - 1 + ); + } + } else { + if (e.target.parentNode.classList.contains("jexcel_nested")) { + if (e.target.getAttribute("data-column")) { + var column = e.target.getAttribute("data-column").split(","); + var c1 = parseInt(column[0]); + var c2 = parseInt(column[column.length - 1]); + } else { + var c1 = 0; + var c2 = jexcel.current.options.columns.length - 1; + } + jexcel.current.updateSelectionFromCoords( + c1, + 0, + c2, + jexcel.current.options.data.length - 1 + ); + } + } + } else { + jexcel.current.selectedHeader = false; + } + + // Body found + if (jexcelTable[1] == 2) { + var rowId = e.target.getAttribute("data-y"); + + if (e.target.classList.contains("jexcel_row")) { + var info = e.target.getBoundingClientRect(); + if ( + jexcel.current.options.rowResize == true && + info.height - e.offsetY < 6 + ) { + // Resize helper + jexcel.current.resizing = { + element: e.target.parentNode, + mousePosition: e.pageY, + row: rowId, + height: info.height, + }; + // Border indication + e.target.parentNode.classList.add("resizing"); + } else if ( + jexcel.current.options.rowDrag == true && + info.width - e.offsetX < 6 + ) { + if (jexcel.current.isRowMerged(rowId).length) { + console.error( + "Jspreadsheet: This row is part of a merged cell" + ); + } else if ( + jexcel.current.options.search == true && + jexcel.current.results + ) { + console.error( + "Jspreadsheet: Please clear your search before perform this action" + ); + } else { + // Reset selection + jexcel.current.resetSelection(); + // Drag helper + jexcel.current.dragging = { + element: e.target.parentNode, + row: rowId, + destination: rowId, + }; + // Border indication + e.target.parentNode.classList.add("dragging"); + } + } else { + if (jexcel.current.selectedRow && (e.shiftKey || e.ctrlKey)) { + var o = jexcel.current.selectedRow; + var d = rowId; + } else { + // Keep track of which header was selected first + jexcel.current.selectedRow = rowId; + + // Update selection single column + var o = rowId; + var d = rowId; + } + + // Update selection + jexcel.current.updateSelectionFromCoords( + 0, + o, + jexcel.current.options.data[0].length - 1, + d + ); + } + } else { + // Jclose + if ( + e.target.classList.contains("jclose") && + e.target.clientWidth - e.offsetX < 50 && + e.offsetY < 50 + ) { + jexcel.current.closeEditor(jexcel.current.edition[0], true); + } else { + var getCellCoords = function (element) { + var x = element.getAttribute("data-x"); + var y = element.getAttribute("data-y"); + if (x && y) { + return [x, y]; + } else { + if (element.parentNode) { + return getCellCoords(element.parentNode); + } + } + }; + + var position = getCellCoords(e.target); + if (position) { + var columnId = position[0]; + var rowId = position[1]; + // Close edition + if (jexcel.current.edition) { + if ( + jexcel.current.edition[2] != columnId || + jexcel.current.edition[3] != rowId + ) { + jexcel.current.closeEditor(jexcel.current.edition[0], true); + } + } + + if (!jexcel.current.edition) { + // Update cell selection + if (e.shiftKey) { + jexcel.current.updateSelectionFromCoords( + jexcel.current.selectedCell[0], + jexcel.current.selectedCell[1], + columnId, + rowId + ); + } else { + jexcel.current.updateSelectionFromCoords(columnId, rowId); + } + } + + // No full row selected + jexcel.current.selectedHeader = null; + jexcel.current.selectedRow = null; + } + } + } + } else { + jexcel.current.selectedRow = false; + } + + // Pagination + if (e.target.classList.contains("jexcel_page")) { + if (e.target.textContent == "<") { + jexcel.current.page(0); + } else if (e.target.textContent == ">") { + jexcel.current.page(e.target.getAttribute("title") - 1); + } else { + jexcel.current.page(e.target.textContent - 1); + } + } + } + + if (jexcel.current.edition) { + jexcel.isMouseAction = false; + } else { + jexcel.isMouseAction = true; + } + } else { + jexcel.isMouseAction = false; + } + }; + + jexcel.mouseUpControls = function (e) { + if (jexcel.current) { + // Update cell size + if (jexcel.current.resizing) { + // Columns to be updated + if (jexcel.current.resizing.column) { + // New width + var newWidth = + jexcel.current.colgroup[ + jexcel.current.resizing.column + ].getAttribute("width"); + // Columns + var columns = jexcel.current.getSelectedColumns(); + if (columns.length > 1) { + var currentWidth = []; + for (var i = 0; i < columns.length; i++) { + currentWidth.push( + parseInt( + jexcel.current.colgroup[columns[i]].getAttribute("width") + ) + ); + } + // Previous width + var index = columns.indexOf( + parseInt(jexcel.current.resizing.column) + ); + currentWidth[index] = jexcel.current.resizing.width; + jexcel.current.setWidth(columns, newWidth, currentWidth); + } else { + jexcel.current.setWidth( + jexcel.current.resizing.column, + newWidth, + jexcel.current.resizing.width + ); + } + // Remove border + jexcel.current.headers[ + jexcel.current.resizing.column + ].classList.remove("resizing"); + for (var j = 0; j < jexcel.current.records.length; j++) { + if (jexcel.current.records[j][jexcel.current.resizing.column]) { + jexcel.current.records[j][ + jexcel.current.resizing.column + ].classList.remove("resizing"); + } + } + } else { + // Remove Class + jexcel.current.rows[ + jexcel.current.resizing.row + ].children[0].classList.remove("resizing"); + var newHeight = + jexcel.current.rows[jexcel.current.resizing.row].getAttribute( + "height" + ); + jexcel.current.setHeight( + jexcel.current.resizing.row, + newHeight, + jexcel.current.resizing.height + ); + // Remove border + jexcel.current.resizing.element.classList.remove("resizing"); + } + // Reset resizing helper + jexcel.current.resizing = null; + } else if (jexcel.current.dragging) { + // Reset dragging helper + if (jexcel.current.dragging) { + if (jexcel.current.dragging.column) { + // Target + var columnId = e.target.getAttribute("data-x"); + // Remove move style + jexcel.current.headers[ + jexcel.current.dragging.column + ].classList.remove("dragging"); + for (var j = 0; j < jexcel.current.rows.length; j++) { + if (jexcel.current.records[j][jexcel.current.dragging.column]) { + jexcel.current.records[j][ + jexcel.current.dragging.column + ].classList.remove("dragging"); + } + } + for (var i = 0; i < jexcel.current.headers.length; i++) { + jexcel.current.headers[i].classList.remove("dragging-left"); + jexcel.current.headers[i].classList.remove("dragging-right"); + } + // Update position + if (columnId) { + if ( + jexcel.current.dragging.column != + jexcel.current.dragging.destination + ) { + jexcel.current.moveColumn( + jexcel.current.dragging.column, + jexcel.current.dragging.destination + ); + } + } + } else { + if (jexcel.current.dragging.element.nextSibling) { + var position = parseInt( + jexcel.current.dragging.element.nextSibling.getAttribute( + "data-y" + ) + ); + if (jexcel.current.dragging.row < position) { + position -= 1; + } + } else { + var position = parseInt( + jexcel.current.dragging.element.previousSibling.getAttribute( + "data-y" + ) + ); + } + if ( + jexcel.current.dragging.row != jexcel.current.dragging.destination + ) { + jexcel.current.moveRow( + jexcel.current.dragging.row, + position, + true + ); + } + jexcel.current.dragging.element.classList.remove("dragging"); + } + jexcel.current.dragging = null; + } + } else { + // Close any corner selection + if (jexcel.current.selectedCorner) { + jexcel.current.selectedCorner = false; + + // Data to be copied + if (jexcel.current.selection.length > 0) { + // Copy data + jexcel.current.copyData( + jexcel.current.selection[0], + jexcel.current.selection[jexcel.current.selection.length - 1] + ); + + // Remove selection + jexcel.current.removeCopySelection(); + } + } + } + } + + // Clear any time control + if (jexcel.timeControl) { + clearTimeout(jexcel.timeControl); + jexcel.timeControl = null; + } + + // Mouse up + jexcel.isMouseAction = false; + }; + + // Mouse move controls + jexcel.mouseMoveControls = function (e) { + e = e || window.event; + if (e.buttons) { + var mouseButton = e.buttons; + } else if (e.button) { + var mouseButton = e.button; + } else { + var mouseButton = e.which; + } + + if (!mouseButton) { + jexcel.isMouseAction = false; + } + + if (jexcel.current) { + if (jexcel.isMouseAction == true) { + // Resizing is ongoing + if (jexcel.current.resizing) { + if (jexcel.current.resizing.column) { + var width = e.pageX - jexcel.current.resizing.mousePosition; + + if (jexcel.current.resizing.width + width > 0) { + var tempWidth = jexcel.current.resizing.width + width; + jexcel.current.colgroup[ + jexcel.current.resizing.column + ].setAttribute("width", tempWidth); + + jexcel.current.updateCornerPosition(); + } + } else { + var height = e.pageY - jexcel.current.resizing.mousePosition; + + if (jexcel.current.resizing.height + height > 0) { + var tempHeight = jexcel.current.resizing.height + height; + jexcel.current.rows[jexcel.current.resizing.row].setAttribute( + "height", + tempHeight + ); + + jexcel.current.updateCornerPosition(); + } + } + } else if (jexcel.current.dragging) { + if (jexcel.current.dragging.column) { + var columnId = e.target.getAttribute("data-x"); + if (columnId) { + if (jexcel.current.isColMerged(columnId).length) { + console.error( + "Jspreadsheet: This column is part of a merged cell." + ); + } else { + for (var i = 0; i < jexcel.current.headers.length; i++) { + jexcel.current.headers[i].classList.remove("dragging-left"); + jexcel.current.headers[i].classList.remove("dragging-right"); + } + + if (jexcel.current.dragging.column == columnId) { + jexcel.current.dragging.destination = parseInt(columnId); + } else { + if (e.target.clientWidth / 2 > e.offsetX) { + if (jexcel.current.dragging.column < columnId) { + jexcel.current.dragging.destination = + parseInt(columnId) - 1; + } else { + jexcel.current.dragging.destination = parseInt(columnId); + } + jexcel.current.headers[columnId].classList.add( + "dragging-left" + ); + } else { + if (jexcel.current.dragging.column < columnId) { + jexcel.current.dragging.destination = parseInt(columnId); + } else { + jexcel.current.dragging.destination = + parseInt(columnId) + 1; + } + jexcel.current.headers[columnId].classList.add( + "dragging-right" + ); + } + } + } + } + } else { + var rowId = e.target.getAttribute("data-y"); + if (rowId) { + if (jexcel.current.isRowMerged(rowId).length) { + console.error( + "Jspreadsheet: This row is part of a merged cell." + ); + } else { + var target = + e.target.clientHeight / 2 > e.offsetY + ? e.target.parentNode.nextSibling + : e.target.parentNode; + if (jexcel.current.dragging.element != target) { + e.target.parentNode.parentNode.insertBefore( + jexcel.current.dragging.element, + target + ); + jexcel.current.dragging.destination = + Array.prototype.indexOf.call( + jexcel.current.dragging.element.parentNode.children, + jexcel.current.dragging.element + ); + } + } + } + } + } + } else { + var x = e.target.getAttribute("data-x"); + var y = e.target.getAttribute("data-y"); + var rect = e.target.getBoundingClientRect(); + + if (jexcel.current.cursor) { + jexcel.current.cursor.style.cursor = ""; + jexcel.current.cursor = null; + } + + if ( + e.target.parentNode.parentNode && + e.target.parentNode.parentNode.className + ) { + if (e.target.parentNode.parentNode.classList.contains("resizable")) { + if ( + e.target && + x && + !y && + rect.width - (e.clientX - rect.left) < 6 + ) { + jexcel.current.cursor = e.target; + jexcel.current.cursor.style.cursor = "col-resize"; + } else if ( + e.target && + !x && + y && + rect.height - (e.clientY - rect.top) < 6 + ) { + jexcel.current.cursor = e.target; + jexcel.current.cursor.style.cursor = "row-resize"; + } + } + + if (e.target.parentNode.parentNode.classList.contains("draggable")) { + if ( + e.target && + !x && + y && + rect.width - (e.clientX - rect.left) < 6 + ) { + jexcel.current.cursor = e.target; + jexcel.current.cursor.style.cursor = "move"; + } else if ( + e.target && + x && + !y && + rect.height - (e.clientY - rect.top) < 6 + ) { + jexcel.current.cursor = e.target; + jexcel.current.cursor.style.cursor = "move"; + } + } + } + } + } + }; + + jexcel.mouseOverControls = function (e) { + e = e || window.event; + if (e.buttons) { + var mouseButton = e.buttons; + } else if (e.button) { + var mouseButton = e.button; + } else { + var mouseButton = e.which; + } + + if (!mouseButton) { + jexcel.isMouseAction = false; + } + + if (jexcel.current && jexcel.isMouseAction == true) { + // Get elements + var jexcelTable = jexcel.getElement(e.target); + + if (jexcelTable[0]) { + // Avoid cross reference + if (jexcel.current != jexcelTable[0].jexcel) { + if (jexcel.current) { + return false; + } + } + + var columnId = e.target.getAttribute("data-x"); + var rowId = e.target.getAttribute("data-y"); + if (jexcel.current.resizing || jexcel.current.dragging) { + } else { + // Header found + if (jexcelTable[1] == 1) { + if (jexcel.current.selectedHeader) { + var columnId = e.target.getAttribute("data-x"); + var o = jexcel.current.selectedHeader; + var d = columnId; + // Update selection + jexcel.current.updateSelectionFromCoords( + o, + 0, + d, + jexcel.current.options.data.length - 1 + ); + } + } + + // Body found + if (jexcelTable[1] == 2) { + if (e.target.classList.contains("jexcel_row")) { + if (jexcel.current.selectedRow) { + var o = jexcel.current.selectedRow; + var d = rowId; + // Update selection + jexcel.current.updateSelectionFromCoords( + 0, + o, + jexcel.current.options.data[0].length - 1, + d + ); + } + } else { + // Do not select edtion is in progress + if (!jexcel.current.edition) { + if (columnId && rowId) { + if (jexcel.current.selectedCorner) { + jexcel.current.updateCopySelection(columnId, rowId); + } else { + if (jexcel.current.selectedCell) { + jexcel.current.updateSelectionFromCoords( + jexcel.current.selectedCell[0], + jexcel.current.selectedCell[1], + columnId, + rowId + ); + } + } + } + } + } + } + } + } + } + + // Clear any time control + if (jexcel.timeControl) { + clearTimeout(jexcel.timeControl); + jexcel.timeControl = null; + } + }; + + /** + * Double click event handler: controls the double click in the corner, cell edition or column re-ordering. + */ + jexcel.doubleClickControls = function (e) { + // Jexcel is selected + if (jexcel.current) { + // Corner action + if (e.target.classList.contains("jexcel_corner")) { + // Any selected cells + if (jexcel.current.highlighted.length > 0) { + // Copy from this + var x1 = jexcel.current.highlighted[0].getAttribute("data-x"); + var y1 = + parseInt( + jexcel.current.highlighted[ + jexcel.current.highlighted.length - 1 + ].getAttribute("data-y") + ) + 1; + // Until this + var x2 = + jexcel.current.highlighted[ + jexcel.current.highlighted.length - 1 + ].getAttribute("data-x"); + var y2 = jexcel.current.records.length - 1; + // Execute copy + jexcel.current.copyData( + jexcel.current.records[y1][x1], + jexcel.current.records[y2][x2] + ); + } + } else if (e.target.classList.contains("jexcel_column_filter")) { + // Column + var columnId = e.target.getAttribute("data-x"); + // Open filter + jexcel.current.openFilter(columnId); + } else { + // Get table + var jexcelTable = jexcel.getElement(e.target); + + // Double click over header + if ( + jexcelTable[1] == 1 && + jexcel.current.options.columnSorting == true + ) { + // Check valid column header coords + var columnId = e.target.getAttribute("data-x"); + if (columnId) { + jexcel.current.orderBy(columnId); + } + } + + // Double click over body + if (jexcelTable[1] == 2 && jexcel.current.options.editable == true) { + if (!jexcel.current.edition) { + var getCellCoords = function (element) { + if (element.parentNode) { + var x = element.getAttribute("data-x"); + var y = element.getAttribute("data-y"); + if (x && y) { + return element; + } else { + return getCellCoords(element.parentNode); + } + } + }; + var cell = getCellCoords(e.target); + if (cell && cell.classList.contains("highlight")) { + jexcel.current.openEditor(cell); + } + } + } + } + } + }; + + jexcel.copyControls = function (e) { + if (jexcel.current && jexcel.copyControls.enabled) { + if (!jexcel.current.edition) { + jexcel.current.copy(true); + } + } + }; + + jexcel.cutControls = function (e) { + if (jexcel.current) { + if (!jexcel.current.edition) { + jexcel.current.copy(true); + if (jexcel.current.options.editable == true) { + jexcel.current.setValue(jexcel.current.highlighted, ""); + } + } + } + }; + + jexcel.pasteControls = function (e) { + if (jexcel.current && jexcel.current.selectedCell) { + if (!jexcel.current.edition) { + if (jexcel.current.options.editable == true) { + if (e && e.clipboardData) { + jexcel.current.paste( + jexcel.current.selectedCell[0], + jexcel.current.selectedCell[1], + e.clipboardData.getData("text") + ); + e.preventDefault(); + } else if (window.clipboardData) { + jexcel.current.paste( + jexcel.current.selectedCell[0], + jexcel.current.selectedCell[1], + window.clipboardData.getData("text") + ); + } + } + } + } + }; + + jexcel.contextMenuControls = function (e) { + e = e || window.event; + if ("buttons" in e) { + var mouseButton = e.buttons; + } else { + var mouseButton = e.which || e.button; + } + + if (jexcel.current) { + if (jexcel.current.edition) { + e.preventDefault(); + } else if (jexcel.current.options.contextMenu) { + jexcel.current.contextMenu.contextmenu.close(); + + if (jexcel.current) { + var x = e.target.getAttribute("data-x"); + var y = e.target.getAttribute("data-y"); + + if (x || y) { + if ( + x < parseInt(jexcel.current.selectedCell[0]) || + x > parseInt(jexcel.current.selectedCell[2]) || + y < parseInt(jexcel.current.selectedCell[1]) || + y > parseInt(jexcel.current.selectedCell[3]) + ) { + jexcel.current.updateSelectionFromCoords(x, y, x, y); + } + + // Table found + var items = jexcel.current.options.contextMenu( + jexcel.current, + x, + y, + e + ); + // The id is depending on header and body + jexcel.current.contextMenu.contextmenu.open(e, items); + // Avoid the real one + e.preventDefault(); + } + } + } + } + }; + + jexcel.touchStartControls = function (e) { + var jexcelTable = jexcel.getElement(e.target); + + if (jexcelTable[0]) { + if (jexcel.current != jexcelTable[0].jexcel) { + if (jexcel.current) { + jexcel.current.resetSelection(); + } + jexcel.current = jexcelTable[0].jexcel; + } + } else { + if (jexcel.current) { + jexcel.current.resetSelection(); + jexcel.current = null; + } + } + + if (jexcel.current) { + if (!jexcel.current.edition) { + var columnId = e.target.getAttribute("data-x"); + var rowId = e.target.getAttribute("data-y"); + + if (columnId && rowId) { + jexcel.current.updateSelectionFromCoords(columnId, rowId); + + jexcel.timeControl = setTimeout(function () { + // Keep temporary reference to the element + if (jexcel.current.options.columns[columnId].type == "color") { + jexcel.tmpElement = null; + } else { + jexcel.tmpElement = e.target; + } + jexcel.current.openEditor(e.target, false, e); + }, 500); + } + } + } + }; + + jexcel.touchEndControls = function (e) { + // Clear any time control + if (jexcel.timeControl) { + clearTimeout(jexcel.timeControl); + jexcel.timeControl = null; + // Element + if ( + jexcel.tmpElement && + jexcel.tmpElement.children[0].tagName == "INPUT" + ) { + jexcel.tmpElement.children[0].focus(); + } + jexcel.tmpElement = null; + } + }; + + /** + * Jexcel extensions + */ + + jexcel.tabs = function (tabs, result) { + var instances = []; + // Create tab container + if (!tabs.classList.contains("jexcel_tabs")) { + tabs.innerHTML = ""; + tabs.classList.add("jexcel_tabs"); + tabs.jexcel = []; + + var div = document.createElement("div"); + var headers = tabs.appendChild(div); + var div = document.createElement("div"); + var content = tabs.appendChild(div); + } else { + var headers = tabs.children[0]; + var content = tabs.children[1]; + } + + var spreadsheet = []; + var link = []; + for (var i = 0; i < result.length; i++) { + // Spreadsheet container + spreadsheet[i] = document.createElement("div"); + spreadsheet[i].classList.add("jexcel_tab"); + var worksheet = jexcel(spreadsheet[i], result[i]); + content.appendChild(spreadsheet[i]); + instances[i] = tabs.jexcel.push(worksheet); + + // Tab link + link[i] = document.createElement("div"); + link[i].classList.add("jexcel_tab_link"); + link[i].setAttribute("data-spreadsheet", tabs.jexcel.length - 1); + link[i].innerHTML = result[i].sheetName; + link[i].onclick = function () { + for (var j = 0; j < headers.children.length; j++) { + headers.children[j].classList.remove("selected"); + content.children[j].style.display = "none"; + } + var i = this.getAttribute("data-spreadsheet"); + content.children[i].style.display = "block"; + headers.children[i].classList.add("selected"); + }; + headers.appendChild(link[i]); + } + + // First tab + for (var j = 0; j < headers.children.length; j++) { + headers.children[j].classList.remove("selected"); + content.children[j].style.display = "none"; + } + headers.children[headers.children.length - 1].classList.add("selected"); + content.children[headers.children.length - 1].style.display = "block"; + + return instances; + }; + + // Compability to older versions + jexcel.createTabs = jexcel.tabs; + + jexcel.fromSpreadsheet = function (file, __callback) { + var convert = function (workbook) { + var spreadsheets = []; + workbook.SheetNames.forEach(function (sheetName) { + var spreadsheet = {}; + spreadsheet.rows = []; + spreadsheet.columns = []; + spreadsheet.data = []; + spreadsheet.style = {}; + spreadsheet.sheetName = sheetName; + + // Column widths + var temp = workbook.Sheets[sheetName]["!cols"]; + if (temp && temp.length) { + for (var i = 0; i < temp.length; i++) { + spreadsheet.columns[i] = {}; + if (temp[i] && temp[i].wpx) { + spreadsheet.columns[i].width = temp[i].wpx + "px"; + } + } + } + // Rows heights + var temp = workbook.Sheets[sheetName]["!rows"]; + if (temp && temp.length) { + for (var i = 0; i < temp.length; i++) { + if (temp[i] && temp[i].hpx) { + spreadsheet.rows[i] = {}; + spreadsheet.rows[i].height = temp[i].hpx + "px"; + } + } + } + // Merge cells + var temp = workbook.Sheets[sheetName]["!merges"]; + if (temp && temp.length > 0) { + spreadsheet.mergeCells = []; + for (var i = 0; i < temp.length; i++) { + var x1 = temp[i].s.c; + var y1 = temp[i].s.r; + var x2 = temp[i].e.c; + var y2 = temp[i].e.r; + var key = jexcel.getColumnNameFromId([x1, y1]); + spreadsheet.mergeCells[key] = [x2 - x1 + 1, y2 - y1 + 1]; + } + } + // Data container + var max_x = 0; + var max_y = 0; + var temp = Object.keys(workbook.Sheets[sheetName]); + for (var i = 0; i < temp.length; i++) { + if (temp[i].substr(0, 1) != "!") { + var cell = workbook.Sheets[sheetName][temp[i]]; + var info = jexcel.getIdFromColumnName(temp[i], true); + if (!spreadsheet.data[info[1]]) { + spreadsheet.data[info[1]] = []; + } + spreadsheet.data[info[1]][info[0]] = cell.f ? "=" + cell.f : cell.w; + if (max_x < info[0]) { + max_x = info[0]; + } + if (max_y < info[1]) { + max_y = info[1]; + } + // Style + if (cell.style && Object.keys(cell.style).length > 0) { + spreadsheet.style[temp[i]] = cell.style; + } + if (cell.s && cell.s.fgColor) { + if (spreadsheet.style[temp[i]]) { + spreadsheet.style[temp[i]] += ";"; + } + spreadsheet.style[temp[i]] += + "background-color:#" + cell.s.fgColor.rgb; + } + } + } + var numColumns = spreadsheet.columns; + for (var j = 0; j <= max_y; j++) { + for (var i = 0; i <= max_x; i++) { + if (!spreadsheet.data[j]) { + spreadsheet.data[j] = []; + } + if (!spreadsheet.data[j][i]) { + if (numColumns < i) { + spreadsheet.data[j][i] = ""; + } + } + } + } + spreadsheets.push(spreadsheet); + }); + + return spreadsheets; + }; + + var oReq; + oReq = new XMLHttpRequest(); + oReq.open("GET", file, true); + + if (typeof Uint8Array !== "undefined") { + oReq.responseType = "arraybuffer"; + oReq.onload = function (e) { + var arraybuffer = oReq.response; + var data = new Uint8Array(arraybuffer); + var wb = XLSX.read(data, { + type: "array", + cellFormula: true, + cellStyles: true, + }); + __callback(convert(wb)); + }; + } else { + oReq.setRequestHeader("Accept-Charset", "x-user-defined"); + oReq.onreadystatechange = function () { + if (oReq.readyState == 4 && oReq.status == 200) { + var ff = convertResponseBodyToText(oReq.responseBody); + var wb = XLSX.read(ff, { + type: "binary", + cellFormula: true, + cellStyles: true, + }); + __callback(convert(wb)); + } + }; + } + + oReq.send(); + }; + + /** + * Valid international letter + */ + + jexcel.validLetter = function (text) { + var regex = + /([\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC-\u0400-\u04FF']+)/g; + return text.match(regex) ? 1 : 0; + }; + + /** + * Helper injectArray + */ + jexcel.injectArray = function (o, idx, arr) { + return o.slice(0, idx).concat(arr).concat(o.slice(idx)); + }; + + /** + * Get letter based on a number + * + * @param integer i + * @return string letter + */ + jexcel.getColumnName = function (i) { + var letter = ""; + if (i > 701) { + letter += String.fromCharCode(64 + parseInt(i / 676)); + letter += String.fromCharCode(64 + parseInt((i % 676) / 26)); + } else if (i > 25) { + letter += String.fromCharCode(64 + parseInt(i / 26)); + } + letter += String.fromCharCode(65 + (i % 26)); + + return letter; + }; + + /** + * Convert excel like column to jexcel id + * + * @param string id + * @return string id + */ + jexcel.getIdFromColumnName = function (id, arr) { + // Get the letters + var t = /^[a-zA-Z]+/.exec(id); + + if (t) { + // Base 26 calculation + var code = 0; + for (var i = 0; i < t[0].length; i++) { + code += + parseInt(t[0].charCodeAt(i) - 64) * Math.pow(26, t[0].length - 1 - i); + } + code--; + // Make sure jexcel starts on zero + if (code < 0) { + code = 0; + } + + // Number + var number = parseInt(/[0-9]+$/.exec(id)); + if (number > 0) { + number--; + } + + if (arr == true) { + id = [code, number]; + } else { + id = code + "-" + number; + } + } + + return id; + }; + + /** + * Convert jexcel id to excel like column name + * + * @param string id + * @return string id + */ + jexcel.getColumnNameFromId = function (cellId) { + if (!Array.isArray(cellId)) { + cellId = cellId.split("-"); + } + + return ( + jexcel.getColumnName(parseInt(cellId[0])) + (parseInt(cellId[1]) + 1) + ); + }; + + /** + * Verify element inside jexcel table + * + * @param string id + * @return string id + */ + jexcel.getElement = function (element) { + var jexcelSection = 0; + var jexcelElement = 0; + + function path(element) { + if (element.className) { + if (element.classList.contains("jexcel_container")) { + jexcelElement = element; + } + } + + if (element.tagName == "THEAD") { + jexcelSection = 1; + } else if (element.tagName == "TBODY") { + jexcelSection = 2; + } + + if (element.parentNode) { + if (!jexcelElement) { + path(element.parentNode); + } + } + } + + path(element); + + return [jexcelElement, jexcelSection]; + }; + + jexcel.doubleDigitFormat = function (v) { + v = "" + v; + if (v.length == 1) { + v = "0" + v; + } + return v; + }; + + jexcel.createFromTable = function (el, options) { + if (el.tagName != "TABLE") { + console.log("Element is not a table"); + } else { + // Configuration + if (!options) { + options = {}; + } + options.columns = []; + options.data = []; + + // Colgroup + var colgroup = el.querySelectorAll("colgroup > col"); + if (colgroup.length) { + // Get column width + for (var i = 0; i < colgroup.length; i++) { + var width = colgroup[i].style.width; + if (!width) { + var width = colgroup[i].getAttribute("width"); + } + // Set column width + if (width) { + if (!options.columns[i]) { + options.columns[i] = {}; + } + options.columns[i].width = width; + } + } + } + + // Parse header + var parseHeader = function (header) { + // Get width information + var info = header.getBoundingClientRect(); + var width = info.width > 50 ? info.width : 50; + + // Create column option + if (!options.columns[i]) { + options.columns[i] = {}; + } + if (header.getAttribute("data-celltype")) { + options.columns[i].type = header.getAttribute("data-celltype"); + } else { + options.columns[i].type = "text"; + } + options.columns[i].width = width + "px"; + options.columns[i].title = header.innerHTML; + options.columns[i].align = header.style.textAlign || "center"; + + if ((info = header.getAttribute("name"))) { + options.columns[i].name = info; + } + if ((info = header.getAttribute("id"))) { + options.columns[i].id = info; + } + }; + + // Headers + var nested = []; + var headers = el.querySelectorAll(":scope > thead > tr"); + if (headers.length) { + for (var j = 0; j < headers.length - 1; j++) { + var cells = []; + for (var i = 0; i < headers[j].children.length; i++) { + var row = { + title: headers[j].children[i].textContent, + colspan: headers[j].children[i].getAttribute("colspan") || 1, + }; + cells.push(row); + } + nested.push(cells); + } + // Get the last row in the thead + headers = headers[headers.length - 1].children; + // Go though the headers + for (var i = 0; i < headers.length; i++) { + parseHeader(headers[i]); + } + } + + // Content + var rowNumber = 0; + var mergeCells = {}; + var rows = {}; + var style = {}; + var classes = {}; + + var content = el.querySelectorAll(":scope > tr, :scope > tbody > tr"); + for (var j = 0; j < content.length; j++) { + options.data[rowNumber] = []; + if ( + options.parseTableFirstRowAsHeader == true && + !headers.length && + j == 0 + ) { + for (var i = 0; i < content[j].children.length; i++) { + parseHeader(content[j].children[i]); + } + } else { + for (var i = 0; i < content[j].children.length; i++) { + // WickedGrid formula compatibility + var value = content[j].children[i].getAttribute("data-formula"); + if (value) { + if (value.substr(0, 1) != "=") { + value = "=" + value; + } + } else { + var value = content[j].children[i].innerHTML; + } + options.data[rowNumber].push(value); + + // Key + var cellName = jexcel.getColumnNameFromId([i, j]); + + // Classes + var tmp = content[j].children[i].getAttribute("class"); + if (tmp) { + classes[cellName] = tmp; + } + + // Merged cells + var mergedColspan = + parseInt(content[j].children[i].getAttribute("colspan")) || 0; + var mergedRowspan = + parseInt(content[j].children[i].getAttribute("rowspan")) || 0; + if (mergedColspan || mergedRowspan) { + mergeCells[cellName] = [mergedColspan || 1, mergedRowspan || 1]; + } + + // Avoid problems with hidden cells + if ( + (s = + content[j].children[i].style && + content[j].children[i].style.display == "none") + ) { + content[j].children[i].style.display = ""; + } + // Get style + var s = content[j].children[i].getAttribute("style"); + if (s) { + style[cellName] = s; + } + // Bold + if (content[j].children[i].classList.contains("styleBold")) { + if (style[cellName]) { + style[cellName] += "; font-weight:bold;"; + } else { + style[cellName] = "font-weight:bold;"; + } + } + } + + // Row Height + if (content[j].style && content[j].style.height) { + rows[j] = { height: content[j].style.height }; + } + + // Index + rowNumber++; + } + } + + // Nested + if (Object.keys(nested).length > 0) { + options.nestedHeaders = nested; + } + // Style + if (Object.keys(style).length > 0) { + options.style = style; + } + // Merged + if (Object.keys(mergeCells).length > 0) { + options.mergeCells = mergeCells; + } + // Row height + if (Object.keys(rows).length > 0) { + options.rows = rows; + } + // Classes + if (Object.keys(classes).length > 0) { + options.classes = classes; + } + + var content = el.querySelectorAll("tfoot tr"); + if (content.length) { + var footers = []; + for (var j = 0; j < content.length; j++) { + var footer = []; + for (var i = 0; i < content[j].children.length; i++) { + footer.push(content[j].children[i].textContent); + } + footers.push(footer); + } + if (Object.keys(footers).length > 0) { + options.footers = footers; + } + } + // TODO: data-hiddencolumns="3,4" + + // I guess in terms the better column type + if (options.parseTableAutoCellType == true) { + var pattern = []; + for (var i = 0; i < options.columns.length; i++) { + var test = true; + var testCalendar = true; + pattern[i] = []; + for (var j = 0; j < options.data.length; j++) { + var value = options.data[j][i]; + if (!pattern[i][value]) { + pattern[i][value] = 0; + } + pattern[i][value]++; + if (value.length > 25) { + test = false; + } + if (value.length == 10) { + if (!(value.substr(4, 1) == "-" && value.substr(7, 1) == "-")) { + testCalendar = false; + } + } else { + testCalendar = false; + } + } + + var keys = Object.keys(pattern[i]).length; + if (testCalendar) { + options.columns[i].type = "calendar"; + } else if ( + test == true && + keys > 1 && + keys <= parseInt(options.data.length * 0.1) + ) { + options.columns[i].type = "dropdown"; + options.columns[i].source = Object.keys(pattern[i]); + } + } + } + + return options; + } + }; + + // Helpers + jexcel.helpers = (function () { + var component = {}; + + /** + * Get carret position for one element + */ + component.getCaretIndex = function (e) { + if (this.config.root) { + var d = this.config.root; + } else { + var d = window; + } + var pos = 0; + var s = d.getSelection(); + if (s) { + if (s.rangeCount !== 0) { + var r = s.getRangeAt(0); + var p = r.cloneRange(); + p.selectNodeContents(e); + p.setEnd(r.endContainer, r.endOffset); + pos = p.toString().length; + } + } + return pos; + }; + + /** + * Invert keys and values + */ + component.invert = function (o) { + var d = []; + var k = Object.keys(o); + for (var i = 0; i < k.length; i++) { + d[o[k[i]]] = k[i]; + } + return d; + }; + + /** + * Get letter based on a number + * + * @param integer i + * @return string letter + */ + component.getColumnName = function (i) { + var letter = ""; + if (i > 701) { + letter += String.fromCharCode(64 + parseInt(i / 676)); + letter += String.fromCharCode(64 + parseInt((i % 676) / 26)); + } else if (i > 25) { + letter += String.fromCharCode(64 + parseInt(i / 26)); + } + letter += String.fromCharCode(65 + (i % 26)); + + return letter; + }; + + /** + * Get column name from coords + */ + component.getColumnNameFromCoords = function (x, y) { + return component.getColumnName(parseInt(x)) + (parseInt(y) + 1); + }; + + component.getCoordsFromColumnName = function (columnName) { + // Get the letters + var t = /^[a-zA-Z]+/.exec(columnName); + + if (t) { + // Base 26 calculation + var code = 0; + for (var i = 0; i < t[0].length; i++) { + code += + parseInt(t[0].charCodeAt(i) - 64) * + Math.pow(26, t[0].length - 1 - i); + } + code--; + // Make sure jspreadsheet starts on zero + if (code < 0) { + code = 0; + } + + // Number + var number = parseInt(/[0-9]+$/.exec(columnName)) || null; + if (number > 0) { + number--; + } + + return [code, number]; + } + }; + + /** + * Extract json configuration from a TABLE DOM tag + */ + component.createFromTable = function () {}; + + /** + * Helper injectArray + */ + component.injectArray = function (o, idx, arr) { + return o.slice(0, idx).concat(arr).concat(o.slice(idx)); + }; + + /** + * Parse CSV string to JS array + */ + component.parseCSV = function (str, delimiter) { + // user-supplied delimeter or default comma + delimiter = delimiter || ","; + + // Final data + var col = 0; + var row = 0; + var num = 0; + var data = [[]]; + var limit = 0; + var flag = null; + var inside = false; + var closed = false; + + // Go over all chars + for (var i = 0; i < str.length; i++) { + // Create new row + if (!data[row]) { + data[row] = []; + } + // Create new column + if (!data[row][col]) { + data[row][col] = ""; + } + + // Ignore + if (str[i] == "\r") { + continue; + } + + // New row + if ( + (str[i] == "\n" || str[i] == delimiter) && + (inside == false || closed == true || !flag) + ) { + // Restart flags + flag = null; + inside = false; + closed = false; + + if (data[row][col][0] == '"') { + var val = data[row][col].trim(); + if (val[val.length - 1] == '"') { + data[row][col] = val.substr(1, val.length - 2); + } + } + + // Go to the next cell + if (str[i] == "\n") { + // New line + col = 0; + row++; + } else { + // New column + col++; + if (col > limit) { + // Keep the reference of max column + limit = col; + } + } + } else { + // Inside quotes + if (str[i] == '"') { + inside = !inside; + } + + if (flag === null) { + flag = inside; + if (flag == true) { + continue; + } + } else if (flag === true && !closed) { + if (str[i] == '"') { + if (str[i + 1] == '"') { + inside = true; + data[row][col] += str[i]; + i++; + } else { + closed = true; + } + continue; + } + } + + data[row][col] += str[i]; + } + } + + // Make sure a square matrix is generated + for (var j = 0; j < data.length; j++) { + for (var i = 0; i <= limit; i++) { + if (data[j][i] === undefined) { + data[j][i] = ""; + } + } + } + + return data; + }; + + return component; + })(); + + /** + * Jquery Support + */ + if (typeof jQuery != "undefined") { + (function ($) { + $.fn.jspreadsheet = $.fn.jexcel = function (mixed) { + var spreadsheetContainer = $(this).get(0); + if (!spreadsheetContainer.jexcel) { + return jexcel($(this).get(0), arguments[0]); + } else { + if (Array.isArray(spreadsheetContainer.jexcel)) { + return spreadsheetContainer.jexcel[mixed][arguments[1]].apply( + this, + Array.prototype.slice.call(arguments, 2) + ); + } else { + return spreadsheetContainer.jexcel[mixed].apply( + this, + Array.prototype.slice.call(arguments, 1) + ); + } + } + }; + })(jQuery); + } + + return jexcel; +}); diff --git a/public/javascripts/jsuites/jsuites.js b/public/javascripts/jsuites/jsuites.js new file mode 100644 index 00000000000..0088464e5aa --- /dev/null +++ b/public/javascripts/jsuites/jsuites.js @@ -0,0 +1,12414 @@ +/** + * (c) jSuites Javascript Web Components + * + * Website: https://jsuites.net + * Description: Create amazing web based applications. + * + * MIT License + * + */ +;(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.jSuites = factory(); +}(this, (function () { + + 'use strict'; + +var jSuites = {}; + +var Version = '4.17.5'; + +var Events = function() { + + document.jsuitesComponents = []; + + var find = function(DOMElement, component) { + if (DOMElement[component.type] && DOMElement[component.type] == component) { + return true; + } + if (DOMElement.component && DOMElement.component == component) { + return true; + } + if (DOMElement.parentNode) { + return find(DOMElement.parentNode, component); + } + return false; + } + + var isOpened = function(e) { + if (document.jsuitesComponents && document.jsuitesComponents.length > 0) { + for (var i = 0; i < document.jsuitesComponents.length; i++) { + if (document.jsuitesComponents[i] && ! find(e, document.jsuitesComponents[i])) { + document.jsuitesComponents[i].close(); + } + } + } + } + + // Width of the border + var cornerSize = 15; + + // Current element + var element = null; + + // Controllers + var editorAction = false; + + // Event state + var state = { + x: null, + y: null, + } + + // Tooltip element + var tooltip = document.createElement('div') + tooltip.classList.add('jtooltip'); + + // Events + var mouseDown = function(e) { + // Check if this is the floating + var item = jSuites.findElement(e.target, 'jpanel'); + // Jfloating found + if (item && ! item.classList.contains("readonly")) { + // Add focus to the chart container + item.focus(); + // Keep the tracking information + var rect = e.target.getBoundingClientRect(); + editorAction = { + e: item, + x: e.clientX, + y: e.clientY, + w: rect.width, + h: rect.height, + d: item.style.cursor, + resizing: item.style.cursor ? true : false, + actioned: false, + } + + // Make sure width and height styling is OK + if (! item.style.width) { + item.style.width = rect.width + 'px'; + } + + if (! item.style.height) { + item.style.height = rect.height + 'px'; + } + + // Remove any selection from the page + var s = window.getSelection(); + if (s.rangeCount) { + for (var i = 0; i < s.rangeCount; i++) { + s.removeRange(s.getRangeAt(i)); + } + } + + e.preventDefault(); + e.stopPropagation(); + } else { + // No floating action found + editorAction = false; + } + + // Verify current components tracking + if (e.changedTouches && e.changedTouches[0]) { + var x = e.changedTouches[0].clientX; + var y = e.changedTouches[0].clientY; + } else { + var x = e.clientX; + var y = e.clientY; + } + + // Which component I am clicking + var path = e.path || (e.composedPath && e.composedPath()); + + // If path available get the first element in the chain + if (path) { + element = path[0]; + } else { + // Try to guess using the coordinates + if (e.target && e.target.shadowRoot) { + var d = e.target.shadowRoot; + } else { + var d = document; + } + // Get the first target element + element = d.elementFromPoint(x, y); + } + + isOpened(element); + } + + var mouseUp = function(e) { + if (editorAction && editorAction.e) { + if (typeof(editorAction.e.refresh) == 'function' && state.actioned) { + editorAction.e.refresh(); + } + editorAction.e.style.cursor = ''; + } + + // Reset + state = { + x: null, + y: null, + } + + editorAction = false; + } + + var mouseMove = function(e) { + if (editorAction) { + var x = e.clientX || e.pageX; + var y = e.clientY || e.pageY; + + // Action on going + if (! editorAction.resizing) { + if (state.x == null && state.y == null) { + state.x = x; + state.y = y; + } + + var dx = x - state.x; + var dy = y - state.y; + var top = editorAction.e.offsetTop + dy; + var left = editorAction.e.offsetLeft + dx; + + // Update position + editorAction.e.style.top = top + 'px'; + editorAction.e.style.left = left + 'px'; + editorAction.e.style.cursor = "move"; + + state.x = x; + state.y = y; + + + // Update element + if (typeof(editorAction.e.refresh) == 'function') { + state.actioned = true; + editorAction.e.refresh('position', top, left); + } + } else { + var width = null; + var height = null; + + if (editorAction.d == 'e-resize' || editorAction.d == 'ne-resize' || editorAction.d == 'se-resize') { + // Update width + width = editorAction.w + (x - editorAction.x); + editorAction.e.style.width = width + 'px'; + + // Update Height + if (e.shiftKey) { + var newHeight = (x - editorAction.x) * (editorAction.h / editorAction.w); + height = editorAction.h + newHeight; + editorAction.e.style.height = height + 'px'; + } else { + var newHeight = false; + } + } + + if (! newHeight) { + if (editorAction.d == 's-resize' || editorAction.d == 'se-resize' || editorAction.d == 'sw-resize') { + height = editorAction.h + (y - editorAction.y); + editorAction.e.style.height = height + 'px'; + } + } + + // Update element + if (typeof(editorAction.e.refresh) == 'function') { + state.actioned = true; + editorAction.e.refresh('dimensions', width, height); + } + } + } else { + // Resizing action + var item = jSuites.findElement(e.target, 'jpanel'); + // Found eligible component + if (item) { + if (item.getAttribute('tabindex')) { + var rect = item.getBoundingClientRect(); + if (e.clientY - rect.top < cornerSize) { + if (rect.width - (e.clientX - rect.left) < cornerSize) { + item.style.cursor = 'ne-resize'; + } else if (e.clientX - rect.left < cornerSize) { + item.style.cursor = 'nw-resize'; + } else { + item.style.cursor = 'n-resize'; + } + } else if (rect.height - (e.clientY - rect.top) < cornerSize) { + if (rect.width - (e.clientX - rect.left) < cornerSize) { + item.style.cursor = 'se-resize'; + } else if (e.clientX - rect.left < cornerSize) { + item.style.cursor = 'sw-resize'; + } else { + item.style.cursor = 's-resize'; + } + } else if (rect.width - (e.clientX - rect.left) < cornerSize) { + item.style.cursor = 'e-resize'; + } else if (e.clientX - rect.left < cornerSize) { + item.style.cursor = 'w-resize'; + } else { + item.style.cursor = ''; + } + } + } + } + } + + var mouseOver = function(e) { + var message = e.target.getAttribute('data-tooltip'); + if (message) { + // Instructions + tooltip.innerText = message; + + // Position + if (e.changedTouches && e.changedTouches[0]) { + var x = e.changedTouches[0].clientX; + var y = e.changedTouches[0].clientY; + } else { + var x = e.clientX; + var y = e.clientY; + } + + tooltip.style.top = y + 'px'; + tooltip.style.left = x + 'px'; + document.body.appendChild(tooltip); + } else if (tooltip.innerText) { + tooltip.innerText = ''; + document.body.removeChild(tooltip); + } + } + + var dblClick = function(e) { + var item = jSuites.findElement(e.target, 'jpanel'); + if (item && typeof(item.dblclick) == 'function') { + // Create edition + item.dblclick(e); + } + } + + var contextMenu = function(e) { + var item = document.activeElement; + if (item && typeof(item.contextmenu) == 'function') { + // Create edition + item.contextmenu(e); + + e.preventDefault(); + e.stopImmediatePropagation(); + } else { + // Search for possible context menus + item = jSuites.findElement(e.target, function(o) { + return o.tagName && o.getAttribute('aria-contextmenu-id'); + }); + + if (item) { + var o = document.querySelector('#' + item); + if (! o) { + console.error('JSUITES: contextmenu id not found: ' + item); + } else { + o.contextmenu.open(e); + e.preventDefault(); + e.stopImmediatePropagation(); + } + } + } + } + + var keyDown = function(e) { + var item = document.activeElement; + if (item) { + if (e.key == "Delete" && typeof(item.delete) == 'function') { + item.delete(); + e.preventDefault(); + e.stopImmediatePropagation(); + } + } + + if (document.jsuitesComponents && document.jsuitesComponents.length) { + if (item = document.jsuitesComponents[document.jsuitesComponents.length - 1]) { + if (e.key == "Escape" && typeof(item.isOpened) == 'function' && typeof(item.close) == 'function') { + if (item.isOpened()) { + item.close(); + e.preventDefault(); + e.stopImmediatePropagation(); + } + } + } + } + } + + document.addEventListener('mouseup', mouseUp); + document.addEventListener("mousedown", mouseDown); + document.addEventListener('mousemove', mouseMove); + document.addEventListener('mouseover', mouseOver); + document.addEventListener('dblclick', dblClick); + document.addEventListener('keydown', keyDown); + document.addEventListener('contextmenu', contextMenu); +} + +/** + * Global jsuites event + */ +if (typeof(document) !== "undefined" && ! document.jsuitesComponents) { + Events(); +} + +jSuites.version = Version; + +jSuites.setExtensions = function(o) { + if (typeof(o) == 'object') { + var k = Object.keys(o); + for (var i = 0; i < k.length; i++) { + jSuites[k[i]] = o[k[i]]; + } + } +} + +jSuites.tracking = function(component, state) { + if (state == true) { + document.jsuitesComponents = document.jsuitesComponents.filter(function(v) { + return v !== null; + }); + + // Start after all events + setTimeout(function() { + document.jsuitesComponents.push(component); + }, 0); + + } else { + var index = document.jsuitesComponents.indexOf(component); + if (index >= 0) { + document.jsuitesComponents.splice(index, 1); + } + } +} + +/** + * Get or set a property from a JSON from a string. + */ +jSuites.path = function(str, val) { + str = str.split('.'); + if (str.length) { + var o = this; + var p = null; + while (str.length > 1) { + // Get the property + p = str.shift(); + // Check if the property exists + if (o.hasOwnProperty(p)) { + o = o[p]; + } else { + // Property does not exists + if (val === undefined) { + return undefined; + } else { + // Create the property + o[p] = {}; + // Next property + o = o[p]; + } + } + } + // Get the property + p = str.shift(); + // Set or get the value + if (val !== undefined) { + o[p] = val; + // Success + return true; + } else { + // Return the value + if (o) { + return o[p]; + } + } + } + // Something went wrong + return false; +} + +// Update dictionary +jSuites.setDictionary = function(d) { + if (! document.dictionary) { + document.dictionary = {} + } + // Replace the key into the dictionary and append the new ones + var k = Object.keys(d); + for (var i = 0; i < k.length; i++) { + document.dictionary[k[i]] = d[k[i]]; + } + + // Translations + var t = null; + for (var i = 0; i < jSuites.calendar.weekdays.length; i++) { + t = jSuites.translate(jSuites.calendar.weekdays[i]); + if (jSuites.calendar.weekdays[i]) { + jSuites.calendar.weekdays[i] = t; + jSuites.calendar.weekdaysShort[i] = t.substr(0,3); + } + } + for (var i = 0; i < jSuites.calendar.months.length; i++) { + t = jSuites.translate(jSuites.calendar.months[i]); + if (t) { + jSuites.calendar.months[i] = t; + jSuites.calendar.monthsShort[i] = t.substr(0,3); + } + } +} + +// Translate +jSuites.translate = function(t) { + if (typeof(document) !== "undefined" && document.dictionary) { + return document.dictionary[t] || t; + } else { + return t; + } +} + +jSuites.ajax = (function(options, complete) { + if (Array.isArray(options)) { + // Create multiple request controller + var multiple = { + instance: [], + complete: complete, + } + + if (options.length > 0) { + for (var i = 0; i < options.length; i++) { + options[i].multiple = multiple; + multiple.instance.push(jSuites.ajax(options[i])); + } + } + + return multiple; + } + + if (! options.data) { + options.data = {}; + } + + if (options.type) { + options.method = options.type; + } + + // Default method + if (! options.method) { + options.method = 'GET'; + } + + // Default type + if (! options.dataType) { + options.dataType = 'json'; + } + + if (options.data) { + // Parse object to variables format + var parseData = function (value, key) { + var vars = []; + if (value) { + var keys = Object.keys(value); + if (keys.length) { + for (var i = 0; i < keys.length; i++) { + if (key) { + var k = key + '[' + keys[i] + ']'; + } else { + var k = keys[i]; + } + + if (value[k] instanceof FileList) { + vars[k] = value[keys[i]]; + } else if (value[keys[i]] === null || value[keys[i]] === undefined) { + vars[k] = ''; + } else if (typeof(value[keys[i]]) == 'object') { + var r = parseData(value[keys[i]], k); + var o = Object.keys(r); + for (var j = 0; j < o.length; j++) { + vars[o[j]] = r[o[j]]; + } + } else { + vars[k] = value[keys[i]]; + } + } + } + } + + return vars; + } + + var d = parseData(options.data); + var k = Object.keys(d); + + // Data form + if (options.method == 'GET') { + if (k.length) { + var data = []; + for (var i = 0; i < k.length; i++) { + data.push(k[i] + '=' + encodeURIComponent(d[k[i]])); + } + + if (options.url.indexOf('?') < 0) { + options.url += '?'; + } + options.url += data.join('&'); + } + } else { + var data = new FormData(); + for (var i = 0; i < k.length; i++) { + if (d[k[i]] instanceof FileList) { + if (d[k[i]].length) { + for (var j = 0; j < d[k[i]].length; j++) { + data.append(k[i], d[k[i]][j], d[k[i]][j].name); + } + } + } else { + data.append(k[i], d[k[i]]); + } + } + } + } + + var httpRequest = new XMLHttpRequest(); + httpRequest.open(options.method, options.url, true); + httpRequest.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + // Content type + if (options.contentType) { + httpRequest.setRequestHeader('Content-Type', options.contentType); + } + + // Headers + if (options.method == 'POST') { + httpRequest.setRequestHeader('Accept', 'application/json'); + } else { + if (options.dataType == 'blob') { + httpRequest.responseType = "blob"; + } else { + if (! options.contentType) { + if (options.dataType == 'json') { + httpRequest.setRequestHeader('Content-Type', 'text/json'); + } else if (options.dataType == 'html') { + httpRequest.setRequestHeader('Content-Type', 'text/html'); + } + } + } + } + + // No cache + if (options.cache != true) { + httpRequest.setRequestHeader('pragma', 'no-cache'); + httpRequest.setRequestHeader('cache-control', 'no-cache'); + } + + // Authentication + if (options.withCredentials == true) { + httpRequest.withCredentials = true + } + + // Before send + if (typeof(options.beforeSend) == 'function') { + options.beforeSend(httpRequest); + } + + // Before send + if (typeof(jSuites.ajax.beforeSend) == 'function') { + jSuites.ajax.beforeSend(httpRequest); + } + + if (document.ajax && typeof(document.ajax.beforeSend) == 'function') { + document.ajax.beforeSend(httpRequest); + } + + httpRequest.onload = function() { + if (httpRequest.status === 200) { + if (options.dataType == 'json') { + try { + var result = JSON.parse(httpRequest.responseText); + + if (options.success && typeof(options.success) == 'function') { + options.success(result); + } + } catch(err) { + if (options.error && typeof(options.error) == 'function') { + options.error(err, result); + } + } + } else { + if (options.dataType == 'blob') { + var result = httpRequest.response; + } else { + var result = httpRequest.responseText; + } + + if (options.success && typeof(options.success) == 'function') { + options.success(result); + } + } + } else { + if (options.error && typeof(options.error) == 'function') { + options.error(httpRequest.responseText, httpRequest.status); + } + } + + // Global queue + if (jSuites.ajax.queue && jSuites.ajax.queue.length > 0) { + jSuites.ajax.send(jSuites.ajax.queue.shift()); + } + + // Global complete method + if (jSuites.ajax.requests && jSuites.ajax.requests.length) { + // Get index of this request in the container + var index = jSuites.ajax.requests.indexOf(httpRequest); + // Remove from the ajax requests container + jSuites.ajax.requests.splice(index, 1); + // Deprected: Last one? + if (! jSuites.ajax.requests.length) { + // Object event + if (options.complete && typeof(options.complete) == 'function') { + options.complete(result); + } + } + // Group requests + if (options.group) { + if (jSuites.ajax.oncomplete && typeof(jSuites.ajax.oncomplete[options.group]) == 'function') { + if (! jSuites.ajax.pending(options.group)) { + jSuites.ajax.oncomplete[options.group](); + jSuites.ajax.oncomplete[options.group] = null; + } + } + } + // Multiple requests controller + if (options.multiple && options.multiple.instance) { + // Get index of this request in the container + var index = options.multiple.instance.indexOf(httpRequest); + // Remove from the ajax requests container + options.multiple.instance.splice(index, 1); + // If this is the last one call method complete + if (! options.multiple.instance.length) { + if (options.multiple.complete && typeof(options.multiple.complete) == 'function') { + options.multiple.complete(result); + } + } + } + } + } + + // Keep the options + httpRequest.options = options; + // Data + httpRequest.data = data; + + // Queue + if (options.queue == true && jSuites.ajax.requests.length > 0) { + jSuites.ajax.queue.push(httpRequest); + } else { + jSuites.ajax.send(httpRequest) + } + + return httpRequest; +}); + +jSuites.ajax.send = function(httpRequest) { + if (httpRequest.data) { + if (Array.isArray(httpRequest.data)) { + httpRequest.send(httpRequest.data.join('&')); + } else { + httpRequest.send(httpRequest.data); + } + } else { + httpRequest.send(); + } + + jSuites.ajax.requests.push(httpRequest); +} + +jSuites.ajax.exists = function(url, __callback) { + var http = new XMLHttpRequest(); + http.open('HEAD', url, false); + http.send(); + if (http.status) { + __callback(http.status); + } +} + +jSuites.ajax.pending = function(group) { + var n = 0; + var o = jSuites.ajax.requests; + if (o && o.length) { + for (var i = 0; i < o.length; i++) { + if (! group || group == o[i].options.group) { + n++ + } + } + } + return n; +} + +jSuites.ajax.oncomplete = {}; +jSuites.ajax.requests = []; +jSuites.ajax.queue = []; + +jSuites.alert = function(message) { + if (jSuites.getWindowWidth() < 800 && jSuites.dialog) { + jSuites.dialog.open({ + title:'Alert', + message:message, + }); + } else { + alert(message); + } +} + +jSuites.animation = {}; + +jSuites.animation.slideLeft = function(element, direction, done) { + if (direction == true) { + element.classList.add('slide-left-in'); + setTimeout(function() { + element.classList.remove('slide-left-in'); + if (typeof(done) == 'function') { + done(); + } + }, 400); + } else { + element.classList.add('slide-left-out'); + setTimeout(function() { + element.classList.remove('slide-left-out'); + if (typeof(done) == 'function') { + done(); + } + }, 400); + } +} + +jSuites.animation.slideRight = function(element, direction, done) { + if (direction == true) { + element.classList.add('slide-right-in'); + setTimeout(function() { + element.classList.remove('slide-right-in'); + if (typeof(done) == 'function') { + done(); + } + }, 400); + } else { + element.classList.add('slide-right-out'); + setTimeout(function() { + element.classList.remove('slide-right-out'); + if (typeof(done) == 'function') { + done(); + } + }, 400); + } +} + +jSuites.animation.slideTop = function(element, direction, done) { + if (direction == true) { + element.classList.add('slide-top-in'); + setTimeout(function() { + element.classList.remove('slide-top-in'); + if (typeof(done) == 'function') { + done(); + } + }, 400); + } else { + element.classList.add('slide-top-out'); + setTimeout(function() { + element.classList.remove('slide-top-out'); + if (typeof(done) == 'function') { + done(); + } + }, 400); + } +} + +jSuites.animation.slideBottom = function(element, direction, done) { + if (direction == true) { + element.classList.add('slide-bottom-in'); + setTimeout(function() { + element.classList.remove('slide-bottom-in'); + if (typeof(done) == 'function') { + done(); + } + }, 400); + } else { + element.classList.add('slide-bottom-out'); + setTimeout(function() { + element.classList.remove('slide-bottom-out'); + if (typeof(done) == 'function') { + done(); + } + }, 100); + } +} + +jSuites.animation.fadeIn = function(element, done) { + element.style.display = ''; + element.classList.add('fade-in'); + setTimeout(function() { + element.classList.remove('fade-in'); + if (typeof(done) == 'function') { + done(); + } + }, 2000); +} + +jSuites.animation.fadeOut = function(element, done) { + element.classList.add('fade-out'); + setTimeout(function() { + element.style.display = 'none'; + element.classList.remove('fade-out'); + if (typeof(done) == 'function') { + done(); + } + }, 1000); +} + +jSuites.calendar = (function(el, options) { + // Already created, update options + if (el.calendar) { + return el.calendar.setOptions(options, true); + } + + // New instance + var obj = { type:'calendar' }; + obj.options = {}; + + // Date + obj.date = null; + + /** + * Update options + */ + obj.setOptions = function(options, reset) { + // Default configuration + var defaults = { + // Render type: [ default | year-month-picker ] + type: 'default', + // Restrictions + validRange: null, + // Starting weekday - 0 for sunday, 6 for saturday + startingDay: null, + // Date format + format: 'DD/MM/YYYY', + // Allow keyboard date entry + readonly: true, + // Today is default + today: false, + // Show timepicker + time: false, + // Show the reset button + resetButton: true, + // Placeholder + placeholder: '', + // Translations can be done here + months: jSuites.calendar.monthsShort, + monthsFull: jSuites.calendar.months, + weekdays: jSuites.calendar.weekdays, + textDone: jSuites.translate('Done'), + textReset: jSuites.translate('Reset'), + textUpdate: jSuites.translate('Update'), + // Value + value: null, + // Fullscreen (this is automatic set for screensize < 800) + fullscreen: false, + // Create the calendar closed as default + opened: false, + // Events + onopen: null, + onclose: null, + onchange: null, + onupdate: null, + // Internal mode controller + mode: null, + position: null, + // Data type + dataType: null, + // Controls + controls: true, + } + + // Loop through our object + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + if (typeof(obj.options[property]) == 'undefined' || reset === true) { + obj.options[property] = defaults[property]; + } + } + } + + // Reset button + if (obj.options.resetButton == false) { + calendarReset.style.display = 'none'; + } else { + calendarReset.style.display = ''; + } + + // Readonly + if (obj.options.readonly) { + el.setAttribute('readonly', 'readonly'); + } else { + el.removeAttribute('readonly'); + } + + // Placeholder + if (obj.options.placeholder) { + el.setAttribute('placeholder', obj.options.placeholder); + } else { + el.removeAttribute('placeholder'); + } + + if (jSuites.isNumeric(obj.options.value) && obj.options.value > 0) { + obj.options.value = jSuites.calendar.numToDate(obj.options.value); + // Data type numeric + obj.options.dataType = 'numeric'; + } + + // Texts + calendarReset.innerHTML = obj.options.textReset; + calendarConfirm.innerHTML = obj.options.textDone; + calendarControlsUpdateButton.innerHTML = obj.options.textUpdate; + + // Define mask + el.setAttribute('data-mask', obj.options.format.toLowerCase()); + + // Value + if (! obj.options.value && obj.options.today) { + var value = jSuites.calendar.now(); + } else { + var value = obj.options.value; + } + + // Set internal date + if (value) { + // Force the update + obj.options.value = null; + // New value + obj.setValue(value); + } + + return obj; + } + + /** + * Open the calendar + */ + obj.open = function (value) { + if (! calendar.classList.contains('jcalendar-focus')) { + if (! calendar.classList.contains('jcalendar-inline')) { + // Current + jSuites.calendar.current = obj; + // Start tracking + jSuites.tracking(obj, true); + // Create the days + obj.getDays(); + // Render months + if (obj.options.type == 'year-month-picker') { + obj.getMonths(); + } + // Get time + if (obj.options.time) { + calendarSelectHour.value = obj.date[3]; + calendarSelectMin.value = obj.date[4]; + } + + // Show calendar + calendar.classList.add('jcalendar-focus'); + + // Get the position of the corner helper + if (jSuites.getWindowWidth() < 800 || obj.options.fullscreen) { + calendar.classList.add('jcalendar-fullsize'); + // Animation + jSuites.animation.slideBottom(calendarContent, 1); + } else { + calendar.classList.remove('jcalendar-fullsize'); + + var rect = el.getBoundingClientRect(); + var rectContent = calendarContent.getBoundingClientRect(); + + if (obj.options.position) { + calendarContainer.style.position = 'fixed'; + if (window.innerHeight < rect.bottom + rectContent.height) { + calendarContainer.style.top = (rect.top - (rectContent.height + 2)) + 'px'; + } else { + calendarContainer.style.top = (rect.top + rect.height + 2) + 'px'; + } + calendarContainer.style.left = rect.left + 'px'; + } else { + if (window.innerHeight < rect.bottom + rectContent.height) { + var d = -1 * (rect.height + rectContent.height + 2); + if (d + rect.top < 0) { + d = -1 * (rect.top + rect.height); + } + calendarContainer.style.top = d + 'px'; + } else { + calendarContainer.style.top = 2 + 'px'; + } + + if (window.innerWidth < rect.left + rectContent.width) { + var d = window.innerWidth - (rect.left + rectContent.width + 20); + calendarContainer.style.left = d + 'px'; + } else { + calendarContainer.style.left = '0px'; + } + } + } + + // Events + if (typeof(obj.options.onopen) == 'function') { + obj.options.onopen(el); + } + } + } + } + + obj.close = function (ignoreEvents, update) { + if (calendar.classList.contains('jcalendar-focus')) { + if (update !== false) { + var element = calendar.querySelector('.jcalendar-selected'); + + if (typeof(update) == 'string') { + var value = update; + } else if (! element || element.classList.contains('jcalendar-disabled')) { + var value = obj.options.value + } else { + var value = obj.getValue(); + } + + obj.setValue(value); + } + + // Events + if (! ignoreEvents && typeof(obj.options.onclose) == 'function') { + obj.options.onclose(el); + } + // Hide + calendar.classList.remove('jcalendar-focus'); + // Stop tracking + jSuites.tracking(obj, false); + // Current + jSuites.calendar.current = null; + } + + return obj.options.value; + } + + obj.prev = function() { + // Check if the visualization is the days picker or years picker + if (obj.options.mode == 'years') { + obj.date[0] = obj.date[0] - 12; + + // Update picker table of days + obj.getYears(); + } else if (obj.options.mode == 'months') { + obj.date[0] = parseInt(obj.date[0]) - 1; + // Update picker table of months + obj.getMonths(); + } else { + // Go to the previous month + if (obj.date[1] < 2) { + obj.date[0] = obj.date[0] - 1; + obj.date[1] = 12; + } else { + obj.date[1] = obj.date[1] - 1; + } + + // Update picker table of days + obj.getDays(); + } + } + + obj.next = function() { + // Check if the visualization is the days picker or years picker + if (obj.options.mode == 'years') { + obj.date[0] = parseInt(obj.date[0]) + 12; + + // Update picker table of days + obj.getYears(); + } else if (obj.options.mode == 'months') { + obj.date[0] = parseInt(obj.date[0]) + 1; + // Update picker table of months + obj.getMonths(); + } else { + // Go to the previous month + if (obj.date[1] > 11) { + obj.date[0] = parseInt(obj.date[0]) + 1; + obj.date[1] = 1; + } else { + obj.date[1] = parseInt(obj.date[1]) + 1; + } + + // Update picker table of days + obj.getDays(); + } + } + + /** + * Set today + */ + obj.setToday = function() { + // Today + var value = new Date().toISOString().substr(0, 10); + // Change value + obj.setValue(value); + // Value + return value; + } + + obj.setValue = function(val) { + if (! val) { + val = '' + val; + } + // Values + var newValue = val; + var oldValue = obj.options.value; + + if (oldValue != newValue) { + // Set label + if (! newValue) { + obj.date = null; + var val = ''; + el.classList.remove('jcalendar_warning'); + el.title = ''; + } else { + var value = obj.setLabel(newValue, obj.options); + var date = newValue.split(' '); + if (! date[1]) { + date[1] = '00:00:00'; + } + var time = date[1].split(':') + var date = date[0].split('-'); + var y = parseInt(date[0]); + var m = parseInt(date[1]); + var d = parseInt(date[2]); + var h = parseInt(time[0]); + var i = parseInt(time[1]); + obj.date = [ y, m, d, h, i, 0 ]; + var val = obj.setLabel(newValue, obj.options); + + // Current selection day + var current = jSuites.calendar.now(new Date(y, m-1, d), true); + + // Available ranges + if (obj.options.validRange) { + if (! obj.options.validRange[0] || current >= obj.options.validRange[0]) { + var test1 = true; + } else { + var test1 = false; + } + + if (! obj.options.validRange[1] || current <= obj.options.validRange[1]) { + var test2 = true; + } else { + var test2 = false; + } + + if (! (test1 && test2)) { + el.classList.add('jcalendar_warning'); + el.title = jSuites.translate('Date outside the valid range'); + } else { + el.classList.remove('jcalendar_warning'); + el.title = ''; + } + } else { + el.classList.remove('jcalendar_warning'); + el.title = ''; + } + } + + // New value + obj.options.value = newValue; + + if (typeof(obj.options.onchange) == 'function') { + obj.options.onchange(el, newValue, oldValue); + } + + // Lemonade JS + if (el.value != val) { + el.value = val; + if (typeof(el.oninput) == 'function') { + el.oninput({ + type: 'input', + target: el, + value: el.value + }); + } + } + } + + obj.getDays(); + // Render months + if (obj.options.type == 'year-month-picker') { + obj.getMonths(); + } + } + + obj.getValue = function() { + if (obj.date) { + if (obj.options.time) { + return jSuites.two(obj.date[0]) + '-' + jSuites.two(obj.date[1]) + '-' + jSuites.two(obj.date[2]) + ' ' + jSuites.two(obj.date[3]) + ':' + jSuites.two(obj.date[4]) + ':' + jSuites.two(0); + } else { + return jSuites.two(obj.date[0]) + '-' + jSuites.two(obj.date[1]) + '-' + jSuites.two(obj.date[2]) + ' ' + jSuites.two(0) + ':' + jSuites.two(0) + ':' + jSuites.two(0); + } + } else { + return ""; + } + } + + /** + * Calendar + */ + obj.update = function(element, v) { + if (element.classList.contains('jcalendar-disabled')) { + // Do nothing + } else { + var elements = calendar.querySelector('.jcalendar-selected'); + if (elements) { + elements.classList.remove('jcalendar-selected'); + } + element.classList.add('jcalendar-selected'); + + if (element.classList.contains('jcalendar-set-month')) { + obj.date[1] = v; + obj.date[2] = 1; // first day of the month + } else { + obj.date[2] = element.innerText; + } + + if (! obj.options.time) { + obj.close(); + } else { + obj.date[3] = calendarSelectHour.value; + obj.date[4] = calendarSelectMin.value; + } + } + + // Update + updateActions(); + } + + /** + * Set to blank + */ + obj.reset = function() { + // Close calendar + obj.setValue(''); + obj.date = null; + obj.close(false, false); + } + + /** + * Get calendar days + */ + obj.getDays = function() { + // Mode + obj.options.mode = 'days'; + + // Setting current values in case of NULLs + var date = new Date(); + + // Current selection + var year = obj.date && jSuites.isNumeric(obj.date[0]) ? obj.date[0] : parseInt(date.getFullYear()); + var month = obj.date && jSuites.isNumeric(obj.date[1]) ? obj.date[1] : parseInt(date.getMonth()) + 1; + var day = obj.date && jSuites.isNumeric(obj.date[2]) ? obj.date[2] : parseInt(date.getDate()); + var hour = obj.date && jSuites.isNumeric(obj.date[3]) ? obj.date[3] : parseInt(date.getHours()); + var min = obj.date && jSuites.isNumeric(obj.date[4]) ? obj.date[4] : parseInt(date.getMinutes()); + + // Selection container + obj.date = [ year, month, day, hour, min, 0 ]; + + // Update title + calendarLabelYear.innerHTML = year; + calendarLabelMonth.innerHTML = obj.options.months[month - 1]; + + // Current month and Year + var isCurrentMonthAndYear = (date.getMonth() == month - 1) && (date.getFullYear() == year) ? true : false; + var currentDay = date.getDate(); + + // Number of days in the month + var date = new Date(year, month, 0, 0, 0); + var numberOfDays = date.getDate(); + + // First day + var date = new Date(year, month-1, 0, 0, 0); + var firstDay = date.getDay() + 1; + + // Index value + var index = obj.options.startingDay || 0; + + // First of day relative to the starting calendar weekday + firstDay = firstDay - index; + + // Reset table + calendarBody.innerHTML = ''; + + // Weekdays Row + var row = document.createElement('tr'); + row.setAttribute('align', 'center'); + calendarBody.appendChild(row); + + // Create weekdays row + for (var i = 0; i < 7; i++) { + var cell = document.createElement('td'); + cell.classList.add('jcalendar-weekday') + cell.innerHTML = obj.options.weekdays[index].substr(0,1); + row.appendChild(cell); + // Next week day + index++; + // Restart index + if (index > 6) { + index = 0; + } + } + + // Index of days + var index = 0; + var d = 0; + + // Calendar table + for (var j = 0; j < 6; j++) { + // Reset cells container + var row = document.createElement('tr'); + row.setAttribute('align', 'center'); + row.style.height = '34px'; + + // Create cells + for (var i = 0; i < 7; i++) { + // Create cell + var cell = document.createElement('td'); + cell.classList.add('jcalendar-set-day'); + + if (index >= firstDay && index < (firstDay + numberOfDays)) { + // Day cell + d++; + cell.innerHTML = d; + + // Selected + if (d == day) { + cell.classList.add('jcalendar-selected'); + } + + // Current selection day is today + if (isCurrentMonthAndYear && currentDay == d) { + cell.style.fontWeight = 'bold'; + } + + // Current selection day + var current = jSuites.calendar.now(new Date(year, month-1, d), true); + + // Available ranges + if (obj.options.validRange) { + if (! obj.options.validRange[0] || current >= obj.options.validRange[0]) { + var test1 = true; + } else { + var test1 = false; + } + + if (! obj.options.validRange[1] || current <= obj.options.validRange[1]) { + var test2 = true; + } else { + var test2 = false; + } + + if (! (test1 && test2)) { + cell.classList.add('jcalendar-disabled'); + } + } + } + // Day cell + row.appendChild(cell); + // Index + index++; + } + + // Add cell to the calendar body + calendarBody.appendChild(row); + } + + // Show time controls + if (obj.options.time) { + calendarControlsTime.style.display = ''; + } else { + calendarControlsTime.style.display = 'none'; + } + + // Update + updateActions(); + } + + obj.getMonths = function() { + // Mode + obj.options.mode = 'months'; + + // Loading month labels + var months = obj.options.months; + + // Value + var value = obj.options.value; + + // Current date + var date = new Date(); + var currentYear = parseInt(date.getFullYear()); + var currentMonth = parseInt(date.getMonth()) + 1; + var selectedYear = obj.date && jSuites.isNumeric(obj.date[0]) ? obj.date[0] : currentYear; + var selectedMonth = obj.date && jSuites.isNumeric(obj.date[1]) ? obj.date[1] : currentMonth; + + // Update title + calendarLabelYear.innerHTML = obj.date[0]; + calendarLabelMonth.innerHTML = months[selectedMonth-1]; + + // Table + var table = document.createElement('table'); + table.setAttribute('width', '100%'); + + // Row + var row = null; + + // Calendar table + for (var i = 0; i < 12; i++) { + if (! (i % 4)) { + // Reset cells container + var row = document.createElement('tr'); + row.setAttribute('align', 'center'); + table.appendChild(row); + } + + // Create cell + var cell = document.createElement('td'); + cell.classList.add('jcalendar-set-month'); + cell.setAttribute('data-value', i+1); + cell.innerText = months[i]; + + if (obj.options.validRange) { + var current = selectedYear + '-' + jSuites.two(i+1); + if (! obj.options.validRange[0] || current >= obj.options.validRange[0].substr(0,7)) { + var test1 = true; + } else { + var test1 = false; + } + + if (! obj.options.validRange[1] || current <= obj.options.validRange[1].substr(0,7)) { + var test2 = true; + } else { + var test2 = false; + } + + if (! (test1 && test2)) { + cell.classList.add('jcalendar-disabled'); + } + } + + if (i+1 == selectedMonth) { + cell.classList.add('jcalendar-selected'); + } + + if (currentYear == selectedYear && i+1 == currentMonth) { + cell.style.fontWeight = 'bold'; + } + + row.appendChild(cell); + } + + calendarBody.innerHTML = ''; + calendarBody.children[0].children[0].appendChild(table); + + // Update + updateActions(); + } + + obj.getYears = function() { + // Mode + obj.options.mode = 'years'; + + // Current date + var date = new Date(); + var currentYear = date.getFullYear(); + var selectedYear = obj.date && jSuites.isNumeric(obj.date[0]) ? obj.date[0] : parseInt(date.getFullYear()); + + // Array of years + var y = []; + for (var i = 0; i < 25; i++) { + y[i] = parseInt(obj.date[0]) + (i - 12); + } + + // Assembling the year tables + var table = document.createElement('table'); + table.setAttribute('width', '100%'); + + for (var i = 0; i < 25; i++) { + if (! (i % 5)) { + // Reset cells container + var row = document.createElement('tr'); + row.setAttribute('align', 'center'); + table.appendChild(row); + } + + // Create cell + var cell = document.createElement('td'); + cell.classList.add('jcalendar-set-year'); + cell.innerText = y[i]; + + if (selectedYear == y[i]) { + cell.classList.add('jcalendar-selected'); + } + + if (currentYear == y[i]) { + cell.style.fontWeight = 'bold'; + } + + row.appendChild(cell); + } + + calendarBody.innerHTML = ''; + calendarBody.firstChild.firstChild.appendChild(table); + + // Update + updateActions(); + } + + obj.setLabel = function(value, mixed) { + return jSuites.calendar.getDateString(value, mixed); + } + + obj.fromFormatted = function (value, format) { + return jSuites.calendar.extractDateFromString(value, format); + } + + var mouseUpControls = function(e) { + var element = jSuites.findElement(e.target, 'jcalendar-container'); + if (element) { + var action = e.target.className; + + // Object id + if (action == 'jcalendar-prev') { + obj.prev(); + } else if (action == 'jcalendar-next') { + obj.next(); + } else if (action == 'jcalendar-month') { + obj.getMonths(); + } else if (action == 'jcalendar-year') { + obj.getYears(); + } else if (action == 'jcalendar-set-year') { + obj.date[0] = e.target.innerText; + if (obj.options.type == 'year-month-picker') { + obj.getMonths(); + } else { + obj.getDays(); + } + } else if (e.target.classList.contains('jcalendar-set-month')) { + var month = parseInt(e.target.getAttribute('data-value')); + if (obj.options.type == 'year-month-picker') { + obj.update(e.target, month); + } else { + obj.date[1] = month; + obj.getDays(); + } + } else if (action == 'jcalendar-confirm' || action == 'jcalendar-update' || action == 'jcalendar-close') { + obj.close(); + } else if (action == 'jcalendar-backdrop') { + obj.close(false, false); + } else if (action == 'jcalendar-reset') { + obj.reset(); + } else if (e.target.classList.contains('jcalendar-set-day') && e.target.innerText) { + obj.update(e.target); + } + } else { + obj.close(); + } + } + + var keyUpControls = function(e) { + if (e.target.value && e.target.value.length > 3) { + var test = jSuites.calendar.extractDateFromString(e.target.value, obj.options.format); + if (test) { + obj.setValue(test); + } + } + } + + // Update actions button + var updateActions = function() { + var currentDay = calendar.querySelector('.jcalendar-selected'); + + if (currentDay && currentDay.classList.contains('jcalendar-disabled')) { + calendarControlsUpdateButton.setAttribute('disabled', 'disabled'); + calendarSelectHour.setAttribute('disabled', 'disabled'); + calendarSelectMin.setAttribute('disabled', 'disabled'); + } else { + calendarControlsUpdateButton.removeAttribute('disabled'); + calendarSelectHour.removeAttribute('disabled'); + calendarSelectMin.removeAttribute('disabled'); + } + + // Event + if (typeof(obj.options.onupdate) == 'function') { + obj.options.onupdate(el, obj.getValue()); + } + } + + var calendar = null; + var calendarReset = null; + var calendarConfirm = null; + var calendarContainer = null; + var calendarContent = null; + var calendarLabelYear = null; + var calendarLabelMonth = null; + var calendarTable = null; + var calendarBody = null; + + var calendarControls = null; + var calendarControlsTime = null; + var calendarControlsUpdate = null; + var calendarControlsUpdateButton = null; + var calendarSelectHour = null; + var calendarSelectMin = null; + + var init = function() { + // Get value from initial element if that is an input + if (el.tagName == 'INPUT' && el.value) { + options.value = el.value; + } + + // Calendar DOM elements + calendarReset = document.createElement('div'); + calendarReset.className = 'jcalendar-reset'; + + calendarConfirm = document.createElement('div'); + calendarConfirm.className = 'jcalendar-confirm'; + + calendarControls = document.createElement('div'); + calendarControls.className = 'jcalendar-controls' + calendarControls.style.borderBottom = '1px solid #ddd'; + calendarControls.appendChild(calendarReset); + calendarControls.appendChild(calendarConfirm); + + calendarContainer = document.createElement('div'); + calendarContainer.className = 'jcalendar-container'; + calendarContent = document.createElement('div'); + calendarContent.className = 'jcalendar-content'; + calendarContainer.appendChild(calendarContent); + + // Main element + if (el.tagName == 'DIV') { + calendar = el; + calendar.classList.add('jcalendar-inline'); + } else { + // Add controls to the screen + calendarContent.appendChild(calendarControls); + + calendar = document.createElement('div'); + calendar.className = 'jcalendar'; + } + calendar.classList.add('jcalendar-container'); + calendar.appendChild(calendarContainer); + + // Table container + var calendarTableContainer = document.createElement('div'); + calendarTableContainer.className = 'jcalendar-table'; + calendarContent.appendChild(calendarTableContainer); + + // Previous button + var calendarHeaderPrev = document.createElement('td'); + calendarHeaderPrev.setAttribute('colspan', '2'); + calendarHeaderPrev.className = 'jcalendar-prev'; + + // Header with year and month + calendarLabelYear = document.createElement('span'); + calendarLabelYear.className = 'jcalendar-year'; + calendarLabelMonth = document.createElement('span'); + calendarLabelMonth.className = 'jcalendar-month'; + + var calendarHeaderTitle = document.createElement('td'); + calendarHeaderTitle.className = 'jcalendar-header'; + calendarHeaderTitle.setAttribute('colspan', '3'); + calendarHeaderTitle.appendChild(calendarLabelMonth); + calendarHeaderTitle.appendChild(calendarLabelYear); + + var calendarHeaderNext = document.createElement('td'); + calendarHeaderNext.setAttribute('colspan', '2'); + calendarHeaderNext.className = 'jcalendar-next'; + + var calendarHeader = document.createElement('thead'); + var calendarHeaderRow = document.createElement('tr'); + calendarHeaderRow.appendChild(calendarHeaderPrev); + calendarHeaderRow.appendChild(calendarHeaderTitle); + calendarHeaderRow.appendChild(calendarHeaderNext); + calendarHeader.appendChild(calendarHeaderRow); + + calendarTable = document.createElement('table'); + calendarBody = document.createElement('tbody'); + calendarTable.setAttribute('cellpadding', '0'); + calendarTable.setAttribute('cellspacing', '0'); + calendarTable.appendChild(calendarHeader); + calendarTable.appendChild(calendarBody); + calendarTableContainer.appendChild(calendarTable); + + calendarSelectHour = document.createElement('select'); + calendarSelectHour.className = 'jcalendar-select'; + calendarSelectHour.onchange = function() { + obj.date[3] = this.value; + + // Event + if (typeof(obj.options.onupdate) == 'function') { + obj.options.onupdate(el, obj.getValue()); + } + } + + for (var i = 0; i < 24; i++) { + var element = document.createElement('option'); + element.value = i; + element.innerHTML = jSuites.two(i); + calendarSelectHour.appendChild(element); + } + + calendarSelectMin = document.createElement('select'); + calendarSelectMin.className = 'jcalendar-select'; + calendarSelectMin.onchange = function() { + obj.date[4] = this.value; + + // Event + if (typeof(obj.options.onupdate) == 'function') { + obj.options.onupdate(el, obj.getValue()); + } + } + + for (var i = 0; i < 60; i++) { + var element = document.createElement('option'); + element.value = i; + element.innerHTML = jSuites.two(i); + calendarSelectMin.appendChild(element); + } + + // Footer controls + var calendarControlsFooter = document.createElement('div'); + calendarControlsFooter.className = 'jcalendar-controls'; + + calendarControlsTime = document.createElement('div'); + calendarControlsTime.className = 'jcalendar-time'; + calendarControlsTime.style.maxWidth = '140px'; + calendarControlsTime.appendChild(calendarSelectHour); + calendarControlsTime.appendChild(calendarSelectMin); + + calendarControlsUpdateButton = document.createElement('button'); + calendarControlsUpdateButton.setAttribute('type', 'button'); + calendarControlsUpdateButton.className = 'jcalendar-update'; + + calendarControlsUpdate = document.createElement('div'); + calendarControlsUpdate.style.flexGrow = '10'; + calendarControlsUpdate.appendChild(calendarControlsUpdateButton); + calendarControlsFooter.appendChild(calendarControlsTime); + + // Only show the update button for input elements + if (el.tagName == 'INPUT') { + calendarControlsFooter.appendChild(calendarControlsUpdate); + } + + calendarContent.appendChild(calendarControlsFooter); + + var calendarBackdrop = document.createElement('div'); + calendarBackdrop.className = 'jcalendar-backdrop'; + calendar.appendChild(calendarBackdrop); + + // Handle events + el.addEventListener("keyup", keyUpControls); + + // Add global events + calendar.addEventListener("swipeleft", function(e) { + jSuites.animation.slideLeft(calendarTable, 0, function() { + obj.next(); + jSuites.animation.slideRight(calendarTable, 1); + }); + e.preventDefault(); + e.stopPropagation(); + }); + + calendar.addEventListener("swiperight", function(e) { + jSuites.animation.slideRight(calendarTable, 0, function() { + obj.prev(); + jSuites.animation.slideLeft(calendarTable, 1); + }); + e.preventDefault(); + e.stopPropagation(); + }); + + if ('ontouchend' in document.documentElement === true) { + calendar.addEventListener("touchend", mouseUpControls); + el.addEventListener("touchend", obj.open); + } else { + calendar.addEventListener("mouseup", mouseUpControls); + el.addEventListener("mouseup", obj.open); + } + + // Global controls + if (! jSuites.calendar.hasEvents) { + // Execute only one time + jSuites.calendar.hasEvents = true; + // Enter and Esc + document.addEventListener("keydown", jSuites.calendar.keydown); + } + + // Set configuration + obj.setOptions(options); + + // Append element to the DOM + if (el.tagName == 'INPUT') { + el.parentNode.insertBefore(calendar, el.nextSibling); + // Add properties + el.setAttribute('autocomplete', 'off'); + // Element + el.classList.add('jcalendar-input'); + // Value + el.value = obj.setLabel(obj.getValue(), obj.options); + } else { + // Get days + obj.getDays(); + // Hour + if (obj.options.time) { + calendarSelectHour.value = obj.date[3]; + calendarSelectMin.value = obj.date[4]; + } + } + + // Default opened + if (obj.options.opened == true) { + obj.open(); + } + + // Controls + if (obj.options.controls == false) { + calendarContainer.classList.add('jcalendar-hide-controls'); + } + + // Change method + el.change = obj.setValue; + + // Global generic value handler + el.val = function(val) { + if (val === undefined) { + return obj.getValue(); + } else { + obj.setValue(val); + } + } + + // Keep object available from the node + el.calendar = calendar.calendar = obj; + } + + init(); + + return obj; +}); + +jSuites.calendar.keydown = function(e) { + var calendar = null; + if (calendar = jSuites.calendar.current) { + if (e.which == 13) { + // ENTER + calendar.close(false, true); + } else if (e.which == 27) { + // ESC + calendar.close(false, false); + } + } +} + +jSuites.calendar.prettify = function(d, texts) { + if (! texts) { + var texts = { + justNow: 'Just now', + xMinutesAgo: '{0}m ago', + xHoursAgo: '{0}h ago', + xDaysAgo: '{0}d ago', + xWeeksAgo: '{0}w ago', + xMonthsAgo: '{0} mon ago', + xYearsAgo: '{0}y ago', + } + } + + var d1 = new Date(); + var d2 = new Date(d); + var total = parseInt((d1 - d2) / 1000 / 60); + + String.prototype.format = function(o) { + return this.replace('{0}', o); + } + + if (total == 0) { + var text = texts.justNow; + } else if (total < 90) { + var text = texts.xMinutesAgo.format(total); + } else if (total < 1440) { // One day + var text = texts.xHoursAgo.format(Math.round(total/60)); + } else if (total < 20160) { // 14 days + var text = texts.xDaysAgo.format(Math.round(total / 1440)); + } else if (total < 43200) { // 30 days + var text = texts.xWeeksAgo.format(Math.round(total / 10080)); + } else if (total < 1036800) { // 24 months + var text = texts.xMonthsAgo.format(Math.round(total / 43200)); + } else { // 24 months+ + var text = texts.xYearsAgo.format(Math.round(total / 525600)); + } + + return text; +} + +jSuites.calendar.prettifyAll = function() { + var elements = document.querySelectorAll('.prettydate'); + for (var i = 0; i < elements.length; i++) { + if (elements[i].getAttribute('data-date')) { + elements[i].innerHTML = jSuites.calendar.prettify(elements[i].getAttribute('data-date')); + } else { + if (elements[i].innerHTML) { + elements[i].setAttribute('title', elements[i].innerHTML); + elements[i].setAttribute('data-date', elements[i].innerHTML); + elements[i].innerHTML = jSuites.calendar.prettify(elements[i].innerHTML); + } + } + } +} + +jSuites.calendar.now = function(date, dateOnly) { + if (Array.isArray(date)) { + var y = date[0]; + var m = date[1]; + var d = date[2]; + var h = date[3]; + var i = date[4]; + var s = date[5]; + } else { + if (! date) { + var date = new Date(); + } + var y = date.getFullYear(); + var m = date.getMonth() + 1; + var d = date.getDate(); + var h = date.getHours(); + var i = date.getMinutes(); + var s = date.getSeconds(); + } + + if (dateOnly == true) { + return jSuites.two(y) + '-' + jSuites.two(m) + '-' + jSuites.two(d); + } else { + return jSuites.two(y) + '-' + jSuites.two(m) + '-' + jSuites.two(d) + ' ' + jSuites.two(h) + ':' + jSuites.two(i) + ':' + jSuites.two(s); + } +} + +jSuites.calendar.toArray = function(value) { + var date = value.split(((value.indexOf('T') !== -1) ? 'T' : ' ')); + var time = date[1]; + var date = date[0].split('-'); + var y = parseInt(date[0]); + var m = parseInt(date[1]); + var d = parseInt(date[2]); + + if (time) { + var time = time.split(':'); + var h = parseInt(time[0]); + var i = parseInt(time[1]); + } else { + var h = 0; + var i = 0; + } + return [ y, m, d, h, i, 0 ]; +} + +// Helper to extract date from a string +jSuites.calendar.extractDateFromString = function(date, format) { + var o = jSuites.mask(date, { mask: format }, true); + + // Check if in format Excel (Need difference with format date or type detected is numeric) + if (date > 0 && Number(date) == date && (o.values.join("") !== o.value || o.type == "numeric")) { + var d = new Date(Math.round((date - 25569)*86400*1000)); + return d.getFullYear() + "-" + jSuites.two(d.getMonth()) + "-" + jSuites.two(d.getDate()) + ' 00:00:00'; + } + + var complete = false; + + if (o.values.length === o.tokens.length && o.values[o.values.length-1].length >= o.tokens[o.tokens.length-1].length) { + complete = true; + } + + if (o.date[0] && o.date[1] && (o.date[2] || complete)) { + if (! o.date[2]) { + o.date[2] = 1; + } + + return o.date[0] + '-' + jSuites.two(o.date[1]) + '-' + jSuites.two(o.date[2]) + ' ' + jSuites.two(o.date[3]) + ':' + jSuites.two(o.date[4])+ ':' + jSuites.two(o.date[5]); + } + + return ''; +} + +var excelInitialTime = Date.UTC(1900, 0, 0); +var excelLeapYearBug = Date.UTC(1900, 1, 29); +var millisecondsPerDay = 86400000; + +/** + * Date to number + */ +jSuites.calendar.dateToNum = function(jsDate) { + if (typeof(jsDate) === 'string') { + jsDate = new Date(jsDate + ' GMT+0'); + } + var jsDateInMilliseconds = jsDate.getTime(); + + if (jsDateInMilliseconds >= excelLeapYearBug) { + jsDateInMilliseconds += millisecondsPerDay; + } + + jsDateInMilliseconds -= excelInitialTime; + + return jsDateInMilliseconds / millisecondsPerDay; +} + +/** + * Number to date + */ +// !IMPORTANT! +// Excel incorrectly considers 1900 to be a leap year +jSuites.calendar.numToDate = function(excelSerialNumber) { + var jsDateInMilliseconds = excelInitialTime + excelSerialNumber * millisecondsPerDay; + + if (jsDateInMilliseconds >= excelLeapYearBug) { + jsDateInMilliseconds -= millisecondsPerDay; + } + + const d = new Date(jsDateInMilliseconds); + + var date = [ + d.getUTCFullYear(), + d.getUTCMonth()+1, + d.getUTCDate(), + d.getUTCHours(), + d.getUTCMinutes(), + d.getUTCSeconds(), + ]; + + return jSuites.calendar.now(date); +} + +// Helper to convert date into string +jSuites.calendar.getDateString = function(value, options) { + if (! options) { + var options = {}; + } + + // Labels + if (options && typeof(options) == 'object') { + var format = options.format; + } else { + var format = options; + } + + if (! format) { + format = 'YYYY-MM-DD'; + } + + // Convert to number of hours + if (format.indexOf('[h]') >= 0) { + var result = 0; + if (value && jSuites.isNumeric(value)) { + result = parseFloat(24 * Number(value)); + if (format.indexOf('mm') >= 0) { + var h = (''+result).split('.'); + if (h[1]) { + var d = 60 * parseFloat('0.' + h[1]) + d = parseFloat(d.toFixed(2)); + } else { + var d = 0; + } + result = parseInt(h[0]) + ':' + jSuites.two(d); + } + } + return result; + } + + // Date instance + if (value instanceof Date) { + value = jSuites.calendar.now(value); + } else if (value && jSuites.isNumeric(value)) { + value = jSuites.calendar.numToDate(value); + } + + // Tokens + var tokens = [ 'DAY', 'WD', 'DDDD', 'DDD', 'DD', 'D', 'Q', 'HH24', 'HH12', 'HH', 'H', 'AM/PM', 'MI', 'SS', 'MS', 'YYYY', 'YYY', 'YY', 'Y', 'MONTH', 'MON', 'MMMMM', 'MMMM', 'MMM', 'MM', 'M', '.' ]; + + // Expression to extract all tokens from the string + var e = new RegExp(tokens.join('|'), 'gi'); + // Extract + var t = format.match(e); + + // Compatibility with excel + for (var i = 0; i < t.length; i++) { + if (t[i].toUpperCase() == 'MM') { + // Not a month, correct to minutes + if (t[i-1] && t[i-1].toUpperCase().indexOf('H') >= 0) { + t[i] = 'mi'; + } else if (t[i-2] && t[i-2].toUpperCase().indexOf('H') >= 0) { + t[i] = 'mi'; + } else if (t[i+1] && t[i+1].toUpperCase().indexOf('S') >= 0) { + t[i] = 'mi'; + } else if (t[i+2] && t[i+2].toUpperCase().indexOf('S') >= 0) { + t[i] = 'mi'; + } + } + } + + // Object + var o = { + tokens: t + } + + // Value + if (value) { + var d = ''+value; + var splitStr = (d.indexOf('T') !== -1) ? 'T' : ' '; + d = d.split(splitStr); + + var h = 0; + var m = 0; + var s = 0; + + if (d[1]) { + h = d[1].split(':'); + m = h[1] ? h[1] : 0; + s = h[2] ? h[2] : 0; + h = h[0] ? h[0] : 0; + } + + d = d[0].split('-'); + + if (d[0] && d[1] && d[2] && d[0] > 0 && d[1] > 0 && d[1] < 13 && d[2] > 0 && d[2] < 32) { + + // Data + o.data = [ d[0], d[1], d[2], h, m, s ]; + + // Value + o.value = []; + + // Calendar instance + var calendar = new Date(o.data[0], o.data[1]-1, o.data[2], o.data[3], o.data[4], o.data[5]); + + // Get method + var get = function(i) { + // Token + var t = this.tokens[i]; + // Case token + var s = t.toUpperCase(); + var v = null; + + if (s === 'YYYY') { + v = this.data[0]; + } else if (s === 'YYY') { + v = this.data[0].substring(1,4); + } else if (s === 'YY') { + v = this.data[0].substring(2,4); + } else if (s === 'Y') { + v = this.data[0].substring(3,4); + } else if (t === 'MON') { + v = jSuites.calendar.months[calendar.getMonth()].substr(0,3).toUpperCase(); + } else if (t === 'mon') { + v = jSuites.calendar.months[calendar.getMonth()].substr(0,3).toLowerCase(); + } else if (t === 'MONTH') { + v = jSuites.calendar.months[calendar.getMonth()].toUpperCase(); + } else if (t === 'month') { + v = jSuites.calendar.months[calendar.getMonth()].toLowerCase(); + } else if (s === 'MMMMM') { + v = jSuites.calendar.months[calendar.getMonth()].substr(0, 1); + } else if (s === 'MMMM' || t === 'Month') { + v = jSuites.calendar.months[calendar.getMonth()]; + } else if (s === 'MMM' || t == 'Mon') { + v = jSuites.calendar.months[calendar.getMonth()].substr(0,3); + } else if (s === 'MM') { + v = jSuites.two(this.data[1]); + } else if (s === 'M') { + v = calendar.getMonth()+1; + } else if (t === 'DAY') { + v = jSuites.calendar.weekdays[calendar.getDay()].toUpperCase(); + } else if (t === 'day') { + v = jSuites.calendar.weekdays[calendar.getDay()].toLowerCase(); + } else if (s === 'DDDD' || t == 'Day') { + v = jSuites.calendar.weekdays[calendar.getDay()]; + } else if (s === 'DDD') { + v = jSuites.calendar.weekdays[calendar.getDay()].substr(0,3); + } else if (s === 'DD') { + v = jSuites.two(this.data[2]); + } else if (s === 'D') { + v = this.data[2]; + } else if (s === 'Q') { + v = Math.floor((calendar.getMonth() + 3) / 3); + } else if (s === 'HH24' || s === 'HH') { + v = jSuites.two(this.data[3]); + } else if (s === 'HH12') { + if (this.data[3] > 12) { + v = jSuites.two(this.data[3] - 12); + } else { + v = jSuites.two(this.data[3]); + } + } else if (s === 'H') { + v = this.data[3]; + } else if (s === 'MI') { + v = jSuites.two(this.data[4]); + } else if (s === 'SS') { + v = jSuites.two(this.data[5]); + } else if (s === 'MS') { + v = calendar.getMilliseconds(); + } else if (s === 'AM/PM') { + if (this.data[3] >= 12) { + v = 'PM'; + } else { + v = 'AM'; + } + } else if (s === 'WD') { + v = jSuites.calendar.weekdays[calendar.getDay()]; + } + + if (v === null) { + this.value[i] = this.tokens[i]; + } else { + this.value[i] = v; + } + } + + for (var i = 0; i < o.tokens.length; i++) { + get.call(o, i); + } + // Put pieces together + value = o.value.join(''); + } else { + value = ''; + } + } + + return value; +} + +// Jsuites calendar labels +jSuites.calendar.weekdays = [ 'Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday' ]; +jSuites.calendar.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; +jSuites.calendar.weekdaysShort = [ 'Sun','Mon','Tue','Wed','Thu','Fri','Sat' ]; +jSuites.calendar.monthsShort = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + + +jSuites.color = (function(el, options) { + // Already created, update options + if (el.color) { + return el.color.setOptions(options, true); + } + + // New instance + var obj = { type: 'color' }; + obj.options = {}; + + var container = null; + var backdrop = null; + var content = null; + var resetButton = null; + var closeButton = null; + var tabs = null; + var jsuitesTabs = null; + + /** + * Update options + */ + obj.setOptions = function(options, reset) { + /** + * @typedef {Object} defaults + * @property {(string|Array)} value - Initial value of the compontent + * @property {string} placeholder - The default instruction text on the element + * @property {requestCallback} onchange - Method to be execute after any changes on the element + * @property {requestCallback} onclose - Method to be execute when the element is closed + * @property {string} doneLabel - Label for button done + * @property {string} resetLabel - Label for button reset + * @property {string} resetValue - Value for button reset + * @property {Bool} showResetButton - Active or note for button reset - default false + */ + var defaults = { + placeholder: '', + value: null, + onopen: null, + onclose: null, + onchange: null, + closeOnChange: true, + palette: null, + position: null, + doneLabel: 'Done', + resetLabel: 'Reset', + fullscreen: false, + opened: false, + } + + if (! options) { + options = {}; + } + + if (options && ! options.palette) { + // Default pallete + options.palette = jSuites.palette(); + } + + // Loop through our object + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + if (typeof(obj.options[property]) == 'undefined' || reset === true) { + obj.options[property] = defaults[property]; + } + } + } + + // Update the text of the controls, if they have already been created + if (resetButton) { + resetButton.innerHTML = obj.options.resetLabel; + } + if (closeButton) { + closeButton.innerHTML = obj.options.doneLabel; + } + + // Update the pallete + if (obj.options.palette && jsuitesTabs) { + jsuitesTabs.updateContent(0, table()); + } + + // Value + if (typeof obj.options.value === 'string') { + el.value = obj.options.value; + if (el.tagName === 'INPUT') { + el.style.color = el.value; + el.style.backgroundColor = el.value; + } + } + + // Placeholder + if (obj.options.placeholder) { + el.setAttribute('placeholder', obj.options.placeholder); + } else { + if (el.getAttribute('placeholder')) { + el.removeAttribute('placeholder'); + } + } + + return obj; + } + + obj.select = function(color) { + // Remove current selected mark + var selected = container.querySelector('.jcolor-selected'); + if (selected) { + selected.classList.remove('jcolor-selected'); + } + + // Mark cell as selected + if (obj.values[color]) { + obj.values[color].classList.add('jcolor-selected'); + } + + obj.options.value = color; + } + + /** + * Open color pallete + */ + obj.open = function() { + if (! container.classList.contains('jcolor-focus')) { + // Start tracking + jSuites.tracking(obj, true); + + // Show color picker + container.classList.add('jcolor-focus'); + + // Select current color + if (obj.options.value) { + obj.select(obj.options.value); + } + + // Reset margin + content.style.marginTop = ''; + content.style.marginLeft = ''; + + var rectContent = content.getBoundingClientRect(); + var availableWidth = jSuites.getWindowWidth(); + var availableHeight = jSuites.getWindowHeight(); + + if (availableWidth < 800 || obj.options.fullscreen == true) { + content.classList.add('jcolor-fullscreen'); + jSuites.animation.slideBottom(content, 1); + backdrop.style.display = 'block'; + } else { + if (content.classList.contains('jcolor-fullscreen')) { + content.classList.remove('jcolor-fullscreen'); + backdrop.style.display = ''; + } + + if (obj.options.position) { + content.style.position = 'fixed'; + } else { + content.style.position = ''; + } + + if (rectContent.left + rectContent.width > availableWidth) { + content.style.marginLeft = -1 * (rectContent.left + rectContent.width - (availableWidth - 20)) + 'px'; + } + if (rectContent.top + rectContent.height > availableHeight) { + content.style.marginTop = -1 * (rectContent.top + rectContent.height - (availableHeight - 20)) + 'px'; + } + } + + + if (typeof(obj.options.onopen) == 'function') { + obj.options.onopen(el); + } + + jsuitesTabs.setBorder(jsuitesTabs.getActive()); + + // Update sliders + if (obj.options.value) { + var rgb = HexToRgb(obj.options.value); + + rgbInputs.forEach(function(rgbInput, index) { + rgbInput.value = rgb[index]; + rgbInput.dispatchEvent(new Event('input')); + }); + } + } + } + + /** + * Close color pallete + */ + obj.close = function(ignoreEvents) { + if (container.classList.contains('jcolor-focus')) { + // Remove focus + container.classList.remove('jcolor-focus'); + // Make sure backdrop is hidden + backdrop.style.display = ''; + // Call related events + if (! ignoreEvents && typeof(obj.options.onclose) == 'function') { + obj.options.onclose(el); + } + // Stop the object + jSuites.tracking(obj, false); + } + + return obj.options.value; + } + + /** + * Set value + */ + obj.setValue = function(color) { + if (! color) { + color = ''; + } + + if (color != obj.options.value) { + obj.options.value = color; + slidersResult = color; + + // Remove current selecded mark + obj.select(color); + + // Onchange + if (typeof(obj.options.onchange) == 'function') { + obj.options.onchange(el, color); + } + + // Changes + if (el.value != obj.options.value) { + // Set input value + el.value = obj.options.value; + if (el.tagName === 'INPUT') { + el.style.color = el.value; + el.style.backgroundColor = el.value; + } + + // Element onchange native + if (typeof(el.oninput) == 'function') { + el.oninput({ + type: 'input', + target: el, + value: el.value + }); + } + } + + if (obj.options.closeOnChange == true) { + obj.close(); + } + } + } + + /** + * Get value + */ + obj.getValue = function() { + return obj.options.value; + } + + var backdropClickControl = false; + + // Converts a number in decimal to hexadecimal + var decToHex = function(num) { + var hex = num.toString(16); + return hex.length === 1 ? "0" + hex : hex; + } + + // Converts a color in rgb to hexadecimal + var rgbToHex = function(r, g, b) { + return "#" + decToHex(r) + decToHex(g) + decToHex(b); + } + + // Converts a number in hexadecimal to decimal + var hexToDec = function(hex) { + return parseInt('0x' + hex); + } + + // Converts a color in hexadecimal to rgb + var HexToRgb = function(hex) { + return [hexToDec(hex.substr(1, 2)), hexToDec(hex.substr(3, 2)), hexToDec(hex.substr(5, 2))] + } + + var table = function() { + // Content of the first tab + var tableContainer = document.createElement('div'); + tableContainer.className = 'jcolor-grid'; + + // Cells + obj.values = []; + + // Table pallete + var t = document.createElement('table'); + t.setAttribute('cellpadding', '7'); + t.setAttribute('cellspacing', '0'); + + for (var j = 0; j < obj.options.palette.length; j++) { + var tr = document.createElement('tr'); + for (var i = 0; i < obj.options.palette[j].length; i++) { + var td = document.createElement('td'); + var color = obj.options.palette[j][i]; + if (color.length < 7 && color.substr(0,1) !== '#') { + color = '#' + color; + } + td.style.backgroundColor = color; + td.setAttribute('data-value', color); + td.innerHTML = ''; + tr.appendChild(td); + + // Selected color + if (obj.options.value == color) { + td.classList.add('jcolor-selected'); + } + + // Possible values + obj.values[color] = td; + } + t.appendChild(tr); + } + + // Append to the table + tableContainer.appendChild(t); + + return tableContainer; + } + + // Canvas where the image will be rendered + var canvas = document.createElement('canvas'); + canvas.width = 200; + canvas.height = 160; + var context = canvas.getContext("2d"); + + var resizeCanvas = function() { + // Specifications necessary to correctly obtain colors later in certain positions + var m = tabs.firstChild.getBoundingClientRect(); + canvas.width = m.width - 14; + gradient() + } + + var gradient = function() { + var g = context.createLinearGradient(0, 0, canvas.width, 0); + // Create color gradient + g.addColorStop(0, "rgb(255,0,0)"); + g.addColorStop(0.15, "rgb(255,0,255)"); + g.addColorStop(0.33, "rgb(0,0,255)"); + g.addColorStop(0.49, "rgb(0,255,255)"); + g.addColorStop(0.67, "rgb(0,255,0)"); + g.addColorStop(0.84, "rgb(255,255,0)"); + g.addColorStop(1, "rgb(255,0,0)"); + context.fillStyle = g; + context.fillRect(0, 0, canvas.width, canvas.height); + g = context.createLinearGradient(0, 0, 0, canvas.height); + g.addColorStop(0, "rgba(255,255,255,1)"); + g.addColorStop(0.5, "rgba(255,255,255,0)"); + g.addColorStop(0.5, "rgba(0,0,0,0)"); + g.addColorStop(1, "rgba(0,0,0,1)"); + context.fillStyle = g; + context.fillRect(0, 0, canvas.width, canvas.height); + } + + var hsl = function() { + var element = document.createElement('div'); + element.className = "jcolor-hsl"; + + var point = document.createElement('div'); + point.className = 'jcolor-point'; + + var div = document.createElement('div'); + div.appendChild(canvas); + div.appendChild(point); + element.appendChild(div); + + // Moves the marquee point to the specified position + var update = function(buttons, x, y) { + if (buttons === 1) { + var rect = element.getBoundingClientRect(); + var left = x - rect.left; + var top = y - rect.top; + if (left < 0) { + left = 0; + } + if (top < 0) { + top = 0; + } + if (left > rect.width) { + left = rect.width; + } + if (top > rect.height) { + top = rect.height; + } + point.style.left = left + 'px'; + point.style.top = top + 'px'; + var pixel = context.getImageData(left, top, 1, 1).data; + slidersResult = rgbToHex(pixel[0], pixel[1], pixel[2]); + } + } + + // Applies the point's motion function to the div that contains it + element.addEventListener('mousedown', function(e) { + update(e.buttons, e.clientX, e.clientY); + }); + + element.addEventListener('mousemove', function(e) { + update(e.buttons, e.clientX, e.clientY); + }); + + element.addEventListener('touchmove', function(e) { + update(1, e.changedTouches[0].clientX, e.changedTouches[0].clientY); + }); + + return element; + } + + var slidersResult = ''; + + var rgbInputs = []; + + var changeInputColors = function() { + if (slidersResult !== '') { + for (var j = 0; j < rgbInputs.length; j++) { + var currentColor = HexToRgb(slidersResult); + + currentColor[j] = 0; + + var newGradient = 'linear-gradient(90deg, rgb('; + newGradient += currentColor.join(', '); + newGradient += '), rgb('; + + currentColor[j] = 255; + + newGradient += currentColor.join(', '); + newGradient += '))'; + + rgbInputs[j].style.backgroundImage = newGradient; + } + } + } + + var sliders = function() { + // Content of the third tab + var slidersElement = document.createElement('div'); + slidersElement.className = 'jcolor-sliders'; + + var slidersBody = document.createElement('div'); + + // Creates a range-type input with the specified name + var createSliderInput = function(name) { + var inputContainer = document.createElement('div'); + inputContainer.className = 'jcolor-sliders-input-container'; + + var label = document.createElement('label'); + label.innerText = name; + + var subContainer = document.createElement('div'); + subContainer.className = 'jcolor-sliders-input-subcontainer'; + + var input = document.createElement('input'); + input.type = 'range'; + input.min = 0; + input.max = 255; + input.value = 0; + + inputContainer.appendChild(label); + subContainer.appendChild(input); + + var value = document.createElement('div'); + value.innerText = input.value; + + input.addEventListener('input', function() { + value.innerText = input.value; + }); + + subContainer.appendChild(value); + inputContainer.appendChild(subContainer); + + slidersBody.appendChild(inputContainer); + + return input; + } + + // Creates red, green and blue inputs + rgbInputs = [ + createSliderInput('Red'), + createSliderInput('Green'), + createSliderInput('Blue'), + ]; + + slidersElement.appendChild(slidersBody); + + // Element that prints the current color + var slidersResultColor = document.createElement('div'); + slidersResultColor.className = 'jcolor-sliders-final-color'; + + var resultElement = document.createElement('div'); + resultElement.style.visibility = 'hidden'; + resultElement.innerText = 'a'; + slidersResultColor.appendChild(resultElement) + + // Update the element that prints the current color + var updateResult = function() { + var resultColor = rgbToHex(parseInt(rgbInputs[0].value), parseInt(rgbInputs[1].value), parseInt(rgbInputs[2].value)); + + resultElement.innerText = resultColor; + resultElement.style.color = resultColor; + resultElement.style.removeProperty('visibility'); + + slidersResult = resultColor; + } + + // Apply the update function to color inputs + rgbInputs.forEach(function(rgbInput) { + rgbInput.addEventListener('input', function() { + updateResult(); + changeInputColors(); + }); + }); + + slidersElement.appendChild(slidersResultColor); + + return slidersElement; + } + + var init = function() { + // Initial options + obj.setOptions(options); + + // Add a proper input tag when the element is an input + if (el.tagName == 'INPUT') { + el.classList.add('jcolor-input'); + el.readOnly = true; + } + + // Table container + container = document.createElement('div'); + container.className = 'jcolor'; + + // Table container + backdrop = document.createElement('div'); + backdrop.className = 'jcolor-backdrop'; + container.appendChild(backdrop); + + // Content + content = document.createElement('div'); + content.className = 'jcolor-content'; + + // Controls + var controls = document.createElement('div'); + controls.className = 'jcolor-controls'; + content.appendChild(controls); + + // Reset button + resetButton = document.createElement('div'); + resetButton.className = 'jcolor-reset'; + resetButton.innerHTML = obj.options.resetLabel; + controls.appendChild(resetButton); + + // Close button + closeButton = document.createElement('div'); + closeButton.className = 'jcolor-close'; + closeButton.innerHTML = obj.options.doneLabel; + controls.appendChild(closeButton); + + // Element that will be used to create the tabs + tabs = document.createElement('div'); + content.appendChild(tabs); + + // Starts the jSuites tabs component + jsuitesTabs = jSuites.tabs(tabs, { + animation: true, + data: [ + { + title: 'Grid', + contentElement: table(), + }, + { + title: 'Spectrum', + contentElement: hsl(), + }, + { + title: 'Sliders', + contentElement: sliders(), + } + ], + onchange: function(element, instance, index) { + if (index === 1) { + resizeCanvas(); + } else { + var color = slidersResult !== '' ? slidersResult : obj.getValue(); + + if (index === 2 && color) { + var rgb = HexToRgb(color); + + rgbInputs.forEach(function(rgbInput, index) { + rgbInput.value = rgb[index]; + rgbInput.dispatchEvent(new Event('input')); + }); + } + } + }, + palette: 'modern', + }); + + container.appendChild(content); + + // Insert picker after the element + if (el.tagName == 'INPUT') { + el.parentNode.insertBefore(container, el.nextSibling); + } else { + el.appendChild(container); + } + + container.addEventListener("click", function(e) { + if (e.target.tagName == 'TD') { + var value = e.target.getAttribute('data-value'); + if (value) { + obj.setValue(value); + } + } else if (e.target.classList.contains('jcolor-reset')) { + obj.setValue(''); + obj.close(); + } else if (e.target.classList.contains('jcolor-close')) { + if (jsuitesTabs.getActive() > 0) { + obj.setValue(slidersResult); + } + obj.close(); + } else if (e.target.classList.contains('jcolor-backdrop')) { + obj.close(); + } else { + obj.open(); + } + }); + + /** + * If element is focus open the picker + */ + el.addEventListener("mouseup", function(e) { + obj.open(); + }); + + // If the picker is open on the spectrum tab, it changes the canvas size when the window size is changed + window.addEventListener('resize', function() { + if (container.classList.contains('jcolor-focus') && jsuitesTabs.getActive() == 1) { + resizeCanvas(); + } + }); + + // Default opened + if (obj.options.opened == true) { + obj.open(); + } + + // Change + el.change = obj.setValue; + + // Global generic value handler + el.val = function(val) { + if (val === undefined) { + return obj.getValue(); + } else { + obj.setValue(val); + } + } + + // Keep object available from the node + el.color = obj; + + // Container shortcut + container.color = obj; + } + + obj.toHex = function(rgb) { + var hex = function(x) { + return ("0" + parseInt(x).toString(16)).slice(-2); + } + if (rgb) { + if (/^#[0-9A-F]{6}$/i.test(rgb)) { + return rgb; + } else { + rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); + if (rgb && rgb.length) { + return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); + } else { + return ""; + } + } + } + } + + init(); + + return obj; +}); + + + +jSuites.contextmenu = (function(el, options) { + // New instance + var obj = { type:'contextmenu'}; + obj.options = {}; + + // Default configuration + var defaults = { + items: null, + onclick: null, + }; + + // Loop through our object + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + obj.options[property] = defaults[property]; + } + } + + // Class definition + el.classList.add('jcontextmenu'); + + /** + * Open contextmenu + */ + obj.open = function(e, items) { + if (items) { + // Update content + obj.options.items = items; + // Create items + obj.create(items); + } + + // Close current contextmenu + if (jSuites.contextmenu.current) { + jSuites.contextmenu.current.close(); + } + + // Add to the opened components monitor + jSuites.tracking(obj, true); + + // Show context menu + el.classList.add('jcontextmenu-focus'); + + // Current + jSuites.contextmenu.current = obj; + + // Coordinates + if ((obj.options.items && obj.options.items.length > 0) || el.children.length) { + if (e.target) { + if (e.changedTouches && e.changedTouches[0]) { + x = e.changedTouches[0].clientX; + y = e.changedTouches[0].clientY; + } else { + var x = e.clientX; + var y = e.clientY; + } + } else { + var x = e.x; + var y = e.y; + } + + var rect = el.getBoundingClientRect(); + + if (window.innerHeight < y + rect.height) { + var h = y - rect.height; + if (h < 0) { + h = 0; + } + el.style.top = h + 'px'; + } else { + el.style.top = y + 'px'; + } + + if (window.innerWidth < x + rect.width) { + if (x - rect.width > 0) { + el.style.left = (x - rect.width) + 'px'; + } else { + el.style.left = '10px'; + } + } else { + el.style.left = x + 'px'; + } + } + } + + obj.isOpened = function() { + return el.classList.contains('jcontextmenu-focus') ? true : false; + } + + /** + * Close menu + */ + obj.close = function() { + if (el.classList.contains('jcontextmenu-focus')) { + el.classList.remove('jcontextmenu-focus'); + } + jSuites.tracking(obj, false); + } + + /** + * Create items based on the declared objectd + * @param {object} items - List of object + */ + obj.create = function(items) { + // Update content + el.innerHTML = ''; + + // Add header contextmenu + var itemHeader = createHeader(); + el.appendChild(itemHeader); + + // Append items + for (var i = 0; i < items.length; i++) { + var itemContainer = createItemElement(items[i]); + el.appendChild(itemContainer); + } + } + + /** + * createHeader for context menu + * @private + * @returns {HTMLElement} + */ + function createHeader() { + var header = document.createElement('div'); + header.classList.add("header"); + header.addEventListener("click", function(e) { + e.preventDefault(); + e.stopPropagation(); + }); + var title = document.createElement('a'); + title.classList.add("title"); + title.innerHTML = jSuites.translate("Menu"); + + header.appendChild(title); + + var closeButton = document.createElement('a'); + closeButton.classList.add("close"); + closeButton.innerHTML = jSuites.translate("close"); + closeButton.addEventListener("click", function(e) { + obj.close(); + }); + + header.appendChild(closeButton); + + return header; + } + + /** + * Private function for create a new Item element + * @param {type} item + * @returns {jsuitesL#15.jSuites.contextmenu.createItemElement.itemContainer} + */ + function createItemElement(item) { + if (item.type && (item.type == 'line' || item.type == 'divisor')) { + var itemContainer = document.createElement('hr'); + } else { + var itemContainer = document.createElement('div'); + var itemText = document.createElement('a'); + itemText.innerHTML = item.title; + + if (item.tooltip) { + itemContainer.setAttribute('title', item.tooltip); + } + + if (item.icon) { + itemContainer.setAttribute('data-icon', item.icon); + } + + if (item.id) { + itemContainer.id = item.id; + } + + if (item.disabled) { + itemContainer.className = 'jcontextmenu-disabled'; + } else if (item.onclick) { + itemContainer.method = item.onclick; + itemContainer.addEventListener("mousedown", function(e) { + e.preventDefault(); + }); + itemContainer.addEventListener("mouseup", function(e) { + // Execute method + this.method(this, e); + }); + } + itemContainer.appendChild(itemText); + + if (item.submenu) { + var itemIconSubmenu = document.createElement('span'); + itemIconSubmenu.innerHTML = "►"; + itemContainer.appendChild(itemIconSubmenu); + itemContainer.classList.add('jcontexthassubmenu'); + var el_submenu = document.createElement('div'); + // Class definition + el_submenu.classList.add('jcontextmenu'); + // Focusable + el_submenu.setAttribute('tabindex', '900'); + + // Append items + var submenu = item.submenu; + for (var i = 0; i < submenu.length; i++) { + var itemContainerSubMenu = createItemElement(submenu[i]); + el_submenu.appendChild(itemContainerSubMenu); + } + + itemContainer.appendChild(el_submenu); + } else if (item.shortcut) { + var itemShortCut = document.createElement('span'); + itemShortCut.innerHTML = item.shortcut; + itemContainer.appendChild(itemShortCut); + } + } + return itemContainer; + } + + if (typeof(obj.options.onclick) == 'function') { + el.addEventListener('click', function(e) { + obj.options.onclick(obj, e); + }); + } + + // Create items + if (obj.options.items) { + obj.create(obj.options.items); + } + + window.addEventListener("mousewheel", function() { + obj.close(); + }); + + el.contextmenu = obj; + + return obj; +}); + + +jSuites.dropdown = (function(el, options) { + // Already created, update options + if (el.dropdown) { + return el.dropdown.setOptions(options, true); + } + + // New instance + var obj = { type: 'dropdown' }; + obj.options = {}; + + // Success + var success = function(data, val) { + // Set data + if (data && data.length) { + // Sort + if (obj.options.sortResults !== false) { + if(typeof obj.options.sortResults == "function") { + data.sort(obj.options.sortResults); + } else { + data.sort(sortData); + } + } + + obj.setData(data); + } + + // Onload method + if (typeof(obj.options.onload) == 'function') { + obj.options.onload(el, obj, data, val); + } + + // Set value + if (val) { + applyValue(val); + } + + // Component value + if (val === undefined || val === null) { + obj.options.value = ''; + } + el.value = obj.options.value; + + // Open dropdown + if (obj.options.opened == true) { + obj.open(); + } + } + + + // Default sort + var sortData = function(itemA, itemB) { + var testA, testB; + if(typeof itemA == "string") { + testA = itemA; + } else { + if(itemA.text) { + testA = itemA.text; + } else if(itemA.name) { + testA = itemA.name; + } + } + + if(typeof itemB == "string") { + testB = itemB; + } else { + if(itemB.text) { + testB = itemB.text; + } else if(itemB.name) { + testB = itemB.name; + } + } + + if (typeof testA == "string" || typeof testB == "string") { + if (typeof testA != "string") { testA = ""+testA; } + if (typeof testB != "string") { testB = ""+testB; } + return testA.localeCompare(testB); + } else { + return testA - testB; + } + } + + /** + * Reset the options for the dropdown + */ + var resetValue = function() { + // Reset value container + obj.value = {}; + // Remove selected + for (var i = 0; i < obj.items.length; i++) { + if (obj.items[i].selected == true) { + if (obj.items[i].element) { + obj.items[i].element.classList.remove('jdropdown-selected') + } + obj.items[i].selected = null; + } + } + // Reset options + obj.options.value = ''; + // Reset value + el.value = ''; + } + + /** + * Apply values to the dropdown + */ + var applyValue = function(values) { + // Reset the current values + resetValue(); + + // Read values + if (values !== null) { + if (! values) { + if (typeof(obj.value['']) !== 'undefined') { + obj.value[''] = ''; + } + } else { + if (! Array.isArray(values)) { + values = ('' + values).split(';'); + } + for (var i = 0; i < values.length; i++) { + obj.value[values[i]] = ''; + } + } + } + + // Update the DOM + for (var i = 0; i < obj.items.length; i++) { + if (typeof(obj.value[Value(i)]) !== 'undefined') { + if (obj.items[i].element) { + obj.items[i].element.classList.add('jdropdown-selected') + } + obj.items[i].selected = true; + + // Keep label + obj.value[Value(i)] = Text(i); + } + } + + // Global value + obj.options.value = Object.keys(obj.value).join(';'); + + // Update labels + obj.header.value = obj.getText(); + } + + // Get the value of one item + var Value = function(k, v) { + // Legacy purposes + if (! obj.options.format) { + var property = 'value'; + } else { + var property = 'id'; + } + + if (obj.items[k]) { + if (v !== undefined) { + return obj.items[k].data[property] = v; + } else { + return obj.items[k].data[property]; + } + } + + return ''; + } + + // Get the label of one item + var Text = function(k, v) { + // Legacy purposes + if (! obj.options.format) { + var property = 'text'; + } else { + var property = 'name'; + } + + if (obj.items[k]) { + if (v !== undefined) { + return obj.items[k].data[property] = v; + } else { + return obj.items[k].data[property]; + } + } + + return ''; + } + + var getValue = function() { + return Object.keys(obj.value); + } + + var getText = function() { + var data = []; + var k = Object.keys(obj.value); + for (var i = 0; i < k.length; i++) { + data.push(obj.value[k[i]]); + } + return data; + } + + obj.setOptions = function(options, reset) { + if (! options) { + options = {}; + } + + // Default configuration + var defaults = { + url: null, + data: [], + format: 0, + multiple: false, + autocomplete: false, + remoteSearch: false, + lazyLoading: false, + type: null, + width: null, + maxWidth: null, + opened: false, + value: null, + placeholder: '', + newOptions: false, + position: false, + onchange: null, + onload: null, + onopen: null, + onclose: null, + onfocus: null, + onblur: null, + oninsert: null, + onbeforeinsert: null, + sortResults: false, + autofocus: false, + } + + // Loop through our object + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + if (typeof(obj.options[property]) == 'undefined' || reset === true) { + obj.options[property] = defaults[property]; + } + } + } + + // Force autocomplete search + if (obj.options.remoteSearch == true || obj.options.type === 'searchbar') { + obj.options.autocomplete = true; + } + + // New options + if (obj.options.newOptions == true) { + obj.header.classList.add('jdropdown-add'); + } else { + obj.header.classList.remove('jdropdown-add'); + } + + // Autocomplete + if (obj.options.autocomplete == true) { + obj.header.removeAttribute('readonly'); + } else { + obj.header.setAttribute('readonly', 'readonly'); + } + + // Place holder + if (obj.options.placeholder) { + obj.header.setAttribute('placeholder', obj.options.placeholder); + } else { + obj.header.removeAttribute('placeholder'); + } + + // Remove specific dropdown typing to add again + el.classList.remove('jdropdown-searchbar'); + el.classList.remove('jdropdown-picker'); + el.classList.remove('jdropdown-list'); + + if (obj.options.type == 'searchbar') { + el.classList.add('jdropdown-searchbar'); + } else if (obj.options.type == 'list') { + el.classList.add('jdropdown-list'); + } else if (obj.options.type == 'picker') { + el.classList.add('jdropdown-picker'); + } else { + if (jSuites.getWindowWidth() < 800) { + if (obj.options.autocomplete) { + el.classList.add('jdropdown-searchbar'); + obj.options.type = 'searchbar'; + } else { + el.classList.add('jdropdown-picker'); + obj.options.type = 'picker'; + } + } else { + if (obj.options.width) { + el.style.width = obj.options.width; + el.style.minWidth = obj.options.width; + } else { + el.style.removeProperty('width'); + el.style.removeProperty('min-width'); + } + + el.classList.add('jdropdown-default'); + obj.options.type = 'default'; + } + } + + // Close button + if (obj.options.type == 'searchbar') { + containerHeader.appendChild(closeButton); + } else { + container.insertBefore(closeButton, container.firstChild); + } + + // Load the content + if (obj.options.url && ! options.data) { + jSuites.ajax({ + url: obj.options.url, + method: 'GET', + dataType: 'json', + success: function(data) { + if (data) { + success(data, obj.options.value); + } + } + }); + } else { + success(obj.options.data, obj.options.value); + } + + // Return the instance + return obj; + } + + // Helpers + var containerHeader = null; + var container = null; + var content = null; + var closeButton = null; + var resetButton = null; + var backdrop = null; + + var keyTimer = null; + + /** + * Init dropdown + */ + var init = function() { + // Do not accept null + if (! options) { + options = {}; + } + + // If the element is a SELECT tag, create a configuration object + if (el.tagName == 'SELECT') { + var ret = jSuites.dropdown.extractFromDom(el, options); + el = ret.el; + options = ret.options; + } + + // Place holder + if (! options.placeholder && el.getAttribute('placeholder')) { + options.placeholder = el.getAttribute('placeholder'); + } + + // Value container + obj.value = {}; + // Containers + obj.items = []; + obj.groups = []; + // Search options + obj.search = ''; + obj.results = null; + + // Create dropdown + el.classList.add('jdropdown'); + + // Header container + containerHeader = document.createElement('div'); + containerHeader.className = 'jdropdown-container-header'; + + // Header + obj.header = document.createElement('input'); + obj.header.className = 'jdropdown-header jss_object'; + obj.header.type = 'text'; + obj.header.setAttribute('autocomplete', 'off'); + obj.header.onfocus = function() { + if (typeof(obj.options.onfocus) == 'function') { + obj.options.onfocus(el); + } + } + + obj.header.onblur = function() { + if (typeof(obj.options.onblur) == 'function') { + obj.options.onblur(el); + } + } + + obj.header.onkeyup = function(e) { + if (obj.options.autocomplete == true && ! keyTimer) { + if (obj.search != obj.header.value.trim()) { + keyTimer = setTimeout(function() { + obj.find(obj.header.value.trim()); + keyTimer = null; + }, 400); + } + + if (! el.classList.contains('jdropdown-focus')) { + obj.open(); + } + } else { + if (! obj.options.autocomplete) { + obj.next(e.key); + } + } + } + + // Global controls + if (! jSuites.dropdown.hasEvents) { + // Execute only one time + jSuites.dropdown.hasEvents = true; + // Enter and Esc + document.addEventListener("keydown", jSuites.dropdown.keydown); + } + + // Container + container = document.createElement('div'); + container.className = 'jdropdown-container'; + + // Dropdown content + content = document.createElement('div'); + content.className = 'jdropdown-content'; + + // Close button + closeButton = document.createElement('div'); + closeButton.className = 'jdropdown-close'; + closeButton.innerHTML = 'Done'; + + // Reset button + resetButton = document.createElement('div'); + resetButton.className = 'jdropdown-reset'; + resetButton.innerHTML = 'x'; + resetButton.onclick = function() { + obj.reset(); + obj.close(); + } + + // Create backdrop + backdrop = document.createElement('div'); + backdrop.className = 'jdropdown-backdrop'; + + // Append elements + containerHeader.appendChild(obj.header); + + container.appendChild(content); + el.appendChild(containerHeader); + el.appendChild(container); + el.appendChild(backdrop); + + // Set the otiptions + obj.setOptions(options); + + if ('ontouchsend' in document.documentElement === true) { + el.addEventListener('touchsend', jSuites.dropdown.mouseup); + } else { + el.addEventListener('mouseup', jSuites.dropdown.mouseup); + } + + // Lazyloading + if (obj.options.lazyLoading == true) { + jSuites.lazyLoading(content, { + loadUp: obj.loadUp, + loadDown: obj.loadDown, + }); + } + + content.onwheel = function(e) { + e.stopPropagation(); + } + + // Change method + el.change = obj.setValue; + + // Global generic value handler + el.val = function(val) { + if (val === undefined) { + return obj.getValue(obj.options.multiple ? true : false); + } else { + obj.setValue(val); + } + } + + // Keep object available from the node + el.dropdown = obj; + } + + /** + * Get the current remote source of data URL + */ + obj.getUrl = function() { + return obj.options.url; + } + + /** + * Set the new data from a remote source + * @param {string} url - url from the remote source + * @param {function} callback - callback when the data is loaded + */ + obj.setUrl = function(url, callback) { + obj.options.url = url; + + jSuites.ajax({ + url: obj.options.url, + method: 'GET', + dataType: 'json', + success: function(data) { + obj.setData(data); + // Callback + if (typeof(callback) == 'function') { + callback(obj); + } + } + }); + } + + /** + * Set ID for one item + */ + obj.setId = function(item, v) { + // Legacy purposes + if (! obj.options.format) { + var property = 'value'; + } else { + var property = 'id'; + } + + if (typeof(item) == 'object') { + item[property] = v; + } else { + obj.items[item].data[property] = v; + } + } + + /** + * Add a new item + * @param {string} title - title of the new item + * @param {string} id - value/id of the new item + */ + obj.add = function(title, id) { + if (! title) { + var current = obj.options.autocomplete == true ? obj.header.value : ''; + var title = prompt(jSuites.translate('Add A New Option'), current); + if (! title) { + return false; + } + } + + // Id + if (! id) { + id = jSuites.guid(); + } + + // Create new item + if (! obj.options.format) { + var item = { + value: id, + text: title, + } + } else { + var item = { + id: id, + name: title, + } + } + + // Callback + if (typeof(obj.options.onbeforeinsert) == 'function') { + var ret = obj.options.onbeforeinsert(obj, item); + if (ret === false) { + return false; + } else if (ret) { + item = ret; + } + } + + // Add item to the main list + obj.options.data.push(item); + + // Create DOM + var newItem = obj.createItem(item); + + // Append DOM to the list + content.appendChild(newItem.element); + + // Callback + if (typeof(obj.options.oninsert) == 'function') { + obj.options.oninsert(obj, item, newItem); + } + + // Show content + if (content.style.display == 'none') { + content.style.display = ''; + } + + // Search? + if (obj.results) { + obj.results.push(newItem); + } + + return item; + } + + /** + * Create a new item + */ + obj.createItem = function(data, group, groupName) { + // Keep the correct source of data + if (! obj.options.format) { + if (! data.value && data.id !== undefined) { + data.value = data.id; + //delete data.id; + } + if (! data.text && data.name !== undefined) { + data.text = data.name; + //delete data.name; + } + } else { + if (! data.id && data.value !== undefined) { + data.id = data.value; + //delete data.value; + } + if (! data.name && data.text !== undefined) { + data.name = data.text + //delete data.text; + } + } + + // Create item + var item = {}; + item.element = document.createElement('div'); + item.element.className = 'jdropdown-item'; + item.element.indexValue = obj.items.length; + item.data = data; + + // Groupd DOM + if (group) { + item.group = group; + } + + // Id + if (data.id) { + item.element.setAttribute('id', data.id); + } + + // Disabled + if (data.disabled == true) { + item.element.setAttribute('data-disabled', true); + } + + // Tooltip + if (data.tooltip) { + item.element.setAttribute('title', data.tooltip); + } + + // Image + if (data.image) { + var image = document.createElement('img'); + image.className = 'jdropdown-image'; + image.src = data.image; + if (! data.title) { + image.classList.add('jdropdown-image-small'); + } + item.element.appendChild(image); + } else if (data.icon) { + var icon = document.createElement('span'); + icon.className = "jdropdown-icon material-icons"; + icon.innerText = data.icon; + if (! data.title) { + icon.classList.add('jdropdown-icon-small'); + } + if (data.color) { + icon.style.color = data.color; + } + item.element.appendChild(icon); + } else if (data.color) { + var color = document.createElement('div'); + color.className = 'jdropdown-color'; + color.style.backgroundColor = data.color; + item.element.appendChild(color); + } + + // Set content + if (! obj.options.format) { + var text = data.text; + } else { + var text = data.name; + } + + var node = document.createElement('div'); + node.className = 'jdropdown-description'; + node.innerHTML = text || ' '; + + // Title + if (data.title) { + var title = document.createElement('div'); + title.className = 'jdropdown-title'; + title.innerText = data.title; + node.appendChild(title); + } + + // Set content + if (! obj.options.format) { + var val = data.value; + } else { + var val = data.id; + } + + // Value + if (obj.value[val]) { + item.element.classList.add('jdropdown-selected'); + item.selected = true; + } + + // Keep DOM accessible + obj.items.push(item); + + // Add node to item + item.element.appendChild(node); + + return item; + } + + obj.appendData = function(data) { + // Create elements + if (data.length) { + // Helpers + var items = []; + var groups = []; + + // Prepare data + for (var i = 0; i < data.length; i++) { + // Process groups + if (data[i].group) { + if (! groups[data[i].group]) { + groups[data[i].group] = []; + } + groups[data[i].group].push(i); + } else { + items.push(i); + } + } + + // Number of items counter + var counter = 0; + + // Groups + var groupNames = Object.keys(groups); + + // Append groups in case exists + if (groupNames.length > 0) { + for (var i = 0; i < groupNames.length; i++) { + // Group container + var group = document.createElement('div'); + group.className = 'jdropdown-group'; + // Group name + var groupName = document.createElement('div'); + groupName.className = 'jdropdown-group-name'; + groupName.innerHTML = groupNames[i]; + // Group arrow + var groupArrow = document.createElement('i'); + groupArrow.className = 'jdropdown-group-arrow jdropdown-group-arrow-down'; + groupName.appendChild(groupArrow); + // Group items + var groupContent = document.createElement('div'); + groupContent.className = 'jdropdown-group-items'; + for (var j = 0; j < groups[groupNames[i]].length; j++) { + var item = obj.createItem(data[groups[groupNames[i]][j]], group, groupNames[i]); + + if (obj.options.lazyLoading == false || counter < 200) { + groupContent.appendChild(item.element); + counter++; + } + } + // Group itens + group.appendChild(groupName); + group.appendChild(groupContent); + // Keep group DOM + obj.groups.push(group); + // Only add to the screen if children on the group + if (groupContent.children.length > 0) { + // Add DOM to the content + content.appendChild(group); + } + } + } + + if (items.length) { + for (var i = 0; i < items.length; i++) { + var item = obj.createItem(data[items[i]]); + if (obj.options.lazyLoading == false || counter < 200) { + content.appendChild(item.element); + counter++; + } + } + } + } + } + + obj.setData = function(data) { + // Reset current value + resetValue(); + + // Make sure the content container is blank + content.innerHTML = ''; + + // Reset + obj.header.value = ''; + + // Reset items and values + obj.items = []; + + // Prepare data + if (data && data.length) { + for (var i = 0; i < data.length; i++) { + // Compatibility + if (typeof(data[i]) != 'object') { + // Correct format + if (! obj.options.format) { + data[i] = { + value: data[i], + text: data[i] + } + } else { + data[i] = { + id: data[i], + name: data[i] + } + } + } + } + + // Append data + obj.appendData(data); + + // Update data + obj.options.data = data; + } else { + // Update data + obj.options.data = []; + } + + obj.close(); + } + + obj.getData = function() { + return obj.options.data; + } + + /** + * Get position of the item + */ + obj.getPosition = function(val) { + for (var i = 0; i < obj.items.length; i++) { + if (Value(i) == val) { + return i; + } + } + return false; + } + + /** + * Get dropdown current text + */ + obj.getText = function(asArray) { + // Get value + var v = getText(); + // Return value + if (asArray) { + return v; + } else { + return v.join('; '); + } + } + + /** + * Get dropdown current value + */ + obj.getValue = function(asArray) { + // Get value + var v = getValue(); + // Return value + if (asArray) { + return v; + } else { + return v.join(';'); + } + } + + /** + * Change event + */ + var change = function(oldValue) { + // Lemonade JS + if (el.value != obj.options.value) { + el.value = obj.options.value; + if (typeof(el.oninput) == 'function') { + el.oninput({ + type: 'input', + target: el, + value: el.value + }); + } + } + + // Events + if (typeof(obj.options.onchange) == 'function') { + obj.options.onchange(el, obj, oldValue, obj.options.value); + } + } + + /** + * Set value + */ + obj.setValue = function(newValue) { + // Current value + var oldValue = obj.getValue(); + // New value + if (Array.isArray(newValue)) { + newValue = newValue.join(';') + } + + if (oldValue !== newValue) { + // Set value + applyValue(newValue); + + // Change + change(oldValue); + } + } + + obj.resetSelected = function() { + obj.setValue(null); + } + + obj.selectIndex = function(index, force) { + // Make sure is a number + var index = parseInt(index); + + // Only select those existing elements + if (obj.items && obj.items[index] && (force === true || obj.items[index].data.disabled !== true)) { + // Reset cursor to a new position + obj.setCursor(index, false); + + // Behaviour + if (! obj.options.multiple) { + // Update value + if (obj.items[index].selected) { + obj.setValue(null); + } else { + obj.setValue(Value(index)); + } + + // Close component + obj.close(); + } else { + // Old value + var oldValue = obj.options.value; + + // Toggle option + if (obj.items[index].selected) { + obj.items[index].element.classList.remove('jdropdown-selected'); + obj.items[index].selected = false; + + delete obj.value[Value(index)]; + } else { + // Select element + obj.items[index].element.classList.add('jdropdown-selected'); + obj.items[index].selected = true; + + // Set value + obj.value[Value(index)] = Text(index); + } + + // Global value + obj.options.value = Object.keys(obj.value).join(';'); + + // Update labels for multiple dropdown + if (obj.options.autocomplete == false) { + obj.header.value = getText().join('; '); + } + + // Events + change(oldValue); + } + } + } + + obj.selectItem = function(item) { + obj.selectIndex(item.indexValue); + } + + var exists = function(k, result) { + for (var j = 0; j < result.length; j++) { + if (! obj.options.format) { + if (result[j].value == k) { + return true; + } + } else { + if (result[j].id == k) { + return true; + } + } + } + return false; + } + + obj.find = function(str) { + if (obj.search == str.trim()) { + return false; + } + + // Search term + obj.search = str; + + // Reset index + obj.setCursor(); + + // Remove nodes from all groups + if (obj.groups.length) { + for (var i = 0; i < obj.groups.length; i++) { + obj.groups[i].lastChild.innerHTML = ''; + } + } + + // Remove all nodes + content.innerHTML = ''; + + // Remove current items in the remote search + if (obj.options.remoteSearch == true) { + // Reset results + obj.results = null; + // URL + var url = obj.options.url + (obj.options.url.indexOf('?') > 0 ? '&' : '?') + 'q=' + str; + // Remote search + jSuites.ajax({ + url: url, + method: 'GET', + dataType: 'json', + success: function(result) { + // Reset items + obj.items = []; + + // Add the current selected items to the results in case they are not there + var current = Object.keys(obj.value); + if (current.length) { + for (var i = 0; i < current.length; i++) { + if (! exists(current[i], result)) { + if (! obj.options.format) { + result.unshift({ value: current[i], text: obj.value[current[i]] }); + } else { + result.unshift({ id: current[i], name: obj.value[current[i]] }); + } + } + } + } + // Append data + obj.appendData(result); + // Show or hide results + if (! result.length) { + content.style.display = 'none'; + } else { + content.style.display = ''; + } + } + }); + } else { + // Search terms + str = new RegExp(str, 'gi'); + + // Reset search + var results = []; + + // Append options + for (var i = 0; i < obj.items.length; i++) { + // Item label + var label = Text(i); + // Item title + var title = obj.items[i].data.title || ''; + // Group name + var groupName = obj.items[i].data.group || ''; + // Synonym + var synonym = obj.items[i].data.synonym || ''; + if (synonym) { + synonym = synonym.join(' '); + } + + if (str == null || obj.items[i].selected == true || label.match(str) || title.match(str) || groupName.match(str) || synonym.match(str)) { + results.push(obj.items[i]); + } + } + + if (! results.length) { + content.style.display = 'none'; + + // Results + obj.results = null; + } else { + content.style.display = ''; + + // Results + obj.results = results; + + // Show 200 items at once + var number = results.length || 0; + + // Lazyloading + if (obj.options.lazyLoading == true && number > 200) { + number = 200; + } + + for (var i = 0; i < number; i++) { + if (obj.results[i].group) { + if (! obj.results[i].group.parentNode) { + content.appendChild(obj.results[i].group); + } + obj.results[i].group.lastChild.appendChild(obj.results[i].element); + } else { + content.appendChild(obj.results[i].element); + } + } + } + } + + // Auto focus + if (obj.options.autofocus == true) { + obj.first(); + } + } + + obj.open = function() { + // Focus + if (! el.classList.contains('jdropdown-focus')) { + // Current dropdown + jSuites.dropdown.current = obj; + + // Start tracking + jSuites.tracking(obj, true); + + // Add focus + el.classList.add('jdropdown-focus'); + + // Animation + if (jSuites.getWindowWidth() < 800) { + if (obj.options.type == null || obj.options.type == 'picker') { + jSuites.animation.slideBottom(container, 1); + } + } + + // Filter + if (obj.options.autocomplete == true) { + obj.header.value = obj.search; + obj.header.focus(); + } + + // Set cursor for the first or first selected element + var k = getValue(); + if (k[0]) { + var cursor = obj.getPosition(k[0]); + if (cursor !== false) { + obj.setCursor(cursor); + } + } + + // Container Size + if (! obj.options.type || obj.options.type == 'default') { + var rect = el.getBoundingClientRect(); + var rectContainer = container.getBoundingClientRect(); + + if (obj.options.position) { + container.style.position = 'fixed'; + if (window.innerHeight < rect.bottom + rectContainer.height) { + container.style.top = ''; + container.style.bottom = (window.innerHeight - rect.top ) + 1 + 'px'; + } else { + container.style.top = rect.bottom + 'px'; + container.style.bottom = ''; + } + container.style.left = rect.left + 'px'; + } else { + if (window.innerHeight < rect.bottom + rectContainer.height) { + container.style.top = ''; + container.style.bottom = rect.height + 1 + 'px'; + } else { + container.style.top = ''; + container.style.bottom = ''; + } + } + + container.style.minWidth = rect.width + 'px'; + + if (obj.options.maxWidth) { + container.style.maxWidth = obj.options.maxWidth; + } + + if (! obj.items.length && obj.options.autocomplete == true) { + content.style.display = 'none'; + } else { + content.style.display = ''; + } + } + } + + // Events + if (typeof(obj.options.onopen) == 'function') { + obj.options.onopen(el); + } + } + + obj.close = function(ignoreEvents) { + if (el.classList.contains('jdropdown-focus')) { + // Update labels + obj.header.value = obj.getText(); + // Remove cursor + obj.setCursor(); + // Events + if (! ignoreEvents && typeof(obj.options.onclose) == 'function') { + obj.options.onclose(el); + } + // Blur + if (obj.header.blur) { + obj.header.blur(); + } + // Remove focus + el.classList.remove('jdropdown-focus'); + // Start tracking + jSuites.tracking(obj, false); + // Current dropdown + jSuites.dropdown.current = null; + } + + return obj.getValue(); + } + + /** + * Set cursor + */ + obj.setCursor = function(index, setPosition) { + // Remove current cursor + if (obj.currentIndex != null) { + // Remove visual cursor + if (obj.items && obj.items[obj.currentIndex]) { + obj.items[obj.currentIndex].element.classList.remove('jdropdown-cursor'); + } + } + + if (index == undefined) { + obj.currentIndex = null; + } else { + index = parseInt(index); + + // Cursor only for visible items + if (obj.items[index].element.parentNode) { + obj.items[index].element.classList.add('jdropdown-cursor'); + obj.currentIndex = index; + + // Update scroll to the cursor element + if (setPosition !== false && obj.items[obj.currentIndex].element) { + var container = content.scrollTop; + var element = obj.items[obj.currentIndex].element; + content.scrollTop = element.offsetTop - element.scrollTop + element.clientTop - 95; + } + } + } + } + + // Compatibility + obj.resetCursor = obj.setCursor; + obj.updateCursor = obj.setCursor; + + /** + * Reset cursor and selected items + */ + obj.reset = function() { + // Reset cursor + obj.setCursor(); + + // Reset selected + obj.setValue(null); + } + + /** + * First available item + */ + obj.first = function() { + if (obj.options.lazyLoading === true) { + obj.loadFirst(); + } + + var items = content.querySelectorAll('.jdropdown-item'); + if (items.length) { + var newIndex = items[0].indexValue; + obj.setCursor(newIndex); + } + } + + /** + * Last available item + */ + obj.last = function() { + if (obj.options.lazyLoading === true) { + obj.loadLast(); + } + + var items = content.querySelectorAll('.jdropdown-item'); + if (items.length) { + var newIndex = items[items.length-1].indexValue; + obj.setCursor(newIndex); + } + } + + obj.next = function(letter) { + var newIndex = null; + + if (letter) { + if (letter.length == 1) { + // Current index + var current = obj.currentIndex || -1; + // Letter + letter = letter.toLowerCase(); + + var e = null; + var l = null; + var items = content.querySelectorAll('.jdropdown-item'); + if (items.length) { + for (var i = 0; i < items.length; i++) { + if (items[i].indexValue > current) { + if (e = obj.items[items[i].indexValue]) { + if (l = e.element.innerText[0]) { + l = l.toLowerCase(); + if (letter == l) { + newIndex = items[i].indexValue; + break; + } + } + } + } + } + obj.setCursor(newIndex); + } + } + } else { + if (obj.currentIndex == undefined || obj.currentIndex == null) { + obj.first(); + } else { + var element = obj.items[obj.currentIndex].element; + + var next = element.nextElementSibling; + if (next) { + if (next.classList.contains('jdropdown-group')) { + next = next.lastChild.firstChild; + } + newIndex = next.indexValue; + } else { + if (element.parentNode.classList.contains('jdropdown-group-items')) { + if (next = element.parentNode.parentNode.nextElementSibling) { + if (next.classList.contains('jdropdown-group')) { + next = next.lastChild.firstChild; + } else if (next.classList.contains('jdropdown-item')) { + newIndex = next.indexValue; + } else { + next = null; + } + } + + if (next) { + newIndex = next.indexValue; + } + } + } + + if (newIndex !== null) { + obj.setCursor(newIndex); + } + } + } + } + + obj.prev = function() { + var newIndex = null; + + if (obj.currentIndex === null) { + obj.first(); + } else { + var element = obj.items[obj.currentIndex].element; + + var prev = element.previousElementSibling; + if (prev) { + if (prev.classList.contains('jdropdown-group')) { + prev = prev.lastChild.lastChild; + } + newIndex = prev.indexValue; + } else { + if (element.parentNode.classList.contains('jdropdown-group-items')) { + if (prev = element.parentNode.parentNode.previousElementSibling) { + if (prev.classList.contains('jdropdown-group')) { + prev = prev.lastChild.lastChild; + } else if (prev.classList.contains('jdropdown-item')) { + newIndex = prev.indexValue; + } else { + prev = null + } + } + + if (prev) { + newIndex = prev.indexValue; + } + } + } + } + + if (newIndex !== null) { + obj.setCursor(newIndex); + } + } + + obj.loadFirst = function() { + // Search + if (obj.results) { + var results = obj.results; + } else { + var results = obj.items; + } + + // Show 200 items at once + var number = results.length || 0; + + // Lazyloading + if (obj.options.lazyLoading == true && number > 200) { + number = 200; + } + + // Reset container + content.innerHTML = ''; + + // First 200 items + for (var i = 0; i < number; i++) { + if (results[i].group) { + if (! results[i].group.parentNode) { + content.appendChild(results[i].group); + } + results[i].group.lastChild.appendChild(results[i].element); + } else { + content.appendChild(results[i].element); + } + } + + // Scroll go to the begin + content.scrollTop = 0; + } + + obj.loadLast = function() { + // Search + if (obj.results) { + var results = obj.results; + } else { + var results = obj.items; + } + + // Show first page + var number = results.length; + + // Max 200 items + if (number > 200) { + number = number - 200; + + // Reset container + content.innerHTML = ''; + + // First 200 items + for (var i = number; i < results.length; i++) { + if (results[i].group) { + if (! results[i].group.parentNode) { + content.appendChild(results[i].group); + } + results[i].group.lastChild.appendChild(results[i].element); + } else { + content.appendChild(results[i].element); + } + } + + // Scroll go to the begin + content.scrollTop = content.scrollHeight; + } + } + + obj.loadUp = function() { + var test = false; + + // Search + if (obj.results) { + var results = obj.results; + } else { + var results = obj.items; + } + + var items = content.querySelectorAll('.jdropdown-item'); + var fistItem = items[0].indexValue; + fistItem = obj.items[fistItem]; + var index = results.indexOf(fistItem) - 1; + + if (index > 0) { + var number = 0; + + while (index > 0 && results[index] && number < 200) { + if (results[index].group) { + if (! results[index].group.parentNode) { + content.insertBefore(results[index].group, content.firstChild); + } + results[index].group.lastChild.insertBefore(results[index].element, results[index].group.lastChild.firstChild); + } else { + content.insertBefore(results[index].element, content.firstChild); + } + + index--; + number++; + } + + // New item added + test = true; + } + + return test; + } + + obj.loadDown = function() { + var test = false; + + // Search + if (obj.results) { + var results = obj.results; + } else { + var results = obj.items; + } + + var items = content.querySelectorAll('.jdropdown-item'); + var lastItem = items[items.length-1].indexValue; + lastItem = obj.items[lastItem]; + var index = results.indexOf(lastItem) + 1; + + if (index < results.length) { + var number = 0; + while (index < results.length && results[index] && number < 200) { + if (results[index].group) { + if (! results[index].group.parentNode) { + content.appendChild(results[index].group); + } + results[index].group.lastChild.appendChild(results[index].element); + } else { + content.appendChild(results[index].element); + } + + index++; + number++; + } + + // New item added + test = true; + } + + return test; + } + + init(); + + return obj; +}); + +jSuites.dropdown.keydown = function(e) { + var dropdown = null; + if (dropdown = jSuites.dropdown.current) { + if (e.which == 13 || e.which == 9) { // enter or tab + if (dropdown.header.value && dropdown.currentIndex == null && dropdown.options.newOptions) { + // if they typed something in, but it matched nothing, and newOptions are allowed, start that flow + dropdown.add(); + } else { + // Quick Select/Filter + if (dropdown.currentIndex == null && dropdown.options.autocomplete == true && dropdown.header.value != "") { + dropdown.find(dropdown.header.value); + } + dropdown.selectIndex(dropdown.currentIndex); + } + } else if (e.which == 38) { // up arrow + if (dropdown.currentIndex == null) { + dropdown.first(); + } else if (dropdown.currentIndex > 0) { + dropdown.prev(); + } + e.preventDefault(); + } else if (e.which == 40) { // down arrow + if (dropdown.currentIndex == null) { + dropdown.first(); + } else if (dropdown.currentIndex + 1 < dropdown.items.length) { + dropdown.next(); + } + e.preventDefault(); + } else if (e.which == 36) { + dropdown.first(); + if (! e.target.classList.contains('jdropdown-header')) { + e.preventDefault(); + } + } else if (e.which == 35) { + dropdown.last(); + if (! e.target.classList.contains('jdropdown-header')) { + e.preventDefault(); + } + } else if (e.which == 27) { + dropdown.close(); + } else if (e.which == 33) { // page up + if (dropdown.currentIndex == null) { + dropdown.first(); + } else if (dropdown.currentIndex > 0) { + for (var i = 0; i < 7; i++) { + dropdown.prev() + } + } + e.preventDefault(); + } else if (e.which == 34) { // page down + if (dropdown.currentIndex == null) { + dropdown.first(); + } else if (dropdown.currentIndex + 1 < dropdown.items.length) { + for (var i = 0; i < 7; i++) { + dropdown.next() + } + } + e.preventDefault(); + } + } +} + +jSuites.dropdown.mouseup = function(e) { + var element = jSuites.findElement(e.target, 'jdropdown'); + if (element) { + var dropdown = element.dropdown; + if (e.target.classList.contains('jdropdown-header')) { + if (element.classList.contains('jdropdown-focus') && element.classList.contains('jdropdown-default')) { + var rect = element.getBoundingClientRect(); + + if (e.changedTouches && e.changedTouches[0]) { + var x = e.changedTouches[0].clientX; + var y = e.changedTouches[0].clientY; + } else { + var x = e.clientX; + var y = e.clientY; + } + + if (rect.width - (x - rect.left) < 30) { + if (e.target.classList.contains('jdropdown-add')) { + dropdown.add(); + } else { + dropdown.close(); + } + } else { + if (dropdown.options.autocomplete == false) { + dropdown.close(); + } + } + } else { + dropdown.open(); + } + } else if (e.target.classList.contains('jdropdown-group-name')) { + var items = e.target.nextSibling.children; + if (e.target.nextSibling.style.display != 'none') { + for (var i = 0; i < items.length; i++) { + if (items[i].style.display != 'none') { + dropdown.selectItem(items[i]); + } + } + } + } else if (e.target.classList.contains('jdropdown-group-arrow')) { + if (e.target.classList.contains('jdropdown-group-arrow-down')) { + e.target.classList.remove('jdropdown-group-arrow-down'); + e.target.classList.add('jdropdown-group-arrow-up'); + e.target.parentNode.nextSibling.style.display = 'none'; + } else { + e.target.classList.remove('jdropdown-group-arrow-up'); + e.target.classList.add('jdropdown-group-arrow-down'); + e.target.parentNode.nextSibling.style.display = ''; + } + } else if (e.target.classList.contains('jdropdown-item')) { + dropdown.selectItem(e.target); + } else if (e.target.classList.contains('jdropdown-image')) { + dropdown.selectItem(e.target.parentNode); + } else if (e.target.classList.contains('jdropdown-description')) { + dropdown.selectItem(e.target.parentNode); + } else if (e.target.classList.contains('jdropdown-title')) { + dropdown.selectItem(e.target.parentNode.parentNode); + } else if (e.target.classList.contains('jdropdown-close') || e.target.classList.contains('jdropdown-backdrop')) { + dropdown.close(); + } + } +} + +jSuites.dropdown.extractFromDom = function(el, options) { + // Keep reference + var select = el; + if (! options) { + options = {}; + } + // Prepare configuration + if (el.getAttribute('multiple') && (! options || options.multiple == undefined)) { + options.multiple = true; + } + if (el.getAttribute('placeholder') && (! options || options.placeholder == undefined)) { + options.placeholder = el.getAttribute('placeholder'); + } + if (el.getAttribute('data-autocomplete') && (! options || options.autocomplete == undefined)) { + options.autocomplete = true; + } + if (! options || options.width == undefined) { + options.width = el.offsetWidth; + } + if (el.value && (! options || options.value == undefined)) { + options.value = el.value; + } + if (! options || options.data == undefined) { + options.data = []; + for (var j = 0; j < el.children.length; j++) { + if (el.children[j].tagName == 'OPTGROUP') { + for (var i = 0; i < el.children[j].children.length; i++) { + options.data.push({ + value: el.children[j].children[i].value, + text: el.children[j].children[i].innerHTML, + group: el.children[j].getAttribute('label'), + }); + } + } else { + options.data.push({ + value: el.children[j].value, + text: el.children[j].innerHTML, + }); + } + } + } + if (! options || options.onchange == undefined) { + options.onchange = function(a,b,c,d) { + if (options.multiple == true) { + if (obj.items[b].classList.contains('jdropdown-selected')) { + select.options[b].setAttribute('selected', 'selected'); + } else { + select.options[b].removeAttribute('selected'); + } + } else { + select.value = d; + } + } + } + // Create DIV + var div = document.createElement('div'); + el.parentNode.insertBefore(div, el); + el.style.display = 'none'; + el = div; + + return { el:el, options:options }; +} + +jSuites.editor = (function(el, options) { + // New instance + var obj = { type:'editor' }; + obj.options = {}; + + // Default configuration + var defaults = { + // Load data from a remove location + url: null, + // Initial HTML content + value: '', + // Initial snippet + snippet: null, + // Add toolbar + toolbar: true, + toolbarOnTop: false, + // Website parser is to read websites and images from cross domain + remoteParser: null, + // Placeholder + placeholder: null, + // Parse URL + filterPaste: true, + // Accept drop files + dropZone: true, + dropAsSnippet: false, + acceptImages: true, + acceptFiles: false, + maxFileSize: 5000000, + allowImageResize: true, + // Style + maxHeight: null, + height: null, + focus: false, + // Events + onclick: null, + onfocus: null, + onblur: null, + onload: null, + onkeyup: null, + onkeydown: null, + onchange: null, + extensions: null, + type: null, + }; + + // Loop through our object + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + obj.options[property] = defaults[property]; + } + } + + // Private controllers + var editorTimer = null; + var editorAction = null; + var files = []; + + // Keep the reference for the container + obj.el = el; + + if (typeof(obj.options.onclick) == 'function') { + el.onclick = function(e) { + obj.options.onclick(el, obj, e); + } + } + + // Prepare container + el.classList.add('jeditor-container'); + + // Snippet + var snippet = document.createElement('div'); + snippet.className = 'jsnippet'; + snippet.setAttribute('contenteditable', false); + + // Toolbar + var toolbar = document.createElement('div'); + toolbar.className = 'jeditor-toolbar'; + + obj.editor = document.createElement('div'); + obj.editor.setAttribute('contenteditable', true); + obj.editor.setAttribute('spellcheck', false); + obj.editor.classList.add('jeditor'); + + // Placeholder + if (obj.options.placeholder) { + obj.editor.setAttribute('data-placeholder', obj.options.placeholder); + } + + // Max height + if (obj.options.maxHeight || obj.options.height) { + obj.editor.style.overflowY = 'auto'; + + if (obj.options.maxHeight) { + obj.editor.style.maxHeight = obj.options.maxHeight; + } + if (obj.options.height) { + obj.editor.style.height = obj.options.height; + } + } + + // Set editor initial value + if (obj.options.url) { + jSuites.ajax({ + url: obj.options.url, + dataType: 'html', + success: function(result) { + obj.editor.innerHTML = result; + + jSuites.editor.setCursor(obj.editor, obj.options.focus == 'initial' ? true : false); + } + }) + } else { + if (obj.options.value) { + obj.editor.innerHTML = obj.options.value; + } else { + // Create from existing elements + for (var i = 0; i < el.children.length; i++) { + obj.editor.appendChild(el.children[i]); + } + } + } + + // Make sure element is empty + el.innerHTML = ''; + + /** + * Onchange event controllers + */ + var change = function(e) { + if (typeof(obj.options.onchange) == 'function') { + obj.options.onchange(el, obj, e); + } + + // Update value + obj.options.value = obj.getData(); + + // Lemonade JS + if (el.value != obj.options.value) { + el.value = obj.options.value; + if (typeof(el.oninput) == 'function') { + el.oninput({ + type: 'input', + target: el, + value: el.value + }); + } + } + } + + /** + * Extract images from a HTML string + */ + var extractImageFromHtml = function(html) { + // Create temp element + var div = document.createElement('div'); + div.innerHTML = html; + + // Extract images + var img = div.querySelectorAll('img'); + + if (img.length) { + for (var i = 0; i < img.length; i++) { + obj.addImage(img[i].src); + } + } + } + + /** + * Insert node at caret + */ + var insertNodeAtCaret = function(newNode) { + var sel, range; + + if (window.getSelection) { + sel = window.getSelection(); + if (sel.rangeCount) { + range = sel.getRangeAt(0); + var selectedText = range.toString(); + range.deleteContents(); + range.insertNode(newNode); + // move the cursor after element + range.setStartAfter(newNode); + range.setEndAfter(newNode); + sel.removeAllRanges(); + sel.addRange(range); + } + } + } + + var updateTotalImages = function() { + var o = null; + if (o = snippet.children[0]) { + // Make sure is a grid + if (! o.classList.contains('jslider-grid')) { + o.classList.add('jslider-grid'); + } + // Quantify of images + var number = o.children.length; + // Set the configuration of the grid + o.setAttribute('data-number', number > 4 ? 4 : number); + // Total of images inside the grid + if (number > 4) { + o.setAttribute('data-total', number - 4); + } else { + o.removeAttribute('data-total'); + } + } + } + + /** + * Append image to the snippet + */ + var appendImage = function(image) { + if (! snippet.innerHTML) { + obj.appendSnippet({}); + } + snippet.children[0].appendChild(image); + updateTotalImages(); + } + + /** + * Append snippet + * @Param object data + */ + obj.appendSnippet = function(data) { + // Reset snippet + snippet.innerHTML = ''; + + // Attributes + var a = [ 'image', 'title', 'description', 'host', 'url' ]; + + for (var i = 0; i < a.length; i++) { + var div = document.createElement('div'); + div.className = 'jsnippet-' + a[i]; + div.setAttribute('data-k', a[i]); + snippet.appendChild(div); + if (data[a[i]]) { + if (a[i] == 'image') { + if (! Array.isArray(data.image)) { + data.image = [ data.image ]; + } + for (var j = 0; j < data.image.length; j++) { + var img = document.createElement('img'); + img.src = data.image[j]; + div.appendChild(img); + } + } else { + div.innerHTML = data[a[i]]; + } + } + } + + obj.editor.appendChild(document.createElement('br')); + obj.editor.appendChild(snippet); + } + + /** + * Set editor value + */ + obj.setData = function(o) { + if (typeof(o) == 'object') { + obj.editor.innerHTML = o.content; + } else { + obj.editor.innerHTML = o; + } + + if (obj.options.focus) { + jSuites.editor.setCursor(obj.editor, true); + } + + // Reset files container + files = []; + } + + obj.getFiles = function() { + var f = obj.editor.querySelectorAll('.jfile'); + var d = []; + for (var i = 0; i < f.length; i++) { + if (files[f[i].src]) { + d.push(files[f[i].src]); + } + } + return d; + } + + obj.getText = function() { + return obj.editor.innerText; + } + + /** + * Get editor data + */ + obj.getData = function(json) { + if (! json) { + var data = obj.editor.innerHTML; + } else { + var data = { + content : '', + } + + // Get snippet + if (snippet.innerHTML) { + var index = 0; + data.snippet = {}; + for (var i = 0; i < snippet.children.length; i++) { + // Get key from element + var key = snippet.children[i].getAttribute('data-k'); + if (key) { + if (key == 'image') { + if (! data.snippet.image) { + data.snippet.image = []; + } + // Get all images + for (var j = 0; j < snippet.children[i].children.length; j++) { + data.snippet.image.push(snippet.children[i].children[j].getAttribute('src')) + } + } else { + data.snippet[key] = snippet.children[i].innerHTML; + } + } + } + } + + // Get files + var f = Object.keys(files); + if (f.length) { + data.files = []; + for (var i = 0; i < f.length; i++) { + data.files.push(files[f[i]]); + } + } + + // Get content + var d = document.createElement('div'); + d.innerHTML = obj.editor.innerHTML; + var s = d.querySelector('.jsnippet'); + if (s) { + s.remove(); + } + + var text = d.innerHTML; + text = text.replace(/
/g, "\n"); + text = text.replace(/<\/div>/g, "<\/div>\n"); + text = text.replace(/<(?:.|\n)*?>/gm, ""); + data.content = text.trim(); + + // Process extensions + processExtensions('getData', data); + } + + return data; + } + + // Reset + obj.reset = function() { + obj.editor.innerHTML = ''; + snippet.innerHTML = ''; + files = []; + } + + obj.addPdf = function(data) { + if (data.result.substr(0,4) != 'data') { + console.error('Invalid source'); + } else { + var canvas = document.createElement('canvas'); + canvas.width = 60; + canvas.height = 60; + + var img = new Image(); + var ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + + canvas.toBlob(function(blob) { + var newImage = document.createElement('img'); + newImage.src = window.URL.createObjectURL(blob); + newImage.title = data.name; + newImage.className = 'jfile pdf'; + + files[newImage.src] = { + file: newImage.src, + extension: 'pdf', + content: data.result, + } + + //insertNodeAtCaret(newImage); + document.execCommand('insertHtml', false, newImage.outerHTML); + }); + } + } + + obj.addImage = function(src, asSnippet) { + if (! src) { + src = ''; + } + + if (src.substr(0,4) != 'data' && ! obj.options.remoteParser) { + console.error('remoteParser not defined in your initialization'); + } else { + // This is to process cross domain images + if (src.substr(0,4) == 'data') { + var extension = src.split(';') + extension = extension[0].split('/'); + extension = extension[1]; + } else { + var extension = src.substr(src.lastIndexOf('.') + 1); + // Work for cross browsers + src = obj.options.remoteParser + src; + } + + var img = new Image(); + + img.onload = function onload() { + var canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + + var ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + + canvas.toBlob(function(blob) { + var newImage = document.createElement('img'); + newImage.src = window.URL.createObjectURL(blob); + newImage.classList.add('jfile'); + newImage.setAttribute('tabindex', '900'); + newImage.setAttribute('width', img.width); + newImage.setAttribute('height', img.height); + files[newImage.src] = { + file: newImage.src, + extension: extension, + content: canvas.toDataURL(), + } + + if (obj.options.dropAsSnippet || asSnippet) { + appendImage(newImage); + // Just to understand the attachment is part of a snippet + files[newImage.src].snippet = true; + } else { + //insertNodeAtCaret(newImage); + document.execCommand('insertHtml', false, newImage.outerHTML); + } + + change(); + }); + }; + + img.src = src; + } + } + + obj.addFile = function(files) { + var reader = []; + + for (var i = 0; i < files.length; i++) { + if (files[i].size > obj.options.maxFileSize) { + alert('The file is too big'); + } else { + // Only PDF or Images + var type = files[i].type.split('/'); + + if (type[0] == 'image') { + type = 1; + } else if (type[1] == 'pdf') { + type = 2; + } else { + type = 0; + } + + if (type) { + // Create file + reader[i] = new FileReader(); + reader[i].index = i; + reader[i].type = type; + reader[i].name = files[i].name; + reader[i].date = files[i].lastModified; + reader[i].size = files[i].size; + reader[i].addEventListener("load", function (data) { + // Get result + if (data.target.type == 2) { + if (obj.options.acceptFiles == true) { + obj.addPdf(data.target); + } + } else { + obj.addImage(data.target.result); + } + }, false); + + reader[i].readAsDataURL(files[i]) + } else { + alert('The extension is not allowed'); + } + } + } + } + + // Destroy + obj.destroy = function() { + obj.editor.removeEventListener('mouseup', editorMouseUp); + obj.editor.removeEventListener('mousedown', editorMouseDown); + obj.editor.removeEventListener('mousemove', editorMouseMove); + obj.editor.removeEventListener('keyup', editorKeyUp); + obj.editor.removeEventListener('keydown', editorKeyDown); + obj.editor.removeEventListener('dragstart', editorDragStart); + obj.editor.removeEventListener('dragenter', editorDragEnter); + obj.editor.removeEventListener('dragover', editorDragOver); + obj.editor.removeEventListener('drop', editorDrop); + obj.editor.removeEventListener('paste', editorPaste); + obj.editor.removeEventListener('blur', editorBlur); + obj.editor.removeEventListener('focus', editorFocus); + + el.editor = null; + el.classList.remove('jeditor-container'); + + toolbar.remove(); + snippet.remove(); + obj.editor.remove(); + } + + obj.upload = function() { + jSuites.click(obj.file); + } + + // Elements to be removed + var remove = [ + HTMLUnknownElement, + HTMLAudioElement, + HTMLEmbedElement, + HTMLIFrameElement, + HTMLTextAreaElement, + HTMLInputElement, + HTMLScriptElement + ]; + + // Valid properties + var validProperty = ['width', 'height', 'align', 'border', 'src', 'tabindex']; + + // Valid CSS attributes + var validStyle = ['color', 'font-weight', 'font-size', 'background', 'background-color', 'margin']; + + var parse = function(element) { + // Remove attributes + if (element.attributes && element.attributes.length) { + var image = null; + var style = null; + // Process style attribute + var elementStyle = element.getAttribute('style'); + if (elementStyle) { + style = []; + var t = elementStyle.split(';'); + for (var j = 0; j < t.length; j++) { + var v = t[j].trim().split(':'); + if (validStyle.indexOf(v[0].trim()) >= 0) { + var k = v.shift(); + var v = v.join(':'); + style.push(k + ':' + v); + } + } + } + // Process image + if (element.tagName.toUpperCase() == 'IMG') { + if (! obj.options.acceptImages || ! element.src) { + element.parentNode.removeChild(element); + } else { + // Check if is data + element.setAttribute('tabindex', '900'); + // Check attributes for persistance + obj.addImage(element.src); + } + } + // Remove attributes + var attr = []; + var numAttributes = element.attributes.length - 1; + if (numAttributes > 0) { + for (var i = numAttributes; i >= 0 ; i--) { + attr.push(element.attributes[i].name); + } + attr.forEach(function(v) { + if (validProperty.indexOf(v) == -1) { + element.removeAttribute(v); + } + }); + } + element.style = ''; + // Add valid style + if (style && style.length) { + element.setAttribute('style', style.join(';')); + } + } + // Parse children + if (element.children.length) { + for (var i = 0; i < element.children.length; i++) { + parse(element.children[i]); + } + } + + if (remove.indexOf(element.constructor) >= 0) { + element.remove(); + } + } + + var select = function(e) { + var s = window.getSelection() + var r = document.createRange(); + r.selectNode(e); + s.addRange(r) + } + + var filter = function(data) { + if (data) { + data = data.replace(new RegExp('', 'gsi'), ''); + } + var parser = new DOMParser(); + var d = parser.parseFromString(data, "text/html"); + parse(d); + var span = document.createElement('span'); + span.innerHTML = d.firstChild.innerHTML; + return span; + } + + var editorPaste = function(e) { + if (obj.options.filterPaste == true) { + if (e.clipboardData || e.originalEvent.clipboardData) { + var html = (e.originalEvent || e).clipboardData.getData('text/html'); + var text = (e.originalEvent || e).clipboardData.getData('text/plain'); + var file = (e.originalEvent || e).clipboardData.files + } else if (window.clipboardData) { + var html = window.clipboardData.getData('Html'); + var text = window.clipboardData.getData('Text'); + var file = window.clipboardData.files + } + + if (file.length) { + // Paste a image from the clipboard + obj.addFile(file); + } else { + if (! html) { + html = text.split('\r\n'); + if (! e.target.innerText) { + html.map(function(v) { + var d = document.createElement('div'); + d.innerText = v; + obj.editor.appendChild(d); + }); + } else { + html = html.map(function(v) { + return '
' + v + '
'; + }); + document.execCommand('insertHtml', false, html.join('')); + } + } else { + var d = filter(html); + // Paste to the editor + //insertNodeAtCaret(d); + document.execCommand('insertHtml', false, d.innerHTML); + } + } + + e.preventDefault(); + } + } + + var editorDragStart = function(e) { + if (editorAction && editorAction.e) { + e.preventDefault(); + } + } + + var editorDragEnter = function(e) { + if (editorAction || obj.options.dropZone == false) { + // Do nothing + } else { + el.classList.add('jeditor-dragging'); + e.preventDefault(); + } + } + + var editorDragOver = function(e) { + if (editorAction || obj.options.dropZone == false) { + // Do nothing + } else { + if (editorTimer) { + clearTimeout(editorTimer); + } + + editorTimer = setTimeout(function() { + el.classList.remove('jeditor-dragging'); + }, 100); + e.preventDefault(); + } + } + + var editorDrop = function(e) { + if (editorAction || obj.options.dropZone == false) { + // Do nothing + } else { + // Position caret on the drop + var range = null; + if (document.caretRangeFromPoint) { + range=document.caretRangeFromPoint(e.clientX, e.clientY); + } else if (e.rangeParent) { + range=document.createRange(); + range.setStart(e.rangeParent,e.rangeOffset); + } + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + sel.anchorNode.parentNode.focus(); + + var html = (e.originalEvent || e).dataTransfer.getData('text/html'); + var text = (e.originalEvent || e).dataTransfer.getData('text/plain'); + var file = (e.originalEvent || e).dataTransfer.files; + + if (file.length) { + obj.addFile(file); + } else if (text) { + extractImageFromHtml(html); + } + + el.classList.remove('jeditor-dragging'); + e.preventDefault(); + } + } + + var editorBlur = function(e) { + // Process extensions + processExtensions('onevent', e); + // Apply changes + change(e); + // Blur + if (typeof(obj.options.onblur) == 'function') { + obj.options.onblur(el, obj, e); + } + } + + var editorFocus = function(e) { + // Focus + if (typeof(obj.options.onfocus) == 'function') { + obj.options.onfocus(el, obj, e); + } + } + + var editorKeyUp = function(e) { + if (! obj.editor.innerHTML) { + obj.editor.innerHTML = '

'; + } + if (typeof(obj.options.onkeyup) == 'function') { + obj.options.onkeyup(el, obj, e); + } + } + + var editorKeyDown = function(e) { + // Process extensions + processExtensions('onevent', e); + + if (e.key == 'Delete') { + if (e.target.tagName == 'IMG') { + var parent = e.target.parentNode; + select(e.target); + if (parent.classList.contains('jsnippet-image')) { + updateTotalImages(); + } + } + } + + if (typeof(obj.options.onkeydown) == 'function') { + obj.options.onkeydown(el, obj, e); + } + } + + var editorMouseUp = function(e) { + if (editorAction && editorAction.e) { + editorAction.e.classList.remove('resizing'); + + if (editorAction.e.changed == true) { + var image = editorAction.e.cloneNode() + image.width = parseInt(editorAction.e.style.width) || editorAction.e.getAttribute('width'); + image.height = parseInt(editorAction.e.style.height) || editorAction.e.getAttribute('height'); + editorAction.e.style.width = ''; + editorAction.e.style.height = ''; + select(editorAction.e); + document.execCommand('insertHtml', false, image.outerHTML); + } + } + + editorAction = false; + } + + var editorMouseDown = function(e) { + var close = function(snippet) { + var rect = snippet.getBoundingClientRect(); + if (rect.width - (e.clientX - rect.left) < 40 && e.clientY - rect.top < 40) { + snippet.innerHTML = ''; + snippet.remove(); + } + } + + if (e.target.tagName == 'IMG') { + if (e.target.style.cursor) { + var rect = e.target.getBoundingClientRect(); + editorAction = { + e: e.target, + x: e.clientX, + y: e.clientY, + w: rect.width, + h: rect.height, + d: e.target.style.cursor, + } + + if (! e.target.getAttribute('width')) { + e.target.setAttribute('width', rect.width) + } + + if (! e.target.getAttribute('height')) { + e.target.setAttribute('height', rect.height) + } + + var s = window.getSelection(); + if (s.rangeCount) { + for (var i = 0; i < s.rangeCount; i++) { + s.removeRange(s.getRangeAt(i)); + } + } + + e.target.classList.add('resizing'); + } else { + editorAction = true; + } + } else { + if (e.target.classList.contains('jsnippet')) { + close(e.target); + } else if (e.target.parentNode.classList.contains('jsnippet')) { + close(e.target.parentNode); + } + + editorAction = true; + } + } + + var editorMouseMove = function(e) { + if (e.target.tagName == 'IMG' && ! e.target.parentNode.classList.contains('jsnippet-image') && obj.options.allowImageResize == true) { + if (e.target.getAttribute('tabindex')) { + var rect = e.target.getBoundingClientRect(); + if (e.clientY - rect.top < 5) { + if (rect.width - (e.clientX - rect.left) < 5) { + e.target.style.cursor = 'ne-resize'; + } else if (e.clientX - rect.left < 5) { + e.target.style.cursor = 'nw-resize'; + } else { + e.target.style.cursor = 'n-resize'; + } + } else if (rect.height - (e.clientY - rect.top) < 5) { + if (rect.width - (e.clientX - rect.left) < 5) { + e.target.style.cursor = 'se-resize'; + } else if (e.clientX - rect.left < 5) { + e.target.style.cursor = 'sw-resize'; + } else { + e.target.style.cursor = 's-resize'; + } + } else if (rect.width - (e.clientX - rect.left) < 5) { + e.target.style.cursor = 'e-resize'; + } else if (e.clientX - rect.left < 5) { + e.target.style.cursor = 'w-resize'; + } else { + e.target.style.cursor = ''; + } + } + } + + // Move + if (e.which == 1 && editorAction && editorAction.d) { + if (editorAction.d == 'e-resize' || editorAction.d == 'ne-resize' || editorAction.d == 'se-resize') { + editorAction.e.style.width = (editorAction.w + (e.clientX - editorAction.x)); + + if (e.shiftKey) { + var newHeight = (e.clientX - editorAction.x) * (editorAction.h / editorAction.w); + editorAction.e.style.height = editorAction.h + newHeight; + } else { + var newHeight = null; + } + } + + if (! newHeight) { + if (editorAction.d == 's-resize' || editorAction.d == 'se-resize' || editorAction.d == 'sw-resize') { + if (! e.shiftKey) { + editorAction.e.style.height = editorAction.h + (e.clientY - editorAction.y); + } + } + } + + editorAction.e.changed = true; + } + } + + var processExtensions = function(method, data) { + if (obj.options.extensions) { + var ext = Object.keys(obj.options.extensions); + if (ext.length) { + for (var i = 0; i < ext.length; i++) + if (obj.options.extensions[ext[i]] && typeof(obj.options.extensions[ext[i]][method]) == 'function') { + obj.options.extensions[ext[i]][method].call(obj, data); + } + } + } + } + + var loadExtensions = function() { + if (obj.options.extensions) { + var ext = Object.keys(obj.options.extensions); + if (ext.length) { + for (var i = 0; i < ext.length; i++) { + if (obj.options.extensions[ext[i]] && typeof (obj.options.extensions[ext[i]]) == 'function') { + obj.options.extensions[ext[i]] = obj.options.extensions[ext[i]](el, obj); + } + } + } + } + } + + document.addEventListener('mouseup', editorMouseUp); + document.addEventListener('mousemove', editorMouseMove); + obj.editor.addEventListener('mousedown', editorMouseDown); + obj.editor.addEventListener('keyup', editorKeyUp); + obj.editor.addEventListener('keydown', editorKeyDown); + obj.editor.addEventListener('dragstart', editorDragStart); + obj.editor.addEventListener('dragenter', editorDragEnter); + obj.editor.addEventListener('dragover', editorDragOver); + obj.editor.addEventListener('drop', editorDrop); + obj.editor.addEventListener('paste', editorPaste); + obj.editor.addEventListener('focus', editorFocus); + obj.editor.addEventListener('blur', editorBlur); + + // Append editor to the container + el.appendChild(obj.editor); + // Snippet + if (obj.options.snippet) { + obj.appendSnippet(obj.options.snippet); + } + + // Add toolbar + if (obj.options.toolbar) { + // Default toolbar configuration + if (Array.isArray(obj.options.toolbar)) { + var toolbarOptions = { + container: true, + responsive: true, + items: obj.options.toolbar + } + } else if (obj.options.toolbar === true) { + var toolbarOptions = { + container: true, + responsive: true, + items: [], + } + } else { + var toolbarOptions = obj.options.toolbar; + } + + // Default items + if (! (toolbarOptions.items && toolbarOptions.items.length)) { + toolbarOptions.items = jSuites.editor.getDefaultToolbar(obj); + } + + if (obj.options.toolbarOnTop) { + // Add class + el.classList.add('toolbar-on-top'); + // Append to the DOM + el.insertBefore(toolbar, el.firstChild); + } else { + // Add padding to the editor + obj.editor.style.padding = '15px'; + // Append to the DOM + el.appendChild(toolbar); + } + + // Create toolbar + jSuites.toolbar(toolbar, toolbarOptions); + + toolbar.addEventListener('click', function() { + obj.editor.focus(); + }) + } + + // Upload file + obj.file = document.createElement('input'); + obj.file.style.display = 'none'; + obj.file.type = 'file'; + obj.file.setAttribute('accept', 'image/*'); + obj.file.onchange = function() { + obj.addFile(this.files); + } + el.appendChild(obj.file); + + // Focus to the editor + if (obj.editor.innerHTML && obj.options.focus) { + jSuites.editor.setCursor(obj.editor, obj.options.focus == 'initial' ? true : false); + } + + // Change method + el.change = obj.setData; + + // Global generic value handler + el.val = function(val) { + if (val === undefined) { + // Data type + var o = el.getAttribute('data-html') === 'true' ? false : true; + return obj.getData(o); + } else { + obj.setData(val); + } + } + + loadExtensions(); + + el.editor = obj; + + // Onload + if (typeof(obj.options.onload) == 'function') { + obj.options.onload(el, obj, obj.editor); + } + + return obj; +}); + +jSuites.editor.setCursor = function(element, first) { + element.focus(); + document.execCommand('selectAll'); + var sel = window.getSelection(); + var range = sel.getRangeAt(0); + if (first == true) { + var node = range.startContainer; + var size = 0; + } else { + var node = range.endContainer; + var size = node.length; + } + range.setStart(node, size); + range.setEnd(node, size); + sel.removeAllRanges(); + sel.addRange(range); +} + +jSuites.editor.getDefaultToolbar = function(obj) { + + var color = function(a,b,c) { + if (! c.color) { + var t = null; + var colorPicker = jSuites.color(c, { + onchange: function(o, v) { + if (c.k === 'color') { + document.execCommand('foreColor', false, v); + } else { + document.execCommand('backColor', false, v); + } + } + }); + c.color.open(); + } + } + + var items = []; + + items.push({ + content: 'undo', + onclick: function() { + document.execCommand('undo'); + } + }); + + items.push({ + content: 'redo', + onclick: function() { + document.execCommand('redo'); + } + }); + + items.push({ + type: 'divisor' + }); + + if (obj.options.toolbarOnTop) { + items.push({ + type: 'select', + width: '140px', + options: ['Default', 'Verdana', 'Arial', 'Courier New'], + render: function (e) { + return '' + e + ''; + }, + onchange: function (a,b,c,d,e) { + document.execCommand("fontName", false, d); + } + }); + + items.push({ + type: 'select', + content: 'format_size', + options: ['x-small', 'small', 'medium', 'large', 'x-large'], + render: function (e) { + return '' + e + ''; + }, + onchange: function (a,b,c,d,e) { + //var html = `${text}`; + //document.execCommand('insertHtml', false, html); + document.execCommand("fontSize", false, parseInt(e)+1); + //var f = window.getSelection().anchorNode.parentNode + //f.removeAttribute("size"); + //f.style.fontSize = d; + } + }); + + items.push({ + type: 'select', + options: ['format_align_left', 'format_align_center', 'format_align_right', 'format_align_justify'], + render: function (e) { + return '' + e + ''; + }, + onchange: function (a,b,c,d,e) { + var options = ['JustifyLeft','justifyCenter','justifyRight','justifyFull']; + document.execCommand(options[e]); + } + }); + + items.push({ + type: 'divisor' + }); + + items.push({ + content: 'format_color_text', + k: 'color', + onclick: color, + }); + + items.push({ + content: 'format_color_fill', + k: 'background-color', + onclick: color, + }); + } + + items.push({ + content: 'format_bold', + onclick: function(a,b,c) { + document.execCommand('bold'); + + if (document.queryCommandState("bold")) { + c.classList.add('selected'); + } else { + c.classList.remove('selected'); + } + } + }); + + items.push({ + content: 'format_italic', + onclick: function(a,b,c) { + document.execCommand('italic'); + + if (document.queryCommandState("italic")) { + c.classList.add('selected'); + } else { + c.classList.remove('selected'); + } + } + }); + + items.push({ + content: 'format_underline', + onclick: function(a,b,c) { + document.execCommand('underline'); + + if (document.queryCommandState("underline")) { + c.classList.add('selected'); + } else { + c.classList.remove('selected'); + } + } + }); + + items.push({ + type:'divisor' + }); + + items.push({ + content: 'format_list_bulleted', + onclick: function(a,b,c) { + document.execCommand('insertUnorderedList'); + + if (document.queryCommandState("insertUnorderedList")) { + c.classList.add('selected'); + } else { + c.classList.remove('selected'); + } + } + }); + + items.push({ + content: 'format_list_numbered', + onclick: function(a,b,c) { + document.execCommand('insertOrderedList'); + + if (document.queryCommandState("insertOrderedList")) { + c.classList.add('selected'); + } else { + c.classList.remove('selected'); + } + } + }); + + items.push({ + content: 'format_indent_increase', + onclick: function(a,b,c) { + document.execCommand('indent', true, null); + + if (document.queryCommandState("indent")) { + c.classList.add('selected'); + } else { + c.classList.remove('selected'); + } + } + }); + + items.push({ + content: 'format_indent_decrease', + onclick: function() { + document.execCommand('outdent'); + + if (document.queryCommandState("outdent")) { + this.classList.add('selected'); + } else { + this.classList.remove('selected'); + } + } + }); + + if (obj.options.toolbarOnTop) { + items.push({ + type: 'divisor' + }); + + items.push({ + content: 'photo', + onclick: function () { + obj.upload(); + } + }); + + items.push({ + type: 'select', + content: 'table_view', + columns: 10, + right: true, + options: [ + '0x0', '1x0', '2x0', '3x0', '4x0', '5x0', '6x0', '7x0', '8x0', '9x0', + '0x1', '1x1', '2x1', '3x1', '4x1', '5x1', '6x1', '7x1', '8x1', '9x1', + '0x2', '1x2', '2x2', '3x2', '4x2', '5x2', '6x2', '7x2', '8x2', '9x2', + '0x3', '1x3', '2x3', '3x3', '4x3', '5x3', '6x3', '7x3', '8x3', '9x3', + '0x4', '1x4', '2x4', '3x4', '4x4', '5x4', '6x4', '7x4', '8x4', '9x4', + '0x5', '1x5', '2x5', '3x5', '4x5', '5x5', '6x5', '7x5', '8x5', '9x5', + '0x6', '1x6', '2x6', '3x6', '4x6', '5x6', '6x6', '7x6', '8x6', '9x6', + '0x7', '1x7', '2x7', '3x7', '4x7', '5x7', '6x7', '7x7', '8x7', '9x7', + '0x8', '1x8', '2x8', '3x8', '4x8', '5x8', '6x8', '7x8', '8x8', '9x8', + '0x9', '1x9', '2x9', '3x9', '4x9', '5x9', '6x9', '7x9', '8x9', '9x9', + ], + render: function (e, item) { + if (item) { + item.onmouseover = this.onmouseover; + e = e.split('x'); + item.setAttribute('data-x', e[0]); + item.setAttribute('data-y', e[1]); + } + var element = document.createElement('div'); + item.style.margin = '1px'; + item.style.border = '1px solid #ddd'; + return element; + }, + onmouseover: function (e) { + var x = parseInt(e.target.getAttribute('data-x')); + var y = parseInt(e.target.getAttribute('data-y')); + for (var i = 0; i < e.target.parentNode.children.length; i++) { + var element = e.target.parentNode.children[i]; + var ex = parseInt(element.getAttribute('data-x')); + var ey = parseInt(element.getAttribute('data-y')); + if (ex <= x && ey <= y) { + element.style.backgroundColor = '#cae1fc'; + element.style.borderColor = '#2977ff'; + } else { + element.style.backgroundColor = ''; + element.style.borderColor = '#ddd'; + } + } + }, + onchange: function (a, b, c) { + c = c.split('x'); + var table = document.createElement('table'); + var tbody = document.createElement('tbody'); + for (var y = 0; y <= c[1]; y++) { + var tr = document.createElement('tr'); + for (var x = 0; x <= c[0]; x++) { + var td = document.createElement('td'); + td.innerHTML = ''; + tr.appendChild(td); + } + tbody.appendChild(tr); + } + table.appendChild(tbody); + table.setAttribute('width', '100%'); + table.setAttribute('cellpadding', '6'); + table.setAttribute('cellspacing', '0'); + document.execCommand('insertHTML', false, table.outerHTML); + } + }); + } + + return items; +} + + +jSuites.form = (function(el, options) { + var obj = {}; + obj.options = {}; + + // Default configuration + var defaults = { + url: null, + message: 'Are you sure? There are unsaved information in your form', + ignore: false, + currentHash: null, + submitButton:null, + validations: null, + onbeforeload: null, + onload: null, + onbeforesave: null, + onsave: null, + onbeforeremove: null, + onremove: null, + onerror: function(el, message) { + jSuites.alert(message); + } + }; + + // Loop through our object + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + obj.options[property] = defaults[property]; + } + } + + // Validations + if (! obj.options.validations) { + obj.options.validations = {}; + } + + // Submit Button + if (! obj.options.submitButton) { + obj.options.submitButton = el.querySelector('input[type=submit]'); + } + + if (obj.options.submitButton && obj.options.url) { + obj.options.submitButton.onclick = function() { + obj.save(); + } + } + + if (! obj.options.validations.email) { + obj.options.validations.email = jSuites.validations.email; + } + + if (! obj.options.validations.length) { + obj.options.validations.length = jSuites.validations.length; + } + + if (! obj.options.validations.required) { + obj.options.validations.required = jSuites.validations.required; + } + + obj.setUrl = function(url) { + obj.options.url = url; + } + + obj.load = function() { + jSuites.ajax({ + url: obj.options.url, + method: 'GET', + dataType: 'json', + queue: true, + success: function(data) { + // Overwrite values from the backend + if (typeof(obj.options.onbeforeload) == 'function') { + var ret = obj.options.onbeforeload(el, data); + if (ret) { + data = ret; + } + } + // Apply values to the form + jSuites.form.setElements(el, data); + // Onload methods + if (typeof(obj.options.onload) == 'function') { + obj.options.onload(el, data); + } + } + }); + } + + obj.save = function() { + var test = obj.validate(); + + if (test) { + obj.options.onerror(el, test); + } else { + var data = jSuites.form.getElements(el, true); + + if (typeof(obj.options.onbeforesave) == 'function') { + var data = obj.options.onbeforesave(el, data); + + if (data === false) { + return; + } + } + + jSuites.ajax({ + url: obj.options.url, + method: 'POST', + dataType: 'json', + data: data, + success: function(result) { + if (typeof(obj.options.onsave) == 'function') { + obj.options.onsave(el, data, result); + } + } + }); + } + } + + obj.remove = function() { + if (typeof(obj.options.onbeforeremove) == 'function') { + var ret = obj.options.onbeforeremove(el, obj); + if (ret === false) { + return false; + } + } + + jSuites.ajax({ + url: obj.options.url, + method: 'DELETE', + dataType: 'json', + success: function(result) { + if (typeof(obj.options.onremove) == 'function') { + obj.options.onremove(el, obj, result); + } + + obj.reset(); + } + }); + } + + var addError = function(element) { + // Add error in the element + element.classList.add('error'); + // Submit button + if (obj.options.submitButton) { + obj.options.submitButton.setAttribute('disabled', true); + } + // Return error message + var error = element.getAttribute('data-error') || 'There is an error in the form'; + element.setAttribute('title', error); + return error; + } + + var delError = function(element) { + var error = false; + // Remove class from this element + element.classList.remove('error'); + element.removeAttribute('title'); + // Get elements in the form + var elements = el.querySelectorAll("input, select, textarea, div[name]"); + // Run all elements + for (var i = 0; i < elements.length; i++) { + if (elements[i].getAttribute('data-validation')) { + if (elements[i].classList.contains('error')) { + error = true; + } + } + } + + if (obj.options.submitButton) { + if (error) { + obj.options.submitButton.setAttribute('disabled', true); + } else { + obj.options.submitButton.removeAttribute('disabled'); + } + } + } + + obj.validateElement = function(element) { + // Test results + var test = false; + // Value + var value = jSuites.form.getValue(element); + // Validation + var validation = element.getAttribute('data-validation'); + // Parse + if (typeof(obj.options.validations[validation]) == 'function' && ! obj.options.validations[validation](value, element)) { + // Not passed in the test + test = addError(element); + } else { + if (element.classList.contains('error')) { + delError(element); + } + } + + return test; + } + + obj.reset = function() { + // Get elements in the form + var name = null; + var elements = el.querySelectorAll("input, select, textarea, div[name]"); + // Run all elements + for (var i = 0; i < elements.length; i++) { + if (name = elements[i].getAttribute('name')) { + if (elements[i].type == 'checkbox' || elements[i].type == 'radio') { + elements[i].checked = false; + } else { + if (typeof(elements[i].val) == 'function') { + elements[i].val(''); + } else { + elements[i].value = ''; + } + } + } + } + } + + // Run form validation + obj.validate = function() { + var test = []; + // Get elements in the form + var elements = el.querySelectorAll("input, select, textarea, div[name]"); + // Run all elements + for (var i = 0; i < elements.length; i++) { + // Required + if (elements[i].getAttribute('data-validation')) { + var res = obj.validateElement(elements[i]); + if (res) { + test.push(res); + } + } + } + if (test.length > 0) { + return test.join('
'); + } else { + return false; + } + } + + // Check the form + obj.getError = function() { + // Validation + return obj.validation() ? true : false; + } + + // Return the form hash + obj.setHash = function() { + return obj.getHash(jSuites.form.getElements(el)); + } + + // Get the form hash + obj.getHash = function(str) { + var hash = 0, i, chr; + + if (str.length === 0) { + return hash; + } else { + for (i = 0; i < str.length; i++) { + chr = str.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; + } + } + + return hash; + } + + // Is there any change in the form since start tracking? + obj.isChanged = function() { + var hash = obj.setHash(); + return (obj.options.currentHash != hash); + } + + // Restart tracking + obj.resetTracker = function() { + obj.options.currentHash = obj.setHash(); + obj.options.ignore = false; + } + + // Ignore flag + obj.setIgnore = function(ignoreFlag) { + obj.options.ignore = ignoreFlag ? true : false; + } + + // Start tracking in one second + setTimeout(function() { + obj.options.currentHash = obj.setHash(); + }, 1000); + + // Validations + el.addEventListener("keyup", function(e) { + if (e.target.getAttribute('data-validation')) { + obj.validateElement(e.target); + } + }); + + // Alert + if (! jSuites.form.hasEvents) { + window.addEventListener("beforeunload", function (e) { + if (obj.isChanged() && obj.options.ignore == false) { + var confirmationMessage = obj.options.message? obj.options.message : "\o/"; + + if (confirmationMessage) { + if (typeof e == 'undefined') { + e = window.event; + } + + if (e) { + e.returnValue = confirmationMessage; + } + + return confirmationMessage; + } else { + return void(0); + } + } + }); + + jSuites.form.hasEvents = true; + } + + el.form = obj; + + return obj; +}); + +// Get value from one element +jSuites.form.getValue = function(element) { + var value = null; + if (element.type == 'checkbox') { + if (element.checked == true) { + value = element.value || true; + } + } else if (element.type == 'radio') { + if (element.checked == true) { + value = element.value; + } + } else if (element.type == 'file') { + value = element.files; + } else if (element.tagName == 'select' && element.multiple == true) { + value = []; + var options = element.querySelectorAll("options[selected]"); + for (var j = 0; j < options.length; j++) { + value.push(options[j].value); + } + } else if (typeof(element.val) == 'function') { + value = element.val(); + } else { + value = element.value || ''; + } + + return value; +} + +// Get form elements +jSuites.form.getElements = function(el, asArray) { + var data = {}; + var name = null; + var elements = el.querySelectorAll("input, select, textarea, div[name]"); + + for (var i = 0; i < elements.length; i++) { + if (name = elements[i].getAttribute('name')) { + data[name] = jSuites.form.getValue(elements[i]) || ''; + } + } + + return asArray == true ? data : JSON.stringify(data); +} + +//Get form elements +jSuites.form.setElements = function(el, data) { + var name = null; + var value = null; + var elements = el.querySelectorAll("input, select, textarea, div[name]"); + for (var i = 0; i < elements.length; i++) { + // Attributes + var type = elements[i].getAttribute('type'); + if (name = elements[i].getAttribute('name')) { + // Transform variable names in pathname + name = name.replace(new RegExp(/\[(.*?)\]/ig), '.$1'); + value = null; + // Seach for the data in the path + if (name.match(/\./)) { + var tmp = jSuites.path.call(data, name) || ''; + if (typeof(tmp) !== 'undefined') { + value = tmp; + } + } else { + if (typeof(data[name]) !== 'undefined') { + value = data[name]; + } + } + // Set the values + if (value !== null) { + if (type == 'checkbox' || type == 'radio') { + elements[i].checked = value ? true : false; + } else if (type == 'file') { + // Do nothing + } else { + if (typeof (elements[i].val) == 'function') { + elements[i].val(value); + } else { + elements[i].value = value; + } + } + } + } + } +} + +// Legacy +jSuites.tracker = jSuites.form; + +jSuites.focus = function(el) { + if (el.innerText.length) { + var range = document.createRange(); + var sel = window.getSelection(); + var node = el.childNodes[el.childNodes.length-1]; + range.setStart(node, node.length) + range.collapse(true) + sel.removeAllRanges() + sel.addRange(range) + el.scrollLeft = el.scrollWidth; + } +} + +jSuites.isNumeric = (function (num) { + if (typeof(num) === 'string') { + num = num.trim(); + } + return !isNaN(num) && num !== null && num !== ''; +}); + +jSuites.guid = function() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} + +jSuites.getNode = function() { + var node = document.getSelection().anchorNode; + if (node) { + return (node.nodeType == 3 ? node.parentNode : node); + } else { + return null; + } +} +/** + * Generate hash from a string + */ +jSuites.hash = function(str) { + var hash = 0, i, chr; + + if (str.length === 0) { + return hash; + } else { + for (i = 0; i < str.length; i++) { + chr = str.charCodeAt(i); + if (chr > 32) { + hash = ((hash << 5) - hash) + chr; + hash |= 0; + } + } + } + return hash; +} + +/** + * Generate a random color + */ +jSuites.randomColor = function(h) { + var lum = -0.25; + var hex = String('#' + Math.random().toString(16).slice(2, 8).toUpperCase()).replace(/[^0-9a-f]/gi, ''); + if (hex.length < 6) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; + } + var rgb = [], c, i; + for (i = 0; i < 3; i++) { + c = parseInt(hex.substr(i * 2, 2), 16); + c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16); + rgb.push(("00" + c).substr(c.length)); + } + + // Return hex + if (h == true) { + return '#' + jSuites.two(rgb[0].toString(16)) + jSuites.two(rgb[1].toString(16)) + jSuites.two(rgb[2].toString(16)); + } + + return rgb; +} + +jSuites.getWindowWidth = function() { + var w = window, + d = document, + e = d.documentElement, + g = d.getElementsByTagName('body')[0], + x = w.innerWidth || e.clientWidth || g.clientWidth; + return x; +} + +jSuites.getWindowHeight = function() { + var w = window, + d = document, + e = d.documentElement, + g = d.getElementsByTagName('body')[0], + y = w.innerHeight|| e.clientHeight|| g.clientHeight; + return y; +} + +jSuites.getPosition = function(e) { + if (e.changedTouches && e.changedTouches[0]) { + var x = e.changedTouches[0].pageX; + var y = e.changedTouches[0].pageY; + } else { + var x = (window.Event) ? e.pageX : e.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft); + var y = (window.Event) ? e.pageY : e.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop); + } + + return [ x, y ]; +} + +jSuites.click = function(el) { + if (el.click) { + el.click(); + } else { + var evt = new MouseEvent('click', { + bubbles: true, + cancelable: true, + view: window + }); + el.dispatchEvent(evt); + } +} + +jSuites.findElement = function(element, condition) { + var foundElement = false; + + function path (element) { + if (element && ! foundElement) { + if (typeof(condition) == 'function') { + foundElement = condition(element) + } else if (typeof(condition) == 'string') { + if (element.classList && element.classList.contains(condition)) { + foundElement = element; + } + } + } + + if (element.parentNode && ! foundElement) { + path(element.parentNode); + } + } + + path(element); + + return foundElement; +} + +// Two digits +jSuites.two = function(value) { + value = '' + value; + if (value.length == 1) { + value = '0' + value; + } + return value; +} + +jSuites.sha512 = (function(str) { + function int64(msint_32, lsint_32) { + this.highOrder = msint_32; + this.lowOrder = lsint_32; + } + + var H = [new int64(0x6a09e667, 0xf3bcc908), new int64(0xbb67ae85, 0x84caa73b), + new int64(0x3c6ef372, 0xfe94f82b), new int64(0xa54ff53a, 0x5f1d36f1), + new int64(0x510e527f, 0xade682d1), new int64(0x9b05688c, 0x2b3e6c1f), + new int64(0x1f83d9ab, 0xfb41bd6b), new int64(0x5be0cd19, 0x137e2179)]; + + var K = [new int64(0x428a2f98, 0xd728ae22), new int64(0x71374491, 0x23ef65cd), + new int64(0xb5c0fbcf, 0xec4d3b2f), new int64(0xe9b5dba5, 0x8189dbbc), + new int64(0x3956c25b, 0xf348b538), new int64(0x59f111f1, 0xb605d019), + new int64(0x923f82a4, 0xaf194f9b), new int64(0xab1c5ed5, 0xda6d8118), + new int64(0xd807aa98, 0xa3030242), new int64(0x12835b01, 0x45706fbe), + new int64(0x243185be, 0x4ee4b28c), new int64(0x550c7dc3, 0xd5ffb4e2), + new int64(0x72be5d74, 0xf27b896f), new int64(0x80deb1fe, 0x3b1696b1), + new int64(0x9bdc06a7, 0x25c71235), new int64(0xc19bf174, 0xcf692694), + new int64(0xe49b69c1, 0x9ef14ad2), new int64(0xefbe4786, 0x384f25e3), + new int64(0x0fc19dc6, 0x8b8cd5b5), new int64(0x240ca1cc, 0x77ac9c65), + new int64(0x2de92c6f, 0x592b0275), new int64(0x4a7484aa, 0x6ea6e483), + new int64(0x5cb0a9dc, 0xbd41fbd4), new int64(0x76f988da, 0x831153b5), + new int64(0x983e5152, 0xee66dfab), new int64(0xa831c66d, 0x2db43210), + new int64(0xb00327c8, 0x98fb213f), new int64(0xbf597fc7, 0xbeef0ee4), + new int64(0xc6e00bf3, 0x3da88fc2), new int64(0xd5a79147, 0x930aa725), + new int64(0x06ca6351, 0xe003826f), new int64(0x14292967, 0x0a0e6e70), + new int64(0x27b70a85, 0x46d22ffc), new int64(0x2e1b2138, 0x5c26c926), + new int64(0x4d2c6dfc, 0x5ac42aed), new int64(0x53380d13, 0x9d95b3df), + new int64(0x650a7354, 0x8baf63de), new int64(0x766a0abb, 0x3c77b2a8), + new int64(0x81c2c92e, 0x47edaee6), new int64(0x92722c85, 0x1482353b), + new int64(0xa2bfe8a1, 0x4cf10364), new int64(0xa81a664b, 0xbc423001), + new int64(0xc24b8b70, 0xd0f89791), new int64(0xc76c51a3, 0x0654be30), + new int64(0xd192e819, 0xd6ef5218), new int64(0xd6990624, 0x5565a910), + new int64(0xf40e3585, 0x5771202a), new int64(0x106aa070, 0x32bbd1b8), + new int64(0x19a4c116, 0xb8d2d0c8), new int64(0x1e376c08, 0x5141ab53), + new int64(0x2748774c, 0xdf8eeb99), new int64(0x34b0bcb5, 0xe19b48a8), + new int64(0x391c0cb3, 0xc5c95a63), new int64(0x4ed8aa4a, 0xe3418acb), + new int64(0x5b9cca4f, 0x7763e373), new int64(0x682e6ff3, 0xd6b2b8a3), + new int64(0x748f82ee, 0x5defb2fc), new int64(0x78a5636f, 0x43172f60), + new int64(0x84c87814, 0xa1f0ab72), new int64(0x8cc70208, 0x1a6439ec), + new int64(0x90befffa, 0x23631e28), new int64(0xa4506ceb, 0xde82bde9), + new int64(0xbef9a3f7, 0xb2c67915), new int64(0xc67178f2, 0xe372532b), + new int64(0xca273ece, 0xea26619c), new int64(0xd186b8c7, 0x21c0c207), + new int64(0xeada7dd6, 0xcde0eb1e), new int64(0xf57d4f7f, 0xee6ed178), + new int64(0x06f067aa, 0x72176fba), new int64(0x0a637dc5, 0xa2c898a6), + new int64(0x113f9804, 0xbef90dae), new int64(0x1b710b35, 0x131c471b), + new int64(0x28db77f5, 0x23047d84), new int64(0x32caab7b, 0x40c72493), + new int64(0x3c9ebe0a, 0x15c9bebc), new int64(0x431d67c4, 0x9c100d4c), + new int64(0x4cc5d4be, 0xcb3e42b6), new int64(0x597f299c, 0xfc657e2a), + new int64(0x5fcb6fab, 0x3ad6faec), new int64(0x6c44198c, 0x4a475817)]; + + var W = new Array(64); + var a, b, c, d, e, f, g, h, i, j; + var T1, T2; + var charsize = 8; + + function utf8_encode(str) { + return unescape(encodeURIComponent(str)); + } + + function str2binb(str) { + var bin = []; + var mask = (1 << charsize) - 1; + var len = str.length * charsize; + + for (var i = 0; i < len; i += charsize) { + bin[i >> 5] |= (str.charCodeAt(i / charsize) & mask) << (32 - charsize - (i % 32)); + } + + return bin; + } + + function binb2hex(binarray) { + var hex_tab = "0123456789abcdef"; + var str = ""; + var length = binarray.length * 4; + var srcByte; + + for (var i = 0; i < length; i += 1) { + srcByte = binarray[i >> 2] >> ((3 - (i % 4)) * 8); + str += hex_tab.charAt((srcByte >> 4) & 0xF) + hex_tab.charAt(srcByte & 0xF); + } + + return str; + } + + function safe_add_2(x, y) { + var lsw, msw, lowOrder, highOrder; + + lsw = (x.lowOrder & 0xFFFF) + (y.lowOrder & 0xFFFF); + msw = (x.lowOrder >>> 16) + (y.lowOrder >>> 16) + (lsw >>> 16); + lowOrder = ((msw & 0xFFFF) << 16) | (lsw & 0xFFFF); + + lsw = (x.highOrder & 0xFFFF) + (y.highOrder & 0xFFFF) + (msw >>> 16); + msw = (x.highOrder >>> 16) + (y.highOrder >>> 16) + (lsw >>> 16); + highOrder = ((msw & 0xFFFF) << 16) | (lsw & 0xFFFF); + + return new int64(highOrder, lowOrder); + } + + function safe_add_4(a, b, c, d) { + var lsw, msw, lowOrder, highOrder; + + lsw = (a.lowOrder & 0xFFFF) + (b.lowOrder & 0xFFFF) + (c.lowOrder & 0xFFFF) + (d.lowOrder & 0xFFFF); + msw = (a.lowOrder >>> 16) + (b.lowOrder >>> 16) + (c.lowOrder >>> 16) + (d.lowOrder >>> 16) + (lsw >>> 16); + lowOrder = ((msw & 0xFFFF) << 16) | (lsw & 0xFFFF); + + lsw = (a.highOrder & 0xFFFF) + (b.highOrder & 0xFFFF) + (c.highOrder & 0xFFFF) + (d.highOrder & 0xFFFF) + (msw >>> 16); + msw = (a.highOrder >>> 16) + (b.highOrder >>> 16) + (c.highOrder >>> 16) + (d.highOrder >>> 16) + (lsw >>> 16); + highOrder = ((msw & 0xFFFF) << 16) | (lsw & 0xFFFF); + + return new int64(highOrder, lowOrder); + } + + function safe_add_5(a, b, c, d, e) { + var lsw, msw, lowOrder, highOrder; + + lsw = (a.lowOrder & 0xFFFF) + (b.lowOrder & 0xFFFF) + (c.lowOrder & 0xFFFF) + (d.lowOrder & 0xFFFF) + (e.lowOrder & 0xFFFF); + msw = (a.lowOrder >>> 16) + (b.lowOrder >>> 16) + (c.lowOrder >>> 16) + (d.lowOrder >>> 16) + (e.lowOrder >>> 16) + (lsw >>> 16); + lowOrder = ((msw & 0xFFFF) << 16) | (lsw & 0xFFFF); + + lsw = (a.highOrder & 0xFFFF) + (b.highOrder & 0xFFFF) + (c.highOrder & 0xFFFF) + (d.highOrder & 0xFFFF) + (e.highOrder & 0xFFFF) + (msw >>> 16); + msw = (a.highOrder >>> 16) + (b.highOrder >>> 16) + (c.highOrder >>> 16) + (d.highOrder >>> 16) + (e.highOrder >>> 16) + (lsw >>> 16); + highOrder = ((msw & 0xFFFF) << 16) | (lsw & 0xFFFF); + + return new int64(highOrder, lowOrder); + } + + function maj(x, y, z) { + return new int64( + (x.highOrder & y.highOrder) ^ (x.highOrder & z.highOrder) ^ (y.highOrder & z.highOrder), + (x.lowOrder & y.lowOrder) ^ (x.lowOrder & z.lowOrder) ^ (y.lowOrder & z.lowOrder) + ); + } + + function ch(x, y, z) { + return new int64( + (x.highOrder & y.highOrder) ^ (~x.highOrder & z.highOrder), + (x.lowOrder & y.lowOrder) ^ (~x.lowOrder & z.lowOrder) + ); + } + + function rotr(x, n) { + if (n <= 32) { + return new int64( + (x.highOrder >>> n) | (x.lowOrder << (32 - n)), + (x.lowOrder >>> n) | (x.highOrder << (32 - n)) + ); + } else { + return new int64( + (x.lowOrder >>> n) | (x.highOrder << (32 - n)), + (x.highOrder >>> n) | (x.lowOrder << (32 - n)) + ); + } + } + + function sigma0(x) { + var rotr28 = rotr(x, 28); + var rotr34 = rotr(x, 34); + var rotr39 = rotr(x, 39); + + return new int64( + rotr28.highOrder ^ rotr34.highOrder ^ rotr39.highOrder, + rotr28.lowOrder ^ rotr34.lowOrder ^ rotr39.lowOrder + ); + } + + function sigma1(x) { + var rotr14 = rotr(x, 14); + var rotr18 = rotr(x, 18); + var rotr41 = rotr(x, 41); + + return new int64( + rotr14.highOrder ^ rotr18.highOrder ^ rotr41.highOrder, + rotr14.lowOrder ^ rotr18.lowOrder ^ rotr41.lowOrder + ); + } + + function gamma0(x) { + var rotr1 = rotr(x, 1), rotr8 = rotr(x, 8), shr7 = shr(x, 7); + + return new int64( + rotr1.highOrder ^ rotr8.highOrder ^ shr7.highOrder, + rotr1.lowOrder ^ rotr8.lowOrder ^ shr7.lowOrder + ); + } + + function gamma1(x) { + var rotr19 = rotr(x, 19); + var rotr61 = rotr(x, 61); + var shr6 = shr(x, 6); + + return new int64( + rotr19.highOrder ^ rotr61.highOrder ^ shr6.highOrder, + rotr19.lowOrder ^ rotr61.lowOrder ^ shr6.lowOrder + ); + } + + function shr(x, n) { + if (n <= 32) { + return new int64( + x.highOrder >>> n, + x.lowOrder >>> n | (x.highOrder << (32 - n)) + ); + } else { + return new int64( + 0, + x.highOrder << (32 - n) + ); + } + } + + var str = utf8_encode(str); + var strlen = str.length*charsize; + str = str2binb(str); + + str[strlen >> 5] |= 0x80 << (24 - strlen % 32); + str[(((strlen + 128) >> 10) << 5) + 31] = strlen; + + for (var i = 0; i < str.length; i += 32) { + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + f = H[5]; + g = H[6]; + h = H[7]; + + for (var j = 0; j < 80; j++) { + if (j < 16) { + W[j] = new int64(str[j*2 + i], str[j*2 + i + 1]); + } else { + W[j] = safe_add_4(gamma1(W[j - 2]), W[j - 7], gamma0(W[j - 15]), W[j - 16]); + } + + T1 = safe_add_5(h, sigma1(e), ch(e, f, g), K[j], W[j]); + T2 = safe_add_2(sigma0(a), maj(a, b, c)); + h = g; + g = f; + f = e; + e = safe_add_2(d, T1); + d = c; + c = b; + b = a; + a = safe_add_2(T1, T2); + } + + H[0] = safe_add_2(a, H[0]); + H[1] = safe_add_2(b, H[1]); + H[2] = safe_add_2(c, H[2]); + H[3] = safe_add_2(d, H[3]); + H[4] = safe_add_2(e, H[4]); + H[5] = safe_add_2(f, H[5]); + H[6] = safe_add_2(g, H[6]); + H[7] = safe_add_2(h, H[7]); + } + + var binarray = []; + for (var i = 0; i < H.length; i++) { + binarray.push(H[i].highOrder); + binarray.push(H[i].lowOrder); + } + + return binb2hex(binarray); +}); + + +jSuites.image = jSuites.upload = (function(el, options) { + var obj = {}; + obj.options = {}; + + // Default configuration + var defaults = { + type: 'image', + extension: '*', + input: false, + minWidth: false, + maxWidth: null, + maxHeight: null, + maxJpegSizeBytes: null, // For example, 350Kb would be 350000 + onchange: null, + multiple: false, + remoteParser: null, + text:{ + extensionNotAllowed:'The extension is not allowed', + } + }; + + // Loop through our object + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + obj.options[property] = defaults[property]; + } + } + + // Multiple + if (obj.options.multiple == true) { + el.setAttribute('data-multiple', true); + } + + // Container + el.content = []; + + // Upload icon + el.classList.add('jupload'); + + if (obj.options.input == true) { + el.classList.add('input'); + } + + obj.add = function(data) { + // Reset container for single files + if (obj.options.multiple == false) { + el.content = []; + el.innerText = ''; + } + + // Append to the element + if (obj.options.type == 'image') { + var img = document.createElement('img'); + img.setAttribute('src', data.file); + img.setAttribute('tabindex', -1); + if (! el.getAttribute('name')) { + img.className = 'jfile'; + img.content = data; + } + el.appendChild(img); + } else { + if (data.name) { + var name = data.name; + } else { + var name = data.file; + } + var div = document.createElement('div'); + div.innerText = name || obj.options.type; + div.classList.add('jupload-item'); + div.setAttribute('tabindex', -1); + el.appendChild(div); + } + + if (data.content) { + data.file = jSuites.guid(); + } + + // Push content + el.content.push(data); + + // Onchange + if (typeof(obj.options.onchange) == 'function') { + obj.options.onchange(el, data); + } + } + + obj.addFromFile = function(file) { + var type = file.type.split('/'); + if (type[0] == obj.options.type) { + var readFile = new FileReader(); + readFile.addEventListener("load", function (v) { + var data = { + file: v.srcElement.result, + extension: file.name.substr(file.name.lastIndexOf('.') + 1), + name: file.name, + size: file.size, + lastmodified: file.lastModified, + content: v.srcElement.result, + } + + obj.add(data); + }); + + readFile.readAsDataURL(file); + } else { + alert(obj.options.text.extensionNotAllowed); + } + } + + obj.addFromUrl = function(src) { + if (src.substr(0,4) != 'data' && ! obj.options.remoteParser) { + console.error('remoteParser not defined in your initialization'); + } else { + // This is to process cross domain images + if (src.substr(0,4) == 'data') { + var extension = src.split(';') + extension = extension[0].split('/'); + var type = extension[0].replace('data:',''); + if (type == obj.options.type) { + var data = { + file: src, + name: '', + extension: extension[1], + content: src, + } + obj.add(data); + } else { + alert(obj.options.text.extensionNotAllowed); + } + } else { + var extension = src.substr(src.lastIndexOf('.') + 1); + // Work for cross browsers + src = obj.options.remoteParser + src; + // Get remove content + jSuites.ajax({ + url: src, + type: 'GET', + dataType: 'blob', + success: function(data) { + //add(extension[0].replace('data:',''), data); + } + }) + } + } + } + + var getDataURL = function(canvas, type) { + var compression = 0.92; + var lastContentLength = null; + var content = canvas.toDataURL(type, compression); + while (obj.options.maxJpegSizeBytes && type === 'image/jpeg' && + content.length > obj.options.maxJpegSizeBytes && content.length !== lastContentLength) { + // Apply the compression + compression *= 0.9; + lastContentLength = content.length; + content = canvas.toDataURL(type, compression); + } + return content; + } + + var mime = obj.options.type + '/' + obj.options.extension; + var input = document.createElement('input'); + input.type = 'file'; + input.setAttribute('accept', mime); + input.onchange = function() { + for (var i = 0; i < this.files.length; i++) { + obj.addFromFile(this.files[i]); + } + } + + // Allow multiple files + if (obj.options.multiple == true) { + input.setAttribute('multiple', true); + } + + var current = null; + + el.addEventListener("click", function(e) { + current = null; + if (! el.children.length || e.target === el) { + jSuites.click(input); + } else { + if (e.target.parentNode == el) { + current = e.target; + } + } + }); + + el.addEventListener("dblclick", function(e) { + jSuites.click(input); + }); + + el.addEventListener('dragenter', function(e) { + el.style.border = '1px dashed #000'; + }); + + el.addEventListener('dragleave', function(e) { + el.style.border = '1px solid #eee'; + }); + + el.addEventListener('dragstop', function(e) { + el.style.border = '1px solid #eee'; + }); + + el.addEventListener('dragover', function(e) { + e.preventDefault(); + }); + + el.addEventListener('keydown', function(e) { + if (current && e.which == 46) { + var index = Array.prototype.indexOf.call(el.children, current); + if (index >= 0) { + el.content.splice(index, 1); + current.remove(); + current = null; + } + } + }); + + el.addEventListener('drop', function(e) { + e.preventDefault(); + e.stopPropagation(); + + var html = (e.originalEvent || e).dataTransfer.getData('text/html'); + var file = (e.originalEvent || e).dataTransfer.files; + + if (file.length) { + for (var i = 0; i < e.dataTransfer.files.length; i++) { + obj.addFromFile(e.dataTransfer.files[i]); + } + } else if (html) { + if (obj.options.multiple == false) { + el.innerText = ''; + } + + // Create temp element + var div = document.createElement('div'); + div.innerHTML = html; + + // Extract images + var img = div.querySelectorAll('img'); + + if (img.length) { + for (var i = 0; i < img.length; i++) { + obj.addFromUrl(img[i].src); + } + } + } + + el.style.border = '1px solid #eee'; + + return false; + }); + + el.val = function(val) { + if (val === undefined) { + return el.content && el.content.length ? el.content : null; + } else { + // Reset + el.innerText = ''; + el.content = []; + + if (val) { + if (Array.isArray(val)) { + for (var i = 0; i < val.length; i++) { + if (typeof(val[i]) == 'string') { + obj.add({ file: val[i] }); + } else { + obj.add(val[i]); + } + } + } else if (typeof(val) == 'string') { + obj.add({ file: val }); + } + } + } + } + + el.upload = el.image = obj; + + return obj; +}); + +jSuites.image.create = function(data) { + var img = document.createElement('img'); + img.setAttribute('src', data.file); + img.className = 'jfile'; + img.setAttribute('tabindex', -1); + img.content = data; + + return img; +} + + +jSuites.lazyLoading = (function(el, options) { + var obj = {} + + // Mandatory options + if (! options.loadUp || typeof(options.loadUp) != 'function') { + options.loadUp = function() { + return false; + } + } + if (! options.loadDown || typeof(options.loadDown) != 'function') { + options.loadDown = function() { + return false; + } + } + // Timer ms + if (! options.timer) { + options.timer = 100; + } + + // Timer + var timeControlLoading = null; + + // Controls + var scrollControls = function(e) { + if (timeControlLoading == null) { + var event = false; + var scrollTop = el.scrollTop; + if (el.scrollTop + (el.clientHeight * 2) >= el.scrollHeight) { + if (options.loadDown()) { + if (scrollTop == el.scrollTop) { + el.scrollTop = el.scrollTop - (el.clientHeight); + } + event = true; + } + } else if (el.scrollTop <= el.clientHeight) { + if (options.loadUp()) { + if (scrollTop == el.scrollTop) { + el.scrollTop = el.scrollTop + (el.clientHeight); + } + event = true; + } + } + + timeControlLoading = setTimeout(function() { + timeControlLoading = null; + }, options.timer); + + if (event) { + if (typeof(options.onupdate) == 'function') { + options.onupdate(); + } + } + } + } + + // Onscroll + el.onscroll = function(e) { + scrollControls(e); + } + + el.onwheel = function(e) { + scrollControls(e); + } + + return obj; +}); + +jSuites.loading = (function() { + var obj = {}; + + var loading = null; + + obj.show = function(timeout) { + if (! loading) { + loading = document.createElement('div'); + loading.className = 'jloading'; + } + document.body.appendChild(loading); + + // Max timeout in seconds + if (timeout > 0) { + setTimeout(function() { + obj.hide(); + }, timeout * 1000) + } + } + + obj.hide = function() { + if (loading && loading.parentNode) { + document.body.removeChild(loading); + } + } + + return obj; +})(); + +jSuites.mask = (function() { + // Currency + var tokens = { + // Text + text: [ '@' ], + // Currency tokens + currency: [ '#(.{1})##0?(.{1}0+)?( ?;(.*)?)?', '#' ], + // Percentage + percentage: [ '0{1}(.{1}0+)?%' ], + // Number + numeric: [ '0{1}(.{1}0+)?' ], + // Data tokens + datetime: [ 'YYYY', 'YYY', 'YY', 'MMMMM', 'MMMM', 'MMM', 'MM', 'DDDDD', 'DDDD', 'DDD', 'DD', 'DY', 'DAY', 'WD', 'D', 'Q', 'MONTH', 'MON', 'HH24', 'HH12', 'HH', '\\[H\\]', 'H', 'AM/PM', 'PM', 'AM', 'MI', 'SS', 'MS', 'Y', 'M' ], + // Other + general: [ 'A', '0', '[0-9a-zA-Z\$]+', '.'] + } + + var getDate = function() { + if (this.mask.toLowerCase().indexOf('[h]') !== -1) { + var m = 0; + if (this.date[4]) { + m = parseFloat(this.date[4] / 60); + } + var v = parseInt(this.date[3]) + m; + v /= 24; + } else if (! (this.date[0] && this.date[1] && this.date[2]) && (this.date[3] || this.date[4])) { + v = jSuites.two(this.date[3]) + ':' + jSuites.two(this.date[4]) + ':' + jSuites.two(this.date[5]) + } else { + if (this.date[0] && this.date[1] && ! this.date[2]) { + this.date[2] = 1; + } + v = jSuites.two(this.date[0]) + '-' + jSuites.two(this.date[1]) + '-' + jSuites.two(this.date[2]); + + if (this.date[3] || this.date[4] || this.date[5]) { + v += ' ' + jSuites.two(this.date[3]) + ':' + jSuites.two(this.date[4]) + ':' + jSuites.two(this.date[5]); + } + } + + return v; + } + + var extractDate = function() { + var v = ''; + if (! (this.date[0] && this.date[1] && this.date[2]) && (this.date[3] || this.date[4])) { + if (this.mask.toLowerCase().indexOf('[h]') !== -1) { + v = parseInt(this.date[3]); + } else { + v = parseInt(this.date[3]) % 24; + } + if (this.date[4]) { + v += parseFloat(this.date[4] / 60); + } + v /= 24; + } else if (this.date[0] || this.date[1] || this.date[2] || this.date[3] || this.date[4] || this.date[5]) { + if (this.date[0] && this.date[1] && ! this.date[2]) { + this.date[2] = 1; + } + var t = jSuites.calendar.now(this.date); + v = jSuites.calendar.dateToNum(t); + if (this.date[4]) { + v += parseFloat(this.date[4] / 60); + } + } + return v; + } + + var isBlank = function(v) { + return v === null || v === '' || v === undefined ? true : false; + } + + var isFormula = function(value) { + return (''+value).chartAt(0) == '='; + } + + var isNumeric = function(t) { + return t === 'currency' || t === 'percentage' || t === 'numeric' ? true : false; + } + /** + * Get the decimal defined in the mask configuration + */ + var getDecimal = function(v) { + if (v && Number(v) == v) { + return '.'; + } else { + if (this.options.decimal) { + return this.options.decimal; + } else { + if (this.locale) { + var t = Intl.NumberFormat(this.locale).format(1.1); + return this.options.decimal = t[1]; + } else { + if (! v) { + v = this.mask; + } + var e = new RegExp('0{1}(.{1})0+', 'ig'); + var t = e.exec(v); + if (t && t[1] && t[1].length == 1) { + // Save decimal + this.options.decimal = t[1]; + // Return decimal + return t[1]; + } else { + // Did not find any decimal last resort the default + var e = new RegExp('#{1}(.{1})#+', 'ig'); + var t = e.exec(v); + if (t && t[1] && t[1].length == 1) { + if (t[1] === ',') { + this.options.decimal = '.'; + } else { + this.options.decimal = ','; + } + } else { + this.options.decimal = '1.1'.toLocaleString().substring(1,2); + } + } + } + } + } + + if (this.options.decimal) { + return this.options.decimal; + } else { + return null; + } + } + + var ParseValue = function(v, decimal) { + if (v == '') { + return ''; + } + + // Get decimal + if (! decimal) { + decimal = getDecimal.call(this); + } + + // New value + v = (''+v).split(decimal); + + // Signal + var signal = v[0].match(/[-]+/g); + if (signal && signal.length) { + signal = true; + } else { + signal = false; + } + + v[0] = v[0].match(/[0-9]+/g); + + if (v[0]) { + if (signal) { + v[0].unshift('-'); + } + v[0] = v[0].join(''); + } else { + if (signal) { + v[0] = '-'; + } + } + + if (v[0] || v[1]) { + if (v[1] !== undefined) { + v[1] = v[1].match(/[0-9]+/g); + if (v[1]) { + v[1] = v[1].join(''); + } else { + v[1] = ''; + } + } + } else { + return ''; + } + return v; + } + + var FormatValue = function(v, event) { + if (v == '') { + return ''; + } + // Get decimal + var d = getDecimal.call(this); + // Convert value + var o = this.options; + // Parse value + v = ParseValue.call(this, v); + if (v == '') { + return ''; + } + // Temporary value + if (v[0]) { + var t = parseFloat(v[0] + '.1'); + if (o.style == 'percent') { + t /= 100; + } + } else { + var t = null; + } + + if ((v[0] == '-' || v[0] == '-00') && ! v[1] && (event && event.inputType == "deleteContentBackward")) { + return ''; + } + + var n = new Intl.NumberFormat(this.locale, o).format(t); + n = n.split(d); + if (typeof(n[1]) !== 'undefined') { + var s = n[1].replace(/[0-9]*/g, ''); + if (s) { + n[2] = s; + } + } + + if (v[1] !== undefined) { + n[1] = d + v[1]; + } else { + n[1] = ''; + } + + return n.join(''); + } + + var Format = function(e, event) { + var v = Value.call(e); + if (! v) { + return; + } + + // Get decimal + var d = getDecimal.call(this); + var n = FormatValue.call(this, v, event); + var t = (n.length) - v.length; + var index = Caret.call(e) + t; + // Set value and update caret + Value.call(e, n, index, true); + } + + var Extract = function(v) { + // Keep the raw value + var current = ParseValue.call(this, v); + if (current) { + // Negative values + if (current[0] === '-') { + current[0] = '-0'; + } + return parseFloat(current.join('.')); + } + return null; + } + + /** + * Caret getter and setter methods + */ + var Caret = function(index, adjustNumeric) { + if (index === undefined) { + if (this.tagName == 'DIV') { + var pos = 0; + var s = window.getSelection(); + if (s) { + if (s.rangeCount !== 0) { + var r = s.getRangeAt(0); + var p = r.cloneRange(); + p.selectNodeContents(this); + p.setEnd(r.endContainer, r.endOffset); + pos = p.toString().length; + } + } + return pos; + } else { + return this.selectionStart; + } + } else { + // Get the current value + var n = Value.call(this); + + // Review the position + if (adjustNumeric) { + var p = null; + for (var i = 0; i < n.length; i++) { + if (n[i].match(/[\-0-9]/g) || n[i] == '.' || n[i] == ',') { + p = i; + } + } + + // If the string has no numbers + if (p === null) { + p = n.indexOf(' '); + } + + if (index >= p) { + index = p + 1; + } + } + + // Do not update caret + if (index > n.length) { + index = n.length; + } + + if (index) { + // Set caret + if (this.tagName == 'DIV') { + var s = window.getSelection(); + var r = document.createRange(); + + if (this.childNodes[0]) { + r.setStart(this.childNodes[0], index); + s.removeAllRanges(); + s.addRange(r); + } + } else { + this.selectionStart = index; + this.selectionEnd = index; + } + } + } + } + + /** + * Value getter and setter method + */ + var Value = function(v, updateCaret, adjustNumeric) { + if (this.tagName == 'DIV') { + if (v === undefined) { + var v = this.innerText; + if (this.value && this.value.length > v.length) { + v = this.value; + } + return v; + } else { + if (this.innerText !== v) { + this.innerText = v; + + if (updateCaret) { + Caret.call(this, updateCaret, adjustNumeric); + } + } + } + } else { + if (v === undefined) { + return this.value; + } else { + if (this.value !== v) { + this.value = v; + if (updateCaret) { + Caret.call(this, updateCaret, adjustNumeric); + } + } + } + } + } + + // Labels + var weekDaysFull = jSuites.calendar.weekdays; + var weekDays = jSuites.calendar.weekdaysShort; + var monthsFull = jSuites.calendar.months; + var months = jSuites.calendar.monthsShort; + + var parser = { + 'YEAR': function(v, s) { + var y = ''+new Date().getFullYear(); + + if (typeof(this.values[this.index]) === 'undefined') { + this.values[this.index] = ''; + } + if (parseInt(v) >= 0 && parseInt(v) <= 10) { + if (this.values[this.index].length < s) { + this.values[this.index] += v; + } + } + if (this.values[this.index].length == s) { + if (s == 2) { + var y = y.substr(0,2) + this.values[this.index]; + } else if (s == 3) { + var y = y.substr(0,1) + this.values[this.index]; + } else if (s == 4) { + var y = this.values[this.index]; + } + this.date[0] = y; + this.index++; + } + }, + 'YYYY': function(v) { + parser.YEAR.call(this, v, 4); + }, + 'YYY': function(v) { + parser.YEAR.call(this, v, 3); + }, + 'YY': function(v) { + parser.YEAR.call(this, v, 2); + }, + 'FIND': function(v, a) { + if (isBlank(this.values[this.index])) { + this.values[this.index] = ''; + } + if (this.event && this.event.inputType && this.event.inputType.indexOf('delete') > -1) { + this.values[this.index] += v; + return; + } + var pos = 0; + var count = 0; + var value = (this.values[this.index] + v).toLowerCase(); + for (var i = 0; i < a.length; i++) { + if (a[i].toLowerCase().indexOf(value) == 0) { + pos = i; + count++; + } + } + if (count > 1) { + this.values[this.index] += v; + } else if (count == 1) { + // Jump number of chars + var t = (a[pos].length - this.values[this.index].length) - 1; + this.position += t; + + this.values[this.index] = a[pos]; + this.index++; + return pos; + } + }, + 'MMM': function(v) { + var ret = parser.FIND.call(this, v, months); + if (ret !== undefined) { + this.date[1] = ret + 1; + } + }, + 'MON': function(v) { + parser['MMM'].call(this, v); + }, + 'MMMM': function(v) { + var ret = parser.FIND.call(this, v, monthsFull); + if (ret !== undefined) { + this.date[1] = ret + 1; + } + }, + 'MONTH': function(v) { + parser['MMMM'].call(this, v); + }, + 'MMMMM': function(v) { + if (isBlank(this.values[this.index])) { + this.values[this.index] = ''; + } + var pos = 0; + var count = 0; + var value = (this.values[this.index] + v).toLowerCase(); + for (var i = 0; i < monthsFull.length; i++) { + if (monthsFull[i][0].toLowerCase().indexOf(value) == 0) { + this.values[this.index] = monthsFull[i][0]; + this.date[1] = i + 1; + this.index++; + break; + } + } + }, + 'MM': function(v) { + if (isBlank(this.values[this.index])) { + if (parseInt(v) > 1 && parseInt(v) < 10) { + this.date[1] = this.values[this.index] = '0' + v; + this.index++; + } else if (parseInt(v) < 2) { + this.values[this.index] = v; + } + } else { + if (this.values[this.index] == 1 && parseInt(v) < 3) { + this.date[1] = this.values[this.index] += v; + this.index++; + } else if (this.values[this.index] == 0 && parseInt(v) > 0 && parseInt(v) < 10) { + this.date[1] = this.values[this.index] += v; + this.index++; + } + } + }, + 'M': function(v) { + var test = false; + if (parseInt(v) >= 0 && parseInt(v) < 10) { + if (isBlank(this.values[this.index])) { + this.values[this.index] = v; + if (v > 1) { + this.date[1] = this.values[this.index]; + this.index++; + } + } else { + if (this.values[this.index] == 1 && parseInt(v) < 3) { + this.date[1] = this.values[this.index] += v; + this.index++; + } else if (this.values[this.index] == 0 && parseInt(v) > 0) { + this.date[1] = this.values[this.index] += v; + this.index++; + } else { + var test = true; + } + } + } else { + var test = true; + } + + // Re-test + if (test == true) { + var t = parseInt(this.values[this.index]); + if (t > 0 && t < 12) { + this.date[2] = this.values[this.index]; + this.index++; + // Repeat the character + this.position--; + } + } + }, + 'D': function(v) { + var test = false; + if (parseInt(v) >= 0 && parseInt(v) < 10) { + if (isBlank(this.values[this.index])) { + this.values[this.index] = v; + if (parseInt(v) > 3) { + this.date[2] = this.values[this.index]; + this.index++; + } + } else { + if (this.values[this.index] == 3 && parseInt(v) < 2) { + this.date[2] = this.values[this.index] += v; + this.index++; + } else if (this.values[this.index] == 1 || this.values[this.index] == 2) { + this.date[2] = this.values[this.index] += v; + this.index++; + } else if (this.values[this.index] == 0 && parseInt(v) > 0) { + this.date[2] = this.values[this.index] += v; + this.index++; + } else { + var test = true; + } + } + } else { + var test = true; + } + + // Re-test + if (test == true) { + var t = parseInt(this.values[this.index]); + if (t > 0 && t < 32) { + this.date[2] = this.values[this.index]; + this.index++; + // Repeat the character + this.position--; + } + } + }, + 'DD': function(v) { + if (isBlank(this.values[this.index])) { + if (parseInt(v) > 3 && parseInt(v) < 10) { + this.date[2] = this.values[this.index] = '0' + v; + this.index++; + } else if (parseInt(v) < 10) { + this.values[this.index] = v; + } + } else { + if (this.values[this.index] == 3 && parseInt(v) < 2) { + this.date[2] = this.values[this.index] += v; + this.index++; + } else if ((this.values[this.index] == 1 || this.values[this.index] == 2) && parseInt(v) < 10) { + this.date[2] = this.values[this.index] += v; + this.index++; + } else if (this.values[this.index] == 0 && parseInt(v) > 0 && parseInt(v) < 10) { + this.date[2] = this.values[this.index] += v; + this.index++; + } + } + }, + 'DDD': function(v) { + parser.FIND.call(this, v, weekDays); + }, + 'DY': function(v) { + parser['DDD'].call(this, v); + }, + 'DDDD': function(v) { + parser.FIND.call(this, v, weekDaysFull); + }, + 'DAY': function(v) { + parser['DDDD'].call(this, v); + }, + 'HH12': function(v, two) { + if (isBlank(this.values[this.index])) { + if (parseInt(v) > 1 && parseInt(v) < 10) { + if (two) { + v = 0 + v; + } + this.date[3] = this.values[this.index] = v; + this.index++; + } else if (parseInt(v) < 10) { + this.values[this.index] = v; + } + } else { + if (this.values[this.index] == 1 && parseInt(v) < 3) { + this.date[3] = this.values[this.index] += v; + this.index++; + } else if (this.values[this.index] < 1 && parseInt(v) < 10) { + this.date[3] = this.values[this.index] += v; + this.index++; + } + } + }, + 'HH24': function(v, two) { + var test = false; + if (parseInt(v) >= 0 && parseInt(v) < 10) { + if (this.values[this.index] == null || this.values[this.index] == '') { + if (parseInt(v) > 2 && parseInt(v) < 10) { + if (two) { + v = 0 + v; + } + this.date[3] = this.values[this.index] = v; + this.index++; + } else if (parseInt(v) < 10) { + this.values[this.index] = v; + } + } else { + if (this.values[this.index] == 2 && parseInt(v) < 4) { + this.date[3] = this.values[this.index] += v; + this.index++; + } else if (this.values[this.index] < 2 && parseInt(v) < 10) { + this.date[3] = this.values[this.index] += v; + this.index++; + } + } + } + }, + 'HH': function(v) { + parser['HH24'].call(this, v, 1); + }, + 'H': function(v) { + parser['HH24'].call(this, v, 0); + }, + '\\[H\\]': function(v) { + if (this.values[this.index] == undefined) { + this.values[this.index] = ''; + } + if (v.match(/[0-9]/g)) { + this.date[3] = this.values[this.index] += v; + } else { + if (this.values[this.index].match(/[0-9]/g)) { + this.date[3] = this.values[this.index]; + this.index++; + // Repeat the character + this.position--; + } + } + }, + 'N60': function(v, i) { + if (this.values[this.index] == null || this.values[this.index] == '') { + if (parseInt(v) > 5 && parseInt(v) < 10) { + this.date[i] = this.values[this.index] = '0' + v; + this.index++; + } else if (parseInt(v) < 10) { + this.values[this.index] = v; + } + } else { + if (parseInt(v) < 10) { + this.date[i] = this.values[this.index] += v; + this.index++; + } + } + }, + 'MI': function(v) { + parser.N60.call(this, v, 4); + }, + 'SS': function(v) { + parser.N60.call(this, v, 5); + }, + 'AM/PM': function(v) { + this.values[this.index] = ''; + if (v) { + if (this.date[3] > 12) { + this.values[this.index] = 'PM'; + } else { + this.values[this.index] = 'AM'; + } + } + this.index++; + }, + 'WD': function(v) { + if (typeof(this.values[this.index]) === 'undefined') { + this.values[this.index] = ''; + } + if (parseInt(v) >= 0 && parseInt(v) < 7) { + this.values[this.index] = v; + } + if (this.value[this.index].length == 1) { + this.index++; + } + }, + '0{1}(.{1}0+)?': function(v) { + // Get decimal + var decimal = getDecimal.call(this); + // Negative number + var neg = false; + // Create if is blank + if (isBlank(this.values[this.index])) { + this.values[this.index] = ''; + } else { + if (this.values[this.index] == '-') { + neg = true; + } + } + var current = ParseValue.call(this, this.values[this.index], decimal); + if (current) { + this.values[this.index] = current.join(decimal); + } + // New entry + if (parseInt(v) >= 0 && parseInt(v) < 10) { + // Replace the zero for a number + if (this.values[this.index] == '0' && v > 0) { + this.values[this.index] = ''; + } else if (this.values[this.index] == '-0' && v > 0) { + this.values[this.index] = '-'; + } + // Don't add up zeros because does not mean anything here + if ((this.values[this.index] != '0' && this.values[this.index] != '-0') || v == decimal) { + this.values[this.index] += v; + } + } else if (decimal && v == decimal) { + if (this.values[this.index].indexOf(decimal) == -1) { + if (! this.values[this.index]) { + this.values[this.index] = '0'; + } + this.values[this.index] += v; + } + } else if (v == '-') { + // Negative signed + neg = true; + } + + if (neg === true && this.values[this.index][0] !== '-') { + this.values[this.index] = '-' + this.values[this.index]; + } + }, + '0{1}(.{1}0+)?%': function(v) { + parser['0{1}(.{1}0+)?'].call(this, v); + + if (this.values[this.index].match(/[\-0-9]/g)) { + if (this.values[this.index] && this.values[this.index].indexOf('%') == -1) { + this.values[this.index] += '%'; + } + } else { + this.values[this.index] = ''; + } + }, + '#(.{1})##0?(.{1}0+)?( ?;(.*)?)?': function(v) { + // Parse number + parser['0{1}(.{1}0+)?'].call(this, v); + // Get decimal + var decimal = getDecimal.call(this); + // Get separator + var separator = this.tokens[this.index].substr(1,1); + // Negative + var negative = this.values[this.index][0] === '-' ? true : false; + // Current value + var current = ParseValue.call(this, this.values[this.index], decimal); + + // Get main and decimal parts + if (current !== '') { + // Format number + var n = current[0].match(/[0-9]/g); + if (n) { + // Format + n = n.join(''); + var t = []; + var s = 0; + for (var j = n.length - 1; j >= 0 ; j--) { + t.push(n[j]); + s++; + if (! (s % 3)) { + t.push(separator); + } + } + t = t.reverse(); + current[0] = t.join(''); + if (current[0].substr(0,1) == separator) { + current[0] = current[0].substr(1); + } + } else { + current[0] = ''; + } + + // Value + this.values[this.index] = current.join(decimal); + + // Negative + if (negative) { + this.values[this.index] = '-' + this.values[this.index]; + } + } + }, + '0': function(v) { + if (v.match(/[0-9]/g)) { + this.values[this.index] = v; + this.index++; + } + }, + '[0-9a-zA-Z$]+': function(v) { + if (isBlank(this.values[this.index])) { + this.values[this.index] = ''; + } + var t = this.tokens[this.index]; + var s = this.values[this.index]; + var i = s.length; + + if (t[i] == v) { + this.values[this.index] += v; + + if (this.values[this.index] == t) { + this.index++; + } + } else { + this.values[this.index] = t; + this.index++; + + if (v.match(/[\-0-9]/g)) { + // Repeat the character + this.position--; + } + } + }, + 'A': function(v) { + if (v.match(/[a-zA-Z]/gi)) { + this.values[this.index] = v; + this.index++; + } + }, + '.': function(v) { + parser['[0-9a-zA-Z$]+'].call(this, v); + }, + '@': function(v) { + if (isBlank(this.values[this.index])) { + this.values[this.index] = ''; + } + this.values[this.index] += v; + } + } + + /** + * Get the tokens in the mask string + */ + var getTokens = function(str) { + if (this.type == 'general') { + var t = [].concat(tokens.general); + } else { + var t = [].concat(tokens.currency, tokens.datetime, tokens.percentage, tokens.numeric, tokens.text, tokens.general); + } + // Expression to extract all tokens from the string + var e = new RegExp(t.join('|'), 'gi'); + // Extract + return str.match(e); + } + + /** + * Get the method of one given token + */ + var getMethod = function(str) { + if (! this.type) { + var types = Object.keys(tokens); + } else if (this.type == 'text') { + var types = [ 'text' ]; + } else if (this.type == 'general') { + var types = [ 'general' ]; + } else if (this.type == 'datetime') { + var types = [ 'numeric', 'datetime', 'general' ]; + } else { + var types = [ 'currency', 'percentage', 'numeric', 'general' ]; + } + + // Found + for (var i = 0; i < types.length; i++) { + var type = types[i]; + for (var j = 0; j < tokens[type].length; j++) { + var e = new RegExp(tokens[type][j], 'gi'); + var r = str.match(e); + if (r) { + return { type: type, method: tokens[type][j] } + } + } + } + } + + /** + * Identify each method for each token + */ + var getMethods = function(t) { + var result = []; + for (var i = 0; i < t.length; i++) { + var m = getMethod.call(this, t[i]); + if (m) { + result.push(m.method); + } else { + result.push(null); + } + } + + // Compatibility with excel + for (var i = 0; i < result.length; i++) { + if (result[i] == 'MM') { + // Not a month, correct to minutes + if (result[i-1] && result[i-1].indexOf('H') >= 0) { + result[i] = 'MI'; + } else if (result[i-2] && result[i-2].indexOf('H') >= 0) { + result[i] = 'MI'; + } else if (result[i+1] && result[i+1].indexOf('S') >= 0) { + result[i] = 'MI'; + } else if (result[i+2] && result[i+2].indexOf('S') >= 0) { + result[i] = 'MI'; + } + } + } + + return result; + } + + /** + * Get the type for one given token + */ + var getType = function(str) { + var m = getMethod.call(this, str); + if (m) { + var type = m.type; + } + + if (type) { + var numeric = 0; + // Make sure the correct type + var t = getTokens.call(this, str); + for (var i = 0; i < t.length; i++) { + m = getMethod.call(this, t[i]); + if (m && isNumeric(m.type)) { + numeric++; + } + } + if (numeric > 1) { + type = 'general'; + } + } + + return type; + } + + /** + * Parse character per character using the detected tokens in the mask + */ + var parse = function() { + // Parser method for this position + if (typeof(parser[this.methods[this.index]]) == 'function') { + parser[this.methods[this.index]].call(this, this.value[this.position]); + this.position++; + } else { + this.values[this.index] = this.tokens[this.index]; + this.index++; + } + } + + var isFormula = function(value) { + var v = (''+value)[0]; + return v == '=' ? true : false; + } + + var toPlainString = function(num) { + return (''+ +num).replace(/(-?)(\d*)\.?(\d*)e([+-]\d+)/, + function(a,b,c,d,e) { + return e < 0 + ? b + '0.' + Array(1-e-c.length).join(0) + c + d + : b + c + d + Array(e-d.length+1).join(0); + }); + } + + /** + * Mask function + * @param {mixed|string} JS input or a string to be parsed + * @param {object|string} When the first param is a string, the second is the mask or object with the mask options + */ + var obj = function(e, config, returnObject) { + // Options + var r = null; + var t = null; + var o = { + // Element + input: null, + // Current value + value: null, + // Mask options + options: {}, + // New values for each token found + values: [], + // Token position + index: 0, + // Character position + position: 0, + // Date raw values + date: [0,0,0,0,0,0], + // Raw number for the numeric values + number: 0, + } + + // This is a JavaScript Event + if (typeof(e) == 'object') { + // Element + o.input = e.target; + // Current value + o.value = Value.call(e.target); + // Current caret position + o.caret = Caret.call(e.target); + // Mask + if (t = e.target.getAttribute('data-mask')) { + o.mask = t; + } + // Type + if (t = e.target.getAttribute('data-type')) { + o.type = t; + } + // Options + if (e.target.mask) { + if (e.target.mask.options) { + o.options = e.target.mask.options; + } + if (e.target.mask.locale) { + o.locale = e.target.mask.locale; + } + } else { + // Locale + if (t = e.target.getAttribute('data-locale')) { + o.locale = t; + if (o.mask) { + o.options.style = o.mask; + } + } + } + // Extra configuration + if (e.target.attributes && e.target.attributes.length) { + for (var i = 0; i < e.target.attributes.length; i++) { + var k = e.target.attributes[i].name; + var v = e.target.attributes[i].value; + if (k.substr(0,4) == 'data') { + o.options[k.substr(5)] = v; + } + } + } + } else { + // Options + if (typeof(config) == 'string') { + // Mask + o.mask = config; + } else { + // Mask + var k = Object.keys(config); + for (var i = 0; i < k.length; i++) { + o[k[i]] = config[k[i]]; + } + } + + if (typeof(e) === 'number') { + // Get decimal + getDecimal.call(o, o.mask); + // Replace to the correct decimal + e = (''+e).replace('.', o.options.decimal); + } + + // Current + o.value = e; + + if (o.input) { + // Value + Value.call(o.input, e); + // Focus + jSuites.focus(o.input); + // Caret + o.caret = Caret.call(o.input); + } + } + + // Mask detected start the process + if (! isFormula(o.value) && (o.mask || o.locale)) { + // Compatibility fixes + if (o.mask) { + // Remove [] + o.mask = o.mask.replace(new RegExp(/\[h]/),'|h|'); + o.mask = o.mask.replace(new RegExp(/\[.*?\]/),''); + o.mask = o.mask.replace(new RegExp(/\|h\|/),'[h]'); + if (o.mask.indexOf(';') !== -1) { + var t = o.mask.split(';'); + o.mask = t[0]; + } + // Excel mask TODO: Improve + if (o.mask.indexOf('##') !== -1) { + var d = o.mask.split(';'); + if (d[0]) { + d[0] = d[0].replace('*', '\t'); + d[0] = d[0].replace(new RegExp(/_-/g), ' '); + d[0] = d[0].replace(new RegExp(/_/g), ''); + d[0] = d[0].replace('##0.###','##0.000'); + d[0] = d[0].replace('##0.##','##0.00'); + d[0] = d[0].replace('##0.#','##0.0'); + d[0] = d[0].replace('##0,###','##0,000'); + d[0] = d[0].replace('##0,##','##0,00'); + d[0] = d[0].replace('##0,#','##0,0'); + } + o.mask = d[0]; + } + // Get type + if (! o.type) { + o.type = getType.call(o, o.mask); + } + // Get tokens + o.tokens = getTokens.call(o, o.mask); + } + + // On new input + if (typeof(e) !== 'object' || ! e.inputType || ! e.inputType.indexOf('insert') || ! e.inputType.indexOf('delete')) { + // Start transformation + if (o.locale) { + if (o.input) { + Format.call(o, o.input, e); + } else { + var newValue = FormatValue.call(o, o.value); + } + } else { + // Get tokens + o.methods = getMethods.call(o, o.tokens); + o.event = e; + + // Go through all tokes + while (o.position < o.value.length && typeof(o.tokens[o.index]) !== 'undefined') { + // Get the appropriate parser + parse.call(o); + } + + // New value + var newValue = o.values.join(''); + + // Add tokens to the end of string only if string is not empty + if (isNumeric(o.type) && newValue !== '') { + // Complement things in the end of the mask + while (typeof(o.tokens[o.index]) !== 'undefined') { + var t = getMethod.call(o, o.tokens[o.index]); + if (t && t.type == 'general') { + o.values[o.index] = o.tokens[o.index]; + } + o.index++; + } + + var adjustNumeric = true; + } else { + var adjustNumeric = false; + } + + // New value + newValue = o.values.join(''); + + // Reset value + if (o.input) { + t = newValue.length - o.value.length; + if (t > 0) { + var caret = o.caret + t; + } else { + var caret = o.caret; + } + Value.call(o.input, newValue, caret, adjustNumeric); + } + } + } + + // Update raw data + if (o.input) { + var label = null; + if (isNumeric(o.type)) { + // Extract the number + o.number = Extract.call(o, Value.call(o.input)); + // Keep the raw data as a property of the tag + if (o.type == 'percentage') { + label = o.number / 100; + } else { + label = o.number; + } + } else if (o.type == 'datetime') { + label = getDate.call(o); + + if (o.date[0] && o.date[1] && o.date[2]) { + o.input.setAttribute('data-completed', true); + } + } + + if (label) { + o.input.setAttribute('data-value', label); + } + } + + if (newValue !== undefined) { + if (returnObject) { + return o; + } else { + return newValue; + } + } + } + } + + // Get the type of the mask + obj.getType = getType; + + // Extract the tokens from a mask + obj.prepare = function(str, o) { + if (! o) { + o = {}; + } + return getTokens.call(o, str); + } + + /** + * Apply the mask to a element (legacy) + */ + obj.apply = function(e) { + var v = Value.call(e.target); + if (e.key.length == 1) { + v += e.key; + } + Value.call(e.target, obj(v, e.target.getAttribute('data-mask'))); + } + + /** + * Legacy support + */ + obj.run = function(value, mask, decimal) { + return obj(value, { mask: mask, decimal: decimal }); + } + + /** + * Extract number from masked string + */ + obj.extract = function(v, options, returnObject) { + if (isBlank(v)) { + return v; + } + if (typeof(options) != 'object') { + return value; + } else { + options = Object.assign({}, options); + + if (! options.options) { + options.options = {}; + } + } + + // Compatibility + if (! options.mask && options.format) { + options.mask = options.format; + } + + // Remove [] + if (options.mask) { + if (options.mask.indexOf(';') !== -1) { + var t = options.mask.split(';'); + options.mask = t[0]; + } + options.mask = options.mask.replace(new RegExp(/\[h]/),'|h|'); + options.mask = options.mask.replace(new RegExp(/\[.*?\]/),''); + options.mask = options.mask.replace(new RegExp(/\|h\|/),'[h]'); + } + + // Get decimal + getDecimal.call(options, options.mask); + + var type = null; + if (options.type == 'percent' || options.options.style == 'percent') { + type = 'percentage'; + } else if (options.mask) { + type = getType.call(options, options.mask); + } + + if (type === 'general') { + var o = obj(v, options, true); + + value = v; + } else if (type === 'datetime') { + if (v instanceof Date) { + var t = jSuites.calendar.getDateString(value, options.mask); + } + + var o = obj(v, options, true); + + if (jSuites.isNumeric(v)) { + value = v; + } else { + var value = extractDate.call(o); + } + } else { + var value = Extract.call(options, v); + // Percentage + if (type == 'percentage') { + value /= 100; + } + var o = options; + } + + o.value = value; + + if (! o.type && type) { + o.type = type; + } + + if (returnObject) { + return o; + } else { + return value; + } + } + + /** + * Render + */ + obj.render = function(value, options, fullMask) { + if (isBlank(value)) { + return value; + } + + if (typeof(options) != 'object') { + return value; + } else { + options = Object.assign({}, options); + + if (! options.options) { + options.options = {}; + } + } + + // Compatibility + if (! options.mask && options.format) { + options.mask = options.format; + } + + // Remove [] + if (options.mask) { + if (options.mask.indexOf(';') !== -1) { + var t = options.mask.split(';'); + options.mask = t[0]; + } + options.mask = options.mask.replace(new RegExp(/\[h]/),'|h|'); + options.mask = options.mask.replace(new RegExp(/\[.*?\]/),''); + options.mask = options.mask.replace(new RegExp(/\|h\|/),'[h]'); + } + + var type = null; + if (options.type == 'percent' || options.options.style == 'percent') { + type = 'percentage'; + } else if (options.mask) { + type = getType.call(options, options.mask); + } else if (value instanceof Date) { + type = 'datetime'; + } + + // Fill with blanks + var fillWithBlanks = false; + + if (type =='datetime' || options.type == 'calendar') { + var t = jSuites.calendar.getDateString(value, options.mask); + if (t) { + value = t; + } + if (options.mask && fullMask) { + fillWithBlanks = true; + } + } else { + // Percentage + if (type == 'percentage') { + value *= 100; + } + // Number of decimal places + if (typeof(value) === 'number') { + var t = null; + if (options.mask && fullMask && ((''+value).indexOf('e') === -1)) { + var d = getDecimal.call(options, options.mask); + if (options.mask.indexOf(d) !== -1) { + d = options.mask.split(d); + d = (''+d[1].match(/[0-9]+/g)) + d = d.length; + t = value.toFixed(d); + } else { + t = value.toFixed(0); + } + } else if (options.locale && fullMask) { + // Append zeros + var d = (''+value).split('.'); + if (options.options) { + if (typeof(d[1]) === 'undefined') { + d[1] = ''; + } + var len = d[1].length; + if (options.options.minimumFractionDigits > len) { + for (var i = 0; i < options.options.minimumFractionDigits - len; i++) { + d[1] += '0'; + } + } + } + if (! d[1].length) { + t = d[0] + } else { + t = d.join('.'); + } + var len = d[1].length; + if (options.options && options.options.maximumFractionDigits < len) { + t = parseFloat(t).toFixed(options.options.maximumFractionDigits); + } + } else { + t = toPlainString(value); + } + + if (t !== null) { + value = t; + // Get decimal + getDecimal.call(options, options.mask); + // Replace to the correct decimal + if (options.options.decimal) { + value = value.replace('.', options.options.decimal); + } + } + } else { + if (options.mask && fullMask) { + fillWithBlanks = true; + } + } + } + + if (fillWithBlanks) { + var s = options.mask.length - value.length; + if (s > 0) { + for (var i = 0; i < s; i++) { + value += ' '; + } + } + } + + value = obj(value, options); + + // Numeric mask, number of zeros + if (fullMask && type === 'numeric') { + var maskZeros = options.mask.match(new RegExp(/^[0]+$/gm)); + if (maskZeros && maskZeros.length === 1) { + var maskLength = maskZeros[0].length; + if (maskLength > 3) { + value = '' + value; + while (value.length < maskLength) { + value = '0' + value; + } + } + } + } + + return value; + } + + obj.set = function(e, m) { + if (m) { + e.setAttribute('data-mask', m); + // Reset the value + var event = new Event('input', { + bubbles: true, + cancelable: true, + }); + e.dispatchEvent(event); + } + } + + if (typeof document !== 'undefined') { + document.addEventListener('input', function(e) { + if (e.target.getAttribute('data-mask') || e.target.mask) { + obj(e); + } + }); + } + + return obj; +})(); + +jSuites.modal = (function(el, options) { + var obj = {}; + obj.options = {}; + + // Default configuration + var defaults = { + url: null, + onopen: null, + onclose: null, + onload: null, + closed: false, + width: null, + height: null, + title: null, + padding: null, + backdrop: true, + icon: null, + }; + + // Loop through our object + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + obj.options[property] = defaults[property]; + } + } + + // Title + if (! obj.options.title && el.getAttribute('title')) { + obj.options.title = el.getAttribute('title'); + } + + var temp = document.createElement('div'); + while (el.children[0]) { + temp.appendChild(el.children[0]); + } + + obj.title = document.createElement('div'); + obj.title.className = 'jmodal_title'; + if (obj.options.icon) { + obj.title.setAttribute('data-icon', obj.options.icon); + } + + obj.content = document.createElement('div'); + obj.content.className = 'jmodal_content'; + obj.content.innerHTML = el.innerHTML; + + while (temp.children[0]) { + obj.content.appendChild(temp.children[0]); + } + + obj.container = document.createElement('div'); + obj.container.className = 'jmodal'; + obj.container.appendChild(obj.title); + obj.container.appendChild(obj.content); + + if (obj.options.padding) { + obj.content.style.padding = obj.options.padding; + } + if (obj.options.width) { + obj.container.style.width = obj.options.width; + } + if (obj.options.height) { + obj.container.style.height = obj.options.height; + } + if (obj.options.title) { + var title = document.createElement('h4'); + title.innerText = obj.options.title; + obj.title.appendChild(title); + } + + el.innerHTML = ''; + el.style.display = 'none'; + el.appendChild(obj.container); + + // Backdrop + if (obj.options.backdrop) { + var backdrop = document.createElement('div'); + backdrop.className = 'jmodal_backdrop'; + backdrop.onclick = function () { + obj.close(); + } + el.appendChild(backdrop); + } + + obj.open = function() { + el.style.display = 'block'; + // Fullscreen + var rect = obj.container.getBoundingClientRect(); + if (jSuites.getWindowWidth() < rect.width) { + obj.container.style.top = ''; + obj.container.style.left = ''; + obj.container.classList.add('jmodal_fullscreen'); + jSuites.animation.slideBottom(obj.container, 1); + } else { + if (obj.options.backdrop) { + backdrop.style.display = 'block'; + } + } + // Event + if (typeof(obj.options.onopen) == 'function') { + obj.options.onopen(el, obj); + } + } + + obj.resetPosition = function() { + obj.container.style.top = ''; + obj.container.style.left = ''; + } + + obj.isOpen = function() { + return el.style.display != 'none' ? true : false; + } + + obj.close = function() { + if (obj.isOpen()) { + el.style.display = 'none'; + if (obj.options.backdrop) { + // Backdrop + backdrop.style.display = ''; + } + // Remove fullscreen class + obj.container.classList.remove('jmodal_fullscreen'); + // Event + if (typeof(obj.options.onclose) == 'function') { + obj.options.onclose(el, obj); + } + } + } + + if (! jSuites.modal.hasEvents) { + // Position + var tracker = null; + + document.addEventListener('keydown', function(e) { + if (e.which == 27) { + var modals = document.querySelectorAll('.jmodal'); + for (var i = 0; i < modals.length; i++) { + modals[i].parentNode.modal.close(); + } + } + }); + + document.addEventListener('mouseup', function(e) { + var item = jSuites.findElement(e.target, 'jmodal'); + if (item) { + // Get target info + var rect = item.getBoundingClientRect(); + + if (e.changedTouches && e.changedTouches[0]) { + var x = e.changedTouches[0].clientX; + var y = e.changedTouches[0].clientY; + } else { + var x = e.clientX; + var y = e.clientY; + } + + if (rect.width - (x - rect.left) < 50 && (y - rect.top) < 50) { + item.parentNode.modal.close(); + } + } + + if (tracker) { + tracker.element.style.cursor = 'auto'; + tracker = null; + } + }); + + document.addEventListener('mousedown', function(e) { + var item = jSuites.findElement(e.target, 'jmodal'); + if (item) { + // Get target info + var rect = item.getBoundingClientRect(); + + if (e.changedTouches && e.changedTouches[0]) { + var x = e.changedTouches[0].clientX; + var y = e.changedTouches[0].clientY; + } else { + var x = e.clientX; + var y = e.clientY; + } + + if (rect.width - (x - rect.left) < 50 && (y - rect.top) < 50) { + // Do nothing + } else { + if (y - rect.top < 50) { + if (document.selection) { + document.selection.empty(); + } else if ( window.getSelection ) { + window.getSelection().removeAllRanges(); + } + + tracker = { + left: rect.left, + top: rect.top, + x: e.clientX, + y: e.clientY, + width: rect.width, + height: rect.height, + element: item, + } + } + } + } + }); + + document.addEventListener('mousemove', function(e) { + if (tracker) { + e = e || window.event; + if (e.buttons) { + var mouseButton = e.buttons; + } else if (e.button) { + var mouseButton = e.button; + } else { + var mouseButton = e.which; + } + + if (mouseButton) { + tracker.element.style.top = (tracker.top + (e.clientY - tracker.y) + (tracker.height / 2)) + 'px'; + tracker.element.style.left = (tracker.left + (e.clientX - tracker.x) + (tracker.width / 2)) + 'px'; + tracker.element.style.cursor = 'move'; + } else { + tracker.element.style.cursor = 'auto'; + } + } + }); + + jSuites.modal.hasEvents = true; + } + + if (obj.options.url) { + jSuites.ajax({ + url: obj.options.url, + method: 'GET', + dataType: 'text/html', + success: function(data) { + obj.content.innerHTML = data; + + if (! obj.options.closed) { + obj.open(); + } + + if (typeof(obj.options.onload) === 'function') { + obj.options.onload(obj); + } + } + }); + } else { + if (! obj.options.closed) { + obj.open(); + } + + if (typeof(obj.options.onload) === 'function') { + obj.options.onload(obj); + } + } + + // Keep object available from the node + el.modal = obj; + + return obj; +}); + + +jSuites.notification = (function(options) { + var obj = {}; + obj.options = {}; + + // Default configuration + var defaults = { + icon: null, + name: 'Notification', + date: null, + error: null, + title: null, + message: null, + timeout: 4000, + autoHide: true, + closeable: true, + }; + + // Loop through our object + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + obj.options[property] = defaults[property]; + } + } + + var notification = document.createElement('div'); + notification.className = 'jnotification'; + + if (obj.options.error) { + notification.classList.add('jnotification-error'); + } + + var notificationContainer = document.createElement('div'); + notificationContainer.className = 'jnotification-container'; + notification.appendChild(notificationContainer); + + var notificationHeader = document.createElement('div'); + notificationHeader.className = 'jnotification-header'; + notificationContainer.appendChild(notificationHeader); + + var notificationImage = document.createElement('div'); + notificationImage.className = 'jnotification-image'; + notificationHeader.appendChild(notificationImage); + + if (obj.options.icon) { + var notificationIcon = document.createElement('img'); + notificationIcon.src = obj.options.icon; + notificationImage.appendChild(notificationIcon); + } + + var notificationName = document.createElement('div'); + notificationName.className = 'jnotification-name'; + notificationName.innerHTML = obj.options.name; + notificationHeader.appendChild(notificationName); + + if (obj.options.closeable == true) { + var notificationClose = document.createElement('div'); + notificationClose.className = 'jnotification-close'; + notificationClose.onclick = function() { + obj.hide(); + } + notificationHeader.appendChild(notificationClose); + } + + var notificationDate = document.createElement('div'); + notificationDate.className = 'jnotification-date'; + notificationHeader.appendChild(notificationDate); + + var notificationContent = document.createElement('div'); + notificationContent.className = 'jnotification-content'; + notificationContainer.appendChild(notificationContent); + + if (obj.options.title) { + var notificationTitle = document.createElement('div'); + notificationTitle.className = 'jnotification-title'; + notificationTitle.innerHTML = obj.options.title; + notificationContent.appendChild(notificationTitle); + } + + var notificationMessage = document.createElement('div'); + notificationMessage.className = 'jnotification-message'; + notificationMessage.innerHTML = obj.options.message; + notificationContent.appendChild(notificationMessage); + + obj.show = function() { + document.body.appendChild(notification); + if (jSuites.getWindowWidth() > 800) { + jSuites.animation.fadeIn(notification); + } else { + jSuites.animation.slideTop(notification, 1); + } + } + + obj.hide = function() { + if (jSuites.getWindowWidth() > 800) { + jSuites.animation.fadeOut(notification, function() { + if (notification.parentNode) { + notification.parentNode.removeChild(notification); + if (notificationTimeout) { + clearTimeout(notificationTimeout); + } + } + }); + } else { + jSuites.animation.slideTop(notification, 0, function() { + if (notification.parentNode) { + notification.parentNode.removeChild(notification); + if (notificationTimeout) { + clearTimeout(notificationTimeout); + } + } + }); + } + }; + + obj.show(); + + if (obj.options.autoHide == true) { + var notificationTimeout = setTimeout(function() { + obj.hide(); + }, obj.options.timeout); + } + + if (jSuites.getWindowWidth() < 800) { + notification.addEventListener("swipeup", function(e) { + obj.hide(); + e.preventDefault(); + e.stopPropagation(); + }); + } + + return obj; +}); + +jSuites.notification.isVisible = function() { + var j = document.querySelector('.jnotification'); + return j && j.parentNode ? true : false; +} + +// More palettes https://coolors.co/ or https://gka.github.io/palettes/#/10|s|003790,005647,ffffe0|ffffe0,ff005e,93003a|1|1 +jSuites.palette = (function() { + /** + * Available palettes + */ + var palette = { + material: [ + [ "#ffebee", "#fce4ec", "#f3e5f5", "#e8eaf6", "#e3f2fd", "#e0f7fa", "#e0f2f1", "#e8f5e9", "#f1f8e9", "#f9fbe7", "#fffde7", "#fff8e1", "#fff3e0", "#fbe9e7", "#efebe9", "#fafafa", "#eceff1" ], + [ "#ffcdd2", "#f8bbd0", "#e1bee7", "#c5cae9", "#bbdefb", "#b2ebf2", "#b2dfdb", "#c8e6c9", "#dcedc8", "#f0f4c3", "#fff9c4", "#ffecb3", "#ffe0b2", "#ffccbc", "#d7ccc8", "#f5f5f5", "#cfd8dc" ], + [ "#ef9a9a", "#f48fb1", "#ce93d8", "#9fa8da", "#90caf9", "#80deea", "#80cbc4", "#a5d6a7", "#c5e1a5", "#e6ee9c", "#fff59d", "#ffe082", "#ffcc80", "#ffab91", "#bcaaa4", "#eeeeee", "#b0bec5" ], + [ "#e57373", "#f06292", "#ba68c8", "#7986cb", "#64b5f6", "#4dd0e1", "#4db6ac", "#81c784", "#aed581", "#dce775", "#fff176", "#ffd54f", "#ffb74d", "#ff8a65", "#a1887f", "#e0e0e0", "#90a4ae" ], + [ "#ef5350", "#ec407a", "#ab47bc", "#5c6bc0", "#42a5f5", "#26c6da", "#26a69a", "#66bb6a", "#9ccc65", "#d4e157", "#ffee58", "#ffca28", "#ffa726", "#ff7043", "#8d6e63", "#bdbdbd", "#78909c" ], + [ "#f44336", "#e91e63", "#9c27b0", "#3f51b5", "#2196f3", "#00bcd4", "#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722", "#795548", "#9e9e9e", "#607d8b" ], + [ "#e53935", "#d81b60", "#8e24aa", "#3949ab", "#1e88e5", "#00acc1", "#00897b", "#43a047", "#7cb342", "#c0ca33", "#fdd835", "#ffb300", "#fb8c00", "#f4511e", "#6d4c41", "#757575", "#546e7a" ], + [ "#d32f2f", "#c2185b", "#7b1fa2", "#303f9f", "#1976d2", "#0097a7", "#00796b", "#388e3c", "#689f38", "#afb42b", "#fbc02d", "#ffa000", "#f57c00", "#e64a19", "#5d4037", "#616161", "#455a64" ], + [ "#c62828", "#ad1457", "#6a1b9a", "#283593", "#1565c0", "#00838f", "#00695c", "#2e7d32", "#558b2f", "#9e9d24", "#f9a825", "#ff8f00", "#ef6c00", "#d84315", "#4e342e", "#424242", "#37474f" ], + [ "#b71c1c", "#880e4f", "#4a148c", "#1a237e", "#0d47a1", "#006064", "#004d40", "#1b5e20", "#33691e", "#827717", "#f57f17", "#ff6f00", "#e65100", "#bf360c", "#3e2723", "#212121", "#263238" ], + ], + fire: [ + ["0b1a6d","840f38","b60718","de030b","ff0c0c","fd491c","fc7521","faa331","fbb535","ffc73a"], + ["071147","5f0b28","930513","be0309","ef0000","fa3403","fb670b","f9991b","faad1e","ffc123"], + ["03071e","370617","6a040f","9d0208","d00000","dc2f02","e85d04","f48c06","faa307","ffba08"], + ["020619","320615","61040d","8c0207","bc0000","c82a02","d05203","db7f06","e19405","efab00"], + ["020515","2d0513","58040c","7f0206","aa0000","b62602","b94903","c57205","ca8504","d89b00"], + ], + baby: [ + ["eddcd2","fff1e6","fde2e4","fad2e1","c5dedd","dbe7e4","f0efeb","d6e2e9","bcd4e6","99c1de"], + ["e1c4b3","ffd5b5","fab6ba","f5a8c4","aacecd","bfd5cf","dbd9d0","baceda","9dc0db","7eb1d5"], + ["daa990","ffb787","f88e95","f282a9","8fc4c3","a3c8be","cec9b3","9dbcce","82acd2","649dcb"], + ["d69070","ff9c5e","f66770","f05f8f","74bbb9","87bfae","c5b993","83aac3","699bca","4d89c2"], + ["c97d5d","f58443","eb4d57","e54a7b","66a9a7","78ae9c","b5a67e","7599b1","5c88b7","4978aa"], + ], + chart: [ + ['#C1D37F','#4C5454','#FFD275','#66586F','#D05D5B','#C96480','#95BF8F','#6EA240','#0F0F0E','#EB8258','#95A3B3','#995D81'], + ], + } + + /** + * Get a pallete + */ + var component = function(o) { + // Otherwise get palette value + if (palette[o]) { + return palette[o]; + } else { + return palette.material; + } + } + + component.get = function(o) { + // Otherwise get palette value + if (palette[o]) { + return palette[o]; + } else { + return palette; + } + } + + component.set = function(o, v) { + palette[o] = v; + } + + return component; +})(); + + +jSuites.picker = (function(el, options) { + // Already created, update options + if (el.picker) { + return el.picker.setOptions(options, true); + } + + // New instance + var obj = { type: 'picker' }; + obj.options = {}; + + var dropdownHeader = null; + var dropdownContent = null; + + /** + * The element passed is a DOM element + */ + var isDOM = function(o) { + return (o instanceof Element || o instanceof HTMLDocument); + } + + /** + * Create the content options + */ + var createContent = function() { + dropdownContent.innerHTML = ''; + + // Create items + var keys = Object.keys(obj.options.data); + + // Go though all options + for (var i = 0; i < keys.length; i++) { + // Item + var dropdownItem = document.createElement('div'); + dropdownItem.classList.add('jpicker-item'); + dropdownItem.k = keys[i]; + dropdownItem.v = obj.options.data[keys[i]]; + // Label + var item = obj.getLabel(keys[i], dropdownItem); + if (isDOM(item)) { + dropdownItem.appendChild(item); + } else { + dropdownItem.innerHTML = item; + } + // Append + dropdownContent.appendChild(dropdownItem); + } + } + + /** + * Set or reset the options for the picker + */ + obj.setOptions = function(options, reset) { + // Default configuration + var defaults = { + value: 0, + data: null, + render: null, + onchange: null, + onmouseover: null, + onselect: null, + onopen: null, + onclose: null, + onload: null, + width: null, + header: true, + right: false, + bottom: false, + content: false, + columns: null, + grid: null, + height: null, + } + + // Legacy purpose only + if (options && options.options) { + options.data = options.options; + } + + // Loop through the initial configuration + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + if (typeof(obj.options[property]) == 'undefined' || reset === true) { + obj.options[property] = defaults[property]; + } + } + } + + // Start using the options + if (obj.options.header === false) { + dropdownHeader.style.display = 'none'; + } else { + dropdownHeader.style.display = ''; + } + + // Width + if (obj.options.width) { + dropdownHeader.style.width = parseInt(obj.options.width) + 'px'; + } else { + dropdownHeader.style.width = ''; + } + + // Height + if (obj.options.height) { + dropdownContent.style.maxHeight = obj.options.height + 'px'; + dropdownContent.style.overflow = 'scroll'; + } else { + dropdownContent.style.overflow = ''; + } + + if (obj.options.columns > 0) { + if (! obj.options.grid) { + dropdownContent.classList.add('jpicker-columns'); + dropdownContent.style.width = obj.options.width ? obj.options.width : 36 * obj.options.columns + 'px'; + } else { + dropdownContent.classList.add('jpicker-grid'); + dropdownContent.style.gridTemplateColumns = 'repeat(' + obj.options.grid + ', 1fr)'; + } + } + + if (isNaN(obj.options.value)) { + obj.options.value = '0'; + } + + // Create list from data + createContent(); + + // Set value + obj.setValue(obj.options.value); + + // Set options all returns the own instance + return obj; + } + + obj.getValue = function() { + return obj.options.value; + } + + obj.setValue = function(v) { + // Set label + obj.setLabel(v); + + // Update value + obj.options.value = String(v); + + // Lemonade JS + if (el.value != obj.options.value) { + el.value = obj.options.value; + if (typeof(el.oninput) == 'function') { + el.oninput({ + type: 'input', + target: el, + value: el.value + }); + } + } + + if (dropdownContent.children[v].getAttribute('type') !== 'generic') { + obj.close(); + } + } + + obj.getLabel = function(v, item) { + var label = obj.options.data[v] || null; + if (typeof(obj.options.render) == 'function') { + label = obj.options.render(label, item); + } + return label; + } + + obj.setLabel = function(v) { + var item; + + if (obj.options.content) { + item = '' + obj.options.content + ''; + } else { + item = obj.getLabel(v, null); + } + // Label + if (isDOM(item)) { + dropdownHeader.innerHTML = ''; + dropdownHeader.appendChild(item); + } else { + dropdownHeader.innerHTML = item; + } + } + + obj.open = function() { + if (! el.classList.contains('jpicker-focus')) { + // Start tracking the element + jSuites.tracking(obj, true); + + // Open picker + el.classList.add('jpicker-focus'); + el.focus(); + + var top = 0; + var left = 0; + + dropdownContent.style.marginLeft = ''; + + var rectHeader = dropdownHeader.getBoundingClientRect(); + var rectContent = dropdownContent.getBoundingClientRect(); + + if (window.innerHeight < rectHeader.bottom + rectContent.height || obj.options.bottom) { + top = -1 * (rectContent.height + 4); + } else { + top = rectHeader.height + 4; + } + + if (obj.options.right === true) { + left = -1 * rectContent.width + rectHeader.width; + } + + if (rectContent.left + left < 0) { + left = left + rectContent.left + 10; + } + if (rectContent.left + rectContent.width > window.innerWidth) { + left = -1 * (10 + rectContent.left + rectContent.width - window.innerWidth); + } + + dropdownContent.style.marginTop = parseInt(top) + 'px'; + dropdownContent.style.marginLeft = parseInt(left) + 'px'; + + //dropdownContent.style.marginTop + if (typeof obj.options.onopen == 'function') { + obj.options.onopen(el, obj); + } + } + } + + obj.close = function() { + if (el.classList.contains('jpicker-focus')) { + el.classList.remove('jpicker-focus'); + + // Start tracking the element + jSuites.tracking(obj, false); + + if (typeof obj.options.onclose == 'function') { + obj.options.onclose(el, obj); + } + } + } + + /** + * Create floating picker + */ + var init = function() { + // Class + el.classList.add('jpicker'); + el.setAttribute('tabindex', '900'); + el.onmousedown = function(e) { + if (! el.classList.contains('jpicker-focus')) { + obj.open(); + } + } + + // Dropdown Header + dropdownHeader = document.createElement('div'); + dropdownHeader.classList.add('jpicker-header'); + + // Dropdown content + dropdownContent = document.createElement('div'); + dropdownContent.classList.add('jpicker-content'); + dropdownContent.onclick = function(e) { + var item = jSuites.findElement(e.target, 'jpicker-item'); + if (item) { + if (item.parentNode === dropdownContent) { + // Update label + obj.setValue(item.k); + // Call method + if (typeof(obj.options.onchange) == 'function') { + obj.options.onchange.call(obj, el, obj, item.v, item.v, item.k, e); + } + } + } + } + + // Append content and header + el.appendChild(dropdownHeader); + el.appendChild(dropdownContent); + + // Default value + el.value = options.value || 0; + + // Set options + obj.setOptions(options); + + if (typeof(obj.options.onload) == 'function') { + obj.options.onload(el, obj); + } + + // Change + el.change = obj.setValue; + + // Global generic value handler + el.val = function(val) { + if (val === undefined) { + return obj.getValue(); + } else { + obj.setValue(val); + } + } + + // Reference + el.picker = obj; + } + + init(); + + return obj; +}); + +jSuites.progressbar = (function(el, options) { + var obj = {}; + obj.options = {}; + + // Default configuration + var defaults = { + value: 0, + onchange: null, + width: null, + }; + + // Loop through the initial configuration + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + obj.options[property] = defaults[property]; + } + } + + // Class + el.classList.add('jprogressbar'); + el.setAttribute('tabindex', 1); + el.setAttribute('data-value', obj.options.value); + + var bar = document.createElement('div'); + bar.style.width = obj.options.value + '%'; + bar.style.color = '#fff'; + el.appendChild(bar); + + if (obj.options.width) { + el.style.width = obj.options.width; + } + + // Set value + obj.setValue = function(value) { + value = parseInt(value); + obj.options.value = value; + bar.style.width = value + '%'; + el.setAttribute('data-value', value + '%'); + + if (value < 6) { + el.style.color = '#000'; + } else { + el.style.color = '#fff'; + } + + // Update value + obj.options.value = value; + + if (typeof(obj.options.onchange) == 'function') { + obj.options.onchange(el, value); + } + + // Lemonade JS + if (el.value != obj.options.value) { + el.value = obj.options.value; + if (typeof(el.oninput) == 'function') { + el.oninput({ + type: 'input', + target: el, + value: el.value + }); + } + } + } + + obj.getValue = function() { + return obj.options.value; + } + + var action = function(e) { + if (e.which) { + // Get target info + var rect = el.getBoundingClientRect(); + + if (e.changedTouches && e.changedTouches[0]) { + var x = e.changedTouches[0].clientX; + var y = e.changedTouches[0].clientY; + } else { + var x = e.clientX; + var y = e.clientY; + } + + obj.setValue(Math.round((x - rect.left) / rect.width * 100)); + } + } + + // Events + if ('touchstart' in document.documentElement === true) { + el.addEventListener('touchstart', action); + el.addEventListener('touchend', action); + } else { + el.addEventListener('mousedown', action); + el.addEventListener("mousemove", action); + } + + // Change + el.change = obj.setValue; + + // Global generic value handler + el.val = function(val) { + if (val === undefined) { + return obj.getValue(); + } else { + obj.setValue(val); + } + } + + // Reference + el.progressbar = obj; + + return obj; +}); + +jSuites.rating = (function(el, options) { + // Already created, update options + if (el.rating) { + return el.rating.setOptions(options, true); + } + + // New instance + var obj = {}; + obj.options = {}; + + obj.setOptions = function(options, reset) { + // Default configuration + var defaults = { + number: 5, + value: 0, + tooltip: [ 'Very bad', 'Bad', 'Average', 'Good', 'Very good' ], + onchange: null, + }; + + // Loop through the initial configuration + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + if (typeof(obj.options[property]) == 'undefined' || reset === true) { + obj.options[property] = defaults[property]; + } + } + } + + // Make sure the container is empty + el.innerHTML = ''; + + // Add elements + for (var i = 0; i < obj.options.number; i++) { + var div = document.createElement('div'); + div.setAttribute('data-index', (i + 1)) + div.setAttribute('title', obj.options.tooltip[i]) + el.appendChild(div); + } + + // Selected option + if (obj.options.value) { + for (var i = 0; i < obj.options.number; i++) { + if (i < obj.options.value) { + el.children[i].classList.add('jrating-selected'); + } + } + } + + return obj; + } + + // Set value + obj.setValue = function(index) { + for (var i = 0; i < obj.options.number; i++) { + if (i < index) { + el.children[i].classList.add('jrating-selected'); + } else { + el.children[i].classList.remove('jrating-over'); + el.children[i].classList.remove('jrating-selected'); + } + } + + obj.options.value = index; + + if (typeof(obj.options.onchange) == 'function') { + obj.options.onchange(el, index); + } + + // Lemonade JS + if (el.value != obj.options.value) { + el.value = obj.options.value; + if (typeof(el.oninput) == 'function') { + el.oninput({ + type: 'input', + target: el, + value: el.value + }); + } + } + } + + obj.getValue = function() { + return obj.options.value; + } + + var init = function() { + // Start plugin + obj.setOptions(options); + + // Class + el.classList.add('jrating'); + + // Events + el.addEventListener("click", function(e) { + var index = e.target.getAttribute('data-index'); + if (index != undefined) { + if (index == obj.options.value) { + obj.setValue(0); + } else { + obj.setValue(index); + } + } + }); + + el.addEventListener("mouseover", function(e) { + var index = e.target.getAttribute('data-index'); + for (var i = 0; i < obj.options.number; i++) { + if (i < index) { + el.children[i].classList.add('jrating-over'); + } else { + el.children[i].classList.remove('jrating-over'); + } + } + }); + + el.addEventListener("mouseout", function(e) { + for (var i = 0; i < obj.options.number; i++) { + el.children[i].classList.remove('jrating-over'); + } + }); + + // Change + el.change = obj.setValue; + + // Global generic value handler + el.val = function(val) { + if (val === undefined) { + return obj.getValue(); + } else { + obj.setValue(val); + } + } + + // Reference + el.rating = obj; + } + + init(); + + return obj; +}); + + +jSuites.search = (function(el, options) { + if (el.search) { + return el.search; + } + + var index = null; + + var select = function(e) { + if (e.target.classList.contains('jsearch_item')) { + var element = e.target; + } else { + var element = e.target.parentNode; + } + + obj.selectIndex(element); + e.preventDefault(); + } + + var createList = function(data) { + // Reset container + container.innerHTML = ''; + // Print results + if (! data.length) { + // Show container + el.style.display = ''; + } else { + // Show container + el.style.display = 'block'; + + // Show items (only 10) + var len = data.length < 11 ? data.length : 10; + for (var i = 0; i < len; i++) { + if (typeof(data[i]) == 'string') { + var text = data[i]; + var value = data[i]; + } else { + // Legacy + var text = data[i].text; + if (! text && data[i].name) { + text = data[i].name; + } + var value = data[i].value; + if (! value && data[i].id) { + value = data[i].id; + } + } + + var div = document.createElement('div'); + div.setAttribute('data-value', value); + div.setAttribute('data-text', text); + div.className = 'jsearch_item'; + + if (data[i].id) { + div.setAttribute('id', data[i].id) + } + + if (obj.options.forceSelect && i == 0) { + div.classList.add('selected'); + } + var img = document.createElement('img'); + if (data[i].image) { + img.src = data[i].image; + } else { + img.style.display = 'none'; + } + div.appendChild(img); + + var item = document.createElement('div'); + item.innerHTML = text; + div.appendChild(item); + + // Append item to the container + container.appendChild(div); + } + } + } + + var execute = function(str) { + if (str != obj.terms) { + // New terms + obj.terms = str; + // New index + if (obj.options.forceSelect) { + index = 0; + } else { + index = null; + } + // Array or remote search + if (Array.isArray(obj.options.data)) { + var test = function(o) { + if (typeof(o) == 'string') { + if ((''+o).toLowerCase().search(str.toLowerCase()) >= 0) { + return true; + } + } else { + for (var key in o) { + var value = o[key]; + if ((''+value).toLowerCase().search(str.toLowerCase()) >= 0) { + return true; + } + } + } + return false; + } + + var results = obj.options.data.filter(function(item) { + return test(item); + }); + + // Show items + createList(results); + } else { + // Get remove results + jSuites.ajax({ + url: obj.options.data + str, + method: 'GET', + dataType: 'json', + success: function(data) { + // Show items + createList(data); + } + }); + } + } + } + + // Search timer + var timer = null; + + // Search methods + var obj = function(str) { + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(function() { + execute(str); + }, 500); + } + if(options.forceSelect === null) { + options.forceSelect = true; + } + obj.options = { + data: options.data || null, + input: options.input || null, + searchByNode: options.searchByNode || null, + onselect: options.onselect || null, + forceSelect: options.forceSelect, + onbeforesearch: options.onbeforesearch || null, + }; + + obj.selectIndex = function(item) { + var id = item.getAttribute('id'); + var text = item.getAttribute('data-text'); + var value = item.getAttribute('data-value'); + // Onselect + if (typeof(obj.options.onselect) == 'function') { + obj.options.onselect(obj, text, value, id); + } + // Close container + obj.close(); + } + + obj.open = function() { + el.style.display = 'block'; + } + + obj.close = function() { + if (timer) { + clearTimeout(timer); + } + // Current terms + obj.terms = ''; + // Remove results + container.innerHTML = ''; + // Hide + el.style.display = ''; + } + + obj.isOpened = function() { + return el.style.display ? true : false; + } + + obj.keydown = function(e) { + if (obj.isOpened()) { + if (e.key == 'Enter') { + // Enter + if (index!==null && container.children[index]) { + obj.selectIndex(container.children[index]); + e.preventDefault(); + } else { + obj.close(); + } + } else if (e.key === 'ArrowUp') { + // Up + if (index!==null && container.children[0]) { + container.children[index].classList.remove('selected'); + if(!obj.options.forceSelect && index === 0) { + index = null; + } else { + index = Math.max(0, index-1); + container.children[index].classList.add('selected'); + } + } + e.preventDefault(); + } else if (e.key === 'ArrowDown') { + // Down + if(index == null) { + index = -1; + } else { + container.children[index].classList.remove('selected'); + } + if (index < 9 && container.children[index+1]) { + index++; + } + container.children[index].classList.add('selected'); + e.preventDefault(); + } + } + } + + obj.keyup = function(e) { + if (! obj.options.searchByNode && obj.options.input) { + if (obj.options.input.tagName === 'DIV') { + var terms = obj.options.input.innerText; + } else { + var terms = obj.options.input.value; + } + } else { + // Current node + var node = jSuites.getNode(); + if (node) { + var terms = node.innerText; + } + } + + if (typeof(obj.options.onbeforesearch) == 'function') { + var ret = obj.options.onbeforesearch(obj, terms); + if (ret) { + terms = ret; + } else { + if (ret === false) { + // Ignore event + return; + } + } + } + + obj(terms); + } + + // Add events + if (obj.options.input) { + obj.options.input.addEventListener("keyup", obj.keyup); + obj.options.input.addEventListener("keydown", obj.keydown); + } + + // Append element + var container = document.createElement('div'); + container.classList.add('jsearch_container'); + container.onmousedown = select; + el.appendChild(container); + + el.classList.add('jsearch'); + el.search = obj; + + return obj; +}); + + +jSuites.slider = (function(el, options) { + var obj = {}; + obj.options = {}; + obj.currentImage = null; + + if (options) { + obj.options = options; + } + + // Focus + el.setAttribute('tabindex', '900') + + // Items + obj.options.items = []; + + if (! el.classList.contains('jslider')) { + el.classList.add('jslider'); + el.classList.add('unselectable'); + + if (obj.options.height) { + el.style.minHeight = obj.options.height; + } + if (obj.options.width) { + el.style.width = obj.options.width; + } + if (obj.options.grid) { + el.classList.add('jslider-grid'); + var number = el.children.length; + if (number > 4) { + el.setAttribute('data-total', number - 4); + } + el.setAttribute('data-number', (number > 4 ? 4 : number)); + } + + // Add slider counter + var counter = document.createElement('div'); + counter.classList.add('jslider-counter'); + + // Move children inside + if (el.children.length > 0) { + // Keep children items + for (var i = 0; i < el.children.length; i++) { + obj.options.items.push(el.children[i]); + + // counter click event + var item = document.createElement('div'); + item.onclick = function() { + var index = Array.prototype.slice.call(counter.children).indexOf(this); + obj.show(obj.currentImage = obj.options.items[index]); + } + counter.appendChild(item); + } + } + // Add caption + var caption = document.createElement('div'); + caption.className = 'jslider-caption'; + + // Add close buttom + var controls = document.createElement('div'); + var close = document.createElement('div'); + close.className = 'jslider-close'; + close.innerHTML = ''; + + close.onclick = function() { + obj.close(); + } + controls.appendChild(caption); + controls.appendChild(close); + } + + obj.updateCounter = function(index) { + for (var i = 0; i < counter.children.length; i ++) { + if (counter.children[i].classList.contains('jslider-counter-focus')) { + counter.children[i].classList.remove('jslider-counter-focus'); + break; + } + } + counter.children[index].classList.add('jslider-counter-focus'); + } + + obj.show = function(target) { + if (! target) { + var target = el.children[0]; + } + + // Focus element + el.classList.add('jslider-focus'); + el.classList.remove('jslider-grid'); + el.appendChild(controls); + el.appendChild(counter); + + // Update counter + var index = obj.options.items.indexOf(target); + obj.updateCounter(index); + + // Remove display + for (var i = 0; i < el.children.length; i++) { + el.children[i].style.display = ''; + } + target.style.display = 'block'; + + // Is there any previous + if (target.previousElementSibling) { + el.classList.add('jslider-left'); + } else { + el.classList.remove('jslider-left'); + } + + // Is there any next + if (target.nextElementSibling && target.nextElementSibling.tagName == 'IMG') { + el.classList.add('jslider-right'); + } else { + el.classList.remove('jslider-right'); + } + + obj.currentImage = target; + + // Vertical image + if (obj.currentImage.offsetHeight > obj.currentImage.offsetWidth) { + obj.currentImage.classList.add('jslider-vertical'); + } + + controls.children[0].innerText = obj.currentImage.getAttribute('title'); + } + + obj.open = function() { + obj.show(); + + // Event + if (typeof(obj.options.onopen) == 'function') { + obj.options.onopen(el); + } + } + + obj.close = function() { + // Remove control classes + el.classList.remove('jslider-focus'); + el.classList.remove('jslider-left'); + el.classList.remove('jslider-right'); + // Show as a grid depending on the configuration + if (obj.options.grid) { + el.classList.add('jslider-grid'); + } + // Remove display + for (var i = 0; i < el.children.length; i++) { + el.children[i].style.display = ''; + } + // Remove controls from the component + counter.remove(); + controls.remove(); + // Current image + obj.currentImage = null; + // Event + if (typeof(obj.options.onclose) == 'function') { + obj.options.onclose(el); + } + } + + obj.reset = function() { + el.innerHTML = ''; + } + + obj.next = function() { + var nextImage = obj.currentImage.nextElementSibling; + if (nextImage && nextImage.tagName === 'IMG') { + obj.show(obj.currentImage.nextElementSibling); + } + } + + obj.prev = function() { + if (obj.currentImage.previousElementSibling) { + obj.show(obj.currentImage.previousElementSibling); + } + } + + var mouseUp = function(e) { + // Open slider + if (e.target.tagName == 'IMG') { + obj.show(e.target); + } else if (! e.target.classList.contains('jslider-close') && ! (e.target.parentNode.classList.contains('jslider-counter') || e.target.classList.contains('jslider-counter'))){ + // Arrow controls + var offsetX = e.offsetX || e.changedTouches[0].clientX; + if (e.target.clientWidth - offsetX < 40) { + // Show next image + obj.next(); + } else if (offsetX < 40) { + // Show previous image + obj.prev(); + } + } + } + + if ('ontouchend' in document.documentElement === true) { + el.addEventListener('touchend', mouseUp); + } else { + el.addEventListener('mouseup', mouseUp); + } + + // Add global events + el.addEventListener("swipeleft", function(e) { + obj.next(); + e.preventDefault(); + e.stopPropagation(); + }); + + el.addEventListener("swiperight", function(e) { + obj.prev(); + e.preventDefault(); + e.stopPropagation(); + }); + + el.addEventListener('keydown', function(e) { + if (e.which == 27) { + obj.close(); + } + }); + + el.slider = obj; + + return obj; +}); + +jSuites.sorting = (function(el, options) { + var obj = {}; + obj.options = {}; + + var defaults = { + pointer: null, + direction: null, + ondragstart: null, + ondragend: null, + ondrop: null, + } + + var dragElement = null; + + // Loop through the initial configuration + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + obj.options[property] = defaults[property]; + } + } + + el.classList.add('jsorting'); + + el.addEventListener('dragstart', function(e) { + var position = Array.prototype.indexOf.call(e.target.parentNode.children, e.target); + dragElement = { + element: e.target, + o: position, + d: position + } + e.target.style.opacity = '0.25'; + + if (typeof(obj.options.ondragstart) == 'function') { + obj.options.ondragstart(el, e.target, e); + } + }); + + el.addEventListener('dragover', function(e) { + e.preventDefault(); + + if (getElement(e.target) && dragElement) { + if (e.target.getAttribute('draggable') == 'true' && dragElement.element != e.target) { + if (! obj.options.direction) { + var condition = e.target.clientHeight / 2 > e.offsetY; + } else { + var condition = e.target.clientWidth / 2 > e.offsetX; + } + + if (condition) { + e.target.parentNode.insertBefore(dragElement.element, e.target); + } else { + e.target.parentNode.insertBefore(dragElement.element, e.target.nextSibling); + } + + dragElement.d = Array.prototype.indexOf.call(e.target.parentNode.children, dragElement.element); + } + } + }); + + el.addEventListener('dragleave', function(e) { + e.preventDefault(); + }); + + el.addEventListener('dragend', function(e) { + e.preventDefault(); + + if (dragElement) { + if (typeof(obj.options.ondragend) == 'function') { + obj.options.ondragend(el, dragElement.element, e); + } + + // Cancelled put element to the original position + if (dragElement.o < dragElement.d) { + e.target.parentNode.insertBefore(dragElement.element, e.target.parentNode.children[dragElement.o]); + } else { + e.target.parentNode.insertBefore(dragElement.element, e.target.parentNode.children[dragElement.o].nextSibling); + } + + dragElement.element.style.opacity = ''; + dragElement = null; + } + }); + + el.addEventListener('drop', function(e) { + e.preventDefault(); + + if (dragElement && (dragElement.o != dragElement.d)) { + if (typeof(obj.options.ondrop) == 'function') { + obj.options.ondrop(el, dragElement.o, dragElement.d, dragElement.element, e.target, e); + } + } + + dragElement.element.style.opacity = ''; + dragElement = null; + }); + + var getElement = function(element) { + var sorting = false; + + function path (element) { + if (element.className) { + if (element.classList.contains('jsorting')) { + sorting = true; + } + } + + if (! sorting) { + path(element.parentNode); + } + } + + path(element); + + return sorting; + } + + for (var i = 0; i < el.children.length; i++) { + if (! el.children[i].hasAttribute('draggable')) { + el.children[i].setAttribute('draggable', 'true'); + } + } + + el.val = function() { + var id = null; + var data = []; + for (var i = 0; i < el.children.length; i++) { + if (id = el.children[i].getAttribute('data-id')) { + data.push(id); + } + } + return data; + } + + return el; +}); + +jSuites.tabs = (function(el, options) { + var obj = {}; + obj.options = {}; + + // Default configuration + var defaults = { + data: [], + position: null, + allowCreate: false, + allowChangePosition: false, + onclick: null, + onload: null, + onchange: null, + oncreate: null, + ondelete: null, + onbeforecreate: null, + onchangeposition: null, + animation: false, + hideHeaders: false, + padding: null, + palette: null, + maxWidth: null, + } + + // Loop through the initial configuration + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + obj.options[property] = defaults[property]; + } + } + + // Class + el.classList.add('jtabs'); + + var prev = null; + var next = null; + var border = null; + + // Helpers + var setBorder = function(index) { + if (obj.options.animation) { + setTimeout(function() { + var rect = obj.headers.children[index].getBoundingClientRect(); + + if (obj.options.palette == 'modern') { + border.style.width = rect.width - 4 + 'px'; + border.style.left = obj.headers.children[index].offsetLeft + 2 + 'px'; + } else { + border.style.width = rect.width + 'px'; + border.style.left = obj.headers.children[index].offsetLeft + 'px'; + } + + if (obj.options.position == 'bottom') { + border.style.top = '0px'; + } else { + border.style.bottom = '0px'; + } + }, 150); + } + } + + var updateControls = function(x) { + if (typeof(obj.headers.scrollTo) == 'function') { + obj.headers.scrollTo({ + left: x, + behavior: 'smooth', + }); + } else { + obj.headers.scrollLeft = x; + } + + if (x <= 1) { + prev.classList.add('disabled'); + } else { + prev.classList.remove('disabled'); + } + + if (x >= obj.headers.scrollWidth - obj.headers.offsetWidth) { + next.classList.add('disabled'); + } else { + next.classList.remove('disabled'); + } + + if (obj.headers.scrollWidth <= obj.headers.offsetWidth) { + prev.style.display = 'none'; + next.style.display = 'none'; + } else { + prev.style.display = ''; + next.style.display = ''; + } + } + + obj.setBorder = setBorder; + + // Set value + obj.open = function(index) { + var previous = null; + for (var i = 0; i < obj.headers.children.length; i++) { + if (obj.headers.children[i].classList.contains('jtabs-selected')) { + // Current one + previous = i; + } + // Remote selected + obj.headers.children[i].classList.remove('jtabs-selected'); + if (obj.content.children[i]) { + obj.content.children[i].classList.remove('jtabs-selected'); + } + } + + obj.headers.children[index].classList.add('jtabs-selected'); + if (obj.content.children[index]) { + obj.content.children[index].classList.add('jtabs-selected'); + } + + if (previous != index && typeof(obj.options.onchange) == 'function') { + if (obj.content.children[index]) { + obj.options.onchange(el, obj, index, obj.headers.children[index], obj.content.children[index]); + } + } + + // Hide + if (obj.options.hideHeaders == true && (obj.headers.children.length < 3 && obj.options.allowCreate == false)) { + obj.headers.parentNode.style.display = 'none'; + } else { + // Set border + setBorder(index); + + obj.headers.parentNode.style.display = ''; + + var x1 = obj.headers.children[index].offsetLeft; + var x2 = x1 + obj.headers.children[index].offsetWidth; + var r1 = obj.headers.scrollLeft; + var r2 = r1 + obj.headers.offsetWidth; + + if (! (r1 <= x1 && r2 >= x2)) { + // Out of the viewport + updateControls(x1 - 1); + } + } + } + + obj.selectIndex = function(a) { + var index = Array.prototype.indexOf.call(obj.headers.children, a); + if (index >= 0) { + obj.open(index); + } + + return index; + } + + obj.rename = function(i, title) { + if (! title) { + title = prompt('New title', obj.headers.children[i].innerText); + } + obj.headers.children[i].innerText = title; + obj.open(i); + } + + obj.create = function(title, url) { + if (typeof(obj.options.onbeforecreate) == 'function') { + var ret = obj.options.onbeforecreate(el); + if (ret === false) { + return false; + } else { + title = ret; + } + } + + var div = obj.appendElement(title); + + if (typeof(obj.options.oncreate) == 'function') { + obj.options.oncreate(el, div) + } + + setBorder(); + + return div; + } + + obj.remove = function(index) { + return obj.deleteElement(index); + } + + obj.nextNumber = function() { + var num = 0; + for (var i = 0; i < obj.headers.children.length; i++) { + var tmp = obj.headers.children[i].innerText.match(/[0-9].*/); + if (tmp > num) { + num = parseInt(tmp); + } + } + if (! num) { + num = 1; + } else { + num++; + } + + return num; + } + + obj.deleteElement = function(index) { + if (! obj.headers.children[index]) { + return false; + } else { + obj.headers.removeChild(obj.headers.children[index]); + obj.content.removeChild(obj.content.children[index]); + } + + obj.open(0); + + if (typeof(obj.options.ondelete) == 'function') { + obj.options.ondelete(el, index) + } + } + + obj.appendElement = function(title, cb) { + if (! title) { + var title = prompt('Title?', ''); + } + + if (title) { + // Add content + var div = document.createElement('div'); + obj.content.appendChild(div); + + // Add headers + var h = document.createElement('div'); + h.innerHTML = title; + h.content = div; + obj.headers.insertBefore(h, obj.headers.lastChild); + + // Sortable + if (obj.options.allowChangePosition) { + h.setAttribute('draggable', 'true'); + } + // Open new tab + obj.selectIndex(h); + + // Callback + if (typeof(cb) == 'function') { + cb(div, h); + } + + // Return element + return div; + } + } + + obj.getActive = function() { + for (var i = 0; i < obj.headers.children.length; i++) { + if (obj.headers.children[i].classList.contains('jtabs-selected')) { + return i + } + } + return 0; + } + + obj.updateContent = function(position, newContent) { + if (typeof newContent !== 'string') { + var contentItem = newContent; + } else { + var contentItem = document.createElement('div'); + contentItem.innerHTML = newContent; + } + + if (obj.content.children[position].classList.contains('jtabs-selected')) { + newContent.classList.add('jtabs-selected'); + } + + obj.content.replaceChild(newContent, obj.content.children[position]); + + setBorder(); + } + + obj.updatePosition = function(f, t) { + // Ondrop update position of content + if (f > t) { + obj.content.insertBefore(obj.content.children[f], obj.content.children[t]); + } else { + obj.content.insertBefore(obj.content.children[f], obj.content.children[t].nextSibling); + } + + // Open destination tab + obj.open(t); + + // Call event + if (typeof(obj.options.onchangeposition) == 'function') { + obj.options.onchangeposition(obj.headers, f, t); + } + } + + obj.move = function(f, t) { + if (f > t) { + obj.headers.insertBefore(obj.headers.children[f], obj.headers.children[t]); + } else { + obj.headers.insertBefore(obj.headers.children[f], obj.headers.children[t].nextSibling); + } + + obj.updatePosition(f, t); + } + + obj.setBorder = setBorder; + + obj.init = function() { + el.innerHTML = ''; + + // Make sure the component is blank + obj.headers = document.createElement('div'); + obj.content = document.createElement('div'); + obj.headers.classList.add('jtabs-headers'); + obj.content.classList.add('jtabs-content'); + + if (obj.options.palette) { + el.classList.add('jtabs-modern'); + } else { + el.classList.remove('jtabs-modern'); + } + + // Padding + if (obj.options.padding) { + obj.content.style.padding = parseInt(obj.options.padding) + 'px'; + } + + // Header + var header = document.createElement('div'); + header.className = 'jtabs-headers-container'; + header.appendChild(obj.headers); + if (obj.options.maxWidth) { + header.style.maxWidth = parseInt(obj.options.maxWidth) + 'px'; + } + + // Controls + var controls = document.createElement('div'); + controls.className = 'jtabs-controls'; + controls.setAttribute('draggable', 'false'); + header.appendChild(controls); + + // Append DOM elements + if (obj.options.position == 'bottom') { + el.appendChild(obj.content); + el.appendChild(header); + } else { + el.appendChild(header); + el.appendChild(obj.content); + } + + // New button + if (obj.options.allowCreate == true) { + var add = document.createElement('div'); + add.className = 'jtabs-add'; + add.onclick = function() { + obj.create(); + } + controls.appendChild(add); + } + + prev = document.createElement('div'); + prev.className = 'jtabs-prev'; + prev.onclick = function() { + updateControls(obj.headers.scrollLeft - obj.headers.offsetWidth); + } + controls.appendChild(prev); + + next = document.createElement('div'); + next.className = 'jtabs-next'; + next.onclick = function() { + updateControls(obj.headers.scrollLeft + obj.headers.offsetWidth); + } + controls.appendChild(next); + + // Data + for (var i = 0; i < obj.options.data.length; i++) { + // Title + if (obj.options.data[i].titleElement) { + var headerItem = obj.options.data[i].titleElement; + } else { + var headerItem = document.createElement('div'); + } + // Icon + if (obj.options.data[i].icon) { + var iconContainer = document.createElement('div'); + var icon = document.createElement('i'); + icon.classList.add('material-icons'); + icon.innerHTML = obj.options.data[i].icon; + iconContainer.appendChild(icon); + headerItem.appendChild(iconContainer); + } + // Title + if (obj.options.data[i].title) { + var title = document.createTextNode(obj.options.data[i].title); + headerItem.appendChild(title); + } + // Width + if (obj.options.data[i].width) { + headerItem.style.width = obj.options.data[i].width; + } + // Content + if (obj.options.data[i].contentElement) { + var contentItem = obj.options.data[i].contentElement; + } else { + var contentItem = document.createElement('div'); + contentItem.innerHTML = obj.options.data[i].content; + } + obj.headers.appendChild(headerItem); + obj.content.appendChild(contentItem); + } + + // Animation + border = document.createElement('div'); + border.className = 'jtabs-border'; + obj.headers.appendChild(border); + + if (obj.options.animation) { + el.classList.add('jtabs-animation'); + } + + // Events + obj.headers.addEventListener("click", function(e) { + if (e.target.parentNode.classList.contains('jtabs-headers')) { + var target = e.target; + } else { + if (e.target.tagName == 'I') { + var target = e.target.parentNode.parentNode; + } else { + var target = e.target.parentNode; + } + } + + var index = obj.selectIndex(target); + + if (typeof(obj.options.onclick) == 'function') { + obj.options.onclick(el, obj, index, obj.headers.children[index], obj.content.children[index]); + } + }); + + obj.headers.addEventListener("contextmenu", function(e) { + obj.selectIndex(e.target); + }); + + if (obj.headers.children.length) { + // Open first tab + obj.open(0); + } + + // Update controls + updateControls(0); + + if (obj.options.allowChangePosition == true) { + jSuites.sorting(obj.headers, { + direction: 1, + ondrop: function(a,b,c) { + obj.updatePosition(b,c); + }, + }); + } + + if (typeof(obj.options.onload) == 'function') { + obj.options.onload(el, obj); + } + } + + // Loading existing nodes as the data + if (el.children[0] && el.children[0].children.length) { + // Create from existing elements + for (var i = 0; i < el.children[0].children.length; i++) { + var item = obj.options.data && obj.options.data[i] ? obj.options.data[i] : {}; + + if (el.children[1] && el.children[1].children[i]) { + item.titleElement = el.children[0].children[i]; + item.contentElement = el.children[1].children[i]; + } else { + item.contentElement = el.children[0].children[i]; + } + + obj.options.data[i] = item; + } + } + + // Remote controller flag + var loadingRemoteData = false; + + // Create from data + if (obj.options.data) { + // Append children + for (var i = 0; i < obj.options.data.length; i++) { + if (obj.options.data[i].url) { + jSuites.ajax({ + url: obj.options.data[i].url, + type: 'GET', + dataType: 'text/html', + index: i, + success: function(result) { + obj.options.data[this.index].content = result; + }, + complete: function() { + obj.init(); + } + }); + + // Flag loading + loadingRemoteData = true; + } + } + } + + if (! loadingRemoteData) { + obj.init(); + } + + el.tabs = obj; + + return obj; +}); + +jSuites.tags = (function(el, options) { + // Redefine configuration + if (el.tags) { + return el.tags.setOptions(options, true); + } + + var obj = { type:'tags' }; + obj.options = {}; + + // Limit + var limit = function() { + return obj.options.limit && el.children.length >= obj.options.limit ? true : false; + } + + // Search helpers + var search = null; + var searchContainer = null; + + obj.setOptions = function(options, reset) { + /** + * @typedef {Object} defaults + * @property {(string|Array)} value - Initial value of the compontent + * @property {number} limit - Max number of tags inside the element + * @property {string} search - The URL for suggestions + * @property {string} placeholder - The default instruction text on the element + * @property {validation} validation - Method to validate the tags + * @property {requestCallback} onbeforechange - Method to be execute before any changes on the element + * @property {requestCallback} onchange - Method to be execute after any changes on the element + * @property {requestCallback} onfocus - Method to be execute when on focus + * @property {requestCallback} onblur - Method to be execute when on blur + * @property {requestCallback} onload - Method to be execute when the element is loaded + */ + var defaults = { + value: '', + limit: null, + limitMessage: null, + search: null, + placeholder: null, + validation: null, + onbeforepaste: null, + onbeforechange: null, + onlimit: null, + onchange: null, + onfocus: null, + onblur: null, + onload: null, + colors: null, + } + + // Loop through though the default configuration + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + if (typeof(obj.options[property]) == 'undefined' || reset === true) { + obj.options[property] = defaults[property]; + } + } + } + + // Placeholder + if (obj.options.placeholder) { + el.setAttribute('data-placeholder', obj.options.placeholder); + } else { + el.removeAttribute('data-placeholder'); + } + el.placeholder = obj.options.placeholder; + + // Update value + obj.setValue(obj.options.value); + + // Validate items + filter(); + + // Create search box + if (obj.options.search) { + if (! searchContainer) { + searchContainer = document.createElement('div'); + el.parentNode.insertBefore(searchContainer, el.nextSibling); + + // Create container + search = jSuites.search(searchContainer, { + data: obj.options.search, + onselect: function(a,b,c) { + obj.selectIndex(b,c); + } + }); + } + } else { + if (searchContainer) { + search = null; + searchContainer.remove(); + searchContainer = null; + } + } + + return obj; + } + + /** + * Add a new tag to the element + * @param {(?string|Array)} value - The value of the new element + */ + obj.add = function(value, focus) { + if (typeof(obj.options.onbeforechange) == 'function') { + var ret = obj.options.onbeforechange(el, obj, obj.options.value, value); + if (ret === false) { + return false; + } else { + if (ret != null) { + value = ret; + } + } + } + + // Make sure search is closed + if (search) { + search.close(); + } + + if (limit()) { + if (typeof(obj.options.onlimit) == 'function') { + obj.options.onlimit(obj, obj.options.limit); + } else if (obj.options.limitMessage) { + alert(obj.options.limitMessage + ' ' + obj.options.limit); + } + } else { + // Get node + var node = jSuites.getNode(); + + if (node && node.parentNode && node.parentNode.classList.contains('jtags') && + node.nextSibling && (! (node.nextSibling.innerText && node.nextSibling.innerText.trim()))) { + div = node.nextSibling; + } else { + // Remove not used last item + if (el.lastChild) { + if (! el.lastChild.innerText.trim()) { + el.removeChild(el.lastChild); + } + } + + // Mix argument string or array + if (! value || typeof(value) == 'string') { + var div = createElement(value, value, node); + } else { + for (var i = 0; i <= value.length; i++) { + if (! limit()) { + if (! value[i] || typeof(value[i]) == 'string') { + var t = value[i] || ''; + var v = null; + } else { + var t = value[i].text; + var v = value[i].value; + } + + // Add element + var div = createElement(t, v); + } + } + } + + // Change + change(); + } + + // Place caret + if (focus) { + setFocus(div); + } + } + } + + obj.setLimit = function(limit) { + obj.options.limit = limit; + var n = el.children.length - limit; + while (el.children.length > limit) { + el.removeChild(el.lastChild); + } + } + + // Remove a item node + obj.remove = function(node) { + // Remove node + node.parentNode.removeChild(node); + // Make sure element is not blank + if (! el.children.length) { + obj.add('', true); + } else { + change(); + } + } + + /** + * Get all tags in the element + * @return {Array} data - All tags as an array + */ + obj.getData = function() { + var data = []; + for (var i = 0; i < el.children.length; i++) { + // Get value + var text = el.children[i].innerText.replace("\n", ""); + // Get id + var value = el.children[i].getAttribute('data-value'); + if (! value) { + value = text; + } + // Item + if (text || value) { + data.push({ text: text, value: value }); + } + } + return data; + } + + /** + * Get the value of one tag. Null for all tags + * @param {?number} index - Tag index number. Null for all tags. + * @return {string} value - All tags separated by comma + */ + obj.getValue = function(index) { + var value = null; + + if (index != null) { + // Get one individual value + value = el.children[index].getAttribute('data-value'); + if (! value) { + value = el.children[index].innerText.replace("\n", ""); + } + } else { + // Get all + var data = []; + for (var i = 0; i < el.children.length; i++) { + value = el.children[i].innerText.replace("\n", ""); + if (value) { + data.push(obj.getValue(i)); + } + } + value = data.join(','); + } + + return value; + } + + /** + * Set the value of the element based on a string separeted by (,|;|\r\n) + * @param {mixed} value - A string or array object with values + */ + obj.setValue = function(mixed) { + if (! mixed) { + obj.reset(); + } else { + if (el.value != mixed) { + if (Array.isArray(mixed)) { + obj.add(mixed); + } else { + // Remove whitespaces + var text = (''+mixed).trim(); + // Tags + var data = extractTags(text); + // Reset + el.innerHTML = ''; + // Add tags to the element + obj.add(data); + } + } + } + } + + /** + * Reset the data from the element + */ + obj.reset = function() { + // Empty class + el.classList.add('jtags-empty'); + // Empty element + el.innerHTML = '
'; + // Execute changes + change(); + } + + /** + * Verify if all tags in the element are valid + * @return {boolean} + */ + obj.isValid = function() { + var test = 0; + for (var i = 0; i < el.children.length; i++) { + if (el.children[i].classList.contains('jtags_error')) { + test++; + } + } + return test == 0 ? true : false; + } + + /** + * Add one element from the suggestions to the element + * @param {object} item - Node element in the suggestions container + */ + obj.selectIndex = function(text, value) { + var node = jSuites.getNode(); + if (node) { + // Append text to the caret + node.innerText = text; + // Set node id + if (value) { + node.setAttribute('data-value', value); + } + // Remove any error + node.classList.remove('jtags_error'); + if (! limit()) { + // Add new item + obj.add('', true); + } + } + } + + /** + * Search for suggestions + * @param {object} node - Target node for any suggestions + */ + obj.search = function(node) { + // Search for + var terms = node.innerText; + } + + // Destroy tags element + obj.destroy = function() { + // Bind events + el.removeEventListener('mouseup', tagsMouseUp); + el.removeEventListener('keydown', tagsKeyDown); + el.removeEventListener('keyup', tagsKeyUp); + el.removeEventListener('paste', tagsPaste); + el.removeEventListener('focus', tagsFocus); + el.removeEventListener('blur', tagsBlur); + + // Remove element + el.parentNode.removeChild(el); + } + + var setFocus = function(node) { + if (el.children.length) { + var range = document.createRange(); + var sel = window.getSelection(); + if (! node) { + var node = el.childNodes[el.childNodes.length-1]; + } + range.setStart(node, node.length) + range.collapse(true) + sel.removeAllRanges() + sel.addRange(range) + el.scrollLeft = el.scrollWidth; + } + } + + var createElement = function(label, value, node) { + var div = document.createElement('div'); + div.innerHTML = label ? label : ''; + if (value) { + div.setAttribute('data-value', value); + } + + if (node && node.parentNode.classList.contains('jtags')) { + el.insertBefore(div, node.nextSibling); + } else { + el.appendChild(div); + } + + return div; + } + + var change = function() { + // Value + var value = obj.getValue(); + + if (value != obj.options.value) { + obj.options.value = value; + if (typeof(obj.options.onchange) == 'function') { + obj.options.onchange(el, obj, obj.options.value); + } + + // Lemonade JS + if (el.value != obj.options.value) { + el.value = obj.options.value; + if (typeof(el.oninput) == 'function') { + el.oninput({ + type: 'input', + target: el, + value: el.value + }); + } + } + } + + filter(); + } + + /** + * Filter tags + */ + var filter = function() { + for (var i = 0; i < el.children.length; i++) { + if (el.children[i].tagName === 'DIV') { + // Create label design + if (!obj.getValue(i)) { + el.children[i].classList.remove('jtags_label'); + } else { + el.children[i].classList.add('jtags_label'); + + // Validation in place + if (typeof (obj.options.validation) == 'function') { + if (obj.getValue(i)) { + if (!obj.options.validation(el.children[i], el.children[i].innerText, el.children[i].getAttribute('data-value'))) { + el.children[i].classList.add('jtags_error'); + } else { + el.children[i].classList.remove('jtags_error'); + } + } else { + el.children[i].classList.remove('jtags_error'); + } + } else { + el.children[i].classList.remove('jtags_error'); + } + } + } + } + + isEmpty(); + } + + var isEmpty = function() { + // Can't be empty + if (! el.innerText.trim()) { + if (! el.children.length || el.children[0].tagName === 'BR') { + el.innerHTML = ''; + setFocus(createElement()); + } + } else { + el.classList.remove('jtags-empty'); + } + } + + /** + * Extract tags from a string + * @param {string} text - Raw string + * @return {Array} data - Array with extracted tags + */ + var extractTags = function(text) { + /** @type {Array} */ + var data = []; + + /** @type {string} */ + var word = ''; + + // Remove whitespaces + text = text.trim(); + + if (text) { + for (var i = 0; i < text.length; i++) { + if (text[i] == ',' || text[i] == ';' || text[i] == '\n') { + if (word) { + data.push(word.trim()); + word = ''; + } + } else { + word += text[i]; + } + } + + if (word) { + data.push(word); + } + } + + return data; + } + + /** @type {number} */ + var anchorOffset = 0; + + /** + * Processing event keydown on the element + * @param e {object} + */ + var tagsKeyDown = function(e) { + // Anchoroffset + anchorOffset = window.getSelection().anchorOffset; + + // Verify if is empty + isEmpty(); + + // Comma + if (e.key === 'Tab' || e.key === ';' || e.key === ',') { + var n = window.getSelection().anchorOffset; + if (n > 1) { + if (limit()) { + if (typeof(obj.options.onlimit) == 'function') { + obj.options.onlimit(obj, obj.options.limit) + } + } else { + obj.add('', true); + } + } + e.preventDefault(); + } else if (e.key == 'Enter') { + if (! search || ! search.isOpened()) { + var n = window.getSelection().anchorOffset; + if (n > 1) { + if (! limit()) { + obj.add('', true); + } + } + e.preventDefault(); + } + } else if (e.key == 'Backspace') { + // Back space - do not let last item to be removed + if (el.children.length == 1 && window.getSelection().anchorOffset < 1) { + e.preventDefault(); + } + } + + // Search events + if (search) { + search.keydown(e); + } + + // Verify if is empty + isEmpty(); + } + + /** + * Processing event keyup on the element + * @param e {object} + */ + var tagsKeyUp = function(e) { + if (e.which == 39) { + // Right arrow + var n = window.getSelection().anchorOffset; + if (n > 1 && n == anchorOffset) { + obj.add('', true); + } + } else if (e.which == 13 || e.which == 38 || e.which == 40) { + e.preventDefault(); + } else { + if (search) { + search.keyup(e); + } + } + + filter(); + } + + /** + * Processing event paste on the element + * @param e {object} + */ + var tagsPaste = function(e) { + if (e.clipboardData || e.originalEvent.clipboardData) { + var text = (e.originalEvent || e).clipboardData.getData('text/plain'); + } else if (window.clipboardData) { + var text = window.clipboardData.getData('Text'); + } + + var data = extractTags(text); + + if (typeof(obj.options.onbeforepaste) == 'function') { + var ret = obj.options.onbeforepaste(el, obj, data); + if (ret === false) { + e.preventDefault(); + return false; + } else { + if (ret) { + data = ret; + } + } + } + + if (data.length > 1) { + obj.add(data, true); + e.preventDefault(); + } else if (data[0]) { + document.execCommand('insertText', false, data[0]) + e.preventDefault(); + } + } + + /** + * Processing event mouseup on the element + * @param e {object} + */ + var tagsMouseUp = function(e) { + if (e.target.parentNode && e.target.parentNode.classList.contains('jtags')) { + if (e.target.classList.contains('jtags_label') || e.target.classList.contains('jtags_error')) { + var rect = e.target.getBoundingClientRect(); + if (rect.width - (e.clientX - rect.left) < 16) { + obj.remove(e.target); + } + } + } + + // Set focus in the last item + if (e.target == el) { + setFocus(); + } + } + + var tagsFocus = function() { + if (! el.classList.contains('jtags-focus')) { + if (! el.children.length || obj.getValue(el.children.length - 1)) { + if (! limit()) { + createElement(''); + } + } + + if (typeof(obj.options.onfocus) == 'function') { + obj.options.onfocus(el, obj, obj.getValue()); + } + + el.classList.add('jtags-focus'); + } + } + + var tagsBlur = function() { + if (el.classList.contains('jtags-focus')) { + if (search) { + search.close(); + } + + for (var i = 0; i < el.children.length - 1; i++) { + // Create label design + if (! obj.getValue(i)) { + el.removeChild(el.children[i]); + } + } + + change(); + + el.classList.remove('jtags-focus'); + + if (typeof(obj.options.onblur) == 'function') { + obj.options.onblur(el, obj, obj.getValue()); + } + } + } + + var init = function() { + // Bind events + if ('touchend' in document.documentElement === true) { + el.addEventListener('touchend', tagsMouseUp); + } else { + el.addEventListener('mouseup', tagsMouseUp); + } + + el.addEventListener('keydown', tagsKeyDown); + el.addEventListener('keyup', tagsKeyUp); + el.addEventListener('paste', tagsPaste); + el.addEventListener('focus', tagsFocus); + el.addEventListener('blur', tagsBlur); + + // Editable + el.setAttribute('contenteditable', true); + + // Prepare container + el.classList.add('jtags'); + + // Initial options + obj.setOptions(options); + + if (typeof(obj.options.onload) == 'function') { + obj.options.onload(el, obj); + } + + // Change methods + el.change = obj.setValue; + + // Global generic value handler + el.val = function(val) { + if (val === undefined) { + return obj.getValue(); + } else { + obj.setValue(val); + } + } + + el.tags = obj; + } + + init(); + + return obj; +}); + +jSuites.toolbar = (function(el, options) { + // New instance + var obj = { type:'toolbar' }; + obj.options = {}; + + // Default configuration + var defaults = { + app: null, + container: false, + badge: false, + title: false, + responsive: false, + maxWidth: null, + bottom: true, + items: [], + } + + // Loop through our object + for (var property in defaults) { + if (options && options.hasOwnProperty(property)) { + obj.options[property] = options[property]; + } else { + obj.options[property] = defaults[property]; + } + } + + if (! el && options.app && options.app.el) { + el = document.createElement('div'); + options.app.el.appendChild(el); + } + + // Arrow + var toolbarArrow = document.createElement('div'); + toolbarArrow.classList.add('jtoolbar-item'); + toolbarArrow.classList.add('jtoolbar-arrow'); + + var toolbarFloating = document.createElement('div'); + toolbarFloating.classList.add('jtoolbar-floating'); + toolbarArrow.appendChild(toolbarFloating); + + obj.selectItem = function(element) { + var elements = toolbarContent.children; + for (var i = 0; i < elements.length; i++) { + if (element != elements[i]) { + elements[i].classList.remove('jtoolbar-selected'); + } + } + element.classList.add('jtoolbar-selected'); + } + + obj.hide = function() { + jSuites.animation.slideBottom(el, 0, function() { + el.style.display = 'none'; + }); + } + + obj.show = function() { + el.style.display = ''; + jSuites.animation.slideBottom(el, 1); + } + + obj.get = function() { + return el; + } + + obj.setBadge = function(index, value) { + toolbarContent.children[index].children[1].firstChild.innerHTML = value; + } + + obj.destroy = function() { + toolbar.remove(); + el.innerHTML = ''; + } + + obj.update = function(a, b) { + for (var i = 0; i < toolbarContent.children.length; i++) { + // Toolbar element + var toolbarItem = toolbarContent.children[i]; + // State management + if (typeof(toolbarItem.updateState) == 'function') { + toolbarItem.updateState(el, obj, toolbarItem, a, b); + } + } + for (var i = 0; i < toolbarFloating.children.length; i++) { + // Toolbar element + var toolbarItem = toolbarFloating.children[i]; + // State management + if (typeof(toolbarItem.updateState) == 'function') { + toolbarItem.updateState(el, obj, toolbarItem, a, b); + } + } + } + + obj.create = function(items) { + // Reset anything in the toolbar + toolbarContent.innerHTML = ''; + // Create elements in the toolbar + for (var i = 0; i < items.length; i++) { + var toolbarItem = document.createElement('div'); + toolbarItem.classList.add('jtoolbar-item'); + + if (items[i].width) { + toolbarItem.style.width = parseInt(items[i].width) + 'px'; + } + + if (items[i].k) { + toolbarItem.k = items[i].k; + } + + if (items[i].tooltip) { + toolbarItem.setAttribute('title', items[i].tooltip); + } + + // Id + if (items[i].id) { + toolbarItem.setAttribute('id', items[i].id); + } + + // Selected + if (items[i].updateState) { + toolbarItem.updateState = items[i].updateState; + } + + if (items[i].active) { + toolbarItem.classList.add('jtoolbar-active'); + } + + if (items[i].disabled) { + toolbarItem.classList.add('jtoolbar-disabled'); + } + + if (items[i].type == 'select' || items[i].type == 'dropdown') { + jSuites.picker(toolbarItem, items[i]); + } else if (items[i].type == 'divisor') { + toolbarItem.classList.add('jtoolbar-divisor'); + } else if (items[i].type == 'label') { + toolbarItem.classList.add('jtoolbar-label'); + toolbarItem.innerHTML = items[i].content; + } else { + // Material icons + var toolbarIcon = document.createElement('i'); + if (typeof(items[i].class) === 'undefined') { + toolbarIcon.classList.add('material-icons'); + } else { + var c = items[i].class.split(' '); + for (var j = 0; j < c.length; j++) { + toolbarIcon.classList.add(c[j]); + } + } + toolbarIcon.innerHTML = items[i].content ? items[i].content : ''; + toolbarItem.appendChild(toolbarIcon); + + // Badge options + if (obj.options.badge == true) { + var toolbarBadge = document.createElement('div'); + toolbarBadge.classList.add('jbadge'); + var toolbarBadgeContent = document.createElement('div'); + toolbarBadgeContent.innerHTML = items[i].badge ? items[i].badge : ''; + toolbarBadge.appendChild(toolbarBadgeContent); + toolbarItem.appendChild(toolbarBadge); + } + + // Title + if (items[i].title) { + if (obj.options.title == true) { + var toolbarTitle = document.createElement('span'); + toolbarTitle.innerHTML = items[i].title; + toolbarItem.appendChild(toolbarTitle); + } else { + toolbarItem.setAttribute('title', items[i].title); + } + } + + if (obj.options.app && items[i].route) { + // Route + toolbarItem.route = items[i].route; + // Onclick for route + toolbarItem.onclick = function() { + obj.options.app.pages(this.route); + } + // Create pages + obj.options.app.pages(items[i].route, { + toolbarItem: toolbarItem, + closed: true + }); + } + } + + if (items[i].onclick) { + toolbarItem.onclick = items[i].onclick.bind(items[i], el, obj, toolbarItem); + } + + toolbarContent.appendChild(toolbarItem); + } + + // Fits to the page + setTimeout(function() { + obj.refresh(); + }, 0); + } + + obj.open = function() { + toolbarArrow.classList.add('jtoolbar-arrow-selected'); + + var rectElement = el.getBoundingClientRect(); + var rect = toolbarFloating.getBoundingClientRect(); + if (rect.bottom > window.innerHeight || obj.options.bottom) { + toolbarFloating.style.bottom = '0'; + } else { + toolbarFloating.style.removeProperty('bottom'); + } + + toolbarFloating.style.right = '0'; + + toolbarArrow.children[0].focus(); + // Start tracking + jSuites.tracking(obj, true); + } + + obj.close = function() { + toolbarArrow.classList.remove('jtoolbar-arrow-selected') + // End tracking + jSuites.tracking(obj, false); + } + + obj.refresh = function() { + if (obj.options.responsive == true) { + // Width of the c + var rect = el.parentNode.getBoundingClientRect(); + if (! obj.options.maxWidth) { + obj.options.maxWidth = rect.width; + } + // Available parent space + var available = parseInt(obj.options.maxWidth); + // Remove arrow + if (toolbarArrow.parentNode) { + toolbarArrow.parentNode.removeChild(toolbarArrow); + } + // Move all items to the toolbar + while (toolbarFloating.firstChild) { + toolbarContent.appendChild(toolbarFloating.firstChild); + } + // Toolbar is larger than the parent, move elements to the floating element + if (available < toolbarContent.offsetWidth) { + // Give space to the floating element + available -= 50; + // Move to the floating option + while (toolbarContent.lastChild && available < toolbarContent.offsetWidth) { + toolbarFloating.insertBefore(toolbarContent.lastChild, toolbarFloating.firstChild); + } + } + // Show arrow + if (toolbarFloating.children.length > 0) { + toolbarContent.appendChild(toolbarArrow); + } + } + } + + obj.setReadonly = function(state) { + state = state ? 'add' : 'remove'; + el.classList[state]('jtoolbar-disabled'); + } + + el.onclick = function(e) { + var element = jSuites.findElement(e.target, 'jtoolbar-item'); + if (element) { + obj.selectItem(element); + } + + if (e.target.classList.contains('jtoolbar-arrow')) { + obj.open(); + } + } + + window.addEventListener('resize', function() { + obj.refresh(); + }); + + // Toolbar + el.classList.add('jtoolbar'); + // Reset content + el.innerHTML = ''; + // Container + if (obj.options.container == true) { + el.classList.add('jtoolbar-container'); + } + // Content + var toolbarContent = document.createElement('div'); + el.appendChild(toolbarContent); + // Special toolbar for mobile applications + if (obj.options.app) { + el.classList.add('jtoolbar-mobile'); + } + // Create toolbar + obj.create(obj.options.items); + // Shortcut + el.toolbar = obj; + + return obj; +}); + +jSuites.validations = (function() { + /** + * Options: Object, + * Properties: + * Constraint, + * Reference, + * Value + */ + + var isNumeric = function(num) { + return !isNaN(num) && num !== null && num !== ''; + } + + var numberCriterias = { + 'between': function(value, range) { + return value >= range[0] && value <= range[1]; + }, + 'not between': function(value, range) { + return value < range[0] || value > range[1]; + }, + '<': function(value, range) { + return value < range[0]; + }, + '<=': function(value, range) { + return value <= range[0]; + }, + '>': function(value, range) { + return value > range[0]; + }, + '>=': function(value, range) { + return value >= range[0]; + }, + '=': function(value, range) { + return value == range[0]; + }, + '!=': function(value, range) { + return value != range[0]; + }, + } + + var dateCriterias = { + 'valid date': function() { + return true; + }, + '=': function(value, range) { + return value === range[0]; + }, + '<': function(value, range) { + return value < range[0]; + }, + '<=': function(value, range) { + return value <= range[0]; + }, + '>': function(value, range) { + return value > range[0]; + }, + '>=': function(value, range) { + return value >= range[0]; + }, + 'between': function(value, range) { + return value >= range[0] && value <= range[1]; + }, + 'not between': function(value, range) { + return value < range[0] || value > range[1]; + }, + } + + var textCriterias = { + 'contains': function(value, range) { + return value.includes(range[0]); + }, + 'not contains': function(value, range) { + return !value.includes(range[0]); + }, + 'begins with': function(value, range) { + return value.startsWith(range[0]); + }, + 'ends with': function(value, range) { + return value.endsWith(range[0]); + }, + '=': function(value, range) { + return value === range[0]; + }, + 'valid email': function(value) { + var pattern = new RegExp(/^[^\s@]+@[^\s@]+\.[^\s@]+$/); + + return pattern.test(value); + }, + 'valid url': function(value) { + var pattern = new RegExp(/(((https?:\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]+)/ig); + + return pattern.test(value); + }, + } + + // Component router + var component = function(value, options) { + if (typeof(component[options.type]) === 'function') { + if (options.allowBlank && value === '') { + return true; + } + + return component[options.type](value, options); + } + return null; + } + + component.url = function() { + var pattern = new RegExp(/(((https?:\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]+)/ig); + return pattern.test(data) ? true : false; + } + + component.email = function(data) { + var pattern = new RegExp(/^[^\s@]+@[^\s@]+\.[^\s@]+$/); + return data && pattern.test(data) ? true : false; + } + + component.required = function(data) { + return data.trim() ? true : false; + } + + component.exist = function(data, options) { + return !!data.toString(); + } + + component['not exist'] = function(data, options) { + return !data.toString(); + } + + component.empty = function(data) { + return !data.toString(); + } + + component.notEmpty = function(data) { + return !!data.toString(); + } + + component.number = function(data, options) { + if (! isNumeric(data)) { + return false; + } + + if (!options || !options.criteria) { + return true; + } + + if (!numberCriterias[options.criteria]) { + return false; + } + + var values = options.value.map(function(num) { + return parseFloat(num); + }) + + return numberCriterias[options.criteria](data, values); + }; + + component.login = function(data) { + var pattern = new RegExp(/^[a-zA-Z0-9\_\-\.\s+]+$/); + return data && pattern.test(data) ? true : false; + } + + component.list = function(data, options) { + var dataType = typeof data; + if (dataType !== 'string' && dataType !== 'number') { + return false; + } + if (typeof(options.value[0]) === 'string') { + var list = options.value[0].split(','); + } else { + var list = options.value[0]; + } + + var validOption = list.findIndex(function name(item) { + return item == data; + }); + + return validOption > -1; + } + + component.date = function(data, options) { + if (new Date(data) == 'Invalid Date') { + return false; + } + + if (!options || !options.criteria) { + return true; + } + + if (!dateCriterias[options.criteria]) { + return false; + } + + var values = options.value.map(function(date) { + return new Date(date).getTime(); + }); + + return dateCriterias[options.criteria](new Date(data).getTime(), values); + } + + component.text = function(data, options) { + if (typeof data !== 'string') { + return false; + } + + if (!options || !options.criteria) { + return true; + } + + if (!textCriterias[options.criteria]) { + return false; + } + + return textCriterias[options.criteria](data, options.value); + } + + component.textLength = function(data, options) { + data = data.toString(); + + return component.number(data.length, options); + } + + return component; +})(); + + + + return jSuites; + +}))); diff --git a/script/table_builder_libraries.bash b/script/table_builder_libraries.bash new file mode 100755 index 00000000000..951dbd54d5e --- /dev/null +++ b/script/table_builder_libraries.bash @@ -0,0 +1,97 @@ +#!/bin/bash + +# Script used for updating depencies for the table builder/editor feature. +# Updates the JSpreadsheet and jSuites libraries to the latest available versions. + +# Get the directory of the script +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Construct paths relative to the script directory +SCSS_VENDOR="$SCRIPT_DIR/../app/assets/stylesheets/common/table-builder/vendor/" +JSPREADSHEET_VENDOR="$SCRIPT_DIR/../public/javascripts/jspreadsheet/" +JSUITES_VENDOR="$SCRIPT_DIR/../public/javascripts/jsuites/" + +JSUITES_JS_URL="https://jsuites.net/v4/jsuites.js" +JSPREADSHEET_JS_URL="https://bossanova.uk/jspreadsheet/v4/jexcel.js" + +JSUITES_CSS_URL="https://raw.githubusercontent.com/jsuites/jsuites/master/dist/jsuites.css" +JSPREADSHEET_CSS_URL="https://bossanova.uk/jspreadsheet/v4/jexcel.css" + +JSUITES_CSS_FILE=jsuites.css +JSUITES_SCSS_FILE=jsuites.scss +JSUITES_SCSS_FILE_LOCATION=$SCSS_VENDOR$JSUITES_SCSS_FILE + +JSUITES_JS_FILE=jsuites.js +JSUITES_NEW_JS_FILE=jsuites.js +JSUITES_JS_FILE_LOCATION=$JSUITES_VENDOR$JSUITES_NEW_JS_FILE + +JSPREADSHEET_CSS_FILE=jexcel.css +JSPREADSHEET_SCSS_FILE=jspreadsheet.scss +JSPREADSHEET_SCSS_FILE_LOCATION=$SCSS_VENDOR$JSPREADSHEET_SCSS_FILE + +JSPREADSHEET_JS_FILE=jexcel.js +JSPREADSHEET_NEW_JS_FILE=jspreadsheet.js +JSPREADSHEET_JS_FILE_LOCATION=$JSPREADSHEET_VENDOR$JSPREADSHEET_NEW_JS_FILE + + + + +# Remove all vendor related files: +rm -r ${SCSS_VENDOR}* +rm -r ${JSUITES_VENDOR} +rm -r ${JSPREADSHEET_VENDOR} + +# Recreate vendor directory +mkdir $JSUITES_VENDOR +mkdir $JSPREADSHEET_VENDOR +echo "Old vendor assets have been removed." + + +# STYLESHEETS: +# Add JSuite vendor file +if test -f "$JSUITES_CSS_FILE"; then + echo "$JSUITES_CSS_FILE already exists." +else + # Fetch jsuite stylesheet + wget $JSUITES_CSS_URL + echo "$JSUITES_CSS_FILE has been created in $(pwd)" + # Move jsuite stylesheet to vendor as a scss file + mv $JSUITES_CSS_FILE $JSUITES_SCSS_FILE_LOCATION + echo "$JSUITES_SCSS_FILE has been placed in the scss vendor directory" + # Scope styles to jexcel_container class + sed -i '' '1s/^/.jexcel_container {\n/' $JSUITES_SCSS_FILE_LOCATION + sed -i '' '$a\ + }' $JSUITES_SCSS_FILE_LOCATION + + # Remove conflicting animation classes + # TODO: Improve below code to handle nested code blocks +fi + +# Add JSpreadsheet vendor file +if test -f "$JSPREADSHEET_CSS_FILE"; then + echo "$JSPREADSHEET_CSS_FILE already exists." +else + # Fetch jspreadsheet stylesheet + wget $JSPREADSHEET_CSS_URL + echo "$JSPREADSHEET_CSS_FILE has been created in $(pwd)" + # Move jspreadsheet stylesheet to vendor as a scss file + mv $JSPREADSHEET_CSS_FILE $JSPREADSHEET_SCSS_FILE_LOCATION +fi + +# Apply prettier to vendor files +yarn prettier --write $SCSS_VENDOR + +# JAVASCRIPTS: +if test -f "$JSUITES_JS_FILE"; then + echo "$JSUITES_JS_FILE already exists." +else + wget $JSUITES_JS_URL + mv $JSUITES_JS_FILE $JSUITES_JS_FILE_LOCATION +fi + +if test -f "$JSPREADSHEET_JS_FILE"; then + echo "$JSPREADSHEET_JS_FILE already exists." +else + wget $JSPREADSHEET_JS_URL + mv $JSPREADSHEET_JS_FILE $JSPREADSHEET_JS_FILE_LOCATION +fi diff --git a/spec/system/page_objects/modals/insert_table.rb b/spec/system/page_objects/modals/insert_table.rb new file mode 100644 index 00000000000..9e12d462d22 --- /dev/null +++ b/spec/system/page_objects/modals/insert_table.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module PageObjects + module Modals + class InsertTable < PageObjects::Modals::Base + MODAL_SELECTOR = ".insert-table-modal" + SPREADSHEET_TABLE_SELECTOR = "#{MODAL_SELECTOR} .jexcel" + + def click_insert_table + find("#{MODAL_SELECTOR} .btn-insert-table").click + end + + def cancel + find("#{MODAL_SELECTOR} .d-modal-cancel").click + end + + def click_edit_reason + find("#{MODAL_SELECTOR} .btn-edit-reason").click + end + + def type_edit_reason(text) + find("#{MODAL_SELECTOR} .edit-reason input").send_keys(text) + end + + def find_cell(row, col) + find("#{SPREADSHEET_TABLE_SELECTOR} tbody tr[data-y='#{row}'] td[data-x='#{col}']") + end + + def select_cell(row, col) + find_cell(row, col).double_click + end + + def type_in_cell(row, col, text) + select_cell(row, col) + cell = find_cell(row, col).find("textarea") + cell.send_keys(text, :return) + end + + def has_content_in_cell?(row, col, content) + find_cell(row, col).text == content + end + end + end +end diff --git a/spec/system/table_builder_spec.rb b/spec/system/table_builder_spec.rb new file mode 100644 index 00000000000..ff18795efd8 --- /dev/null +++ b/spec/system/table_builder_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +describe "Table Builder", type: :system do + fab!(:user) + let(:composer) { PageObjects::Components::Composer.new } + let(:insert_table_modal) { PageObjects::Modals::InsertTable.new } + fab!(:topic) { Fabricate(:topic, user: user) } + fab!(:post1) { create_post(user: user, topic: topic, raw: <<~RAW) } + |Make | Model | Year| + |-------| ------- | ----| + |Toyota | Supra | 1998| + |Nissan | Skyline | 1999| + |Honda | S2000 | 2001| + RAW + + let(:topic_page) { PageObjects::Pages::Topic.new } + + before { sign_in(user) } + + def normalize_value(content) + content.strip.gsub(/\s+/, " ").gsub(/\r\n/, "\n") + end + + context "when creating a new table" do + it "should add table items created in spreadsheet to composer input" do + visit("/latest") + page.find("#create-topic").click + page.find(".toolbar-popup-menu-options").click + page.find(".select-kit-row[data-name='Insert Table']").click + insert_table_modal.type_in_cell(0, 0, "Item 1") + insert_table_modal.type_in_cell(0, 1, "Item 2") + insert_table_modal.type_in_cell(0, 2, "Item 3") + insert_table_modal.type_in_cell(0, 3, "Item 4") + insert_table_modal.click_insert_table + + created_table = <<~TABLE + |Column 1 | Column 2 | Column 3 | Column 4| + |--- | --- | --- | ---| + |Item 1 | Item 2 | Item 3 | Item 4| + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + TABLE + + expect(normalize_value(composer.composer_input.value)).to eq(normalize_value(created_table)) + end + + context "when cancelling table creation" do + it "should close the modal if there are no changes made" do + visit("/latest") + page.find("#create-topic").click + page.find(".toolbar-popup-menu-options").click + page.find(".select-kit-row[data-name='Insert Table']").click + insert_table_modal.cancel + expect(page).to have_no_css(".insert-table-modal") + end + + it "should show a warning popup if there are unsaved changes" do + visit("/latest") + page.find("#create-topic").click + page.find(".toolbar-popup-menu-options").click + page.find(".select-kit-row[data-name='Insert Table']").click + insert_table_modal.type_in_cell(0, 0, "Item 1") + insert_table_modal.cancel + expect(page).to have_css(".dialog-container .dialog-content") + end + end + end + + context "when editing a table" do + it "should prefill the spreadsheet with the markdown table items from the post" do + topic_page.visit_topic(topic) + topic_page.find(".btn-edit-table", visible: :all).click + expect(page).to have_selector(".insert-table-modal") + + expected_table_content = [ + %w[Toyota Supra 1998], + %w[Nissan Skyline 1999], + %w[Honda S2000 2001], + ] + + expected_table_content.each_with_index do |row, row_index| + row.each_with_index do |content, col_index| + expect(insert_table_modal).to have_content_in_cell(row_index, col_index, content) + end + end + end + + it "should update the post with the new table content" do + topic_page.visit_topic(topic) + topic_page.find(".btn-edit-table", visible: :all).click + expect(page).to have_selector(".insert-table-modal") + insert_table_modal.type_in_cell(1, 1, " GTR") + insert_table_modal.click_insert_table + + updated_post = <<~RAW + |Make | Model | Year| + |-------| ------- | ----| + |Toyota | Supra | 1998| + |Nissan | Skyline | 1999| + |Honda | S2000 | 2001| + RAW + + expect(normalize_value(post1.reload.raw)).to eq(normalize_value(updated_post)) + end + + context "when adding an edit reason" do + it "should add the edit reason to the edit history" do + edit_reason = "Updated Nissan model" + + topic_page.visit_topic(topic) + topic_page.find(".btn-edit-table", visible: :all).click + expect(page).to have_selector(".insert-table-modal") + insert_table_modal.type_in_cell(1, 1, " GTR") + insert_table_modal.click_edit_reason + insert_table_modal.type_edit_reason(edit_reason) + insert_table_modal.click_insert_table + wait_for { post1.reload.edit_reason == edit_reason } + expect(post1.reload.edit_reason).to eq(edit_reason) + end + end + + context "when cancelling table creation" do + it "should close the modal if there are no changes made" do + topic_page.visit_topic(topic) + topic_page.find(".btn-edit-table", visible: :all).click + expect(page).to have_selector(".insert-table-modal") + insert_table_modal.cancel + expect(page).to have_no_css(".insert-table-modal") + end + + it "should show a warning popup if there are unsaved changes" do + topic_page.visit_topic(topic) + topic_page.find(".btn-edit-table", visible: :all).click + expect(page).to have_selector(".insert-table-modal") + insert_table_modal.type_in_cell(1, 1, " GTR") + insert_table_modal.cancel + expect(page).to have_css(".dialog-container .dialog-content") + end + end + end +end