diff --git a/.gitignore b/.gitignore index c3b59cd..8cefcb4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.psd +*.pdf diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8600fc6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://127.0.0.1:5500", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d009129 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "json.schemas": [ + + { + "fileMatch": [ + "data/spells/*.json" + ], + "url": "/./.vscode/spells-schemas.json" + } + ], +} \ No newline at end of file diff --git a/.vscode/spells-schemas.json b/.vscode/spells-schemas.json new file mode 100644 index 0000000..b5fada0 --- /dev/null +++ b/.vscode/spells-schemas.json @@ -0,0 +1,306 @@ +{ + "$comment": "TODO: Subdivide the schema to have a common definition for each data (spells, books, creatures)", + "definitions": {}, + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/object1678401116.json", + "title": "Root", + "type": "object", + "required": ["spell"], + "properties": { + "spell": { + "$id": "#root/spell", + "title": "Spell", + "type": "array", + "default": [], + "items": { + "$id": "#root/spell/items", + "title": "Items", + "type": "object", + "required": [ + "recid", + "name", + "source", + "page", + "srd", + "level", + "school", + "time", + "range", + "components", + "duration", + "desc" + ], + "properties": { + "recid": { + "$id": "#root/spell/items/recid", + "title": "Recid", + "type": "integer", + "examples": [0], + "default": 0, + "description": "unique id for the spell, for localization purpose", + "uniqueItems": true, + "autoIncrement": true + }, + "name": { + "$id": "#root/spell/items/name", + "title": "Name", + "type": "string", + "default": "", + "examples": ["Acid Arrow"], + "pattern": "^.*$" + }, + "source": { + "$id": "#root/spell/items/source", + "title": "Source", + "description": "what's the source. Each enum is a book code", + "type": "string", + "default": "GRI01", + "enum": ["GRI01", "ENC01", "CRE01", "GRI02", "ENC02", "CRE02"], + "examples": ["GRI01"], + "pattern": "^.*$" + }, + "page": { + "$id": "#root/spell/items/page", + "description": "in the book, page source", + "title": "Page", + "type": "integer", + "examples": [110], + "default": 0 + }, + "srd": { + "$id": "#root/spell/items/srd", + "description": "is srd or a creation", + "title": "Srd", + "type": "boolean", + "examples": [true], + "default": true + }, + "level": { + "$id": "#root/spell/items/level", + "title": "Level", + "type": "integer", + "examples": [2], + "default": 0 + }, + "school": { + "$id": "#root/spell/items/school", + "description": " 'A' : Abjuration,'C' : Conjuration, 'D': Divination, 'E': Enchantment, 'N': Necromancy, 'T' : Transmutation, 'I' : Illusion, 'V' : Evocation", + "enum": ["A", "C", "D", "E", "I", "N", "T", "V"], + "title": "School", + "type": "string", + "examples": ["E"], + "pattern": "^.*$" + }, + "time": { + "$id": "#root/spell/items/time", + "title": "Time", + "type": "array", + "default": [], + "items": { + "$id": "#root/spell/items/time/items", + "title": "Items", + "type": "object", + "required": ["number", "unit"], + "properties": { + "number": { + "$id": "#root/spell/items/time/items/number", + "title": "Number", + "type": "integer", + "examples": [1], + "default": 0 + }, + "unit": { + "$id": "#root/spell/items/time/items/unit", + "title": "Unit", + "type": "string", + "default": "", + "examples": ["action"], + "pattern": "^.*$" + } + }, + "dependentRequired": { + "number": ["unit"] + } + } + }, + "range": { + "$id": "#root/spell/items/range", + "title": "Range", + "type": "object", + "required": ["type", "distance"], + "properties": { + "type": { + "$id": "#root/spell/items/range/type", + "title": "Type", + "type": "string", + "default": "point", + "examples": ["point"], + "pattern": "^.*$", + "enum": ["point", "cone", "sphere", "cylinder", "special"], + "description": "point by default, but can be cone, sphere, cylinder, special " + }, + "distance": { + "$id": "#root/spell/items/range/distance", + "title": "Distance", + "type": "object", + "required": ["type", "amount"], + "properties": { + "type": { + "$id": "#root/spell/items/range/distance/type", + "title": "Type", + "type": "string", + "default": "", + "examples": ["feet"], + "pattern": "^.*$" + }, + "amount": { + "$id": "#root/spell/items/range/distance/amount", + "title": "Amount", + "type": "integer", + "examples": [90], + "default": 0 + } + } + } + } + }, + "components": { + "$id": "#root/spell/items/components", + "title": "Components", + "type": "object", + "properties": { + "v": { + "$id": "#root/spell/items/components/v", + "title": "V", + "type": "boolean", + "examples": [true], + "default": true + }, + "s": { + "$id": "#root/spell/items/components/s", + "title": "S", + "type": "boolean", + "examples": [true], + "default": true + }, + "m": { + "$id": "#root/spell/items/components/m", + "title": "M", + "type": "string", + "default": "", + "examples": ["powdered rhubarb leaf and an adder’s stomach"], + "pattern": "^.*$" + } + } + }, + "duration": { + "$id": "#root/spell/items/duration", + "title": "Duration", + "type": "array", + "default": [], + "items": { + "$id": "#root/spell/items/duration/items", + "title": "Items", + "type": "object", + "required": ["type"], + "properties": { + "type": { + "$id": "#root/spell/items/duration/items/type", + "title": "Type", + "type": "string", + "enum": ["timed", "permanent", "special", "instant"], + "default": "", + "examples": ["instant"], + "pattern": "^.*$" + } + } + } + }, + "concentration": { + "$id": "#root/spell/items/concentration", + "title": "Concentration", + "type": "boolean", + "default": false + }, + "desc": { + "$id": "#root/spell/items/desc", + "title": "Desc", + "type": "array", + "default": [], + "items": { + "$id": "#root/spell/items/desc/items", + "title": "Items", + "type": "string", + "description": "A description for the entry. Do not copy-paste gameplay infos to prevent copyright", + "default": "", + "examples": [ + "A shimmering green arrow streaks toward a target within range and bursts in a spray of acid." + ], + "pattern": "^.*$" + } + }, + "damageInflict": { + "$id": "#root/spell/items/damageInflict", + "title": "Damageinflict", + "type": "array", + "default": [], + "items": { + "$id": "#root/spell/items/damageInflict/items", + "title": "Items", + "type": "string", + "default": "", + "examples": ["acid"], + "pattern": "^.*$" + } + }, + "savingThrow": { + "$id": "#root/spell/items/savingThrow", + "title": "Savingthrow", + "description": "array of saving Throw available", + "type": "array", + "default": [], + "items": { + "$id": "#root/spell/items/savingThrow/items", + "title": "Items", + "type": "string", + "default": "", + "enum": ["Dex", "Str", "Sag", "Cha", "Int", "Con"], + "examples": [""], + "pattern": "^.*$", + "minItems": 1, + "uniqueItems": true + } + }, + "miscTags": { + "$id": "#root/spell/items/miscTags", + "title": "Misctags", + "type": "array", + "default": [], + "items": { + "$id": "#root/spell/items/miscTags/items", + "title": "Items", + "type": "string", + "default": "", + "examples": [""], + "pattern": "^.*$" + } + }, + "areaTags": { + "$id": "#root/spell/items/areaTags", + "title": "Areatags", + "type": "array", + "default": [], + "items": { + "$id": "#root/spell/items/areaTags/items", + "title": "Items", + "type": "string", + "default": "", + "examples": [""], + "pattern": "^.*$" + } + } + } + } + } + } +} diff --git a/README b/README deleted file mode 100644 index b063ed3..0000000 --- a/README +++ /dev/null @@ -1,37 +0,0 @@ -## Spells - -### JSON structure - -| field | type | value | -| ------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| id | _number_ | unique id for the spell, for localization purpose | -| name | _string_ | spell's name | -| source | _number_ | where the spell is coming from | -| page | _number_ | page source | -| srd | _boolean_ | is this a srd or a creation | -| level | _number_ | spell's level | -| school | _string_ | spell school : "A" : Abjuration,"C" : Conjuration, "D": Divination, "E": Enchantment, "N": Necromancy, "T" : Transmutation, "I" : Illusion, "V" : Evocation | -| time | _array_ | incantation time | -| time.number | _number_ | | -| time.unit | _string_ | | -| range | _module_ | | -| range.type | _string_ | point by default, but can be cone, sphere, cylinder, special | -| range.distance | _module_ | | -| range.distance.type | _string_ | feet or meter or other unit measure | -| range.distance.amount | _number_ | amount of distance in `range.distance.type` | -| components | _module_ | | -| components.v | _boolean_ | verbal component | -| components.s | _boolean_ | somatic component | -| components.m | _string_ | materials components | -| duration | _array_ | | -| duration.type | _string_ | timed, permanent, special or instant | -| duration.duration | _module_ | | -| duration.duration.type | _string_ | minute or hours | -| duration.duration.amount | _number_ | amount of `duration.duration.type` | -| concentration | _boolena_ | does spell need concentration ? | -| desc | _array_ | markdown description of the spell | -| damageInflict | _string_ | | -| conditionInflict | _string_ | | -| savingThrow | _array_ | array of string of saving Throw available | -| miscTags | _array_ | | -| | | | diff --git a/README.md b/README.md new file mode 100644 index 0000000..56a71ef --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# FateforgeTools +A suite of tools for Fateforge players and Masters. + +### Lib +- bryntum-grid +- water.css +- data localize +- jquery +- w2ui \ No newline at end of file diff --git a/books.html b/books.html index 5a468a6..a31044d 100644 --- a/books.html +++ b/books.html @@ -25,7 +25,9 @@ Français -

FateforgeTools

+ +

FateforgeTools

+
diff --git a/css/style.css b/css/style.css index ae823ab..bc17a22 100644 --- a/css/style.css +++ b/css/style.css @@ -123,4 +123,40 @@ } .flickity-button { background: #588597; + } +.container { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: stretch; + } +.list-col{ + flex-grow: 2; + min-width: 600px; +} +.view-col { + flex-grow: 1; +} +@media screen and (min-width: 768px) { + + .list-col { + margin-right: 1%; + } + .view-col { + + max-width: 600px; + } +} + +.w2ui-grid-data { + color: black; + word-wrap: normal; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data > div { + overflow: visible; + white-space: break-spaces; +} +.w2ui-grid .w2ui-grid-body table { + +} \ No newline at end of file diff --git a/css/water.css b/css/water.css new file mode 100644 index 0000000..1e29f3d --- /dev/null +++ b/css/water.css @@ -0,0 +1,1691 @@ +/** + * Water.css by kognise + * https://github.com/kognise/water.css + * Automatic version: + * Uses light theme by default but switches to dark theme + * if a system-wide theme preference is set on the user's device. + */ + + :root { + --background-body: #fff; + --background: #efefef; + --background-alt: #f7f7f7; + --selection: #9e9e9e; + --text-main: #363636; + --text-bright: #000; + --text-muted: #70777f; + --links: #0076d1; + --focus: #0096bfab; + --border: #dbdbdb; + --code: #000; + --animation-duration: 0.1s; + --button-base: #d0cfcf; + --button-hover: #9b9b9b; + --scrollbar-thumb: rgb(170, 170, 170); + --scrollbar-thumb-hover: var(--button-hover); + --form-placeholder: #949494; + --form-text: #1d1d1d; + --variable: #39a33c; + --highlight: #ff0; + --select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E"); + } + + @media (prefers-color-scheme: dark) { + :root { + --background-body: #202b38; + --background: #161f27; + --background-alt: #1a242f; + --selection: #1c76c5; + --text-main: #dbdbdb; + --text-bright: #fff; + --text-muted: #a9b1ba; + --links: #41adff; + --focus: #0096bfab; + --border: #526980; + --code: #ffbe85; + --animation-duration: 0.1s; + --button-base: #0c151c; + --button-hover: #040a0f; + --scrollbar-thumb: var(--button-hover); + --scrollbar-thumb-hover: rgb(0, 0, 0); + --form-placeholder: #a9a9a9; + --form-text: #fff; + --variable: #d941e2; + --highlight: #efdb43; + --select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E"); + } + } + + html { + scrollbar-color: rgb(170, 170, 170) #fff; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + scrollbar-width: thin; + } + + @media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } + } + + @media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } + } + + @media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } + } + + @media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } + } + + @media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } + } + + @media (prefers-color-scheme: dark) { + + html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + } + } + + body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif; + line-height: 1.4; + /*max-width: 800px;*/ + margin: 20px auto; + padding: 0 5%; + word-wrap: break-word; + color: #363636; + color: var(--text-main); + background: #fff; + background: var(--background-body); + text-rendering: optimizeLegibility; + } + + @media (prefers-color-scheme: dark) { + + body { + background: #202b38; + background: var(--background-body); + } + } + + @media (prefers-color-scheme: dark) { + + body { + color: #dbdbdb; + color: var(--text-main); + } + } + + button { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; + } + + @media (prefers-color-scheme: dark) { + + button { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; + } + } + + input { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; + } + + @media (prefers-color-scheme: dark) { + + input { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; + } + } + + textarea { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; + } + + @media (prefers-color-scheme: dark) { + + textarea { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; + } + } + + h1 { + font-size: 2.2em; + margin-top: 0; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + margin-bottom: 12px; + margin-top: 24px; + } + + h1 { + color: #000; + color: var(--text-bright); + } + + @media (prefers-color-scheme: dark) { + + h1 { + color: #fff; + color: var(--text-bright); + } + } + + h2 { + color: #000; + color: var(--text-bright); + } + + @media (prefers-color-scheme: dark) { + + h2 { + color: #fff; + color: var(--text-bright); + } + } + + h3 { + color: #000; + color: var(--text-bright); + } + + @media (prefers-color-scheme: dark) { + + h3 { + color: #fff; + color: var(--text-bright); + } + } + + h4 { + color: #000; + color: var(--text-bright); + } + + @media (prefers-color-scheme: dark) { + + h4 { + color: #fff; + color: var(--text-bright); + } + } + + h5 { + color: #000; + color: var(--text-bright); + } + + @media (prefers-color-scheme: dark) { + + h5 { + color: #fff; + color: var(--text-bright); + } + } + + h6 { + color: #000; + color: var(--text-bright); + } + + @media (prefers-color-scheme: dark) { + + h6 { + color: #fff; + color: var(--text-bright); + } + } + + strong { + color: #000; + color: var(--text-bright); + } + + @media (prefers-color-scheme: dark) { + + strong { + color: #fff; + color: var(--text-bright); + } + } + + h1, + h2, + h3, + h4, + h5, + h6, + b, + strong, + th { + font-weight: 600; + } + + q::before { + content: none; + } + + q::after { + content: none; + } + + blockquote { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + margin: 1.5em 0; + padding: 0.5em 1em; + font-style: italic; + } + + @media (prefers-color-scheme: dark) { + + blockquote { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + } + } + + q { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + margin: 1.5em 0; + padding: 0.5em 1em; + font-style: italic; + } + + @media (prefers-color-scheme: dark) { + + q { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + } + } + + blockquote > footer { + font-style: normal; + border: 0; + } + + blockquote cite { + font-style: normal; + } + + address { + font-style: normal; + } + + a[href^='mailto\:']::before { + content: '📧 '; + } + + a[href^='tel\:']::before { + content: '📞 '; + } + + a[href^='sms\:']::before { + content: '💬 '; + } + + mark { + background-color: #ff0; + background-color: var(--highlight); + border-radius: 2px; + padding: 0 2px 0 2px; + color: #000; + } + + @media (prefers-color-scheme: dark) { + + mark { + background-color: #efdb43; + background-color: var(--highlight); + } + } + + a > code, + a > strong { + color: inherit; + } + + button, + select, + input[type='submit'], + input[type='reset'], + input[type='button'], + input[type='checkbox'], + input[type='range'], + input[type='radio'] { + cursor: pointer; + } + + input, + select { + display: block; + } + + [type='checkbox'], + [type='radio'] { + display: initial; + } + + input { + color: #1d1d1d; + color: var(--form-text); + background-color: #efefef; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; + } + + @media (prefers-color-scheme: dark) { + + input { + background-color: #161f27; + background-color: var(--background); + } + } + + @media (prefers-color-scheme: dark) { + + input { + color: #fff; + color: var(--form-text); + } + } + + button { + color: #1d1d1d; + color: var(--form-text); + background-color: #efefef; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; + } + + @media (prefers-color-scheme: dark) { + + button { + background-color: #161f27; + background-color: var(--background); + } + } + + @media (prefers-color-scheme: dark) { + + button { + color: #fff; + color: var(--form-text); + } + } + + textarea { + color: #1d1d1d; + color: var(--form-text); + background-color: #efefef; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; + } + + @media (prefers-color-scheme: dark) { + + textarea { + background-color: #161f27; + background-color: var(--background); + } + } + + @media (prefers-color-scheme: dark) { + + textarea { + color: #fff; + color: var(--form-text); + } + } + + select { + color: #1d1d1d; + color: var(--form-text); + background-color: #efefef; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; + } + + @media (prefers-color-scheme: dark) { + + select { + background-color: #161f27; + background-color: var(--background); + } + } + + @media (prefers-color-scheme: dark) { + + select { + color: #fff; + color: var(--form-text); + } + } + + button { + background-color: #d0cfcf; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; + } + + @media (prefers-color-scheme: dark) { + + button { + background-color: #0c151c; + background-color: var(--button-base); + } + } + + input[type='submit'] { + background-color: #d0cfcf; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; + } + + @media (prefers-color-scheme: dark) { + + input[type='submit'] { + background-color: #0c151c; + background-color: var(--button-base); + } + } + + input[type='reset'] { + background-color: #d0cfcf; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; + } + + @media (prefers-color-scheme: dark) { + + input[type='reset'] { + background-color: #0c151c; + background-color: var(--button-base); + } + } + + input[type='button'] { + background-color: #d0cfcf; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; + } + + @media (prefers-color-scheme: dark) { + + input[type='button'] { + background-color: #0c151c; + background-color: var(--button-base); + } + } + + button:hover { + background: #9b9b9b; + background: var(--button-hover); + } + + @media (prefers-color-scheme: dark) { + + button:hover { + background: #040a0f; + background: var(--button-hover); + } + } + + input[type='submit']:hover { + background: #9b9b9b; + background: var(--button-hover); + } + + @media (prefers-color-scheme: dark) { + + input[type='submit']:hover { + background: #040a0f; + background: var(--button-hover); + } + } + + input[type='reset']:hover { + background: #9b9b9b; + background: var(--button-hover); + } + + @media (prefers-color-scheme: dark) { + + input[type='reset']:hover { + background: #040a0f; + background: var(--button-hover); + } + } + + input[type='button']:hover { + background: #9b9b9b; + background: var(--button-hover); + } + + @media (prefers-color-scheme: dark) { + + input[type='button']:hover { + background: #040a0f; + background: var(--button-hover); + } + } + + input[type='color'] { + min-height: 2rem; + padding: 8px; + cursor: pointer; + } + + input[type='checkbox'], + input[type='radio'] { + height: 1em; + width: 1em; + } + + input[type='radio'] { + border-radius: 100%; + } + + input { + vertical-align: top; + } + + label { + vertical-align: middle; + margin-bottom: 4px; + display: inline-block; + } + + input:not([type='checkbox']):not([type='radio']), + input[type='range'], + select, + button, + textarea { + -webkit-appearance: none; + } + + textarea { + display: block; + margin-right: 0; + box-sizing: border-box; + resize: vertical; + } + + textarea:not([cols]) { + width: 100%; + } + + textarea:not([rows]) { + min-height: 40px; + height: 140px; + } + + select { + background: #efefef url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + padding-right: 35px; + } + + @media (prefers-color-scheme: dark) { + + select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + } + } + + @media (prefers-color-scheme: dark) { + + select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + } + } + + @media (prefers-color-scheme: dark) { + + select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + } + } + + @media (prefers-color-scheme: dark) { + + select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + } + } + + select::-ms-expand { + display: none; + } + + select[multiple] { + padding-right: 10px; + background-image: none; + overflow-y: auto; + } + + input:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } + + @media (prefers-color-scheme: dark) { + + input:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } + } + + select:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } + + @media (prefers-color-scheme: dark) { + + select:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } + } + + button:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } + + @media (prefers-color-scheme: dark) { + + button:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } + } + + textarea:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } + + @media (prefers-color-scheme: dark) { + + textarea:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); + } + } + + input[type='checkbox']:active, + input[type='radio']:active, + input[type='submit']:active, + input[type='reset']:active, + input[type='button']:active, + input[type='range']:active, + button:active { + transform: translateY(2px); + } + + input:disabled, + select:disabled, + button:disabled, + textarea:disabled { + cursor: not-allowed; + opacity: 0.5; + } + + ::-moz-placeholder { + color: #949494; + color: var(--form-placeholder); + } + + :-ms-input-placeholder { + color: #949494; + color: var(--form-placeholder); + } + + ::-ms-input-placeholder { + color: #949494; + color: var(--form-placeholder); + } + + ::placeholder { + color: #949494; + color: var(--form-placeholder); + } + + @media (prefers-color-scheme: dark) { + + ::-moz-placeholder { + color: #a9a9a9; + color: var(--form-placeholder); + } + + :-ms-input-placeholder { + color: #a9a9a9; + color: var(--form-placeholder); + } + + ::-ms-input-placeholder { + color: #a9a9a9; + color: var(--form-placeholder); + } + + ::placeholder { + color: #a9a9a9; + color: var(--form-placeholder); + } + } + + fieldset { + border: 1px #0096bfab solid; + border: 1px var(--focus) solid; + border-radius: 6px; + margin: 0; + margin-bottom: 12px; + padding: 10px; + } + + @media (prefers-color-scheme: dark) { + + fieldset { + border: 1px #0096bfab solid; + border: 1px var(--focus) solid; + } + } + + legend { + font-size: 0.9em; + font-weight: 600; + } + + input[type='range'] { + margin: 10px 0; + padding: 10px 0; + background: transparent; + } + + input[type='range']:focus { + outline: none; + } + + input[type='range']::-webkit-slider-runnable-track { + width: 100%; + height: 9.5px; + -webkit-transition: 0.2s; + transition: 0.2s; + background: #efefef; + background: var(--background); + border-radius: 3px; + } + + @media (prefers-color-scheme: dark) { + + input[type='range']::-webkit-slider-runnable-track { + background: #161f27; + background: var(--background); + } + } + + input[type='range']::-webkit-slider-thumb { + box-shadow: 0 1px 1px #000, 0 0 1px #0d0d0d; + height: 20px; + width: 20px; + border-radius: 50%; + background: #dbdbdb; + background: var(--border); + -webkit-appearance: none; + margin-top: -7px; + } + + @media (prefers-color-scheme: dark) { + + input[type='range']::-webkit-slider-thumb { + background: #526980; + background: var(--border); + } + } + + input[type='range']:focus::-webkit-slider-runnable-track { + background: #efefef; + background: var(--background); + } + + @media (prefers-color-scheme: dark) { + + input[type='range']:focus::-webkit-slider-runnable-track { + background: #161f27; + background: var(--background); + } + } + + input[type='range']::-moz-range-track { + width: 100%; + height: 9.5px; + -moz-transition: 0.2s; + transition: 0.2s; + background: #efefef; + background: var(--background); + border-radius: 3px; + } + + @media (prefers-color-scheme: dark) { + + input[type='range']::-moz-range-track { + background: #161f27; + background: var(--background); + } + } + + input[type='range']::-moz-range-thumb { + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; + height: 20px; + width: 20px; + border-radius: 50%; + background: #dbdbdb; + background: var(--border); + } + + @media (prefers-color-scheme: dark) { + + input[type='range']::-moz-range-thumb { + background: #526980; + background: var(--border); + } + } + + input[type='range']::-ms-track { + width: 100%; + height: 9.5px; + background: transparent; + border-color: transparent; + border-width: 16px 0; + color: transparent; + } + + input[type='range']::-ms-fill-lower { + background: #efefef; + background: var(--background); + border: 0.2px solid #010101; + border-radius: 3px; + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; + } + + @media (prefers-color-scheme: dark) { + + input[type='range']::-ms-fill-lower { + background: #161f27; + background: var(--background); + } + } + + input[type='range']::-ms-fill-upper { + background: #efefef; + background: var(--background); + border: 0.2px solid #010101; + border-radius: 3px; + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; + } + + @media (prefers-color-scheme: dark) { + + input[type='range']::-ms-fill-upper { + background: #161f27; + background: var(--background); + } + } + + input[type='range']::-ms-thumb { + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; + border: 1px solid #000; + height: 20px; + width: 20px; + border-radius: 50%; + background: #dbdbdb; + background: var(--border); + } + + @media (prefers-color-scheme: dark) { + + input[type='range']::-ms-thumb { + background: #526980; + background: var(--border); + } + } + + input[type='range']:focus::-ms-fill-lower { + background: #efefef; + background: var(--background); + } + + @media (prefers-color-scheme: dark) { + + input[type='range']:focus::-ms-fill-lower { + background: #161f27; + background: var(--background); + } + } + + input[type='range']:focus::-ms-fill-upper { + background: #efefef; + background: var(--background); + } + + @media (prefers-color-scheme: dark) { + + input[type='range']:focus::-ms-fill-upper { + background: #161f27; + background: var(--background); + } + } + + a { + text-decoration: none; + color: #0076d1; + color: var(--links); + } + + @media (prefers-color-scheme: dark) { + + a { + color: #41adff; + color: var(--links); + } + } + + a:hover { + text-decoration: underline; + } + + code { + background: #efefef; + background: var(--background); + color: #000; + color: var(--code); + padding: 2.5px 5px; + border-radius: 6px; + font-size: 1em; + } + + @media (prefers-color-scheme: dark) { + + code { + color: #ffbe85; + color: var(--code); + } + } + + @media (prefers-color-scheme: dark) { + + code { + background: #161f27; + background: var(--background); + } + } + + samp { + background: #efefef; + background: var(--background); + color: #000; + color: var(--code); + padding: 2.5px 5px; + border-radius: 6px; + font-size: 1em; + } + + @media (prefers-color-scheme: dark) { + + samp { + color: #ffbe85; + color: var(--code); + } + } + + @media (prefers-color-scheme: dark) { + + samp { + background: #161f27; + background: var(--background); + } + } + + time { + background: #efefef; + background: var(--background); + color: #000; + color: var(--code); + padding: 2.5px 5px; + border-radius: 6px; + font-size: 1em; + } + + @media (prefers-color-scheme: dark) { + + time { + color: #ffbe85; + color: var(--code); + } + } + + @media (prefers-color-scheme: dark) { + + time { + background: #161f27; + background: var(--background); + } + } + + pre > code { + padding: 10px; + display: block; + overflow-x: auto; + } + + var { + color: #39a33c; + color: var(--variable); + font-style: normal; + font-family: monospace; + } + + @media (prefers-color-scheme: dark) { + + var { + color: #d941e2; + color: var(--variable); + } + } + + kbd { + background: #efefef; + background: var(--background); + border: 1px solid #dbdbdb; + border: 1px solid var(--border); + border-radius: 2px; + color: #363636; + color: var(--text-main); + padding: 2px 4px 2px 4px; + } + + @media (prefers-color-scheme: dark) { + + kbd { + color: #dbdbdb; + color: var(--text-main); + } + } + + @media (prefers-color-scheme: dark) { + + kbd { + border: 1px solid #526980; + border: 1px solid var(--border); + } + } + + @media (prefers-color-scheme: dark) { + + kbd { + background: #161f27; + background: var(--background); + } + } + + img, + video { + max-width: 100%; + height: auto; + } + + hr { + border: none; + border-top: 1px solid #dbdbdb; + border-top: 1px solid var(--border); + } + + @media (prefers-color-scheme: dark) { + + hr { + border-top: 1px solid #526980; + border-top: 1px solid var(--border); + } + } + + table { + border-collapse: collapse; + margin-bottom: 10px; + width: 100%; + table-layout: fixed; + } + + table caption { + text-align: left; + } + + td, + th { + padding: 6px; + text-align: left; + vertical-align: top; + word-wrap: break-word; + } + + thead { + border-bottom: 1px solid #dbdbdb; + border-bottom: 1px solid var(--border); + } + + @media (prefers-color-scheme: dark) { + + thead { + border-bottom: 1px solid #526980; + border-bottom: 1px solid var(--border); + } + } + + tfoot { + border-top: 1px solid #dbdbdb; + border-top: 1px solid var(--border); + } + + @media (prefers-color-scheme: dark) { + + tfoot { + border-top: 1px solid #526980; + border-top: 1px solid var(--border); + } + } + + tbody tr:nth-child(even) { + background-color: #efefef; + background-color: var(--background); + } + + @media (prefers-color-scheme: dark) { + + tbody tr:nth-child(even) { + background-color: #161f27; + background-color: var(--background); + } + } + + tbody tr:nth-child(even) button { + background-color: #f7f7f7; + background-color: var(--background-alt); + } + + @media (prefers-color-scheme: dark) { + + tbody tr:nth-child(even) button { + background-color: #1a242f; + background-color: var(--background-alt); + } + } + + tbody tr:nth-child(even) button:hover { + background-color: #fff; + background-color: var(--background-body); + } + + @media (prefers-color-scheme: dark) { + + tbody tr:nth-child(even) button:hover { + background-color: #202b38; + background-color: var(--background-body); + } + } + + ::-webkit-scrollbar { + height: 10px; + width: 10px; + } + + ::-webkit-scrollbar-track { + background: #efefef; + background: var(--background); + border-radius: 6px; + } + + @media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-track { + background: #161f27; + background: var(--background); + } + } + + ::-webkit-scrollbar-thumb { + background: rgb(170, 170, 170); + background: var(--scrollbar-thumb); + border-radius: 6px; + } + + @media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-thumb { + background: #040a0f; + background: var(--scrollbar-thumb); + } + } + + @media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-thumb { + background: #040a0f; + background: var(--scrollbar-thumb); + } + } + + ::-webkit-scrollbar-thumb:hover { + background: #9b9b9b; + background: var(--scrollbar-thumb-hover); + } + + @media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-thumb:hover { + background: rgb(0, 0, 0); + background: var(--scrollbar-thumb-hover); + } + } + + @media (prefers-color-scheme: dark) { + + ::-webkit-scrollbar-thumb:hover { + background: rgb(0, 0, 0); + background: var(--scrollbar-thumb-hover); + } + } + + ::-moz-selection { + background-color: #9e9e9e; + background-color: var(--selection); + color: #000; + color: var(--text-bright); + } + + ::selection { + background-color: #9e9e9e; + background-color: var(--selection); + color: #000; + color: var(--text-bright); + } + + @media (prefers-color-scheme: dark) { + + ::-moz-selection { + color: #fff; + color: var(--text-bright); + } + + ::selection { + color: #fff; + color: var(--text-bright); + } + } + + @media (prefers-color-scheme: dark) { + + ::-moz-selection { + background-color: #1c76c5; + background-color: var(--selection); + } + + ::selection { + background-color: #1c76c5; + background-color: var(--selection); + } + } + + details { + display: flex; + flex-direction: column; + align-items: flex-start; + background-color: #f7f7f7; + background-color: var(--background-alt); + padding: 10px 10px 0; + margin: 1em 0; + border-radius: 6px; + overflow: hidden; + } + + @media (prefers-color-scheme: dark) { + + details { + background-color: #1a242f; + background-color: var(--background-alt); + } + } + + details[open] { + padding: 10px; + } + + details > :last-child { + margin-bottom: 0; + } + + details[open] summary { + margin-bottom: 10px; + } + + summary { + display: list-item; + background-color: #efefef; + background-color: var(--background); + padding: 10px; + margin: -10px -10px 0; + cursor: pointer; + outline: none; + } + + @media (prefers-color-scheme: dark) { + + summary { + background-color: #161f27; + background-color: var(--background); + } + } + + summary:hover, + summary:focus { + text-decoration: underline; + } + + details > :not(summary) { + margin-top: 0; + } + + summary::-webkit-details-marker { + color: #363636; + color: var(--text-main); + } + + @media (prefers-color-scheme: dark) { + + summary::-webkit-details-marker { + color: #dbdbdb; + color: var(--text-main); + } + } + + dialog { + background-color: #f7f7f7; + background-color: var(--background-alt); + color: #363636; + color: var(--text-main); + border: none; + border-radius: 6px; + border-color: #dbdbdb; + border-color: var(--border); + padding: 10px 30px; + } + + @media (prefers-color-scheme: dark) { + + dialog { + border-color: #526980; + border-color: var(--border); + } + } + + @media (prefers-color-scheme: dark) { + + dialog { + color: #dbdbdb; + color: var(--text-main); + } + } + + @media (prefers-color-scheme: dark) { + + dialog { + background-color: #1a242f; + background-color: var(--background-alt); + } + } + + dialog > header:first-child { + background-color: #efefef; + background-color: var(--background); + border-radius: 6px 6px 0 0; + margin: -10px -30px 10px; + padding: 10px; + text-align: center; + } + + @media (prefers-color-scheme: dark) { + + dialog > header:first-child { + background-color: #161f27; + background-color: var(--background); + } + } + + dialog::-webkit-backdrop { + background: #0000009c; + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); + } + + dialog::backdrop { + background: #0000009c; + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); + } + + footer { + border-top: 1px solid #dbdbdb; + border-top: 1px solid var(--border); + padding-top: 10px; + color: #70777f; + color: var(--text-muted); + } + + @media (prefers-color-scheme: dark) { + + footer { + color: #a9b1ba; + color: var(--text-muted); + } + } + + @media (prefers-color-scheme: dark) { + + footer { + border-top: 1px solid #526980; + border-top: 1px solid var(--border); + } + } + + body > footer { + margin-top: 40px; + } + + @media print { + body, + pre, + code, + summary, + details, + button, + input, + textarea { + background-color: #fff; + } + + button, + input, + textarea { + border: 1px solid #000; + } + + body, + h1, + h2, + h3, + h4, + h5, + h6, + pre, + code, + button, + input, + textarea, + footer, + summary, + strong { + color: #000; + } + + summary::marker { + color: #000; + } + + summary::-webkit-details-marker { + color: #000; + } + + tbody tr:nth-child(even) { + background-color: #f2f2f2; + } + + a { + color: #00f; + text-decoration: underline; + } + } \ No newline at end of file diff --git a/data/spells/spells-grimoire-en.json b/data/spells/spells-grimoire-en.json index 03c1ba8..2435f92 100644 --- a/data/spells/spells-grimoire-en.json +++ b/data/spells/spells-grimoire-en.json @@ -1,9 +1,9 @@ { "spell": [ { - "id": 0, + "recid": 0, "name": "Acid Arrow", - "source": "GRI", + "source": "GRI01", "page": 110, "srd": true, "level": 2, @@ -35,14 +35,13 @@ "A shimmering green arrow streaks toward a target within range and bursts in a spray of acid." ], "damageInflict": ["acid"], - "savingThrow": [""], "miscTags": ["", ""], "areaTags": ["", ""] }, { - "id": 1, + "recid": 1, "name": "Acid Blob", - "source": "GRI", + "source": "GRI01", "page": 110, "srd": false, "level": 1, @@ -73,7 +72,44 @@ "A yellowish bubble shoots from your pointed finger toward a creature of your choice within range" ], "damageInflict": ["acid"], - "savingThrow": [""], + "miscTags": ["", ""], + "areaTags": ["", ""] + }, + { + "recid": 2, + "name": "Acid Splash", + "source": "GRI01", + "page": 126, + "srd": true, + "level": 1, + "school": "I", + "time": [ + { + "number": 1, + "unit": "action" + } + ], + "range": { + "type": "point", + "distance": { + "type": "feet", + "amount": 60 + } + }, + "components": { + "v": true, + "s": true + }, + "duration": [ + { + "type": "instant" + } + ], + "desc": [ + "You hurl a bubble of acid. " + ], + "damageInflict": ["acid"], + "savingThrow": ["Dex"], "miscTags": ["", ""], "areaTags": ["", ""] } diff --git a/data/spells/spells-grimoire-fr.json b/data/spells/spells-grimoire-fr.json index 46062d3..bc0b54c 100644 --- a/data/spells/spells-grimoire-fr.json +++ b/data/spells/spells-grimoire-fr.json @@ -1,9 +1,9 @@ { "spell": [ { - "id": 0, + "recid": 0, "name": "Flèche Acide", - "source": "GRI", + "source": "GRI01", "page": 188, "srd": true, "level": 2, @@ -40,9 +40,9 @@ "areaTags": ["", ""] }, { - "id": 1, + "recid": 1, "name": "Bille Acide", - "source": "GRI", + "source": "GRI01", "page": 134, "srd": false, "level": 1, @@ -76,6 +76,44 @@ "savingThrow": ["Constitution"], "miscTags": ["", ""], "areaTags": ["", ""] + }, + { + "recid": 2, + "name": "Aspersion acide", + "source": "GRI01", + "page": 126, + "srd": true, + "level": 1, + "school": "I", + "time": [ + { + "number": 1, + "unit": "action" + } + ], + "range": { + "type": "point", + "distance": { + "type": "m", + "amount": 18 + } + }, + "components": { + "v": true, + "s": true + }, + "duration": [ + { + "type": "instant" + } + ], + "desc": [ + "Vous lancez une bulle d’acide. " + ], + "damageInflict": ["acid"], + "savingThrow": ["Dexterity"], + "miscTags": ["", ""], + "areaTags": ["", ""] } ] } diff --git a/footer.html b/footer.html new file mode 100644 index 0000000..b173333 --- /dev/null +++ b/footer.html @@ -0,0 +1,25 @@ + +

+ fateforge.tool + by Lucastucious is licensed + under + CC BY-NC 4.0 +

+
+

CUVD +

+

+
\ No newline at end of file diff --git a/index.html b/index.html index 108aea0..2ebe111 100644 --- a/index.html +++ b/index.html @@ -1,36 +1,42 @@ - - + - - - + fateforge-tools - + - + + + + + + - +
+ -

FateforgeTools

-
- A suite of tools for Fateforge players and Masters. -
+ +

FateforgeTools

+
+
+ A suite of tools for Fateforge players and Masters. +
+

- - @@ -40,35 +46,12 @@
- + - - - + + - + diff --git a/js/footer.js b/js/footer.js new file mode 100644 index 0000000..b83a79e --- /dev/null +++ b/js/footer.js @@ -0,0 +1,6 @@ +$(function () { + $("#footer").load("footer.html",function (responseTxt, statusTxt, xhr) { + console.log("bla"); + $("[data-localize]").localize("main", { pathPrefix: "lang" }); + }); + }); \ No newline at end of file diff --git a/js/language.js b/js/language.js index ca6e1c7..c6ea27f 100644 --- a/js/language.js +++ b/js/language.js @@ -17,6 +17,8 @@ function changeLangFR() { }); currentLang = "fr"; } + +// This code defines a function that sets the language to English. function changeLangEN() { $("[data-localize]") .localize("main", { @@ -41,4 +43,4 @@ function refreshBooks() { // Function to fill a data-localize attribute and update DOM function setLocalizeDataAttr(className, dataValue) { $("#" + className).data("localize", dataValue).attr("data-localize", dataValue); -} \ No newline at end of file +} diff --git a/js/spells.js b/js/spells.js index e69de29..7ccc485 100644 --- a/js/spells.js +++ b/js/spells.js @@ -0,0 +1,108 @@ +// Get the spell json based on choosen language defaultLanguage + +var jsonsource = "".concat( + "../data/spells/spells-grimoire-", + $.defaultLanguage.slice(0, 2), + ".json" +); +var alldata; +// Fetch data from server +fetch(jsonsource) + .then((response) => response.json()) + .then((data) => { + // load fetched data into grid + alldata = data.spell; + console.log(alldata); + + //document.getElementById("spell-list") + var grid = $("#spell-list").w2grid({ + name: "Spells", + box: "#spellgrid", + show: { + header: false, + toolbar: true, + footer: false, + selectColumn: true, + }, + multiSelect: true, + liveSearch: true, + multiSearch: true, + fixedBody: false, + records: alldata, + columns: [ + { + field: "name", + text: "Name", + sortable: true, + resizable: true, + searchable: { operator: "contains" }, + }, + { + field: "desc", + text: "Description", + size: "500%", + style: "test", + resizable: true, + searchable: { operator: "contains" }, + render(record, extra) { + return ( + '' + + extra.value + + "" + ); + }, + }, + { + field: "source", + text: "Source", + sortable: true, + tooltip: "Which book is this from ?", + searchable: { operator: "contains" }, + }, + { field: "page", text: "Page", sortable: true }, + { field: "level", text: "Level", sortable: true, size: 65 }, + { field: "school", text: "School", sortable: true }, + { + field: "components", + text: 'Components', + render(record) { + var v = record.components.v + ? 'V' + : ""; + var s = record.components.s ? 'S' : ""; + var m = record.components.m ? 'M' : ""; + //var m = record.components.m ? record.components.m : ""; + var vs=v.concat(', ',s); + return vs.concat(', ',m); + if ((record.components.v = true)) { + console.log(record.components); + } + }, + }, + { field: "duration", text: "Duration" }, + ], + async onSelect(event) { + await event.complete; + console.log("select", event.detail, this.getSelection()); + }, + onClick(event) { + let record = this.get(event.detail.recid); + //grid2.clear() + /*grid2.add([ + { recid: 0, name: 'ID:', value: record.recid }, + { recid: 1, name: 'First Name:', value: record.fname }, + { recid: 2, name: 'Last Name:', value: record.lname }, + { recid: 3, name: 'Email:', value: record.email }, + { recid: 4, name: 'Date:', value: record.sdate } + ])*/ + console.log(record); + }, + async onRefresh(event) { + await event.complete; + console.log("Le tableau est généré et le DOM est modifié."); + $("[data-localize]").localize("main", { pathPrefix: "lang" }); // mettre votre script ici + } + + }) + }); + diff --git a/lang/main-en.json b/lang/main-en.json index 797d67d..840abd9 100644 --- a/lang/main-en.json +++ b/lang/main-en.json @@ -9,5 +9,10 @@ "btn-items": "Magic Items", "CUVD": "Unofficial site for Fateforge RPG. Uses copyrighted content © Agate RPG, courtesy of the publisher under the CUVD license.", "link": "Join the community: https://fateforge.org/en", + "verbal": "Verbal", + "somatic": "Somatic", + "gridColumns":{ + "components": "Components" + }, "btn-books": "Books" } diff --git a/lib/jquery.localize-dev.js b/lib/jquery.localize-dev.js index 8711d02..354a147 100644 --- a/lib/jquery.localize-dev.js +++ b/lib/jquery.localize-dev.js @@ -87,8 +87,11 @@ http://keith-wood.name/localisation.html notifyDelegateLanguageLoaded(intermediateLangData); return loadLanguage(pkg, lang, level + 1); }; - errorFunc = function () { - if (level === 2 && lang.indexOf("-") > -1) { + errorFunc = function (xhr, status, error) { + if (status === "error" && xhr.status === 404) { + // Le fichier n'existe pas + console.log("Le fichier " + file + " n'existe pas."); + } else if (level === 2 && lang.indexOf("-") > -1) { return loadLanguage(pkg, lang, level + 1); } else if (options.fallback && options.fallback !== lang) { return loadLanguage(pkg, options.fallback); @@ -101,6 +104,11 @@ http://keith-wood.name/localisation.html timeout: options.timeout != null ? options.timeout : 500, success: successFunc, error: errorFunc, + statusCode: { + 404: function() { + console.log("Le fichier " + file + " n'existe pas."); + } + } }; if (window.location.protocol === "file:") { ajaxOptions.error = function (xhr) { diff --git a/lib/w2ui/w2ui-1.5.css b/lib/w2ui/w2ui-1.5.css new file mode 100644 index 0000000..57b9f6a --- /dev/null +++ b/lib/w2ui/w2ui-1.5.css @@ -0,0 +1,3720 @@ +/* w2ui 1.5 (c) http://w2ui.com, vitmalina@gmail.com */ +@font-face { + font-family: "w2ui-font"; + src: url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff"); + font-weight: normal; + font-style: normal; +} +[class^="w2ui-icon-"]:before, +[class*=" w2ui-icon-"]:before { + font-family: "w2ui-font"; + display: inline-block; + vertical-align: middle; + line-height: 1; + font-weight: normal; + font-style: normal; + speak: none; + text-decoration: inherit; + text-transform: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +/* Icons */ +.w2ui-icon-check:before { + content: "\f101"; +} +.w2ui-icon-colors:before { + content: "\f102"; +} +.w2ui-icon-columns:before { + content: "\f103"; +} +.w2ui-icon-cross:before { + content: "\f104"; +} +.w2ui-icon-empty:before { + content: "\f105"; +} +.w2ui-icon-info:before { + content: "\f106"; +} +.w2ui-icon-paste:before { + content: "\f107"; +} +.w2ui-icon-pencil:before { + content: "\f108"; +} +.w2ui-icon-plus:before { + content: "\f109"; +} +.w2ui-icon-reload:before { + content: "\f10a"; +} +.w2ui-icon-search:before { + content: "\f10b"; +} +.w2ui-icon-settings:before { + content: "\f10c"; +} +/************************************************* +* --- Reset (used for all w2ui widgets) +* --- The reset is needed to coexist with other CSS +* --- on the same page (for example bootstrap) +*/ +.w2ui-reset { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; + font-family: Verdana, Arial, sans-serif; + font-size: 11px; +} +.w2ui-reset * { + color: default; + line-height: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; + margin: 0px; + padding: 0px; +} +.w2ui-reset table { + max-width: none; + background-color: transparent; + border-collapse: separate; + border-spacing: 0; + border: none; +} +.w2ui-reset table tr th, +.w2ui-reset table tr td { + font-family: Verdana, Arial, sans-serif; + font-size: 11px; +} +.w2ui-reset input:not([type=button]):not([type=submit]):not([type=checkbox]):not([type=radio]), +.w2ui-reset select, +.w2ui-reset textarea { + display: inline-block; + width: auto; + height: auto; + vertical-align: baseline; + padding: 4px; + margin: 0; + font-size: 11px; +} +.w2ui-reset select { + padding: 1px; + height: 23px; + font-size: 11px; +} +.w2ui-centered { + position: absolute; + left: 0px; + right: 0px; + top: 50%; + -webkit-transform: translateY(-50%); + -moz-transform: translateY(-50%); + -ms-transform: translateY(-50%); + -o-transform: translateY(-50%); + transform: translateY(-50%); + max-height: 100%; + margin: 0px; + padding: 0px 10px; + text-align: center; +} +.w2ui-disabled, +.w2ui-readonly { + background-color: #f1f1f1 !important; + color: #777 !important; + outline: none!important; +} +.w2ui-message { + font-size: 12px; + position: absolute; + z-index: 250; + background-color: #F9F9F9; + border: 1px solid #999; + box-shadow: 0px 0px 15px #aaa; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; + border-top: 0px; + border-radius: 0px 0px 6px 6px; + overflow: auto; +} +.w2ui-message .w2ui-message-body { + position: absolute; + top: 0px; + bottom: 45px; + left: 0px; + right: 0px; + overflow: auto; + line-height: 1.5; + font-size: 14px; +} +.w2ui-message .w2ui-message-body .w2ui-centered { + line-height: 1.5; +} +.w2ui-message .w2ui-message-buttons { + position: absolute; + height: 45px; + bottom: 0px; + left: 0px; + right: 0px; + border-top: 1px solid #e5e5e5; + text-align: center; + padding: 8px; +} +/************************************************* +* ---- Input Controls ---- +*/ +div.w2ui-input:focus { + outline-color: #72B2FF; +} +input:not([type=button]):not([type=submit]).w2ui-input, +textarea.w2ui-input { + padding: 4px; + border: 1px solid #cacaca; + border-radius: 3px; + color: black; + background-color: white; + line-height: normal; +} +input:not([type=button]):not([type=submit]).w2ui-input:focus, +textarea.w2ui-input:focus { + outline-color: #72B2FF; +} +input:not([type=button]):not([type=submit]).w2ui-input:disabled, +textarea.w2ui-input:disabled, +input:not([type=button]):not([type=submit]).w2ui-input[readonly], +textarea.w2ui-input[readonly] { + background-color: #f1f1f1; + color: #777; + outline: none!important; +} +/* IE9-11 specific classes */ +/* needs doblue :: */ +input.w2ui-input::-ms-clear { + display: none; +} +input.w2ui-input:-ms-input-placeholder { + color: #aaa !important; +} +select.w2ui-input { + color: black; + padding: 0px 15px 0px 7px; + line-height: 1.8; + border-radius: 3px; + border: 1px solid #cacaca; + -webkit-appearance: none; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAALCAQAAACnzwd+AAAAcklEQVR4AcXMsQFBQQDG4P9tAgC0gJYRQJZgKQMwCqCku6vVAAAA+NJHP4KHOk0aV2pRw61n4BBmyOxKQ8I4ehZeuhd3HTx6DQEGZ7sBfr2OOOOj3Yi43kMKs9sZknofOexqZ8npMygwWZTX51CipP+YA1OiZJbYYg9lAAAAAElFTkSuQmCC'), linear-gradient(to bottom, #FFF 20%, #f6f6f6 50%, #EEE 52%, #f4f4f4 100%); + background-size: 17px 6px, 100% 100%; + background-position: right center, left top; + background-repeat: no-repeat, no-repeat; +} +.w2ui-icon-expand:before { + position: relative; + top: 1px; + left: 1px; + content: ' '; + width: 5px; + height: 5px; + border: 2px solid rgba(150, 150, 150, 0.8); + border-bottom: 0; + border-left: 0; + transform: rotateZ(45deg); +} +.w2ui-icon-collapse:before { + position: relative; + top: -1px; + left: 3px; + content: ' '; + width: 5px; + height: 5px; + border: 2px solid rgba(150, 150, 150, 0.8); + border-bottom: 0; + border-left: 0; + transform: rotateZ(135deg); +} +/* On/Off switch */ +input[type="checkbox"].w2ui-toggle { + position: absolute; + opacity: 0; + width: 46px; + height: 22px; + padding: 0px; + margin: 0px; + margin-left: 2px; + /* Knob */ + /* Green */ + /* Default Blue */ +} +input[type="checkbox"].w2ui-toggle:focus { + box-shadow: 0px 0px 1px 2px #a8cfff; +} +input[type="checkbox"].w2ui-toggle + div { + display: inline-block; + width: 46px; + height: 22px; + border: 1px solid #bbb; + border-radius: 30px; + background-color: #eee; + transition-duration: 0.3s; + transition-property: background-color, box-shadow; + box-shadow: inset 0 0 0 0px rgba(0, 0, 0, 0.4); + margin-left: 2px; +} +input[type="checkbox"].w2ui-toggle.w2ui-small + div { + width: 30px; + height: 16px; +} +input[type="checkbox"].w2ui-toggle:focus + div { + box-shadow: 0px 0px 3px 2px #91baed; +} +input[type="checkbox"].w2ui-toggle:disabled + div { + opacity: 0.3; +} +input[type="checkbox"].w2ui-toggle + div > div { + float: left; + width: 22px; + height: 22px; + border-radius: inherit; + background: #f5f5f5; + transition-duration: 0.3s; + transition-property: transform, background-color, box-shadow; + box-shadow: 0px 0px 1px #323232, 0 0 0 1px rgba(200, 200, 200, 0.6); + pointer-events: none; + margin-top: -1px; + margin-left: -1px; +} +input[type="checkbox"].w2ui-toggle.w2ui-small + div > div { + width: 16px; + height: 16px; +} +input[type="checkbox"].w2ui-toggle:checked + div > div { + transform: translate3d(24px, 0, 0); + background-color: #ffffff; +} +input[type="checkbox"].w2ui-toggle.w2ui-small:checked + div > div { + transform: translate3d(14px, 0, 0); +} +input[type="checkbox"].w2ui-toggle:focus { + outline: none; +} +input[type="checkbox"].w2ui-toggle:checked + div { + border: 1px solid #00a23f; + box-shadow: inset 0 0 0 12px #54B350; +} +input[type="checkbox"].w2ui-toggle:checked:focus + div { + box-shadow: 0px 0px 3px 2px #91baed, inset 0 0 0 12px #54B350; +} +input[type="checkbox"].w2ui-toggle:checked + div > div { + box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px #00a23f; +} +input[type="checkbox"].w2ui-toggle.blue:checked + div { + border: 1px solid #206FAD; + box-shadow: inset 0 0 0 12px #35A6EB; +} +input[type="checkbox"].w2ui-toggle.blue:checked:focus + div { + box-shadow: 0px 0px 3px 2px #91baed, inset 0 0 0 12px #35A6EB; +} +input[type="checkbox"].w2ui-toggle.blue:checked + div > div { + box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.3), 0px 0px 0 1px #206FAD; +} +/************************************************* +* ---- Overlay and Bubble ---- +*/ +.w2ui-overlay { + position: absolute; + margin-top: 4px; + margin-left: -17px; + display: none; + z-index: 1300; + color: inherit; + background-color: #FbFbFb; + border-color: #FbFbFb; + box-shadow: 0px 2px 10px #999; + border-radius: 4px; + text-align: left; +} +.w2ui-overlay > div { + border-radius: 4px; + position: relative; + border: 3px solid #777; +} +.w2ui-overlay table td { + color: inherit; +} +.w2ui-overlay:before { + content: ""; + position: absolute; + border-color: inherit; +} +.w2ui-overlay:after { + content: ""; + position: absolute; + border-color: inherit; + bottom: 100%; + left: 4px; +} +.w2ui-overlay.top-arrow:before { + border-bottom: 12px solid #6f6f6f; + border-right: 12px solid transparent; + border-left: 12px solid transparent; + bottom: 100%; + margin-bottom: -3px; +} +.w2ui-overlay.top-arrow:after { + border-bottom: 8px solid black; + border-bottom-color: inherit; + border-right: 8px solid transparent; + border-left: 8px solid transparent; + bottom: 100%; + margin-bottom: -3px; +} +.w2ui-overlay.bottom-arrow:before { + border-top: 12px solid #6f6f6f; + border-right: 12px solid transparent; + border-left: 12px solid transparent; + top: 100%; + margin-top: -3px; +} +.w2ui-overlay.bottom-arrow:after { + border-top: 8px solid black; + border-top-color: inherit; + border-right: 8px solid transparent; + border-left: 8px solid transparent; + top: 100%; + margin-top: -3px; +} +.w2ui-overlay.w2ui-overlay-popup { + z-index: 1700; +} +.w2ui-overlay .w2ui-grid-searches { + border-top: 3px solid #777 !important; +} +.w2ui-overlay .w2ui-no-color { + border: 1px solid #eee; + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABlBMVEX/////TgCFoIUYAAAAGUlEQVR42uXHIQEAAACDsNO/NJ4Kn9uC8wsJkAARUrXAjwAAAABJRU5ErkJggg=='); + background-size: 15px 15px; +} +.w2ui-tag { + position: absolute; + z-index: 1300; + opacity: 0; + -webkit-transition: opacity 0.3s; + -moz-transition: opacity 0.3s; + -ms-transition: opacity 0.3s; + -o-transition: opacity 0.3s; + transition: opacity 0.3s; +} +.w2ui-tag .w2ui-tag-body { + box-sizing: border-box; + display: inline-block; + position: absolute; + border-radius: 3px; + padding: 6px 10px; + margin-left: 10px; + margin-top: 0px; + background-color: rgba(60, 60, 60, 0.9); + color: white !important; + font-size: 11px; + font-family: verdana; + text-shadow: 1px 1px 3px #000; + line-height: 1.4; + letter-spacing: 0.1px; +} +.w2ui-tag .w2ui-tag-body.w2ui-light { + color: #3c3c3c !important; + background-color: #fffde9; + border: 1px solid #9c9c9c; + box-shadow: 1px 1px 4px #bfbfbf; + text-shadow: none; +} +.w2ui-tag .w2ui-tag-body.w2ui-tag-right:before { + content: ""; + position: absolute; + margin: 2px 0 0 -15px; + border: 1px solid; + border-color: inherit; + background-color: inherit; + width: 7px; + height: 7px; + transform: rotate(135deg); + border-top-left-radius: 20px; + border-top-width: 0; + border-left-width: 0; +} +.w2ui-tag .w2ui-tag-body.w2ui-tag-left:after { + content: ""; + position: absolute; + top: 8px; + margin: 0px 0 0 7px; + border: 1px solid; + border-color: inherit; + background-color: inherit; + width: 7px; + height: 7px; + transform: rotate(-45deg); + border-top-left-radius: 20px; + border-top-width: 0; + border-left-width: 0; +} +.w2ui-tag .w2ui-tag-body.w2ui-tag-bottom:before { + content: ""; + position: absolute; + margin: -12px 0 0 2px; + border: 1px solid; + border-color: inherit; + background-color: inherit; + width: 8px; + height: 9px; + transform: rotate(-135deg); + border-top-left-radius: 20px; + border-top-width: 0; + border-left-width: 0; +} +.w2ui-tag .w2ui-tag-body.w2ui-tag-top:after { + content: ""; + position: absolute; + left: 12px; + bottom: -6px; + border: 1px solid; + border-color: inherit; + background-color: inherit; + width: 8px; + height: 9px; + transform: rotate(45deg); + border-top-left-radius: 20px; + border-top-width: 0; + border-left-width: 0; +} +.w2ui-tag.w2ui-tag-popup { + z-index: 1700; +} +/* +* Drop down menu +*/ +.w2ui-menu { + position: absolute; + top: 0px; + bottom: 0px; +} +.w2ui-menu table { + font-family: verdana; + font-size: 11px; + width: 100%; + color: black; + background-color: white; + padding: 5px 0px; + cursor: default; +} +.w2ui-menu table td { + white-space: nowrap; +} +.w2ui-menu table.sub-menu { + top: 0; + background-color: #fafdff; + border-top: 1px dotted #cccccc; + border-bottom: 1px dotted #cccccc; +} +.w2ui-menu table.sub-menu td:first-child { + padding-left: 10px; +} +.w2ui-menu table.sub-menu td.menu-divider { + padding-left: 16px; +} +.w2ui-menu table .w2ui-item-even { + color: inherit; + background-color: #FFF; +} +.w2ui-menu table .w2ui-item-odd { + color: inherit; + background-color: #F3F6FA; +} +.w2ui-menu table .w2ui-item-group { + color: #444; + font-weight: bold; + background-color: #ECEDF0; + border-bottom: 1px solid #D3D2D4; +} +.w2ui-menu table td.menu-icon { + padding: 3px 0px 4px 6px; + width: 20px; +} +.w2ui-menu table td.menu-icon > span { + height: 20px; + width: 18px; +} +.w2ui-menu table td.menu-divider { + padding: 6px; + pointer-events: none; +} +.w2ui-menu table td.menu-divider div.line { + border-top: 1px solid silver; + color: transparent; + height: 0px; + padding: 0px 25px 0 0px; +} +.w2ui-menu table td.menu-divider.divider-text { + padding: 12px 6px; +} +.w2ui-menu table td.menu-divider.divider-text div.text { + display: block; + position: absolute; + margin-top: -6px; + margin-left: 46%; + background-color: white; + padding: 0px 3px; + color: darkgrey; + transform: translateX(-50%); +} +.w2ui-menu table td.menu-text { + padding: 8px 10px 8px 5px; + width: auto; +} +.w2ui-menu table td.menu-count { + text-align: right; + padding-right: 1px; +} +.w2ui-menu table td.menu-count > span { + border: 1px solid #F6FCF4; + border-radius: 20px; + width: auto; + height: 18px; + padding: 2px 7px; + margin: 3px 5px 0px 5px; + background-color: #F2F8F0; + color: #666; + box-shadow: 0px 0px 2px #474545; + text-shadow: 1px 1px 0px #FFF; +} +.w2ui-menu table td.menu-count > span.hotkey { + border: none; + border-radius: 0px; + background-color: transparent !important; + color: #888; + box-shadow: none; + text-shadow: none; +} +.w2ui-menu table td.menu-count > span.remove { + border-color: transparent; + background-color: transparent; + box-shadow: none; + padding: 2px 5px; +} +.w2ui-menu table td.menu-count > span.remove:hover { + color: #982525; + border-color: #d28d5b; + background-color: #f9e7e7; +} +.w2ui-menu table tr.expanded td.menu-count > span, +.w2ui-menu table tr.collapsed td.menu-count > span { + border-color: transparent; + background-color: transparent; + box-shadow: none; + padding: 2px 5px; + border-radius: 0px; +} +.w2ui-menu table tr.expanded td.menu-count > span:after, +.w2ui-menu table tr.collapsed td.menu-count > span:after { + content: ""; + position: absolute; + border-left: 5px solid #808080; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + transform: rotateZ(-90deg); + pointer-events: none; + margin-left: -2px; +} +.w2ui-menu table tr.expanded td.menu-count > span:hover, +.w2ui-menu table tr.collapsed td.menu-count > span:hover { + border-color: transparent; + background-color: transparent; +} +.w2ui-menu table tr.collapsed td.menu-count span:after { + transform: rotateZ(90deg); +} +.w2ui-menu table tr:hover { + color: inherit; + background-color: #E6F0FF; +} +.w2ui-menu table tr.w2ui-selected { + background-color: #B6D5FB; +} +.w2ui-menu table tr.w2ui-selected td { + color: inherit; +} +.w2ui-menu table tr.w2ui-disabled { + opacity: 0.4; + background-color: white !important; +} +.w2ui-menu table .w2ui-icon { + font-size: 14px; + color: #8D99A7; + display: inline-block; + padding-top: 4px; +} +.w2ui-menu-inline { + padding: 0; + margin: 0; +} +.w2ui-menu-inline .w2ui-menu { + position: static; +} +.w2ui-menu-inline .w2ui-menu > table { + padding: 0px; +} +.w2ui-menu-inline .w2ui-menu table td.menu-icon, +.w2ui-menu-inline .w2ui-menu table td.menu-text { + padding: 1px 1px 1px 5px; +} +/************************************************* +* ---- Common Classes ---- +*/ +.w2ui-marker { + color: #444; + background-color: rgba(252, 244, 161, 0.48); +} +.w2ui-spinner { + display: inline-block; + background-size: 100%; + background-repeat: no-repeat; + background-image: url(data:image/gif;base64,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); +} +/* common icons */ +.w2ui-icon { + background-repeat: no-repeat; + height: 16px; + width: 16px; + overflow: hidden; + margin: 2px 2px; + display: inline-block; +} +.w2ui-icon.icon-search, +.w2ui-icon.icon-search-down { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAgCAYAAAB+ZAqzAAACuElEQVRYw9WXSWhTQRjHR0UKLqhFaV0OUih68GAOWjyJKypCpAoV8aIiioIICiKiB1GMtE3MYmry2moXDz1UDx7sUXHBhQpSaRVxrYpWcMO9avx/8AJh/CbznHkxdeB3Cd/8589kvuUJkWcdjCTHghUgAi6DJ+AVeAqugSQIggniXywcNBJsB70g44EHYBcYXUhTM8EFj4ZkboKqQpiqAv2GprK8o7/f75t6pjn0M3gNPmri3vtycxAZA64qDvkJ2kENqAQTQQWoBg74qth3B4y3NbZDIX4fzNfsnQtuK/YfsjFVCh4pMq3Co0Y5uMVoUGkpy8aFT5xaeSzVEo45bXdBt4LeaLq1k0RXMYJfdDfFmAuAD4zWlty4UNyZEkm19MUb2zMw8Sfp1u+IWSrcIimLnTG8/SijdU6OO5poDESdtgHZVBzUHm/amhW7zoitMTS2mNHqASPk2FDCCcLMYK6p+obmulyxfiYLA4bGKFvfSnrUvkq5+Lpk8z4yRH8r3l/X4WiqJFfspSQ0CGYZGpsMnkt6L+h31Z76hpMdeOwPQ7H0NFnssST0C8wxNDaDKb6kP06150gsHahNNlVzYheZd7HJ0BiX4VRGhpmIhRixKyZilM2M1mnTArtIUbU3/qVO0H0GvmQ4CY4C3YopYYlHjXlggNG4R33Ypi2tVtwaPeTdNMkq9pVQZQdvFPs32zbx4aAjzxhDRfIAWAeWg7VgrzsY5ht/zoNJtubKwA3LITGjSKRyW3NTwaUCmKOSMd3WHH0ZJRQZZkOP1zFKZ3CB++4+aQ6kEeksWAb2a2L7qDv49S1Q6T72MOgEXa6RGFhP3wpS/B6NOWpRs0UxFg7eqTFHjX1hscxtAz/ymEuIYi0cvgF8Y0w5Ro3dZ3M1boJkTaXEUFlug6fsdsRQWzTj0cey+N/Xb2sj5lTh2M6OAAAAAElFTkSuQmCC) no-repeat center !important; + background-size: 14px 12px !important; + opacity: 0.9; +} +.w2ui-icon.icon-folder { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGrSURBVDjLxZO7ihRBFIa/6u0ZW7GHBUV0UQQTZzd3QdhMQxOfwMRXEANBMNQX0MzAzFAwEzHwARbNFDdwEd31Mj3X7a6uOr9BtzNjYjKBJ6nicP7v3KqcJFaxhBVtZUAK8OHlld2st7Xl3DJPVONP+zEUV4HqL5UDYHr5xvuQAjgl/Qs7TzvOOVAjxjlC+ePSwe6DfbVegLVuT4r14eTr6zvA8xSAoBLzx6pvj4l+DZIezuVkG9fY2H7YRQIMZIBwycmzH1/s3F8AapfIPNF3kQk7+kw9PWBy+IZOdg5Ug3mkAATy/t0usovzGeCUWTjCz0B+Sj0ekfdvkZ3abBv+U4GaCtJ1iEm6ANQJ6fEzrG/engcKw/wXQvEKxSEKQxRGKE7Izt+DSiwBJMUSm71rguMYhQKrBygOIRStf4TiFFRBvbRGKiQLWP29yRSHKBTtfdBmHs0BUpgvtgF4yRFR+NUKi0XZcYjCeCG2smkzLAHkbRBmP0/Uk26O5YnUActBp1GsAI+S5nRJJJal5K1aAMrq0d6Tm9uI6zjyf75dAe6tx/SsWeD//o2/Ab6IH3/h25pOAAAAAElFTkSuQmCC) no-repeat center !important; +} +.w2ui-icon.icon-page { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAINSURBVBgZBcG/r55zGAfg6/4+z3va01NHlYgzEfE7MdCIGISFgS4Gk8ViYyM2Mdlsko4GSf8Do0FLRCIkghhYJA3aVBtEz3nP89wf11VJvPDepdd390+8Nso5nESBQoq0pfvXm9fzWf19453LF85vASqJlz748vInb517dIw6EyYBIIG49u+xi9/c9MdvR//99MPPZ7+4cP4IZhhTPbwzT2d+vGoaVRRp1rRliVvHq+cfvM3TD82+7mun0o/ceO7NT+/4/KOXjwZU1ekk0840bAZzMQ2mooqh0A72d5x/6sB9D5zYnff3PoYBoWBgFKPKqDKqjCpjKr//dcu9p489dra88cydps30KswACfNEKanSaxhlntjJ8Mv12Paie+vZ+0+oeSwwQ0Iw1xAR1CiFNJkGO4wu3ZMY1AAzBI0qSgmCNJsJUEOtJSMaCTBDLyQ0CknAGOgyTyFFiLI2awMzdEcSQgSAAKVUmAeNkxvWJWCGtVlDmgYQ0GFtgg4pNtOwbBcwQy/Rife/2yrRRVI0qYCEBly8Z+P4qMEMy7JaVw72N568e+iwhrXoECQkfH91kY7jwwXMsBx1L93ZruqrK6uuiAIdSnTIKKPLPFcvay8ww/Hh+ufeznTXu49v95IMoQG3784gYXdTqvRmqn/Wpa/ADFX58MW3L71SVU9ETgEIQQQIOOzub+fhIvwPRDgeVjWDahIAAAAASUVORK5CYII=) no-repeat center !important; +} +/************************************************* +* ---- Locking portion of the screen (in grid, form, layout) +*/ +.w2ui-lock { + display: none; + position: absolute; + z-index: 1400; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + opacity: 0.15; + filter: alpha(opacity=15); + background-color: #333; +} +.w2ui-lock-msg { + display: none; + position: absolute; + z-index: 1400; + top: 45%; + left: 50%; + -webkit-transform: translateX(-50%) translateY(-50%); + -moz-transform: translateX(-50%) translateY(-50%); + -ms-transform: translateX(-50%) translateY(-50%); + -o-transform: translateX(-50%) translateY(-50%); + transform: translateX(-50%) translateY(-50%); + width: 200px; + height: 80px; + padding: 30px 8px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + font-size: 13px; + font-family: Verdana, Arial, sans-serif; + opacity: 0.8; + filter: alpha(opacity=80); + background-color: #555; + color: white; + text-align: center; + border-radius: 5px; + border: 2px solid #444; +} +.w2ui-lock-msg .w2ui-spinner { + display: inline-block; + width: 24px; + height: 24px; + margin: -3px 8px -7px -10px; +} +/************************************************* +* ---- Scroll contet, used in toolbar and tabs ---- +*/ +.w2ui-scroll-wrapper { + overflow: hidden; +} +.w2ui-scroll-left, +.w2ui-scroll-right { + top: 0; + width: 18px; + height: 100%; + cursor: default !important; + z-index: 10; + display: none; + position: absolute; +} +.w2ui-scroll-left:hover, +.w2ui-scroll-right:hover { + background-color: #ddd; +} +.w2ui-scroll-left { + left: 0; + box-shadow: 0px 0px 7px #5F5F5F; + background: #F7F7F7 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAzklEQVR4Ae2THRDEMBCFzy1ucatb3eJ2uhi3uNUtbnGrW9zi1rOdNzdvdl7nDpvYt/9/r7+/51myZZf/zXkD2iMHHRSb0x3oskwMieK05PwEXqP4ExSL0wp0ROao2OOuMPOMdUL6XU1/oGLcFWb+NqyTd2W/P/2qTr9h+nFXhOkHXRHiNyjrgp/U/V+WaQcaNY13zZI0A1JvcVqAnrGDTdtDtZUHjHIJhxxVLN0iqXgCP1l/7h8U9kc6abyJ4/eNWPpGdBv+XdUK0K8cnvcBly2rDr7C1HQAAAAASUVORK5CYII=') center center no-repeat; + background-size: 15px 12px; +} +.w2ui-scroll-right { + right: 0; + box-shadow: 0px 0px 7px #5F5F5F; + background: #F7F7F7 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVR4Ae2UG7TGMBCEr1vd4la3uMUtuli3utWtbnGLW9zi9l/bDMzJG7u12cfJfLunf1+UEC9Bv0vVQwJ8hjRCaZafflb1C9RQf4OD0gSDE+i+PiJAabFhQc1y1AYYsJGLY3lgxM17uWPO56yPiFDqVPWgRtpIHSd1zPnwkBsdI58OlNwx4fP2X0TgfMTOoHSdKOXkpyNvEyQh7ul+4swxJSTQuwNDxz68l/ukVNbu0Neen5Z+KvzWxBAqHds349uPFJ/jVOrPjxUq++OLf+20q5+noXo0AAAAAElFTkSuQmCC') center center no-repeat; + background-size: 15px 13px; +} +button.w2ui-btn { + position: relative; + display: inline-block; + border-radius: 4px; + margin: 0px 5px; + padding: 6px 12px; + color: #666; + font-size: 12px; + border: 1px solid #B6B6B6; + background-image: -webkit-linear-gradient(#FFF 0%, #E7E7E7 100%); + background-image: -moz-linear-gradient(#FFF 0%, #E7E7E7 100%); + background-image: -ms-linear-gradient(#FFF 0%, #E7E7E7 100%); + background-image: -o-linear-gradient(#FFF 0%, #E7E7E7 100%); + background-image: linear-gradient(#FFF 0%, #E7E7E7 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffe7e7e7', endColorstr='#ffffffff', GradientType=0); + outline: none; + box-shadow: 0px 1px 0px white; + cursor: default; + min-width: 75px; + line-height: 110%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +button.w2ui-btn:hover { + text-decoration: none; + border: 1px solid #bbb; + background-image: -webkit-linear-gradient(#F7F7F7 0%, #DDDDDD 100%); + background-image: -moz-linear-gradient(#F7F7F7 0%, #DDDDDD 100%); + background-image: -ms-linear-gradient(#F7F7F7 0%, #DDDDDD 100%); + background-image: -o-linear-gradient(#F7F7F7 0%, #DDDDDD 100%); + background-image: linear-gradient(#F7F7F7 0%, #DDDDDD 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffdddddd', endColorstr='#fff7f7f7', GradientType=0); + color: #333; +} +button.w2ui-btn:active, +button.w2ui-btn.clicked { + border: 1px solid #999; + background-image: -webkit-linear-gradient(#ccc 0%, #ccc 100%); + background-image: -moz-linear-gradient(#ccc 0%, #ccc 100%); + background-image: -ms-linear-gradient(#ccc 0%, #ccc 100%); + background-image: -o-linear-gradient(#ccc 0%, #ccc 100%); + background-image: linear-gradient(#ccc 0%, #ccc 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffcccccc', endColorstr='#ffcccccc', GradientType=0); + text-shadow: 1px 1px 1px #eee; +} +button.w2ui-btn:disabled { + border: 1px solid #bbb !important; + background: #f7f7f7 !important; + color: #bdbcbc !important; + text-shadow: none !important; +} +button.w2ui-btn:focus:before { + content: ""; + border: 1px dashed #aaa; + border-radius: 3px; + position: absolute; + top: 2px; + bottom: 2px; + left: 2px; + right: 2px; + pointer-events: none; +} +button.w2ui-btn::-moz-focus-inner { + border: 0; +} +button.w2ui-btn-blue { + color: white; + background-image: -webkit-linear-gradient(#80C0F7 0%, #269DF0 100%); + background-image: -moz-linear-gradient(#80C0F7 0%, #269DF0 100%); + background-image: -ms-linear-gradient(#80C0F7 0%, #269DF0 100%); + background-image: -o-linear-gradient(#80C0F7 0%, #269DF0 100%); + background-image: linear-gradient(#80C0F7 0%, #269DF0 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff269df0', endColorstr='#ff80c0f7', GradientType=0); + border: 1px solid #538AB7; + text-shadow: 0px 0px 1px #111; +} +button.w2ui-btn-blue:hover { + color: white; + background-image: -webkit-linear-gradient(#73B6F0 0%, #2391DD 100%); + background-image: -moz-linear-gradient(#73B6F0 0%, #2391DD 100%); + background-image: -ms-linear-gradient(#73B6F0 0%, #2391DD 100%); + background-image: -o-linear-gradient(#73B6F0 0%, #2391DD 100%); + background-image: linear-gradient(#73B6F0 0%, #2391DD 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff2391dd', endColorstr='#ff73b6f0', GradientType=0); + border: 1px solid #497BA3; + text-shadow: 0px 0px 1px #111; +} +button.w2ui-btn-blue:active, +button.w2ui-btn-blue.clicked { + color: white; + background-image: -webkit-linear-gradient(#1E83C9 0%, #1E83C9 100%); + background-image: -moz-linear-gradient(#1E83C9 0%, #1E83C9 100%); + background-image: -ms-linear-gradient(#1E83C9 0%, #1E83C9 100%); + background-image: -o-linear-gradient(#1E83C9 0%, #1E83C9 100%); + background-image: linear-gradient(#1E83C9 0%, #1E83C9 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff1e83c9', endColorstr='#ff1e83c9', GradientType=0); + border: 1px solid #1268A6; + text-shadow: 0px 0px 1px #111; +} +button.w2ui-btn-blue:focus:before { + border: 1px dashed #e8e8e8; +} +button.w2ui-btn-green { + color: white; + background-image: -webkit-linear-gradient(#81CF81 0%, #52A452 100%); + background-image: -moz-linear-gradient(#81CF81 0%, #52A452 100%); + background-image: -ms-linear-gradient(#81CF81 0%, #52A452 100%); + background-image: -o-linear-gradient(#81CF81 0%, #52A452 100%); + background-image: linear-gradient(#81CF81 0%, #52A452 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff52a452', endColorstr='#ff81cf81', GradientType=0); + border: 1px solid #479247; + text-shadow: 0px 0px 1px #111; +} +button.w2ui-btn-green:hover { + color: white; + background-image: -webkit-linear-gradient(#6ABE68 0%, #3F8F3D 100%); + background-image: -moz-linear-gradient(#6ABE68 0%, #3F8F3D 100%); + background-image: -ms-linear-gradient(#6ABE68 0%, #3F8F3D 100%); + background-image: -o-linear-gradient(#6ABE68 0%, #3F8F3D 100%); + background-image: linear-gradient(#6ABE68 0%, #3F8F3D 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff3f8f3d', endColorstr='#ff6abe68', GradientType=0); + border: 1px solid #479247; + text-shadow: 0px 0px 1px #111; +} +button.w2ui-btn-green:active, +button.w2ui-btn-green.clicked { + color: white; + background-image: -webkit-linear-gradient(#377D36 0%, #377D36 100%); + background-image: -moz-linear-gradient(#377D36 0%, #377D36 100%); + background-image: -ms-linear-gradient(#377D36 0%, #377D36 100%); + background-image: -o-linear-gradient(#377D36 0%, #377D36 100%); + background-image: linear-gradient(#377D36 0%, #377D36 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff377d36', endColorstr='#ff377d36', GradientType=0); + border: 1px solid #555 !important; + text-shadow: 0px 0px 1px #111; +} +button.w2ui-btn-green:focus:before { + border: 1px dashed #e8e8e8; +} +button.w2ui-btn-orange { + color: white; + background-image: -webkit-linear-gradient(#FCC272 0%, #FB8822 100%); + background-image: -moz-linear-gradient(#FCC272 0%, #FB8822 100%); + background-image: -ms-linear-gradient(#FCC272 0%, #FB8822 100%); + background-image: -o-linear-gradient(#FCC272 0%, #FB8822 100%); + background-image: linear-gradient(#FCC272 0%, #FB8822 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fffb8822', endColorstr='#fffcc272', GradientType=0); + border: 1px solid #B68B4C; + text-shadow: 0px 0px 1px #111; +} +button.w2ui-btn-orange:hover { + color: white; + background-image: -webkit-linear-gradient(#F4AD59 0%, #F1731F 100%); + background-image: -moz-linear-gradient(#F4AD59 0%, #F1731F 100%); + background-image: -ms-linear-gradient(#F4AD59 0%, #F1731F 100%); + background-image: -o-linear-gradient(#F4AD59 0%, #F1731F 100%); + background-image: linear-gradient(#F4AD59 0%, #F1731F 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fff1731f', endColorstr='#fff4ad59', GradientType=0); + border: 1px solid #B68B4C; + text-shadow: 0px 0px 1px #111; +} +button.w2ui-btn-orange:active, +button.w2ui-btn-orange.clicked { + color: white; + border: 1px solid #666; + background-image: -webkit-linear-gradient(#B98747 0%, #B98747 100%); + background-image: -moz-linear-gradient(#B98747 0%, #B98747 100%); + background-image: -ms-linear-gradient(#B98747 0%, #B98747 100%); + background-image: -o-linear-gradient(#B98747 0%, #B98747 100%); + background-image: linear-gradient(#B98747 0%, #B98747 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffb98747', endColorstr='#ffb98747', GradientType=0); + text-shadow: 0px 0px 1px #111; +} +button.w2ui-btn-orange:focus:before { + border: 1px dashed #f9f9f9; +} +button.w2ui-btn-red { + color: white; + background-image: -webkit-linear-gradient(#FF6E70 0%, #C72D2D 100%); + background-image: -moz-linear-gradient(#FF6E70 0%, #C72D2D 100%); + background-image: -ms-linear-gradient(#FF6E70 0%, #C72D2D 100%); + background-image: -o-linear-gradient(#FF6E70 0%, #C72D2D 100%); + background-image: linear-gradient(#FF6E70 0%, #C72D2D 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffc72d2d', endColorstr='#ffff6e70', GradientType=0); + border: 1px solid #BB3C3E; + text-shadow: 0px 0px 1px #111; +} +button.w2ui-btn-red:hover { + color: white; + background-image: -webkit-linear-gradient(#EE696C 0%, #AE2527 100%); + background-image: -moz-linear-gradient(#EE696C 0%, #AE2527 100%); + background-image: -ms-linear-gradient(#EE696C 0%, #AE2527 100%); + background-image: -o-linear-gradient(#EE696C 0%, #AE2527 100%); + background-image: linear-gradient(#EE696C 0%, #AE2527 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffae2527', endColorstr='#ffee696c', GradientType=0); + border: 1px solid #BB3C3E; + text-shadow: 0px 0px 1px #111; +} +button.w2ui-btn-red:active, +button.w2ui-btn-red.clicked { + color: white; + border: 1px solid #861C1E; + background-image: -webkit-linear-gradient(#9C2123 0%, #9C2123 100%); + background-image: -moz-linear-gradient(#9C2123 0%, #9C2123 100%); + background-image: -ms-linear-gradient(#9C2123 0%, #9C2123 100%); + background-image: -o-linear-gradient(#9C2123 0%, #9C2123 100%); + background-image: linear-gradient(#9C2123 0%, #9C2123 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff9c2123', endColorstr='#ff9c2123', GradientType=0); + text-shadow: 0px 0px 1px #111; +} +button.w2ui-btn-red:focus:before { + border: 1px dashed #ddd; +} +button.w2ui-btn-small { + padding: 5px !important; + margin: 0px; + min-width: 0px; +} +button.w2ui-btn-small:focus:before { + border-radius: 2px; + top: 2px; + bottom: 2px; + left: 2px; + right: 2px; +} +/************************************************* +* ---- Forms ---- +*/ +.w2ui-form { + position: relative; + color: black; + background-color: #F2F2F2; + border: 1px solid #c0c0c0; + border-radius: 3px; + padding: 0px; + overflow: hidden !important; +} +.w2ui-form > div { + position: absolute; + overflow: hidden; +} +.w2ui-form .w2ui-form-header { + position: absolute; + left: 0; + right: 0; + border-bottom: 1px solid #99bbe8 !important; + overflow: hidden; + color: #444; + font-size: 13px; + text-align: center; + padding: 8px; + background-image: -webkit-linear-gradient(#DAE6F3, #C2D5ED); + background-image: -moz-linear-gradient(#DAE6F3, #C2D5ED); + background-image: -ms-linear-gradient(#DAE6F3, #C2D5ED); + background-image: -o-linear-gradient(#DAE6F3, #C2D5ED); + background-image: linear-gradient(#DAE6F3, #C2D5ED); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffdae6f3', endColorstr='#ffc2d5ed', GradientType=0); + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.w2ui-form .w2ui-form-toolbar { + position: absolute; + left: 0px; + right: 0px; + margin: 0px; + padding: 5px 3px; + border-bottom: 1px solid #d5d8d8; +} +.w2ui-form .w2ui-form-tabs { + margin: 0px; + padding: 0px; +} +.w2ui-form .w2ui-tabs { + position: absolute; + left: 0; + right: 0; + height: 33px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + padding-top: 5px !important; + background-color: #fafafa; +} +.w2ui-form .w2ui-tabs .w2ui-tab.active { + background-color: #F2F2F2; +} +.w2ui-form .w2ui-page { + position: absolute; + left: 0; + right: 0; + overflow: auto; + padding: 10px 5px 0 5px; + border-left: 1px solid inherit; + border-right: 1px solid inherit; + background-color: inherit; + border-radius: 3px; +} +.w2ui-form .w2ui-column-container { + display: flex; + padding: 0; +} +.w2ui-form .w2ui-column-container .w2ui-column { + width: 100%; +} +.w2ui-form .w2ui-column-container .w2ui-column.col-0, +.w2ui-form .w2ui-column-container .w2ui-column.col-1, +.w2ui-form .w2ui-column-container .w2ui-column.col-2, +.w2ui-form .w2ui-column-container .w2ui-column.col-3, +.w2ui-form .w2ui-column-container .w2ui-column.col-4, +.w2ui-form .w2ui-column-container .w2ui-column.col-5, +.w2ui-form .w2ui-column-container .w2ui-column.col-6, +.w2ui-form .w2ui-column-container .w2ui-column.col-7, +.w2ui-form .w2ui-column-container .w2ui-column.col-8, +.w2ui-form .w2ui-column-container .w2ui-column.col-9, +.w2ui-form .w2ui-column-container .w2ui-column.col-10 { + padding: 0 5px; +} +.w2ui-form .w2ui-buttons { + position: absolute; + left: 0; + right: 0; + bottom: 0; + text-align: center; + border-top: 1px solid #d5d8d8; + border-bottom: 0px solid #d5d8d8; + background-color: #fafafa; + padding: 15px 0px !important; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; +} +.w2ui-form .w2ui-buttons input[type="button"], +.w2ui-form .w2ui-buttons button { + min-width: 80px; + margin-right: 5px; +} +.w2ui-form input[type=checkbox], +.w2ui-form input[type=radio] { + margin-top: 4px; + margin-bottom: 4px; + width: 14px; + height: 14px; +} +.w2ui-form input[type=checkbox].w2ui-toggle { + margin: 0px; + width: 50px; +} +.w2ui-group-title { + padding: 5px 2px; + color: #8D96A2; + text-shadow: 1px 1px 2px #fdfdfd; + font-size: 120%; +} +.w2ui-group-fields { + background-color: #F9F9F9; + margin: 5px 0px 10px 0px; + padding: 10px 5px; + border-top: 1px solid #DCDCDC; + border-bottom: 1px solid #DCDCDC; +} +.w2ui-field > label { + display: block; + float: left; + margin-top: 7px; + margin-bottom: 3px; + width: 120px; + padding: 0px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: right; + min-height: 20px; + color: #666; +} +.w2ui-field > div { + /* do not include width */ + margin-bottom: 3px; + margin-left: 128px; + padding: 3px; + min-height: 28px; + float: none; +} +.w2ui-field.w2ui-required > div { + position: relative; +} +.w2ui-field.w2ui-required:not(.w2ui-field-inline) > div::before { + content: '*'; + position: absolute; + margin-top: 5px; + margin-left: -9px; + color: red; +} +.w2ui-field.w2ui-required.w2ui-field-inline > div::before { + content: '' !important; +} +.w2ui-field.w2ui-disabled { + opacity: 0.3; + background-color: transparent !important; +} +.w2ui-field.w2ui-span-none > label { + margin: 0; + padding: 5px 12px 0 4px; + display: block; + width: 98%; + text-align: left; +} +.w2ui-field.w2ui-span-none > div { + margin-left: 0; +} +.w2ui-field.w2ui-span0 > label { + display: none; +} +.w2ui-field.w2ui-span0 > div { + margin-left: 0; +} +.w2ui-field.w2ui-span1 > label { + width: 20px; +} +.w2ui-field.w2ui-span1 > div { + margin-left: 28px; +} +.w2ui-field.w2ui-span2 > label { + width: 40px; +} +.w2ui-field.w2ui-span2 > div { + margin-left: 48px; +} +.w2ui-field.w2ui-span3 > label { + width: 60px; +} +.w2ui-field.w2ui-span3 > div { + margin-left: 68px; +} +.w2ui-field.w2ui-span4 > label { + width: 80px; +} +.w2ui-field.w2ui-span4 > div { + margin-left: 88px; +} +.w2ui-field.w2ui-span5 > label { + width: 100px; +} +.w2ui-field.w2ui-span5 > div { + margin-left: 108px; +} +.w2ui-field.w2ui-span6 > label { + width: 120px; +} +.w2ui-field.w2ui-span6 > div { + margin-left: 128px; +} +.w2ui-field.w2ui-span7 > label { + width: 140px; +} +.w2ui-field.w2ui-span7 > div { + margin-left: 148px; +} +.w2ui-field.w2ui-span8 > label { + width: 160px; +} +.w2ui-field.w2ui-span8 > div { + margin-left: 168px; +} +.w2ui-field.w2ui-span9 > label { + width: 180px; +} +.w2ui-field.w2ui-span9 > div { + margin-left: 188px; +} +.w2ui-field.w2ui-span10 > label { + width: 200px; +} +.w2ui-field.w2ui-span10 > div { + margin-left: 208px; +} +.w2ui-field.w2ui-field-inline { + display: inline; +} +.w2ui-field.w2ui-field-inline > div { + display: inline; + margin: 0; + padding: 0; +} +.w2ui-field .w2ui-box-label { + user-select: none; + vertical-align: middle; +} +.w2ui-field .w2ui-box-label span, +.w2ui-field .w2ui-box-label input { + display: inline-block; + vertical-align: middle; +} +.w2ui-field .w2ui-box-label span { + padding-left: 3px; +} +.w2ui-field .w2ui-box-label input { + margin: 4px 0px 3px 0; +} +.w2ui-error { + border: 1px solid #FFA8A8 !important; + background-color: #FFF4EB !important; +} +.w2field { + padding: 3px; + border-radius: 3px; + border: 1px solid silver; +} +.w2ui-field-helper { + position: absolute; + display: inline-block; + line-height: 100%; + /* pointer-events: none; - do not use as IE does not support it */ + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; +} +.w2ui-field-helper .w2ui-field-up { + position: absolute; + top: 0px; + padding: 2px 3px; +} +.w2ui-field-helper .w2ui-field-down { + position: absolute; + bottom: 0px; + padding: 2px 3px; +} +.w2ui-field-helper .arrow-up:hover { + border-bottom-color: #81C6FF; +} +.w2ui-field-helper .arrow-down:hover { + border-top-color: #81C6FF; +} +/* +* ARROWS +*/ +.arrow-up { + background: none; + width: 0; + height: 0; + border-left: 4px solid transparent; + /* left arrow slant */ + border-right: 4px solid transparent; + /* right arrow slant */ + border-bottom: 5px solid #777; + /* bottom, add background color here */ + font-size: 0; + line-height: 0; +} +.arrow-down { + background: none; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid #777; + font-size: 0; + line-height: 0; +} +.arrow-left { + background: none; + width: 0; + height: 0; + border-bottom: 4px solid transparent; + /* left arrow slant */ + border-top: 4px solid transparent; + /* right arrow slant */ + border-right: 5px solid #777; + /* bottom, add background color here */ + font-size: 0; + line-height: 0; +} +.arrow-right { + background: none; + width: 0; + height: 0; + border-bottom: 4px solid transparent; + /* left arrow slant */ + border-top: 4px solid transparent; + /* right arrow slant */ + border-left: 5px solid #777; + /* bottom, add background color here */ + font-size: 0; + line-height: 0; +} +/* +* COLOR overlay +*/ +.w2ui-colors { + padding: 8px 5px 0px 5px; + background-color: white; + border-radius: 3px; +} +.w2ui-colors input:focus { + outline: none; + border: 1px solid #999 !important; +} +.w2ui-colors .w2ui-color-palette { + width: 242px; +} +.w2ui-colors .w2ui-color-palette table { + table-layout: fixed; + width: 160px; +} +.w2ui-colors .w2ui-color-palette table td { + width: 20px; + height: 20px; + text-align: center; +} +.w2ui-colors .w2ui-color-palette table td div { + cursor: pointer; + display: inline-block; + width: 16px; + height: 17px; + padding: 1px 4px; + border: 1px solid transparent; + color: white; + text-shadow: 0px 0px 2px #000; +} +.w2ui-colors .w2ui-color-palette table td div:hover { + outline: 1px solid #666; + border: 1px solid #fff; +} +.w2ui-colors .w2ui-color-palette table td div.w2ui-no-color { + border: 1px solid #efefef; +} +.w2ui-colors .w2ui-color-advanced { + height: 176px; + padding: 1px 2px; +} +.w2ui-colors .w2ui-color-advanced .palette { + position: relative; + width: 150px; + height: 125px; + outline: 1px solid #d2d2d2; + box-sizing: content-box; +} +.w2ui-colors .w2ui-color-advanced .palette .palette-bg { + height: 100%; + background-image: linear-gradient(0deg, #000000, rgba(204, 154, 129, 0)); + pointer-events: none; +} +.w2ui-colors .w2ui-color-advanced .rainbow { + position: relative; + width: 150px; + height: 12px; + margin: 10px 0px 0px 0px; + background: linear-gradient(90deg, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); +} +.w2ui-colors .w2ui-color-advanced .alpha { + position: relative; + width: 150px; + height: 12px; + margin: 10px 0px 0px 0px; + background-color: #fff; + background-image: linear-gradient(45deg, #bbb 25%, transparent 25%, transparent 75%, #bbb 75%, #bbb), linear-gradient(45deg, #bbb 25%, transparent 25%, transparent 75%, #bbb 75%, #bbb); + background-size: 12px 12px; + background-position: 0 0, 6px 6px; +} +.w2ui-colors .w2ui-color-advanced .alpha .alpha-bg { + height: 100%; + background-image: linear-gradient(90deg, rgba(80, 80, 80, 0) 0%, #505050 100%); + pointer-events: none; +} +.w2ui-colors .w2ui-color-advanced .value1 { + pointer-events: none; + position: absolute; + top: 0px; + display: inline-block; + width: 8px; + height: 8px; + border-radius: 10px; + border: 1px solid #999; + background-color: transparent; + box-shadow: 0px 0px 1px white; + transform: translateX(-1px) translateY(-1px); +} +.w2ui-colors .w2ui-color-advanced .value2 { + pointer-events: none; + position: absolute; + top: -2px; + display: inline-block; + width: 8px; + height: 16px; + border-radius: 2px; + border: 1px solid #696969; + background-color: #ffffff; + box-shadow: 0px 0px 1px white; + transform: translateX(-1px); +} +.w2ui-colors .w2ui-color-advanced .color-info { + float: right; + width: 80px; + margin-left: 158px; + margin-top: 0px; +} +.w2ui-colors .w2ui-color-advanced .color-info .color-preview-bg { + box-shadow: 0 0 1px #c3c3c3; + height: 40px; + background-color: #fff; + background-image: linear-gradient(45deg, #bbb 25%, transparent 25%, transparent 75%, #bbb 75%, #bbb), linear-gradient(45deg, #bbb 25%, transparent 25%, transparent 75%, #bbb 75%, #bbb); + background-size: 16px 16px; + background-position: 0 0, 8px 8px; + margin-bottom: 10px; +} +.w2ui-colors .w2ui-color-advanced .color-info .color-preview, +.w2ui-colors .w2ui-color-advanced .color-info .color-original { + height: 40px; + width: 40px; + float: left; +} +.w2ui-colors .w2ui-color-advanced .color-info .color-part { + padding-top: 5px; +} +.w2ui-colors .w2ui-color-advanced .color-info .color-part span { + display: inline-block; + width: 7px; + margin-left: 1px; + color: #666; +} +.w2ui-colors .w2ui-color-advanced .color-info .color-part input { + font-size: 10px!important; + border-radius: 2px; + border: 1px solid #ccc; + width: 26px !important; + padding: 2px !important; + color: #333; +} +.w2ui-colors .w2ui-color-tabs { + background-color: #f7f7f7; + height: 30px; + margin: 10px -5px 0px -5px; + border-top: 1px solid #d6d6d6; +} +.w2ui-colors .w2ui-color-tabs .w2ui-color-tab { + float: left; + display: inline-block; + width: 34px; + height: 25px; + border: 1px solid transparent; + border-radius: 1px; + margin: -1px -3px 0px 8px; + text-align: center; + font-size: 15px; + padding-top: 1px; + color: #7b7b7b; +} +.w2ui-colors .w2ui-color-tabs .w2ui-color-tab.selected { + color: #555; + background-color: white; + border: 1px solid #e0e0e0; + border-top: 1px solid transparent; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} +/* +* DATE overlay +*/ +.w2ui-calendar { + margin: 0px; + padding: 1px; + line-height: 108%; +} +.w2ui-calendar .w2ui-calendar-title { + margin: 0px -1px; + padding: 7px 2px; + background-image: -webkit-linear-gradient(#f6f6f6, #d9d9d9); + background-image: -moz-linear-gradient(#f6f6f6, #d9d9d9); + background-image: -ms-linear-gradient(#f6f6f6, #d9d9d9); + background-image: -o-linear-gradient(#f6f6f6, #d9d9d9); + background-image: linear-gradient(#f6f6f6, #d9d9d9); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fff6f6f6', endColorstr='#ffd9d9d9', GradientType=0); + border-bottom: 1px solid #bbb; + color: #555; + text-align: center; + text-shadow: 1px 1px 1px #eee; + cursor: pointer; +} +.w2ui-calendar .w2ui-calendar-jump { + position: absolute; + top: 27px; + left: 0px; + right: 0px; + bottom: 0px; + background-color: #FaFaFa; +} +.w2ui-calendar .w2ui-calendar-jump > :first-child { + position: absolute; + top: 0px; + left: 0px; + bottom: 0px; + width: 110px; + overflow: hidden; + padding-top: 5px; + border-right: 1px solid silver; +} +.w2ui-calendar .w2ui-calendar-jump > :last-child { + position: absolute; + top: 0px; + right: 0px; + bottom: 0px; + width: 88px; + overflow-x: hidden; + overflow-y: auto; + padding-top: 5px; + text-align: center; +} +.w2ui-calendar .w2ui-calendar-jump .w2ui-jump-month, +.w2ui-calendar .w2ui-calendar-jump .w2ui-jump-year { + display: inline-block; + padding: 5px 0px; + text-align: center; + float: left; + margin: 2px; + width: 50px; + cursor: default; + border: 1px solid transparent; + border-radius: 2px; +} +.w2ui-calendar .w2ui-calendar-jump .w2ui-jump-year { + float: none; + width: 95%; +} +.w2ui-calendar .w2ui-calendar-jump .w2ui-jump-month:hover, +.w2ui-calendar .w2ui-calendar-jump .w2ui-jump-year:hover { + border: 1px solid #ccc; + color: black; + background-color: #efefef; +} +.w2ui-calendar .w2ui-calendar-jump .w2ui-jump-month.selected, +.w2ui-calendar .w2ui-calendar-jump .w2ui-jump-year.selected { + border: 1px solid #ccc; + color: black; + background-color: #dadada; +} +.w2ui-calendar .w2ui-calendar-previous, +.w2ui-calendar .w2ui-calendar-next { + width: 24px; + height: 20px; + color: #666; + border: 1px solid transparent; + border-radius: 3px; + padding: 2px 3px 1px 2px; + margin: -4px 0px 0px 0px; + cursor: default; +} +.w2ui-calendar .w2ui-calendar-previous:hover, +.w2ui-calendar .w2ui-calendar-next:hover { + border: 1px solid silver; + background-color: #efefef; +} +.w2ui-calendar .w2ui-calendar-previous > div, +.w2ui-calendar .w2ui-calendar-next > div { + position: absolute; + border-left: 4px solid #888; + border-top: 4px solid #888; + border-right: 4px solid transparent; + border-bottom: 4px solid transparent; + width: 0px; + height: 0px; + padding: 0px; + margin: 3px 0px 0px 0px; +} +.w2ui-calendar .w2ui-calendar-previous { + float: left; +} +.w2ui-calendar .w2ui-calendar-previous > div { + -webkit-transform: rotate(-45deg); + -moz-transform: rotate(-45deg); + -ms-transform: rotate(-45deg); + -o-transform: rotate(-45deg); + transform: rotate(-45deg); + margin-left: 6px; +} +.w2ui-calendar .w2ui-calendar-next { + float: right; +} +.w2ui-calendar .w2ui-calendar-next > div { + -webkit-transform: rotate(135deg); + -moz-transform: rotate(135deg); + -ms-transform: rotate(135deg); + -o-transform: rotate(135deg); + transform: rotate(135deg); + margin-left: 2px; + margin-right: 2px; +} +.w2ui-calendar .w2ui-calendar-now { + cursor: pointer; + margin-bottom: 10px; + text-align: center; +} +.w2ui-calendar .w2ui-calendar-now:hover { + color: #0A96DE; +} +.w2ui-calendar table.w2ui-calendar-days { + padding: 0px; +} +.w2ui-calendar table.w2ui-calendar-days td { + border: 1px solid #fff; + color: black; + background-color: #f9f9f9; + padding: 6px; + cursor: default; + text-align: right; +} +.w2ui-calendar table.w2ui-calendar-days td.w2ui-saturday, +.w2ui-calendar table.w2ui-calendar-days td.w2ui-sunday { + border: 1px solid #fff; + color: #c8493b; + background-color: #f9f9f9; +} +.w2ui-calendar table.w2ui-calendar-days td.w2ui-saturday:hover, +.w2ui-calendar table.w2ui-calendar-days td.w2ui-sunday:hover { + border: 1px solid #ccc; + color: black; + background-color: #e9e9e9; +} +.w2ui-calendar table.w2ui-calendar-days td.w2ui-saturday.w2ui-blocked, +.w2ui-calendar table.w2ui-calendar-days td.w2ui-sunday.w2ui-blocked { + text-decoration: line-through; + border: 1px solid #fff; + color: #ccc; + background-color: #fff; +} +.w2ui-calendar table.w2ui-calendar-days td.w2ui-today { + color: black; + background-color: #e2f7cd; +} +.w2ui-calendar table.w2ui-calendar-days td:hover { + border: 1px solid #ccc; + color: black; + background-color: #e9e9e9; +} +.w2ui-calendar table.w2ui-calendar-days td.w2ui-date-selected { + border: 1px solid #8cb067; +} +.w2ui-calendar table.w2ui-calendar-days td.w2ui-blocked { + text-decoration: line-through; + border: 1px solid #fff; + color: #ccc; + background-color: #fff; +} +.w2ui-calendar table.w2ui-calendar-days td.w2ui-day-empty { + border: 1px solid #fff; + background-color: #fdfdfd; +} +.w2ui-calendar table.w2ui-calendar-days tr.w2ui-day-title td { + border: 1px solid #fff; + color: gray; + background-color: #fff; + text-align: center; + padding: 6px; +} +/* +* Time +*/ +.w2ui-calendar-time { + padding: 5px; + cursor: default; +} +.w2ui-calendar-time td div { + padding: 7px 10px; + text-align: center; + border: 1px solid transparent; + white-space: nowrap; +} +.w2ui-calendar-time td:nth-child(even) { + background-color: #f6f6f6; +} +.w2ui-calendar-time td div:hover { + border: 1px solid #ccc; + color: black; + background-color: #e9e9e9; +} +.w2ui-calendar-time td div.w2ui-blocked { + text-decoration: line-through; + border: 1px solid #fff; + color: #ccc; + background-color: #fff; +} +.w2ui-select { + cursor: default; + color: black !important; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAALCAQAAACnzwd+AAAAcklEQVR4AcXMsQFBQQDG4P9tAgC0gJYRQJZgKQMwCqCku6vVAAAA+NJHP4KHOk0aV2pRw61n4BBmyOxKQ8I4ehZeuhd3HTx6DQEGZ7sBfr2OOOOj3Yi43kMKs9sZknofOexqZ8npMygwWZTX51CipP+YA1OiZJbYYg9lAAAAAElFTkSuQmCC'), -webkit-linear-gradient(top, #fff 20%, #f6f6f6 50%, #EEE 52%, #f4f4f4 100%); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAALCAQAAACnzwd+AAAAcklEQVR4AcXMsQFBQQDG4P9tAgC0gJYRQJZgKQMwCqCku6vVAAAA+NJHP4KHOk0aV2pRw61n4BBmyOxKQ8I4ehZeuhd3HTx6DQEGZ7sBfr2OOOOj3Yi43kMKs9sZknofOexqZ8npMygwWZTX51CipP+YA1OiZJbYYg9lAAAAAElFTkSuQmCC'), -moz-linear-gradient(top, #fff 20%, #f6f6f6 50%, #EEE 52%, #f4f4f4 100%); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAALCAQAAACnzwd+AAAAcklEQVR4AcXMsQFBQQDG4P9tAgC0gJYRQJZgKQMwCqCku6vVAAAA+NJHP4KHOk0aV2pRw61n4BBmyOxKQ8I4ehZeuhd3HTx6DQEGZ7sBfr2OOOOj3Yi43kMKs9sZknofOexqZ8npMygwWZTX51CipP+YA1OiZJbYYg9lAAAAAElFTkSuQmCC'), -ms-linear-gradient(top, #fff 20%, #f6f6f6 50%, #EEE 52%, #f4f4f4 100%); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAALCAQAAACnzwd+AAAAcklEQVR4AcXMsQFBQQDG4P9tAgC0gJYRQJZgKQMwCqCku6vVAAAA+NJHP4KHOk0aV2pRw61n4BBmyOxKQ8I4ehZeuhd3HTx6DQEGZ7sBfr2OOOOj3Yi43kMKs9sZknofOexqZ8npMygwWZTX51CipP+YA1OiZJbYYg9lAAAAAElFTkSuQmCC'), -o-linear-gradient(top, #fff 20%, #f6f6f6 50%, #EEE 52%, #f4f4f4 100%); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAALCAQAAACnzwd+AAAAcklEQVR4AcXMsQFBQQDG4P9tAgC0gJYRQJZgKQMwCqCku6vVAAAA+NJHP4KHOk0aV2pRw61n4BBmyOxKQ8I4ehZeuhd3HTx6DQEGZ7sBfr2OOOOj3Yi43kMKs9sZknofOexqZ8npMygwWZTX51CipP+YA1OiZJbYYg9lAAAAAElFTkSuQmCC'), linear-gradient(to bottom, #fff 20%, #f6f6f6 50%, #EEE 52%, #f4f4f4 100%); + background-size: 17px 6px, 100% 100%; + background-position: right center, left top; + background-repeat: no-repeat, no-repeat; +} +.w2ui-select[readonly], +.w2ui-select[disabled] { + background-image: none; + background-color: #f1f1f1 !important; + color: #777 !important; + outline: none!important; +} +/* +* ENUM items +*/ +.w2ui-list { + color: inherit; + position: absolute; + padding: 0px; + margin: 0px; + min-height: 25px; + overflow: auto; + border: 1px solid silver; + border-radius: 3px; + font-size: 6px; + line-height: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; + background-color: white; +} +.w2ui-list input[type=text] { + -webkit-box-shadow: none; + -moz-box-shadow: none; + -ms-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; +} +.w2ui-list ul { + list-style-type: none; + background-color: black; + margin: 0px; + padding: 0px; +} +.w2ui-list ul li { + float: left; + margin: 2px 1px 0px 2px; + border-radius: 3px; + width: auto; + padding: 3px 10px 1px 7px; + border: 1px solid #88B0D6; + background-color: #EFF3F5; + white-space: nowrap; + cursor: default; + font-family: verdana; + font-size: 11px; + line-height: 100%; + height: 20px; + overflow: hidden; + text-overflow: ellipsis; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-list ul li:hover { + background-color: #d0dbe1; +} +.w2ui-list ul li:last-child { + border-radius: 0px; + border: 1px solid transparent; + background-color: transparent; +} +.w2ui-list ul li:last-child input { + padding: 1px; + padding-top: 0px; + margin: 0px; + border: 0px; + outline: none; + height: auto; + line-height: 100%; + font-size: inherit; + font-family: inherit; + background-color: transparent; +} +.w2ui-list ul li .w2ui-list-remove { + float: right; + width: 15px; + height: 14px; + margin: -1px -9px 0px 3px; + border-radius: 15px; +} +.w2ui-list ul li .w2ui-list-remove:hover { + background-color: #D77F7F; + color: white; +} +.w2ui-list ul li .w2ui-list-remove:before { + position: relative; + top: 0px; + padding: 0px; + margin: 0px; + left: 5px; + color: inherit; + opacity: 0.7; + text-shadow: inherit; + font-size: inherit; + font-variant: small-caps; + content: 'x'; + line-height: 100%; +} +.w2ui-list ul li > span.file-size { + pointer-events: none; + color: #777; +} +.w2ui-list.w2ui-readonly ul > li:hover { + background-color: #EFF3F5; +} +.w2ui-list .w2ui-enum-placeholder { + display: inline; + position: absolute; + pointer-events: none; + color: #999; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-list.w2ui-file-dragover { + background-color: #E4FFDA; + border: 1px solid #93E07D; +} +/************************************************* +* ---- Layout ---- +*/ +.w2ui-layout { + overflow: hidden !important; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-layout * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-layout > div { + position: absolute; + overflow: hidden; + border: 0px; + margin: 0px; + padding: 0px; + outline: 0px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-layout > div .w2ui-panel { + display: none; + position: absolute; + z-index: 120; +} +.w2ui-layout > div .w2ui-panel .w2ui-panel-title { + position: absolute; + left: 0px; + top: 0px; + right: 0px; + padding: 5px; + background-image: -webkit-linear-gradient(#DAE6F3, #C2D5ED); + background-image: -moz-linear-gradient(#DAE6F3, #C2D5ED); + background-image: -ms-linear-gradient(#DAE6F3, #C2D5ED); + background-image: -o-linear-gradient(#DAE6F3, #C2D5ED); + background-image: linear-gradient(#DAE6F3, #C2D5ED); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffdae6f3', endColorstr='#ffc2d5ed', GradientType=0); + border: 1px solid #B9CEE9; + border-bottom: 1px solid #99bbe8; +} +.w2ui-layout > div .w2ui-panel .w2ui-panel-tabs { + position: absolute; + left: 0px; + top: 0px; + right: 0px; + z-index: 2; + display: none; + overflow: hidden; + background-color: #FAFAFA; + padding: 4px 0px; +} +.w2ui-layout > div .w2ui-panel .w2ui-panel-tabs > .w2ui-tab.active { + background-color: #F2F2F2; +} +.w2ui-layout > div .w2ui-panel .w2ui-panel-toolbar { + position: absolute; + left: 0px; + top: 0px; + right: 0px; + z-index: 2; + display: none; + overflow: hidden; + background-color: #FAFAFA; + border-bottom: 1px solid silver; + padding: 4px; +} +.w2ui-layout > div .w2ui-panel .w2ui-panel-content { + position: absolute; + left: 0px; + top: 0px; + right: 0px; + bottom: 0px; + z-index: 1; + color: inherit; + background-color: #F2F2F2; +} +.w2ui-layout > div .w2ui-resizer { + display: none; + position: absolute; + z-index: 121; + background-color: transparent; +} +.w2ui-layout > div .w2ui-resizer:hover, +.w2ui-layout > div .w2ui-resizer.active { + background-color: #C8CAD1; +} +/************************************************* +* ---- Grid ---- +*/ +.w2ui-grid { + position: relative; + border: 1px solid silver; + border-radius: 2px; + overflow: hidden !important; +} +.w2ui-grid > div { + position: absolute; + overflow: hidden; +} +.w2ui-grid .w2ui-grid-header { + position: absolute; + top: 0; + left: 0; + right: 0; + border-bottom: 1px solid #99bbe8 !important; + height: 28px; + overflow: hidden; + color: #444; + font-size: 13px; + text-align: center; + padding: 7px; + background-image: -webkit-linear-gradient(#DAE6F3, #C2D5ED); + background-image: -moz-linear-gradient(#DAE6F3, #C2D5ED); + background-image: -ms-linear-gradient(#DAE6F3, #C2D5ED); + background-image: -o-linear-gradient(#DAE6F3, #C2D5ED); + background-image: linear-gradient(#DAE6F3, #C2D5ED); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffdae6f3', endColorstr='#ffc2d5ed', GradientType=0); + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} +.w2ui-grid .w2ui-grid-toolbar { + position: absolute; + border-bottom: 1px solid silver; + background-color: #EAEAEA; + height: 38px; + padding: 6px 1px 4px 1px; + margin: 0px; + box-shadow: 0px 1px 2px #ddd; +} +.w2ui-grid .w2ui-toolbar-search { + position: relative; + width: 160px; + margin-right: 3px; +} +.w2ui-grid .w2ui-toolbar-search .w2ui-search-all { + outline: none !important; + width: 160px !important; + border-radius: 3px !important; + line-height: normal !important; + height: 23px !important; + border: 1px solid #B9B9B9 !important; + color: #000 !important; + background-color: white !important; + padding: 3px 18px 3px 21px !important; + margin: 0px !important; + margin-top: 1px !important; +} +.w2ui-grid .w2ui-toolbar-search .w2ui-search-down { + position: absolute; + margin-top: -7px; + margin-left: 4px; +} +.w2ui-grid .w2ui-toolbar-search .w2ui-search-clear { + position: absolute; + width: 16px; + height: 16px; + margin-top: -8px; + margin-left: -20px; + border-radius: 15px; + cursor: default; +} +.w2ui-grid .w2ui-toolbar-search .w2ui-search-clear:hover { + background-color: #D77F7F; + color: white; +} +.w2ui-grid .w2ui-toolbar-search .w2ui-search-clear:before { + position: relative; + top: 2px; + left: 5px; + opacity: 0.6; + color: inherit; + text-shadow: inherit; + content: 'x'; + cursor: default; +} +.w2ui-grid .w2ui-grid-body { + position: absolute; + overflow: hidden; + padding: 0px; + background-color: white; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +.w2ui-grid .w2ui-grid-body input, +.w2ui-grid .w2ui-grid-body select, +.w2ui-grid .w2ui-grid-body textarea { + user-select: text; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + -o-user-select: text; +} +.w2ui-grid .w2ui-grid-body div.w2ui-input { + user-select: text; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + -o-user-select: text; + background-color: white; + padding: 4px 2px; + border: 1px solid transparent; + width: 100%; + height: 100%; + pointer-events: auto; + outline: none; + white-space: pre; + overflow: hidden; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-columns, +.w2ui-grid .w2ui-grid-body .w2ui-grid-fcolumns { + overflow: hidden; + position: absolute; + left: 0px; + top: 0px; + right: 0px; + box-shadow: 0px 1px 4px #ddd; + height: auto; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-columns table, +.w2ui-grid .w2ui-grid-body .w2ui-grid-fcolumns table { + height: auto; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-columns .w2ui-resizer, +.w2ui-grid .w2ui-grid-body .w2ui-grid-fcolumns .w2ui-resizer { + position: absolute; + z-index: 1000; + display: block; + background-image: none; + background-color: rgba(0, 0, 0, 0); + /* needed for IE */ + padding: 0px; + margin: 0px; + width: 6px; + height: 12px; + cursor: col-resize; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords { + position: absolute; + left: 0px; + right: 0px; + top: 0px; + bottom: 0px; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd { + color: inherit; + background-color: white; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd.w2ui-record-hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd.w2ui-record-hover { + color: inherit; + background-color: #E6F0FF; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd.w2ui-empty-record:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd.w2ui-empty-record:hover { + background-color: white; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even { + color: inherit; + background-color: #F3F6FA; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even.w2ui-record-hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even.w2ui-record-hover { + color: inherit; + background-color: #E6F0FF; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even.w2ui-empty-record:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even.w2ui-empty-record:hover { + background-color: #F3F6FA; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-selected, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-selected, +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr td.w2ui-selected, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr td.w2ui-selected { + color: black !important; + background-color: #B6D5FF !important; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-inactive, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-inactive, +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr td.w2ui-inactive, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr td.w2ui-inactive { + background-color: #D8DEE7 !important; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-expanded1, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-expanded1 { + height: 0px; + border-bottom: 1px solid #b2bac0; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-expanded1 > div, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-expanded1 > div { + height: 0px; + border: 0px; + transition: height 0.3s, opacity 0.3s; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-expanded2, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-expanded2 { + height: 0px; + border-radius: 0px; + border-bottom: 1px solid #b2bac0; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-expanded2 > div, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-expanded2 > div { + height: 0px; + border: 0px; + transition: height 0.3s, opacity 0.3s; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-load-more, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-load-more { + cursor: pointer; + background-color: rgba(233, 237, 243, 0.5); + border-right: 1px solid #D6D5D7; + height: 43px; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-load-more > div, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-load-more > div { + text-align: center; + color: #777; + background-color: rgba(233, 237, 243, 0.5); + padding: 10px 0px 15px 0px; + height: 43px; + border-top: 1px dashed #D6D5D7; + border-bottom: 1px dashed #D6D5D7; + font-size: 12px; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-load-more > div:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-load-more > div:hover { + color: #438ba2; + background-color: #E6F0FF; +} +.w2ui-grid .w2ui-grid-body table { + border-spacing: 0px; + border-collapse: collapse; + table-layout: fixed; + width: 1px; +} +.w2ui-grid .w2ui-grid-body table .w2ui-head { + margin: 0px; + padding: 0px; + border-right: 1px solid #c5c5c5; + border-bottom: 1px solid #c5c5c5; + color: black; + background-image: -webkit-linear-gradient(#F9F9F9, #E4E4E4); + background-image: -moz-linear-gradient(#F9F9F9, #E4E4E4); + background-image: -ms-linear-gradient(#F9F9F9, #E4E4E4); + background-image: -o-linear-gradient(#F9F9F9, #E4E4E4); + background-image: linear-gradient(#F9F9F9, #E4E4E4); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#ffe4e4e4', GradientType=0); +} +.w2ui-grid .w2ui-grid-body table .w2ui-head > div { + padding: 7px 3px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + position: relative; +} +.w2ui-grid .w2ui-grid-body table .w2ui-head.w2ui-col-intersection { + border-right-color: #72B2FF; +} +.w2ui-grid .w2ui-grid-body table .w2ui-head.w2ui-reorder-cols-head:hover { + cursor: move; +} +.w2ui-grid .w2ui-grid-body table .w2ui-head .col-intersection-marker { + padding: 0; + position: absolute; + height: 100%; + top: 0; +} +.w2ui-grid .w2ui-grid-body table .w2ui-head .col-intersection-marker.left { + left: 0; + margin-left: -5px; +} +.w2ui-grid .w2ui-grid-body table .w2ui-head .col-intersection-marker.right { + right: 0; + margin-right: -5px; +} +.w2ui-grid .w2ui-grid-body table .w2ui-head .col-intersection-marker .top-marker { + position: absolute; + top: 0; + height: 0; + width: 0; + border-top: 5px solid #72B2FF; + border-left: 5px solid transparent; + border-right: 5px solid transparent; +} +.w2ui-grid .w2ui-grid-body table .w2ui-head .col-intersection-marker .bottom-marker { + position: absolute; + bottom: 0; + height: 0; + width: 0; + border-bottom: 5px solid #72B2FF; + border-left: 5px solid transparent; + border-right: 5px solid transparent; +} +.w2ui-grid .w2ui-grid-body table td { + border-right: 1px solid #D6D5D7; + border-bottom: 0px solid #D6D5D7; + cursor: default; + overflow: hidden; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-soft-span, +.w2ui-grid .w2ui-grid-body table td.w2ui-soft-hidden { + border-right-color: transparent; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data { + margin: 0px; + padding: 0px; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data .w2ui-info { + position: relative; + top: 1px; + font-size: 14px; + color: #8D99A7; + cursor: pointer; + width: 16px; + display: inline-block; + margin-right: 2px; + text-align: center; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data .w2ui-clipboard-copy { + float: right; + margin-top: -15px; + width: 20px; + height: 16px; + padding: 0px; + text-align: center; + cursor: pointer; + font-size: 13px; + color: #8d98a7; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data .w2ui-clipboard-copy:hover { + color: #545961; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data > div { + padding: 3px 3px 3px 3px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data > div.flexible-record { + height: auto; + overflow: visible; + white-space: normal; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data .w2ui-show-children { + width: 16px; + height: 10px; + display: inline-block; + position: relative; + top: -1px; + cursor: pointer; +} +.w2ui-grid .w2ui-grid-body table td:last-child { + border-right: 0px; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-number { + width: 34px; + color: #777; + background-color: rgba(233, 237, 243, 0.5); +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-number div { + padding: 0px 7px 0px 3px; + text-align: right; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-number.w2ui-head { + cursor: pointer; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-select { + width: 26px; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-select div { + padding: 0px 0px; + text-align: center; + overflow: hidden; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-select div input[type=checkbox] { + margin-top: 0px; + margin-bottom: 0px; + position: relative; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-expand { + width: 26px; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-expand div { + padding: 0px 0px; + text-align: center; + font-weight: bold; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-order { + width: 26px; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-order.w2ui-grid-data div { + cursor: move; + height: 18px; + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxyZWN0IHN0eWxlPSJmaWxsOiAjYWFhOyIgeD0iMCIgeT0iNCIgaGVpZ2h0PSIzIiB3aWR0aD0iMTYiPjwvcmVjdD4KICA8cmVjdCBzdHlsZT0iZmlsbDogI2FhYTsiIHg9IjAiIHk9IjkiIGhlaWdodD0iMyIgd2lkdGg9IjE2Ij48L3JlY3Q+Cjwvc3ZnPg=="); + background-position: 5px 2px; + background-size: 14px 12px; + background-repeat: no-repeat; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-selected { + background-color: #d1d1d1 !important; +} +.w2ui-grid .w2ui-grid-body table .w2ui-row-selected { + background-color: #d1d1d1 !important; +} +.w2ui-grid .w2ui-grid-body div.w2ui-col-header { + height: auto !important; + width: 100%; + overflow: hidden; + padding-right: 10px !important; +} +.w2ui-grid .w2ui-grid-body div.w2ui-col-header > div.w2ui-sort-up { + border: 4px solid transparent; + border-bottom: 5px solid #8D99A7; + margin-top: -2px; + margin-right: -7px; + float: right; +} +.w2ui-grid .w2ui-grid-body div.w2ui-col-header > div.w2ui-sort-down { + border: 4px solid transparent; + border-top: 5px solid #8D99A7; + margin-top: 2px; + margin-right: -7px; + float: right; +} +.w2ui-grid .w2ui-grid-body .w2ui-col-group { + text-align: center; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-scroll1 { + position: absolute; + left: 0px; + bottom: 0px; + border-top: 1px solid #ddd; + border-right: 1px solid #ddd; + background-color: #FAFAFA; +} +.w2ui-grid .w2ui-grid-empty-msg { + position: absolute; + top: 27px; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(255, 255, 255, 0.65); +} +.w2ui-grid .w2ui-grid-empty-msg > div { + position: absolute; + left: 0; + right: 0; + top: 45%; + transform: translateY(-45%); + text-align: center; + font-size: 13px; + color: #666; +} +.w2ui-grid .w2ui-changed { + background: url(data:image/gif;base64,R0lGODlhCgAKAJEAALAABf///wAAAAAAACH5BAEAAAIALAAAAAAKAAoAAAIPlI8Hy8mbxIsSUnup3rQAADs=) no-repeat top right; +} +.w2ui-grid .w2ui-edit-box { + position: absolute; + z-index: 1001; + border: 2px solid #6299DA; + pointer-events: auto; +} +.w2ui-grid .w2ui-editable { + overflow: hidden; + height: 100% !important; + margin: 0px !important; + padding: 0px !important; +} +.w2ui-grid .w2ui-editable input { + border: 0px; + border-radius: 0px; + margin: 0px; + padding: 4px 3px; + width: 100%; + height: 100%; +} +.w2ui-grid .w2ui-editable input.w2ui-select { + outline: none !important; + background: #fff; +} +.w2ui-grid .w2ui-grid-summary { + position: absolute; + box-shadow: 0px -1px 4px #aaa; +} +.w2ui-grid .w2ui-grid-summary table { + color: inherit; +} +.w2ui-grid .w2ui-grid-summary table .w2ui-odd { + background-color: #EEF5EB; +} +.w2ui-grid .w2ui-grid-summary table .w2ui-even { + background-color: #F8FFF5; +} +.w2ui-grid .w2ui-grid-footer { + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0px; + padding: 0px; + text-align: center; + height: 24px; + overflow: hidden; + user-select: text; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + -o-user-select: text; + box-shadow: 0px -1px 4px #eee; + color: #444; + background-color: #F8F8F8; + border-top: 1px solid #ddd; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-left { + float: left; + padding-top: 5px; + padding-left: 5px; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-right { + float: right; + padding-top: 5px; + padding-right: 5px; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-center { + padding: 2px; + text-align: center; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-center .w2ui-footer-nav { + width: 110px; + margin: 0 auto; + padding: 0px; + text-align: center; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-center .w2ui-footer-nav input[type=text] { + padding: 1px 2px 2px 2px; + border-radius: 3px; + width: 40px; + text-align: center; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-center .w2ui-footer-nav a.w2ui-footer-btn { + display: inline-block; + border-radius: 3px; + cursor: pointer; + font-size: 11px; + line-height: 16px; + padding: 1px 5px; + width: 30px; + height: 18px; + margin-top: -1px; + color: black; + background-color: transparent; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-center .w2ui-footer-nav a.w2ui-footer-btn:hover { + color: #000; + background-color: #AEC8FF; +} +.w2ui-grid .w2ui-grid-focus-input { + position: absolute; + top: 0; + right: 0; + z-index: -1; + opacity: 0; + overflow: hidden; + padding: 0px; + margin: 0px; + width: 1px; + height: 1px; + resize: none; + border: 0px; +} +/* SpeadSheet */ +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr td.w2ui-selected { + background-color: #EEF4FE !important; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr td.w2ui-inactive { + background-color: #F4F6F9 !important; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table td { + border-right-width: 1px; + border-bottom: 1px solid #efefef; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd, +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even, +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd:hover, +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even:hover { + background-color: inherit; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr:first-child td { + border-top: 0px; + border-bottom: 0px; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr td.w2ui-selected { + background-color: #EEF4FE !important; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr td.w2ui-inactive { + background-color: #F4F6F9 !important; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table td { + border-right-width: 1px; + border-bottom: 1px solid #efefef; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd, +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even, +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd:hover, +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even:hover { + background-color: inherit; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr:first-child td { + border-bottom: 0px; +} +.w2ui-ss .w2ui-grid-body .w2ui-selection { + position: absolute; + z-index: 1000; + border: 2px solid #6299DA; + /* #457FC2; */ + pointer-events: none; +} +.w2ui-ss .w2ui-grid-body .w2ui-selection .w2ui-selection-resizer { + cursor: crosshair; + position: absolute; + bottom: 0px; + right: 0px; + width: 6px; + height: 6px; + margin-right: -3px; + margin-bottom: -3px; + background-color: #457FC2; + border: 0.5px solid #fff; + outline: 1px solid white; + pointer-events: auto; +} +.w2ui-ss .w2ui-grid-body .w2ui-selection.w2ui-inactive { + border: 2px solid #C0C2C5; +} +.w2ui-ss .w2ui-grid-body .w2ui-selection.w2ui-inactive .w2ui-selection-resizer { + background-color: #B0B0B0; +} +.w2ui-ss .w2ui-grid-body .w2ui-soft-range { + position: absolute; + pointer-events: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.w2ui-ss .w2ui-grid-body .w2ui-changed { + background: inherit; + /* do not show changed for spreadsheet */ +} +.w2ui-info-bubble table { + font-size: 11px; + font-family: Verdana, Arial, sans-serif; + color: white; + text-shadow: 1px 1px solid #999; +} +.w2ui-info-bubble table tr td:first-child { + white-space: nowrap; + padding: 2px; + padding-right: 10px; + color: #ddd; + vertical-align: top; +} +.w2ui-info-bubble table tr td:last-child { + white-space: pre; + padding: 2px; +} +.w2ui-overlay .w2ui-select-field { + padding: 4px 0px; + cursor: default; +} +.w2ui-overlay .w2ui-select-field table { + font-size: 11px; + font-family: Verdana, Arial, sans-serif; + border-spacing: 0px; + border-collapse: border-collapse; +} +.w2ui-overlay .w2ui-select-field table tr { + height: 27px; +} +.w2ui-overlay .w2ui-select-field table tr:hover, +.w2ui-overlay .w2ui-select-field table tr.w2ui-selected { + background-color: #B6D5FF; +} +.w2ui-overlay .w2ui-select-field table tr td:nth-child(1) { + width: 26px; + padding-right: 6px; + text-align: right; + color: #888; +} +.w2ui-overlay .w2ui-select-field table tr td:nth-child(2) { + padding: 3px 20px 3px 0px; +} +.w2ui-overlay .w2ui-col-on-off { + padding: 4px 0px; +} +.w2ui-overlay .w2ui-col-on-off table { + border-spacing: 0px; + border-collapse: border-collapse; +} +.w2ui-overlay .w2ui-col-on-off table tr:hover, +.w2ui-overlay .w2ui-col-on-off table tr.w2ui-selected { + background-color: #B6D5FF; +} +.w2ui-overlay .w2ui-col-on-off table td input[type=checkbox] { + margin: 3px 2px 2px 2px; +} +.w2ui-overlay .w2ui-col-on-off table td label { + display: block; + padding: 3px 0px; + padding-right: 10px; +} +.w2ui-overlay .w2ui-col-on-off table td:first-child { + padding: 4px 0px 4px 6px; +} +.w2ui-overlay .w2ui-col-on-off table td:last-child { + padding: 4px 6px 4px 0px; +} +.w2ui-overlay .w2ui-grid-searches { + text-align: left; + padding: 0px; + border-top: 0px; + background-color: #f7f6f0; +} +.w2ui-overlay .w2ui-grid-searches table { + padding: 4px; + padding-top: 12px; + border-collapse: border-collapse; +} +.w2ui-overlay .w2ui-grid-searches table td { + padding: 4px; + /* for IE */ +} +.w2ui-overlay .w2ui-grid-searches table td.close-btn { + width: 20px; + padding-right: 20px; +} +.w2ui-overlay .w2ui-grid-searches table td.close-btn button { + min-width: 24px; + height: 24px; + padding-top: 5px !important; +} +.w2ui-overlay .w2ui-grid-searches table td.caption { + text-align: right; + padding-right: 5px; + border-right: 1px solid #E8E8E3; +} +.w2ui-overlay .w2ui-grid-searches table td.operator { + text-align: left; + padding: 0px 10px; + padding-right: 5px; + border-right: 1px solid #E8E8E3; + height: 31px; +} +.w2ui-overlay .w2ui-grid-searches table td.operator select { + width: 100%; + color: black; +} +.w2ui-overlay .w2ui-grid-searches table td.operator select::-ms-expand { + display: none; +} +.w2ui-overlay .w2ui-grid-searches table td.value { + padding-right: 5px; + padding-left: 5px; +} +.w2ui-overlay .w2ui-grid-searches table td.value input[type=text] { + border-radius: 3px; + padding: 3px; + margin-right: 3px; + height: 23px; +} +.w2ui-overlay .w2ui-grid-searches table td.value select { + padding: 3px; + margin-right: 3px; + height: 23px; +} +.w2ui-overlay .w2ui-grid-searches table td.actions { + border-right: 0px; +} +.w2ui-overlay .w2ui-grid-searches table td.actions > div { + margin: -7px; + margin-top: 15px; + padding: 13px 0px; + text-align: center; + background-color: #EFEFE9; + border-top: 1px solid #E8E8E3; +} +/************************************************* +* ---- Popup ---- +*/ +.w2ui-popup { + position: fixed; + z-index: 1600; + overflow: hidden; + font-family: Verdana, Arial, sans-serif; + border-radius: 6px; + padding: 0px; + margin: 0px; + border: 1px solid #777; + background-color: #eee; + box-shadow: 0px 0px 25px #555; +} +.w2ui-popup, +.w2ui-popup * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-popup.w2ui-popup-opening { + opacity: 0; + -webkit-transform: scale(0.8); + -moz-transform: scale(0.8); + -ms-transform: scale(0.8); + -o-transform: scale(0.8); + transform: scale(0.8); +} +.w2ui-popup.w2ui-popup-closing { + opacity: 0; + -webkit-transform: scale(0.9); + -moz-transform: scale(0.9); + -ms-transform: scale(0.9); + -o-transform: scale(0.9); + transform: scale(0.9); +} +.w2ui-popup .w2ui-popup-title { + padding: 6px; + border-radius: 6px 6px 0px 0px; + background-image: -webkit-linear-gradient(#ECECEC, #DFDFDF); + background-image: -moz-linear-gradient(#ECECEC, #DFDFDF); + background-image: -ms-linear-gradient(#ECECEC, #DFDFDF); + background-image: -o-linear-gradient(#ECECEC, #DFDFDF); + background-image: linear-gradient(#ECECEC, #DFDFDF); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffececec', endColorstr='#ffdfdfdf', GradientType=0); + border-bottom: 2px solid #BFBFBF; + position: absolute; + overflow: hidden; + height: 32px; + left: 0px; + right: 0px; + top: 0px; + text-overflow: ellipsis; + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + cursor: move; + font-size: 15px; + color: #555; + z-index: 300; +} +.w2ui-popup .w2ui-popup-button { + float: right; + width: 18px; + height: 18px; + cursor: pointer; + overflow: hidden; + padding: 0px; + margin: 0px 3px 0px 0px; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAQCAYAAABQrvyxAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAj1JREFUeNrslr9rFFEQxz/zZi/qxSgW2lsqkiYoBku5Ro1o4UFKEYkgSaxSCLYqdv5IEVPYCMJJwERWrK0CKhoQ8hdobQTjXW7njcXlYnLunQQu0YDTLOy+Nzvfme98Z8Td2ckW2OGWdMvRvYfT/RGfBPoBBVpLK0AEPgVkdGL06vt/CoB5nBaRE8AXYKXNsQIwaB4fAwOtH+88mn4m7ifN4vUYebWBKkFKqjIV3N9NjI2Uuw5ARI45fBanH+F77iFnN8JHETmS68P9NHBQNTwHL8foaSN4SqoyA/SZyL4tqQAQBVYCLOFYlNxmq0WorVLpN9Oe5LKt1CsgRVWpAOfB66phBuhTkepSdfnKVjaxNJMSWn/iawmTtpeDp6pWBpaBoqrMqoYU6AOqIbFhxGa3R4V8nfNNKLUESzXJhoCvQC+wF/gW1C5IiC+2XUbD5jA3rd4C26NR3945IA2iRzqRJgdElJJlSQocAKrAD2A/6Ev3cLajjN59MDWHyKl2voOI1zKbv3Xj2lCHJFoz+LXuBoIAjnUklEvJrDDT5LwmdhG8blkyBxRjXSu4loE0X4VEznXKV3SnoOFMB7YUolBcbcKNdxuPXUBPu8pbLXsK0ghebVjEXgNoYmXLtGLuxd6ePU+AQ20AaIrb4DpFycmSv81/7YsiMgAstB1kQgE47O4LuQmCNwGOB7VxCb/URsRSTbhkmU4ifGiZHd1Z5m7fnxoIQSaBo39YJRZj9LGb4yPzXWm1/9voX7afAwAC5tacDTA2XgAAAABJRU5ErkJggg==) no-repeat center left; + background-position: 0px 0px; + color: transparent !important; + border-radius: 3px; + border: 1px solid transparent; +} +.w2ui-popup .w2ui-popup-close { + margin-top: 0px; + background-position: -32px 0px; +} +.w2ui-popup .w2ui-popup-close:hover { + background-color: #ccc; + border: 1px solid #aaa; +} +.w2ui-popup .w2ui-popup-max { + background-position: -16px 0px; +} +.w2ui-popup .w2ui-popup-max:hover { + background-color: #ccc; + border: 1px solid #aaa; +} +.w2ui-popup .w2ui-box, +.w2ui-popup .w2ui-box-temp { + position: absolute; + left: 0px; + right: 0px; + top: 32px; + bottom: 52px; + z-index: 100; +} +.w2ui-popup .w2ui-popup-body { + font-size: 12px; + line-height: 130%; + padding: 0px 7px 7px 7px; + color: #000; + background-color: #eee; + position: absolute; + overflow: auto; + width: 100%; + height: 100%; +} +.w2ui-popup .w2ui-popup-buttons { + font-size: 11px; + padding: 12px; + border-radius: 0px 0px 6px 6px; + border-top: 1px solid #d5d8d8; + background-color: #F1F1F1; + text-align: center; + position: absolute; + overflow: hidden; + height: 52px; + left: 0px; + right: 0px; + bottom: 0px; + z-index: 200; +} +.w2ui-popup .w2ui-popup-no-title { + border-top-left-radius: 6px; + border-top-right-radius: 6px; + top: 0px !important; +} +.w2ui-popup .w2ui-popup-no-buttons { + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + bottom: 0px !important; +} +.w2ui-popup .w2ui-alert-msg, +.w2ui-popup .w2ui-confirm-msg { + font-size: 12px; + line-height: 1.5; +} +.w2ui-popup .w2ui-prompt { + font-size: 12px; + padding: 15px 10px 0 10px; +} +.w2ui-popup .w2ui-prompt > div { + margin-bottom: 5px; +} +.w2ui-popup .w2ui-prompt > label { + margin-right: 5px; +} +.w2ui-popup .w2ui-prompt input { + width: 230px; +} +.w2ui-popup .w2ui-prompt textarea { + width: 100%; + height: 50px; + resize: none; +} +/************************************************* +* ---- Sidebar ---- +*/ +.w2ui-sidebar { + position: relative; + cursor: default; + overflow: hidden !important; + background-color: #fafafa !important; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-sidebar * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-sidebar > div { + position: absolute; + overflow: hidden; +} +.w2ui-sidebar .w2ui-sidebar-top { + position: absolute; + z-index: 2; + top: 0px; + left: 0px; + right: 0px; +} +.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-left, +.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-right { + position: absolute; + right: 2px; + top: 2px; + height: 24px; + padding: 5px; + border-radius: 2px; + background-size: 16px 12px; + background-position: center center; + background-repeat: no-repeat; + background-color: #fafafa; +} +.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-left:hover, +.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-right:hover { + background-color: #f1f1f1; +} +.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-left { + left: auto; + width: 25px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAzklEQVR4Ae2THRDEMBCFzy1ucatb3eJ2uhi3uNUtbnGrW9zi1rOdNzdvdl7nDpvYt/9/r7+/51myZZf/zXkD2iMHHRSb0x3oskwMieK05PwEXqP4ExSL0wp0ROao2OOuMPOMdUL6XU1/oGLcFWb+NqyTd2W/P/2qTr9h+nFXhOkHXRHiNyjrgp/U/V+WaQcaNY13zZI0A1JvcVqAnrGDTdtDtZUHjHIJhxxVLN0iqXgCP1l/7h8U9kc6abyJ4/eNWPpGdBv+XdUK0K8cnvcBly2rDr7C1HQAAAAASUVORK5CYII='); +} +.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-right { + left: 2px; + width: auto; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVR4Ae2UG7TGMBCEr1vd4la3uMUtuli3utWtbnGLW9zi9l/bDMzJG7u12cfJfLunf1+UEC9Bv0vVQwJ8hjRCaZafflb1C9RQf4OD0gSDE+i+PiJAabFhQc1y1AYYsJGLY3lgxM17uWPO56yPiFDqVPWgRtpIHSd1zPnwkBsdI58OlNwx4fP2X0TgfMTOoHSdKOXkpyNvEyQh7ul+4swxJSTQuwNDxz68l/ukVNbu0Neen5Z+KvzWxBAqHds349uPFJ/jVOrPjxUq++OLf+20q5+noXo0AAAAAElFTkSuQmCC'); +} +.w2ui-sidebar .w2ui-sidebar-bottom { + position: absolute; + z-index: 2; + bottom: 0px; + left: 0px; + right: 0px; +} +.w2ui-sidebar .w2ui-sidebar-body { + position: absolute; + z-index: 1; + overflow: auto; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + padding: 2px 0px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node { + position: relative; + background-color: transparent; + border: 1px solid transparent; + border-radius: 4px; + margin: 0px 3px; + padding: 1px 0px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-text, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-image, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-image > span { + color: black; + text-shadow: 0px 0px 0px #fff; + pointer-events: none; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-text:hover, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-image:hover, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-image > span:hover { + color: inherit; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-handle { + display: inline-block; + padding: 0px; + margin: 0px; + height: 100%; + position: absolute; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node:hover { + border-top: 1px solid #f9f9f9; + border-bottom: 1px solid #f9f9f9; + background-color: #f1f1f1; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-image { + width: 22px; + text-align: center; + pointer-events: none; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-image > span { + color: #888 !important; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node input, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node button { + pointer-events: auto; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:hover { + position: relative; + background-color: #E9E9E9; + border: 1px solid #dfdfdf; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected .w2ui-node-text, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:hover .w2ui-node-text, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected .w2ui-node-image, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:hover .w2ui-node-image, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected .w2ui-node-image > span, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:hover .w2ui-node-image > span { + color: inherit !important; + text-shadow: 0px 0px 0px #fff !important; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:before { + content: ""; + border: 1px dashed transparent; + border-radius: 4px; + position: absolute; + top: -1px; + bottom: -1px; + left: -1px; + right: -1px; + pointer-events: none; +} +.w2ui-sidebar .w2ui-sidebar-body.w2ui-focus .w2ui-selected:before { + border: 1px solid #cccccc; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-disabled, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-disabled:hover { + background: transparent !important; + border: 1px solid transparent; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-disabled .w2ui-node-text, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-disabled:hover .w2ui-node-text, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-disabled .w2ui-node-image, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-disabled:hover .w2ui-node-image, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-disabled .w2ui-node-image > span, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-disabled:hover .w2ui-node-image > span { + opacity: 0.4; + filter: alpha(opacity=40); + color: black !important; + text-shadow: 0px 0px 0px #fff !important; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-text { + white-space: nowrap; + padding: 5px 0px 5px 3px; + margin: 1px 0px 1px 22px; + position: relative; + z-index: 1; + font-size: 12px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-group { + white-space: nowrap; + overflow: hidden; + padding: 10px 0px 10px 10px; + margin: 0px; + cursor: default; + color: #8fabca; + background-color: transparent; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-group :nth-child(1) { + /* show / hide link */ + margin-right: 10px; + float: right; + color: transparent; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-group :nth-child(2) { + /* title text */ + font-weight: normal; + text-transform: uppercase; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-sub { + overflow: hidden; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data { + padding: 2px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-node-image { + padding: 3px 0px 0px 0px; + float: left; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-node-image > span { + font-size: 16px; + color: black; + text-shadow: 0px 0px 0px #fff; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-node-image.w2ui-icon { + margin-top: 3px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-node-count { + float: right; + border: 1px solid #F6FCF4; + border-radius: 20px; + width: auto; + height: 18px; + padding: 2px 7px; + margin: 3px 4px -2px 0; + background-color: #F2F8F0; + color: #666; + box-shadow: 0px 0px 2px #474545; + text-shadow: 1px 1px 0px #FFF; + position: relative; + z-index: 2; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-expanded { + float: right; + width: auto; + height: 18px; + position: relative; + z-index: 2; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed span, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-expanded span { + border-color: transparent; + background-color: transparent; + box-shadow: none; + padding: 2px 5px; + border-radius: 0px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed span:after, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-expanded span:after { + content: ""; + position: absolute; + border-left: 5px solid #808080; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + transform: rotateZ(-90deg); + pointer-events: none; + margin-left: -4px; + margin-top: 7px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed span:hover, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-expanded span:hover { + border-color: transparent; + background-color: transparent; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed span:after { + transform: rotateZ(90deg); +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-flat { + display: block; + padding: 2px 0px; + text-align: center; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-flat .w2ui-node-image { + float: none; + text-align: center; + width: auto; + padding: 1px 0px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-flat .w2ui-node-image > span { + font-size: 16px; + color: black; + text-shadow: 0px 0px 0px #fff; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-flat .w2ui-node-image.w2ui-icon { + width: 21px; +} +/************************************************* +* ---- Tabs ---- +*/ +.w2ui-tabs { + cursor: default; + overflow: hidden !important; + position: relative; + background-color: #fafafa; + padding: 3px 0px; +} +.w2ui-tabs .w2ui-scroll-wrapper { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-content: flex-start; + padding: 0 7px; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tabs-line { + border-bottom: 1px solid silver; + position: absolute; + left: 0px; + right: 0px; + top: 3px; + bottom: 3px; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab { + position: relative; + padding: 6px 20px; + text-align: center; + color: black; + background-color: transparent; + border: 1px solid silver; + border-bottom: 1px solid transparent; + white-space: nowrap; + margin: 1px 1px -1px 0px; + border-radius: 4px 4px 0 0; + cursor: default; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab.active { + color: black; + background-color: white; + border: 1px solid silver; + border-bottom: 3px solid 1px solid silver; + margin-bottom: -3px; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab.moving { + color: black; + background-color: #ddd; + border: 1px solid silver; + border-bottom: 3px solid 1px solid silver; + margin-bottom: -3px; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab.closable { + padding: 6px 28px 6px 20px; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab .w2ui-tab-close { + position: absolute; + right: 3px; + top: 3px; + color: #555; + text-shadow: 1px 1px 1px #bbb; + float: right; + padding: 0px 4px; + width: 16px; + height: 16px; + opacity: 0.6; + border: 0px; + border-top: 3px solid transparent; + border-radius: 9px; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab .w2ui-tab-close:hover { + background-color: #D77F7F; + color: white; + opacity: 1; + text-shadow: 0px 0px 1px #777; + font-weight: bold; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab .w2ui-tab-close:before { + position: relative; + top: -2px; + left: 0px; + color: inherit; + text-shadow: inherit; + content: 'x'; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tabs-right { + padding: 5px; + width: 100%; + text-align: right; +} +.w2ui-tabs.w2ui-tabs-up .w2ui-scroll-wrapper .w2ui-tabs-line { + border-bottom: 1px solid transparent; + border-top: 1px solid silver; +} +.w2ui-tabs.w2ui-tabs-up .w2ui-scroll-wrapper .w2ui-tab { + border: 1px solid silver; + border-top: 1px solid white; + margin: -1px 1px 1px 0px; + border-radius: 0 0 4px 4px; +} +.w2ui-tabs.w2ui-tabs-up .w2ui-scroll-wrapper .w2ui-tab.active { + color: black; + background-color: white; + border: 1px solid silver; + margin: 1px 1px 1px 0px; + border-top: 2px solid white; + margin-top: -2px; +} +/************************************************* +* ---- Toolbar ---- +*/ +.w2ui-toolbar { + margin: 0px; + padding: 2px; + outline: 0px; + position: relative; + background-color: #efefef; + overflow: hidden !important; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +.w2ui-toolbar .disabled { + opacity: 0.3; + filter: alpha(opacity=30); +} +.w2ui-toolbar table { + table-layout: auto !important; +} +.w2ui-toolbar table td { + border: 0px !important; +} +.w2ui-toolbar table.w2ui-button { + margin: 0 1px 0 0; + border-radius: 4px; + height: 24px; + min-width: 24px; + border: 1px solid transparent; + background-color: transparent; +} +.w2ui-toolbar table.w2ui-button .w2ui-tb-image { + width: 22px; + height: 16px; + padding: 0; + margin: 5px 1px 3px 1px!important; + border: 0!important; + text-align: center; +} +.w2ui-toolbar table.w2ui-button .w2ui-tb-image > span { + font-size: 15px; + display: block; + color: #8D99A7; +} +.w2ui-toolbar table.w2ui-button .w2ui-tb-image > span:before { + vertical-align: top !important; +} +.w2ui-toolbar table.w2ui-button .w2ui-tb-text { + color: black; + padding: 0px 4px 0px 2px; +} +.w2ui-toolbar table.w2ui-button .w2ui-tb-count { + padding: 0px 3px 0px 1px; +} +.w2ui-toolbar table.w2ui-button .w2ui-tb-count > span { + border: 1px solid #F6FCF4; + border-radius: 11px; + width: auto; + height: 18px; + padding: 0px 6px 1px 6px; + background-color: #F2F8F0; + color: #666; + box-shadow: 0px 0px 2px #474545; + text-shadow: 1px 1px 0px #FFF; +} +.w2ui-toolbar table.w2ui-button .w2ui-tb-down { + padding: 1px 3px 1px 1px; +} +.w2ui-toolbar table.w2ui-button .w2ui-tb-down > div { + border: 4px solid transparent; + border-top: 5px solid #8D99A7; + margin-top: 5px; +} +.w2ui-toolbar table.w2ui-button.over { + border: 1px solid #ccc; + background-color: #eee; +} +.w2ui-toolbar table.w2ui-button.over .w2ui-tb-text { + color: black; +} +.w2ui-toolbar table.w2ui-button.down { + border: 1px solid #aaa; + background-color: #ddd; +} +.w2ui-toolbar table.w2ui-button.down .w2ui-tb-text { + color: #666; +} +.w2ui-toolbar table.w2ui-button.checked { + border: 1px solid #aaa; + background-color: white; +} +.w2ui-toolbar table.w2ui-button.checked .w2ui-tb-text { + color: black; +} +.w2ui-toolbar table.w2ui-button table { + height: 17px; + border-radius: 4px; + cursor: default; +} +.w2ui-toolbar .w2ui-toolbar-new-line { + border-top: 1px solid #dedede; + border-bottom: 1px solid white; + margin: 5px 0 5px 0; +} +.w2ui-toolbar .w2ui-break { + background-image: -webkit-linear-gradient(top, rgba(153, 153, 153, 0.1) 0%, #999 40%, #999 60%, rgba(153, 153, 153, 0.1) 100%); + background-image: -moz-linear-gradient(top, rgba(153, 153, 153, 0.1) 0%, #999 40%, #999 60%, rgba(153, 153, 153, 0.1) 100%); + background-image: -ms-linear-gradient(top, rgba(153, 153, 153, 0.1) 0%, #999 40%, #999 60%, rgba(153, 153, 153, 0.1) 100%); + background-image: -o-linear-gradient(top, rgba(153, 153, 153, 0.1) 0%, #999 40%, #999 60%, rgba(153, 153, 153, 0.1) 100%); + background-image: linear-gradient(to bottom, rgba(153, 153, 153, 0.1) 0%, #999 40%, #999 60%, rgba(153, 153, 153, 0.1) 100%); + filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff999999', endColorstr='#ff999999', GradientType=0); + width: 1px !important; + height: 22px; + padding: 0px; + margin: 0px 6px; +} +.w2ui-listview { + overflow: auto !important; + background-color: white !important; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-listview * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-listview > ul { + list-style-type: none; + margin: 0; + cursor: default; +} +.w2ui-listview > ul > li { + display: inline-block; + vertical-align: top; + overflow: hidden; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + border: 1px solid transparent; + border-radius: 4px; +} +.w2ui-listview > ul > li.w2ui-focused { + border: 1px solid #2661A6; +} +.w2ui-listview > ul > li.w2ui-selected { + border: 1px solid #2661A6; +} +.w2ui-listview > ul > li.w2ui-selected, +.w2ui-listview > ul > li.w2ui-selected.hover { + background-color: #E9E9E9; +} +.w2ui-listview > ul > li.w2ui-selected > div > div.caption, +.w2ui-listview > ul > li.w2ui-selected.hover > div > div.caption { + color: inherit; +} +.w2ui-listview > ul > li.w2ui-selected > div > div.description, +.w2ui-listview > ul > li.w2ui-selected.hover > div > div.description { + color: #DDDDDD; +} +.w2ui-listview > ul > li.w2ui-selected > div > div.extra > div > div, +.w2ui-listview > ul > li.w2ui-selected.hover > div > div.extra > div > div { + color: #DDDDDD; +} +.w2ui-listview > ul > li.hover { + background-color: #f1f1f1; + border: 1px solid #2661A6; +} +.w2ui-listview > ul > li div { + vertical-align: middle; +} +.w2ui-listview > ul > li > div > div.caption { + display: block; + text-align: center; + word-wrap: break-word; + max-height: 50px; + color: black; + font-size: 12px; +} +.w2ui-listview > ul > li > div > div.description { + display: none; + text-align: left; + color: #777777; + font-size: 12px; +} +.w2ui-listview > ul > li > div > div.extra { + display: none; +} +.w2ui-listview > ul > li > div > div.extra > div > div { + color: #777777; +} +.w2ui-icon-small > ul { + padding: 1px 0px 0px 1px; +} +.w2ui-icon-small > ul > li { + margin: 0px 1px 1px 0px; + padding: 2px; + width: 250px; + white-space: nowrap; +} +.w2ui-icon-small > ul > li > div > div.w2ui-listview-img { + display: inline-block; + width: 26px; + height: 22px; + font-size: 21px; + margin-right: 2px; +} +.w2ui-icon-small > ul > li > div > div.caption { + display: inline-block; +} +.w2ui-icon-medium > ul { + padding: 4px 0px 0px 4px; +} +.w2ui-icon-medium > ul > li { + margin: 0px 4px 4px 0px; + padding: 4px; + width: 100px; +} +.w2ui-icon-medium > ul > li > div > div.w2ui-listview-img { + display: block; + width: 92px; + height: 60px; + font-size: 57px; + margin-left: auto; + margin-right: auto; + background-position: center; +} +.w2ui-icon-large > ul { + padding: 4px 0px 0px 4px; +} +.w2ui-icon-large > ul > li { + margin: 0px 4px 4px 0px; + padding: 4px; + width: 160px; +} +.w2ui-icon-large > ul > li > div > div.w2ui-listview-img { + display: block; + width: 152px; + height: 120px; + font-size: 114px; + margin-left: auto; + margin-right: auto; + background-position: center; +} +.w2ui-icon-tile > ul { + padding: 1px 0px 0px 1px; +} +.w2ui-icon-tile > ul > li { + margin: 0px 1px 1px 0px; + padding: 4px; + width: 250px; + white-space: nowrap; +} +.w2ui-icon-tile > ul > li > div > div.w2ui-listview-img { + display: inline-block; + width: 72px; + height: 60px; + font-size: 57px; + float: left; + margin-right: 4px; +} +.w2ui-icon-tile > ul > li > div > div.caption { + text-align: left; +} +.w2ui-icon-tile > ul > li > div > div.description { + display: block; +} +.w2ui-table > ul { + padding: 0; +} +.w2ui-table > ul > li { + width: 100%; + padding: 2px; + border-radius: 0px; + border-bottom: 1px dotted lightgray; +} +.w2ui-table > ul > li > div { + display: inline-block; + position: relative; + width: 100%; + white-space: nowrap; + overflow: hidden; +} +.w2ui-table > ul > li > div > div.w2ui-listview-img { + display: inline-block; + width: 38px; + height: 32px; + font-size: 31px; + margin-right: 2px; +} +.w2ui-table > ul > li > div > div.caption { + display: inline-block; +} +.w2ui-table > ul > li > div > div.extra { + display: inline-block; + position: absolute; + right: 0; + height: 100%; + background-color: white; +} +.w2ui-table > ul > li > div > div.extra > div:before { + display: inline-block; + height: 100%; + width: 0; + content: ''; + vertical-align: middle; +} +.w2ui-table > ul > li > div > div.extra > div { + display: inline; +} +.w2ui-table > ul > li > div > div.extra > div > div { + display: inline-block; + font-size: 12px; +} +.w2ui-table > ul > li.w2ui-selected div.extra, +.w2ui-table > ul > li.w2ui-selected.hover div.extra { + background-color: #E9E9E9; +} +.w2ui-table > ul > li.hover div.extra { + background-color: #f1f1f1; +} +.w2ui-listview > ul > li div.icon-none { + border: 1px solid rgba(102, 102, 102, 0.35); +} diff --git a/lib/w2ui/w2ui-1.5.js b/lib/w2ui/w2ui-1.5.js new file mode 100644 index 0000000..df07023 --- /dev/null +++ b/lib/w2ui/w2ui-1.5.js @@ -0,0 +1,21501 @@ +/* w2ui 1.5 (c) http://w2ui.com, vitmalina@gmail.com */ +var w2ui = w2ui || {}; +var w2obj = w2obj || {}; // expose object to be able to overwrite default functions + +/************************************************ +* Library: Web 1.5 UI for jQuery +* - Following objects are defines +* - w2ui - object that will contain all widgets +* - w2obj - object with widget prototypes +* - w2utils - basic utilities +* - $().w2render - common render +* - $().w2destroy - common destroy +* - $().w2marker - marker plugin +* - $().w2tag - tag plugin +* - $().w2overlay - overlay plugin +* - $().w2menu - menu plugin +* - w2utils.event - generic event object +* - Dependencies: jQuery +* +* == NICE TO HAVE == +* - overlay should be displayed where more space (on top or on bottom) +* - write and article how to replace certain framework functions +* - add maxHeight for the w2menu +* - add time zone +* - TEST On IOS +* - $().w2marker() -- only unmarks first instance +* - subitems for w2menus() +* - add w2utils.lang wrap for all captions in all buttons. +* - $().w2date(), $().w2dateTime() +* +* == 1.5 change +* - parseColor(str) returns rgb +* - rgb2hsv, hsv2rgb +* - color.onSelect +* - color.html +* - refactored w2tag object, it has more potential with $().data('w2tag') +* - added w2utils.tooltip +* - w2tag options.hideOnFocus +* - w2tag options.maxWidth +* - w2tag options.auto - if set to true, then tag will show on mouseover +* - w2tag options.showOn, hideOn - if set to true, then tag will show on mouseover +* - w2tag options.className: 'w2ui-light' - for light color tag +* - w2menu options.items... remove t/f +* - w2menu options.items... keepOpen t/f +* - w2menu options.onRemove +* - w2menu options.hideOnRemove +* - w2menu - can not nest items, item.items and item.expanded +* - w2menu.options.topHTML +* - w2menu.options.menuStyle +* - naturalCompare +* +************************************************/ + +var w2utils = (function ($) { + var tmp = {}; // for some temp variables + var obj = { + version : '1.5', + settings : { + "locale" : "en-us", + "dateFormat" : "m/d/yyyy", + "timeFormat" : "hh:mi pm", + "datetimeFormat" : "m/d/yyyy|hh:mi pm", + "currencyPrefix" : "$", + "currencySuffix" : "", + "currencyPrecision" : 2, + "groupSymbol" : ",", + "decimalSymbol" : ".", + "shortmonths" : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + "fullmonths" : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + "shortdays" : ["M", "T", "W", "T", "F", "S", "S"], + "fulldays" : ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], + "weekStarts" : "M", // can be "M" for Monday or "S" for Sunday + "dataType" : 'HTTPJSON', // can be HTTP, HTTPJSON, RESTFULL, RESTFULLJSON, JSON (case sensitive) + "phrases" : {}, // empty object for english phrases + "dateStartYear" : 1950, // start year for date-picker + "dateEndYear" : 2030, // end year for date picker + "macButtonOrder" : false // if true, Yes on the right side + }, + isBin : isBin, + isInt : isInt, + isFloat : isFloat, + isMoney : isMoney, + isHex : isHex, + isAlphaNumeric : isAlphaNumeric, + isEmail : isEmail, + isIpAddress : isIpAddress, + isDate : isDate, + isTime : isTime, + isDateTime : isDateTime, + age : age, + interval : interval, + date : date, + formatSize : formatSize, + formatNumber : formatNumber, + formatDate : formatDate, + formatTime : formatTime, + formatDateTime : formatDateTime, + stripTags : stripTags, + encodeTags : encodeTags, + decodeTags : decodeTags, + escapeId : escapeId, + base64encode : base64encode, + base64decode : base64decode, + md5 : md5, + transition : transition, + lock : lock, + unlock : unlock, + message : message, + naturalCompare : naturalCompare, + lang : lang, + locale : locale, + getSize : getSize, + getStrWidth : getStrWidth, + scrollBarSize : scrollBarSize, + checkName : checkName, + checkUniqueId : checkUniqueId, + parseRoute : parseRoute, + cssPrefix : cssPrefix, + parseColor : parseColor, + hsv2rgb : hsv2rgb, + rgb2hsv : rgb2hsv, + tooltip : tooltip, + getCursorPosition : getCursorPosition, + setCursorPosition : setCursorPosition, + testLocalStorage : testLocalStorage, + hasLocalStorage : testLocalStorage(), + // some internal variables + isIOS : ((navigator.userAgent.toLowerCase().indexOf('iphone') !== -1 || + navigator.userAgent.toLowerCase().indexOf('ipod') !== -1 || + navigator.userAgent.toLowerCase().indexOf('ipad') !== -1 || + navigator.userAgent.toLowerCase().indexOf('mobile') !== -1 || + navigator.userAgent.toLowerCase().indexOf('android') !== -1) + ? true : false), + isIE : ((navigator.userAgent.toLowerCase().indexOf('msie') !== -1 || + navigator.userAgent.toLowerCase().indexOf('trident') !== -1 ) + ? true : false) + }; + return obj; + + function isBin (val) { + var re = /^[0-1]+$/; + return re.test(val); + } + + function isInt (val) { + var re = /^[-+]?[0-9]+$/; + return re.test(val); + } + + function isFloat (val) { + if (typeof val === 'string') val = val.replace(/\s+/g, '').replace(w2utils.settings.groupSymbol, '').replace(w2utils.settings.decimalSymbol, '.'); + return (typeof val === 'number' || (typeof val === 'string' && val !== '')) && !isNaN(Number(val)); + } + + function isMoney (val) { + var se = w2utils.settings; + var re = new RegExp('^'+ (se.currencyPrefix ? '\\' + se.currencyPrefix + '?' : '') + + '[-+]?'+ (se.currencyPrefix ? '\\' + se.currencyPrefix + '?' : '') + + '[0-9]*[\\'+ se.decimalSymbol +']?[0-9]+'+ (se.currencySuffix ? '\\' + se.currencySuffix + '?' : '') +'$', 'i'); + if (typeof val === 'string') { + val = val.replace(new RegExp(se.groupSymbol, 'g'), ''); + } + if (typeof val === 'object' || val === '') return false; + return re.test(val); + } + + function isHex (val) { + var re = /^(0x)?[0-9a-fA-F]+$/; + return re.test(val); + } + + function isAlphaNumeric (val) { + var re = /^[a-zA-Z0-9_-]+$/; + return re.test(val); + } + + function isEmail (val) { + var email = /^[a-zA-Z0-9._%\-+]+@[а-яА-Яa-zA-Z0-9.-]+\.[а-яА-Яa-zA-Z]+$/; + return email.test(val); + } + + function isIpAddress (val) { + var re = new RegExp('^' + + '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' + + '$'); + return re.test(val); + } + + function isDate (val, format, retDate) { + if (!val) return false; + + var dt = 'Invalid Date'; + var month, day, year; + + if (format == null) format = w2utils.settings.dateFormat; + + if (typeof val.getFullYear === 'function') { // date object + year = val.getFullYear(); + month = val.getMonth() + 1; + day = val.getDate(); + } else if (parseInt(val) == val && parseInt(val) > 0) { + val = new Date(parseInt(val)); + year = val.getFullYear(); + month = val.getMonth() + 1; + day = val.getDate(); + } else { + val = String(val); + // convert month formats + if (new RegExp('mon', 'ig').test(format)) { + format = format.replace(/month/ig, 'm').replace(/mon/ig, 'm').replace(/dd/ig, 'd').replace(/[, ]/ig, '/').replace(/\/\//g, '/').toLowerCase(); + val = val.replace(/[, ]/ig, '/').replace(/\/\//g, '/').toLowerCase(); + for (var m = 0, len = w2utils.settings.fullmonths.length; m < len; m++) { + var t = w2utils.settings.fullmonths[m]; + val = val.replace(new RegExp(t, 'ig'), (parseInt(m) + 1)).replace(new RegExp(t.substr(0, 3), 'ig'), (parseInt(m) + 1)); + } + } + // format date + var tmp = val.replace(/-/g, '/').replace(/\./g, '/').toLowerCase().split('/'); + var tmp2 = format.replace(/-/g, '/').replace(/\./g, '/').toLowerCase(); + if (tmp2 === 'mm/dd/yyyy') { month = tmp[0]; day = tmp[1]; year = tmp[2]; } + if (tmp2 === 'm/d/yyyy') { month = tmp[0]; day = tmp[1]; year = tmp[2]; } + if (tmp2 === 'dd/mm/yyyy') { month = tmp[1]; day = tmp[0]; year = tmp[2]; } + if (tmp2 === 'd/m/yyyy') { month = tmp[1]; day = tmp[0]; year = tmp[2]; } + if (tmp2 === 'yyyy/dd/mm') { month = tmp[2]; day = tmp[1]; year = tmp[0]; } + if (tmp2 === 'yyyy/d/m') { month = tmp[2]; day = tmp[1]; year = tmp[0]; } + if (tmp2 === 'yyyy/mm/dd') { month = tmp[1]; day = tmp[2]; year = tmp[0]; } + if (tmp2 === 'yyyy/m/d') { month = tmp[1]; day = tmp[2]; year = tmp[0]; } + if (tmp2 === 'mm/dd/yy') { month = tmp[0]; day = tmp[1]; year = tmp[2]; } + if (tmp2 === 'm/d/yy') { month = tmp[0]; day = tmp[1]; year = parseInt(tmp[2]) + 1900; } + if (tmp2 === 'dd/mm/yy') { month = tmp[1]; day = tmp[0]; year = parseInt(tmp[2]) + 1900; } + if (tmp2 === 'd/m/yy') { month = tmp[1]; day = tmp[0]; year = parseInt(tmp[2]) + 1900; } + if (tmp2 === 'yy/dd/mm') { month = tmp[2]; day = tmp[1]; year = parseInt(tmp[0]) + 1900; } + if (tmp2 === 'yy/d/m') { month = tmp[2]; day = tmp[1]; year = parseInt(tmp[0]) + 1900; } + if (tmp2 === 'yy/mm/dd') { month = tmp[1]; day = tmp[2]; year = parseInt(tmp[0]) + 1900; } + if (tmp2 === 'yy/m/d') { month = tmp[1]; day = tmp[2]; year = parseInt(tmp[0]) + 1900; } + } + if (!isInt(year)) return false; + if (!isInt(month)) return false; + if (!isInt(day)) return false; + year = +year; + month = +month; + day = +day; + dt = new Date(year, month - 1, day); + dt.setFullYear(year); + // do checks + if (month == null) return false; + if (String(dt) === 'Invalid Date') return false; + if ((dt.getMonth() + 1 !== month) || (dt.getDate() !== day) || (dt.getFullYear() !== year)) return false; + if (retDate === true) return dt; else return true; + } + + function isTime (val, retTime) { + // Both formats 10:20pm and 22:20 + if (val == null) return false; + var max, am, pm; + // -- process american format + val = String(val); + val = val.toUpperCase(); + am = val.indexOf('AM') >= 0; + pm = val.indexOf('PM') >= 0; + var ampm = (pm || am); + if (ampm) max = 12; else max = 24; + val = val.replace('AM', '').replace('PM', ''); + val = $.trim(val); + // --- + var tmp = val.split(':'); + var h = parseInt(tmp[0] || 0), m = parseInt(tmp[1] || 0), s = parseInt(tmp[2] || 0); + // accept edge case: 3PM is a good timestamp, but 3 (without AM or PM) is NOT: + if ((!ampm || tmp.length !== 1) && tmp.length !== 2 && tmp.length !== 3) { return false; } + if (tmp[0] === '' || h < 0 || h > max || !this.isInt(tmp[0]) || tmp[0].length > 2) { return false; } + if (tmp.length > 1 && (tmp[1] === '' || m < 0 || m > 59 || !this.isInt(tmp[1]) || tmp[1].length !== 2)) { return false; } + if (tmp.length > 2 && (tmp[2] === '' || s < 0 || s > 59 || !this.isInt(tmp[2]) || tmp[2].length !== 2)) { return false; } + // check the edge cases: 12:01AM is ok, as is 12:01PM, but 24:01 is NOT ok while 24:00 is (midnight; equivalent to 00:00). + // meanwhile, there is 00:00 which is ok, but 0AM nor 0PM are okay, while 0:01AM and 0:00AM are. + if (!ampm && max === h && (m !== 0 || s !== 0)) { return false; } + if (ampm && tmp.length === 1 && h === 0) { return false; } + + if (retTime === true) { + if (pm && h !== 12) h += 12; // 12:00pm - is noon + if (am && h === 12) h += 12; // 12:00am - is midnight + return { + hours: h, + minutes: m, + seconds: s + }; + } + return true; + } + + function isDateTime (val, format, retDate) { + if (typeof val.getFullYear === 'function') { // date object + if (retDate !== true) return true; + return val; + } + var intVal = parseInt(val); + if (intVal === val) { + if (intVal < 0) return false; + else if (retDate !== true) return true; + else return new Date(intVal); + } + var tmp = String(val).indexOf(' '); + if (tmp < 0) { + if (String(val).indexOf('T') < 0 || String(new Date(val)) == 'Invalid Date') return false; + else if (retDate !== true) return true; + else return new Date(val); + } else { + if (format == null) format = w2utils.settings.datetimeFormat; + var formats = format.split('|'); + var values = [val.substr(0, tmp), val.substr(tmp).trim()]; + formats[0] = formats[0].trim(); + if (formats[1]) formats[1] = formats[1].trim(); + // check + var tmp1 = w2utils.isDate(values[0], formats[0], true); + var tmp2 = w2utils.isTime(values[1], true); + if (tmp1 !== false && tmp2 !== false) { + if (retDate !== true) return true; + tmp1.setHours(tmp2.hours); + tmp1.setMinutes(tmp2.minutes); + tmp1.setSeconds(tmp2.seconds); + return tmp1; + } else { + return false; + } + } + } + + function age(dateStr) { + var d1; + if (dateStr === '' || dateStr == null) return ''; + if (typeof dateStr.getFullYear === 'function') { // date object + d1 = dateStr; + } else if (parseInt(dateStr) == dateStr && parseInt(dateStr) > 0) { + d1 = new Date(parseInt(dateStr)); + } else { + d1 = new Date(dateStr); + } + if (String(d1) === 'Invalid Date') return ''; + + var d2 = new Date(); + var sec = (d2.getTime() - d1.getTime()) / 1000; + var amount = ''; + var type = ''; + if (sec < 0) { + amount = 0; + type = 'sec'; + } else if (sec < 60) { + amount = Math.floor(sec); + type = 'sec'; + if (sec < 0) { amount = 0; type = 'sec'; } + } else if (sec < 60*60) { + amount = Math.floor(sec/60); + type = 'min'; + } else if (sec < 24*60*60) { + amount = Math.floor(sec/60/60); + type = 'hour'; + } else if (sec < 30*24*60*60) { + amount = Math.floor(sec/24/60/60); + type = 'day'; + } else if (sec < 365*24*60*60) { + amount = Math.floor(sec/30/24/60/60*10)/10; + type = 'month'; + } else if (sec < 365*4*24*60*60) { + amount = Math.floor(sec/365/24/60/60*10)/10; + type = 'year'; + } else if (sec >= 365*4*24*60*60) { + // factor in leap year shift (only older then 4 years) + amount = Math.floor(sec/365.25/24/60/60*10)/10; + type = 'year'; + } + return amount + ' ' + type + (amount > 1 ? 's' : ''); + } + + function interval (value) { + var ret = ''; + if (value < 1000) { + ret = "< 1 sec"; + } else if (value < 60000) { + ret = Math.floor(value / 1000) + " secs"; + } else if (value < 3600000) { + ret = Math.floor(value / 60000) + " mins"; + } else if (value < 86400000) { + ret = Math.floor(value / 3600000 * 10) / 10 + " hours"; + } else if (value < 2628000000) { + ret = Math.floor(value / 86400000 * 10) / 10 + " days"; + } else if (value < 3.1536e+10) { + ret = Math.floor(value / 2628000000 * 10) / 10 + " months"; + } else { + ret = Math.floor(value / 3.1536e+9) / 10 + " years"; + } + return ret; + } + + function date (dateStr) { + if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return ''; + var d1 = new Date(dateStr); + if (w2utils.isInt(dateStr)) d1 = new Date(Number(dateStr)); // for unix timestamps + if (String(d1) === 'Invalid Date') return ''; + + var months = w2utils.settings.shortmonths; + var d2 = new Date(); // today + var d3 = new Date(); + d3.setTime(d3.getTime() - 86400000); // yesterday + + var dd1 = months[d1.getMonth()] + ' ' + d1.getDate() + ', ' + d1.getFullYear(); + var dd2 = months[d2.getMonth()] + ' ' + d2.getDate() + ', ' + d2.getFullYear(); + var dd3 = months[d3.getMonth()] + ' ' + d3.getDate() + ', ' + d3.getFullYear(); + + var time = (d1.getHours() - (d1.getHours() > 12 ? 12 :0)) + ':' + (d1.getMinutes() < 10 ? '0' : '') + d1.getMinutes() + ' ' + (d1.getHours() >= 12 ? 'pm' : 'am'); + var time2= (d1.getHours() - (d1.getHours() > 12 ? 12 :0)) + ':' + (d1.getMinutes() < 10 ? '0' : '') + d1.getMinutes() + ':' + (d1.getSeconds() < 10 ? '0' : '') + d1.getSeconds() + ' ' + (d1.getHours() >= 12 ? 'pm' : 'am'); + var dsp = dd1; + if (dd1 === dd2) dsp = time; + if (dd1 === dd3) dsp = w2utils.lang('Yesterday'); + + return ''+ dsp +''; + } + + function formatSize (sizeStr) { + if (!w2utils.isFloat(sizeStr) || sizeStr === '') return ''; + sizeStr = parseFloat(sizeStr); + if (sizeStr === 0) return 0; + var sizes = ['Bt', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB']; + var i = parseInt( Math.floor( Math.log(sizeStr) / Math.log(1024) ) ); + return (Math.floor(sizeStr / Math.pow(1024, i) * 10) / 10).toFixed(i === 0 ? 0 : 1) + ' ' + (sizes[i] || '??'); + } + + function formatNumber (val, fraction, useGrouping) { + if (val == null || val === '' || typeof val === 'object') return ''; + var options = { + minimumFractionDigits : fraction, + maximumFractionDigits : fraction, + useGrouping : useGrouping + }; + if (fraction == null || fraction < 0) { + options.minimumFractionDigits = 0; + options.maximumFractionDigits = 20; + } + return parseFloat(val).toLocaleString(w2utils.settings.locale, options); + } + + function formatDate (dateStr, format) { // IMPORTANT dateStr HAS TO BE valid JavaScript Date String + if (!format) format = this.settings.dateFormat; + if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return ''; + + var dt = new Date(dateStr); + if (w2utils.isInt(dateStr)) dt = new Date(Number(dateStr)); // for unix timestamps + if (String(dt) === 'Invalid Date') return ''; + + var year = dt.getFullYear(); + var month = dt.getMonth(); + var date = dt.getDate(); + return format.toLowerCase() + .replace('month', w2utils.settings.fullmonths[month]) + .replace('mon', w2utils.settings.shortmonths[month]) + .replace(/yyyy/g, ('000' + year).slice(-4)) + .replace(/yyy/g, ('000' + year).slice(-4)) + .replace(/yy/g, ('0' + year).slice(-2)) + .replace(/(^|[^a-z$])y/g, '$1' + year) // only y's that are not preceded by a letter + .replace(/mm/g, ('0' + (month + 1)).slice(-2)) + .replace(/dd/g, ('0' + date).slice(-2)) + .replace(/th/g, (date == 1 ? 'st' : 'th')) + .replace(/th/g, (date == 2 ? 'nd' : 'th')) + .replace(/th/g, (date == 3 ? 'rd' : 'th')) + .replace(/(^|[^a-z$])m/g, '$1' + (month + 1)) // only y's that are not preceded by a letter + .replace(/(^|[^a-z$])d/g, '$1' + date); // only y's that are not preceded by a letter + } + + function formatTime (dateStr, format) { // IMPORTANT dateStr HAS TO BE valid JavaScript Date String + var months = w2utils.settings.shortmonths; + var fullMonths = w2utils.settings.fullmonths; + if (!format) format = this.settings.timeFormat; + if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return ''; + + var dt = new Date(dateStr); + if (w2utils.isInt(dateStr)) dt = new Date(Number(dateStr)); // for unix timestamps + if (w2utils.isTime(dateStr)) { + var tmp = w2utils.isTime(dateStr, true); + dt = new Date(); + dt.setHours(tmp.hours); + dt.setMinutes(tmp.minutes); + } + if (String(dt) === 'Invalid Date') return ''; + + var type = 'am'; + var hour = dt.getHours(); + var h24 = dt.getHours(); + var min = dt.getMinutes(); + var sec = dt.getSeconds(); + if (min < 10) min = '0' + min; + if (sec < 10) sec = '0' + sec; + if (format.indexOf('am') !== -1 || format.indexOf('pm') !== -1) { + if (hour >= 12) type = 'pm'; + if (hour > 12) hour = hour - 12; + if (hour === 0) hour = 12; + } + return format.toLowerCase() + .replace('am', type) + .replace('pm', type) + .replace('hhh', (hour < 10 ? '0' + hour : hour)) + .replace('hh24', (h24 < 10 ? '0' + h24 : h24)) + .replace('h24', h24) + .replace('hh', hour) + .replace('mm', min) + .replace('mi', min) + .replace('ss', sec) + .replace(/(^|[^a-z$])h/g, '$1' + hour) // only y's that are not preceded by a letter + .replace(/(^|[^a-z$])m/g, '$1' + min) // only y's that are not preceded by a letter + .replace(/(^|[^a-z$])s/g, '$1' + sec); // only y's that are not preceded by a letter + } + + function formatDateTime(dateStr, format) { + var fmt; + if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return ''; + if (typeof format !== 'string') { + fmt = [this.settings.dateFormat, this.settings.timeFormat]; + } else { + fmt = format.split('|'); + fmt[0] = fmt[0].trim(); + fmt[1] = (fmt.length > 1 ? fmt[1].trim() : this.settings.timeFormat); + } + // older formats support + if (fmt[1] === 'h12') fmt[1] = 'h:m pm'; + if (fmt[1] === 'h24') fmt[1] = 'h24:m'; + return this.formatDate(dateStr, fmt[0]) + ' ' + this.formatTime(dateStr, fmt[1]); + } + + function stripTags (html) { + if (html == null) return html; + switch (typeof html) { + case 'number': + break; + case 'string': + html = String(html).replace(/<(?:[^>=]|='[^']*'|="[^"]*"|=[^'"][^\s>]*)*>/ig, ""); + break; + case 'object': + // does not modify original object, but creates a copy + if (Array.isArray(html)) { + html = $.extend(true, [], html); + for (var i = 0; i < html.length; i++) html[i] = this.stripTags(html[i]); + } else { + html = $.extend(true, {}, html); + for (var i in html) html[i] = this.stripTags(html[i]); + } + break; + } + return html; + } + + function encodeTags (html) { + if (html == null) return html; + switch (typeof html) { + case 'number': + break; + case 'string': + html = String(html).replace(/&/g, "&").replace(/>/g, ">").replace(/").replace(/</g, "<").replace(/"/g, '"').replace(/&/g, "&"); + break; + case 'object': + // does not modify original object, but creates a copy + if (Array.isArray(html)) { + html = $.extend(true, [], html); + for (var i = 0; i < html.length; i++) html[i] = this.decodeTags(html[i]); + } else { + html = $.extend(true, {}, html); + for (var i in html) html[i] = this.decodeTags(html[i]); + } + break; + } + return html; + } + + function escapeId (id) { + if (id === '' || id == null) return ''; + return String(id).replace(/([;&,\.\+\*\~'`:"\!\^#$%@\[\]\(\)=<>\|\/? {}\\])/g, '\\$1'); + } + + function base64encode (input) { + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + input = utf8_encode(input); + + while (i < input.length) { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4); + } + + function utf8_encode (string) { + string = String(string).replace(/\r\n/g,"\n"); + var utftext = ""; + + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); + if (c < 128) { + utftext += String.fromCharCode(c); + } + else if((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } + else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + } + return utftext; + } + + return output; + } + + function base64decode (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + while (i < input.length) { + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + output = output + String.fromCharCode(chr1); + if (enc3 !== 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 !== 64) { + output = output + String.fromCharCode(chr3); + } + } + output = utf8_decode(output); + + function utf8_decode (utftext) { + var string = ""; + var i = 0; + var c = 0, c2, c3; + + while ( i < utftext.length ) { + c = utftext.charCodeAt(i); + if (c < 128) { + string += String.fromCharCode(c); + i++; + } + else if((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i+1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } + else { + c2 = utftext.charCodeAt(i+1); + c3 = utftext.charCodeAt(i+2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + + return string; + } + + return output; + } + + function md5(input) { + /* + * Based on http://pajhome.org.uk/crypt/md5 + */ + + var hexcase = 0; + var b64pad = ""; + + function __pj_crypt_hex_md5(s) { + return __pj_crypt_rstr2hex(__pj_crypt_rstr_md5(__pj_crypt_str2rstr_utf8(s))); + } + function __pj_crypt_b64_md5(s) { + return __pj_crypt_rstr2b64(__pj_crypt_rstr_md5(__pj_crypt_str2rstr_utf8(s))); + } + function __pj_crypt_any_md5(s, e) { + return __pj_crypt_rstr2any(__pj_crypt_rstr_md5(__pj_crypt_str2rstr_utf8(s)), e); + } + function __pj_crypt_hex_hmac_md5(k, d) + { + return __pj_crypt_rstr2hex(__pj_crypt_rstr_hmac_md5(__pj_crypt_str2rstr_utf8(k), __pj_crypt_str2rstr_utf8(d))); + } + function __pj_crypt_b64_hmac_md5(k, d) + { + return __pj_crypt_rstr2b64(__pj_crypt_rstr_hmac_md5(__pj_crypt_str2rstr_utf8(k), __pj_crypt_str2rstr_utf8(d))); + } + function __pj_crypt_any_hmac_md5(k, d, e) + { + return __pj_crypt_rstr2any(__pj_crypt_rstr_hmac_md5(__pj_crypt_str2rstr_utf8(k), __pj_crypt_str2rstr_utf8(d)), e); + } + + /* + * Calculate the MD5 of a raw string + */ + function __pj_crypt_rstr_md5(s) + { + return __pj_crypt_binl2rstr(__pj_crypt_binl_md5(__pj_crypt_rstr2binl(s), s.length * 8)); + } + + /* + * Calculate the HMAC-MD5, of a key and some data (raw strings) + */ + function __pj_crypt_rstr_hmac_md5(key, data) + { + var bkey = __pj_crypt_rstr2binl(key); + if (bkey.length > 16) + bkey = __pj_crypt_binl_md5(bkey, key.length * 8); + + var ipad = Array(16), opad = Array(16); + for (var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = __pj_crypt_binl_md5(ipad.concat(__pj_crypt_rstr2binl(data)), 512 + data.length * 8); + return __pj_crypt_binl2rstr(__pj_crypt_binl_md5(opad.concat(hash), 512 + 128)); + } + + /* + * Convert a raw string to a hex string + */ + function __pj_crypt_rstr2hex(input) + { + try { + hexcase + } catch (e) { + hexcase = 0; + } + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var output = ""; + var x; + for (var i = 0; i < input.length; i++) + { + x = input.charCodeAt(i); + output += hex_tab.charAt((x >>> 4) & 0x0F) + + hex_tab.charAt(x & 0x0F); + } + return output; + } + + /* + * Convert a raw string to a base-64 string + */ + function __pj_crypt_rstr2b64(input) + { + try { + b64pad + } catch (e) { + b64pad = ''; + } + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var output = ""; + var len = input.length; + for (var i = 0; i < len; i += 3) + { + var triplet = (input.charCodeAt(i) << 16) + | (i + 1 < len ? input.charCodeAt(i + 1) << 8 : 0) + | (i + 2 < len ? input.charCodeAt(i + 2) : 0); + for (var j = 0; j < 4; j++) + { + if (i * 8 + j * 6 > input.length * 8) + output += b64pad; + else + output += tab.charAt((triplet >>> 6 * (3 - j)) & 0x3F); + } + } + return output; + } + + /* + * Convert a raw string to an arbitrary string encoding + */ + function __pj_crypt_rstr2any(input, encoding) + { + var divisor = encoding.length; + var i, j, q, x, quotient; + + /* Convert to an array of 16-bit big-endian values, forming the dividend */ + var dividend = Array(Math.ceil(input.length / 2)); + for (i = 0; i < dividend.length; i++) + { + dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1); + } + + /* + * Repeatedly perform a long division. The binary array forms the dividend, + * the length of the encoding is the divisor. Once computed, the quotient + * forms the dividend for the next step. All remainders are stored for later + * use. + */ + var full_length = Math.ceil(input.length * 8 / + (Math.log(encoding.length) / Math.log(2))); + var remainders = Array(full_length); + for (j = 0; j < full_length; j++) + { + quotient = Array(); + x = 0; + for (i = 0; i < dividend.length; i++) + { + x = (x << 16) + dividend[i]; + q = Math.floor(x / divisor); + x -= q * divisor; + if (quotient.length > 0 || q > 0) + quotient[quotient.length] = q; + } + remainders[j] = x; + dividend = quotient; + } + + /* Convert the remainders to the output string */ + var output = ""; + for (i = remainders.length - 1; i >= 0; i--) + output += encoding.charAt(remainders[i]); + + return output; + } + + /* + * Encode a string as utf-8. + * For efficiency, this assumes the input is valid utf-16. + */ + function __pj_crypt_str2rstr_utf8(input) + { + var output = ""; + var i = -1; + var x, y; + + while (++i < input.length) + { + /* Decode utf-16 surrogate pairs */ + x = input.charCodeAt(i); + y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; + if (0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) + { + x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); + i++; + } + + /* Encode output as utf-8 */ + if (x <= 0x7F) + output += String.fromCharCode(x); + else if (x <= 0x7FF) + output += String.fromCharCode(0xC0 | ((x >>> 6) & 0x1F), + 0x80 | (x & 0x3F)); + else if (x <= 0xFFFF) + output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), + 0x80 | ((x >>> 6) & 0x3F), + 0x80 | (x & 0x3F)); + else if (x <= 0x1FFFFF) + output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), + 0x80 | ((x >>> 12) & 0x3F), + 0x80 | ((x >>> 6) & 0x3F), + 0x80 | (x & 0x3F)); + } + return output; + } + + /* + * Encode a string as utf-16 + */ + function __pj_crypt_str2rstr_utf16le(input) + { + var output = ""; + for (var i = 0; i < input.length; i++) + output += String.fromCharCode(input.charCodeAt(i) & 0xFF, + (input.charCodeAt(i) >>> 8) & 0xFF); + return output; + } + + function __pj_crypt_str2rstr_utf16be(input) + { + var output = ""; + for (var i = 0; i < input.length; i++) + output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF, + input.charCodeAt(i) & 0xFF); + return output; + } + + /* + * Convert a raw string to an array of little-endian words + * Characters >255 have their high-byte silently ignored. + */ + function __pj_crypt_rstr2binl(input) + { + var output = Array(input.length >> 2); + for (var i = 0; i < output.length; i++) + output[i] = 0; + for (var i = 0; i < input.length * 8; i += 8) + output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32); + return output; + } + + /* + * Convert an array of little-endian words to a string + */ + function __pj_crypt_binl2rstr(input) + { + var output = ""; + for (var i = 0; i < input.length * 32; i += 8) + output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF); + return output; + } + + /* + * Calculate the MD5 of an array of little-endian words, and a bit length. + */ + function __pj_crypt_binl_md5(x, len) + { + /* append padding */ + x[len >> 5] |= 0x80 << ((len) % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; + + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + + for (var i = 0; i < x.length; i += 16) + { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + + a = __pj_crypt_md5_ff(a, b, c, d, x[i + 0], 7, -680876936); + d = __pj_crypt_md5_ff(d, a, b, c, x[i + 1], 12, -389564586); + c = __pj_crypt_md5_ff(c, d, a, b, x[i + 2], 17, 606105819); + b = __pj_crypt_md5_ff(b, c, d, a, x[i + 3], 22, -1044525330); + a = __pj_crypt_md5_ff(a, b, c, d, x[i + 4], 7, -176418897); + d = __pj_crypt_md5_ff(d, a, b, c, x[i + 5], 12, 1200080426); + c = __pj_crypt_md5_ff(c, d, a, b, x[i + 6], 17, -1473231341); + b = __pj_crypt_md5_ff(b, c, d, a, x[i + 7], 22, -45705983); + a = __pj_crypt_md5_ff(a, b, c, d, x[i + 8], 7, 1770035416); + d = __pj_crypt_md5_ff(d, a, b, c, x[i + 9], 12, -1958414417); + c = __pj_crypt_md5_ff(c, d, a, b, x[i + 10], 17, -42063); + b = __pj_crypt_md5_ff(b, c, d, a, x[i + 11], 22, -1990404162); + a = __pj_crypt_md5_ff(a, b, c, d, x[i + 12], 7, 1804603682); + d = __pj_crypt_md5_ff(d, a, b, c, x[i + 13], 12, -40341101); + c = __pj_crypt_md5_ff(c, d, a, b, x[i + 14], 17, -1502002290); + b = __pj_crypt_md5_ff(b, c, d, a, x[i + 15], 22, 1236535329); + + a = __pj_crypt_md5_gg(a, b, c, d, x[i + 1], 5, -165796510); + d = __pj_crypt_md5_gg(d, a, b, c, x[i + 6], 9, -1069501632); + c = __pj_crypt_md5_gg(c, d, a, b, x[i + 11], 14, 643717713); + b = __pj_crypt_md5_gg(b, c, d, a, x[i + 0], 20, -373897302); + a = __pj_crypt_md5_gg(a, b, c, d, x[i + 5], 5, -701558691); + d = __pj_crypt_md5_gg(d, a, b, c, x[i + 10], 9, 38016083); + c = __pj_crypt_md5_gg(c, d, a, b, x[i + 15], 14, -660478335); + b = __pj_crypt_md5_gg(b, c, d, a, x[i + 4], 20, -405537848); + a = __pj_crypt_md5_gg(a, b, c, d, x[i + 9], 5, 568446438); + d = __pj_crypt_md5_gg(d, a, b, c, x[i + 14], 9, -1019803690); + c = __pj_crypt_md5_gg(c, d, a, b, x[i + 3], 14, -187363961); + b = __pj_crypt_md5_gg(b, c, d, a, x[i + 8], 20, 1163531501); + a = __pj_crypt_md5_gg(a, b, c, d, x[i + 13], 5, -1444681467); + d = __pj_crypt_md5_gg(d, a, b, c, x[i + 2], 9, -51403784); + c = __pj_crypt_md5_gg(c, d, a, b, x[i + 7], 14, 1735328473); + b = __pj_crypt_md5_gg(b, c, d, a, x[i + 12], 20, -1926607734); + + a = __pj_crypt_md5_hh(a, b, c, d, x[i + 5], 4, -378558); + d = __pj_crypt_md5_hh(d, a, b, c, x[i + 8], 11, -2022574463); + c = __pj_crypt_md5_hh(c, d, a, b, x[i + 11], 16, 1839030562); + b = __pj_crypt_md5_hh(b, c, d, a, x[i + 14], 23, -35309556); + a = __pj_crypt_md5_hh(a, b, c, d, x[i + 1], 4, -1530992060); + d = __pj_crypt_md5_hh(d, a, b, c, x[i + 4], 11, 1272893353); + c = __pj_crypt_md5_hh(c, d, a, b, x[i + 7], 16, -155497632); + b = __pj_crypt_md5_hh(b, c, d, a, x[i + 10], 23, -1094730640); + a = __pj_crypt_md5_hh(a, b, c, d, x[i + 13], 4, 681279174); + d = __pj_crypt_md5_hh(d, a, b, c, x[i + 0], 11, -358537222); + c = __pj_crypt_md5_hh(c, d, a, b, x[i + 3], 16, -722521979); + b = __pj_crypt_md5_hh(b, c, d, a, x[i + 6], 23, 76029189); + a = __pj_crypt_md5_hh(a, b, c, d, x[i + 9], 4, -640364487); + d = __pj_crypt_md5_hh(d, a, b, c, x[i + 12], 11, -421815835); + c = __pj_crypt_md5_hh(c, d, a, b, x[i + 15], 16, 530742520); + b = __pj_crypt_md5_hh(b, c, d, a, x[i + 2], 23, -995338651); + + a = __pj_crypt_md5_ii(a, b, c, d, x[i + 0], 6, -198630844); + d = __pj_crypt_md5_ii(d, a, b, c, x[i + 7], 10, 1126891415); + c = __pj_crypt_md5_ii(c, d, a, b, x[i + 14], 15, -1416354905); + b = __pj_crypt_md5_ii(b, c, d, a, x[i + 5], 21, -57434055); + a = __pj_crypt_md5_ii(a, b, c, d, x[i + 12], 6, 1700485571); + d = __pj_crypt_md5_ii(d, a, b, c, x[i + 3], 10, -1894986606); + c = __pj_crypt_md5_ii(c, d, a, b, x[i + 10], 15, -1051523); + b = __pj_crypt_md5_ii(b, c, d, a, x[i + 1], 21, -2054922799); + a = __pj_crypt_md5_ii(a, b, c, d, x[i + 8], 6, 1873313359); + d = __pj_crypt_md5_ii(d, a, b, c, x[i + 15], 10, -30611744); + c = __pj_crypt_md5_ii(c, d, a, b, x[i + 6], 15, -1560198380); + b = __pj_crypt_md5_ii(b, c, d, a, x[i + 13], 21, 1309151649); + a = __pj_crypt_md5_ii(a, b, c, d, x[i + 4], 6, -145523070); + d = __pj_crypt_md5_ii(d, a, b, c, x[i + 11], 10, -1120210379); + c = __pj_crypt_md5_ii(c, d, a, b, x[i + 2], 15, 718787259); + b = __pj_crypt_md5_ii(b, c, d, a, x[i + 9], 21, -343485551); + + a = __pj_crypt_safe_add(a, olda); + b = __pj_crypt_safe_add(b, oldb); + c = __pj_crypt_safe_add(c, oldc); + d = __pj_crypt_safe_add(d, oldd); + } + return Array(a, b, c, d); + } + + /* + * These functions implement the four basic operations the algorithm uses. + */ + function __pj_crypt_md5_cmn(q, a, b, x, s, t) + { + return __pj_crypt_safe_add(__pj_crypt_bit_rol(__pj_crypt_safe_add(__pj_crypt_safe_add(a, q), __pj_crypt_safe_add(x, t)), s), b); + } + function __pj_crypt_md5_ff(a, b, c, d, x, s, t) + { + return __pj_crypt_md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); + } + function __pj_crypt_md5_gg(a, b, c, d, x, s, t) + { + return __pj_crypt_md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); + } + function __pj_crypt_md5_hh(a, b, c, d, x, s, t) + { + return __pj_crypt_md5_cmn(b ^ c ^ d, a, b, x, s, t); + } + function __pj_crypt_md5_ii(a, b, c, d, x, s, t) + { + return __pj_crypt_md5_cmn(c ^ (b | (~d)), a, b, x, s, t); + } + + /* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ + function __pj_crypt_safe_add(x, y) + { + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + } + + /* + * Bitwise rotate a 32-bit number to the left. + */ + function __pj_crypt_bit_rol(num, cnt) + { + return (num << cnt) | (num >>> (32 - cnt)); + } + + return __pj_crypt_hex_md5(input); + + } + + function transition (div_old, div_new, type, callBack) { + var width = $(div_old).width(); + var height = $(div_old).height(); + var time = 0.5; + + if (!div_old || !div_new) { + console.log('ERROR: Cannot do transition when one of the divs is null'); + return; + } + + div_old.parentNode.style.cssText += 'perspective: 900px; overflow: hidden;'; + div_old.style.cssText += '; position: absolute; z-index: 1019; backface-visibility: hidden'; + div_new.style.cssText += '; position: absolute; z-index: 1020; backface-visibility: hidden'; + + switch (type) { + case 'slide-left': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)'; + div_new.style.cssText += 'overflow: hidden; transform: translate3d('+ width + 'px, 0, 0)'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)'; + div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d(-'+ width +'px, 0, 0)'; + }, 1); + break; + + case 'slide-right': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)'; + div_new.style.cssText += 'overflow: hidden; transform: translate3d(-'+ width +'px, 0, 0)'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0px, 0, 0)'; + div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d('+ width +'px, 0, 0)'; + }, 1); + break; + + case 'slide-down': + // init divs + div_old.style.cssText += 'overflow: hidden; z-index: 1; transform: translate3d(0, 0, 0)'; + div_new.style.cssText += 'overflow: hidden; z-index: 0; transform: translate3d(0, 0, 0)'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)'; + div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, '+ height +'px, 0)'; + }, 1); + break; + + case 'slide-up': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)'; + div_new.style.cssText += 'overflow: hidden; transform: translate3d(0, '+ height +'px, 0)'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)'; + div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)'; + }, 1); + break; + + case 'flip-left': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: rotateY(0deg)'; + div_new.style.cssText += 'overflow: hidden; transform: rotateY(-180deg)'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += 'transition: '+ time +'s; transform: rotateY(0deg)'; + div_old.style.cssText += 'transition: '+ time +'s; transform: rotateY(180deg)'; + }, 1); + break; + + case 'flip-right': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: rotateY(0deg)'; + div_new.style.cssText += 'overflow: hidden; transform: rotateY(180deg)'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += 'transition: '+ time +'s; transform: rotateY(0deg)'; + div_old.style.cssText += 'transition: '+ time +'s; transform: rotateY(-180deg)'; + }, 1); + break; + + case 'flip-down': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: rotateX(0deg)'; + div_new.style.cssText += 'overflow: hidden; transform: rotateX(180deg)'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += 'transition: '+ time +'s; transform: rotateX(0deg)'; + div_old.style.cssText += 'transition: '+ time +'s; transform: rotateX(-180deg)'; + }, 1); + break; + + case 'flip-up': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: rotateX(0deg)'; + div_new.style.cssText += 'overflow: hidden; transform: rotateX(-180deg)'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += 'transition: '+ time +'s; transform: rotateX(0deg)'; + div_old.style.cssText += 'transition: '+ time +'s; transform: rotateX(180deg)'; + }, 1); + break; + + case 'pop-in': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)'; + div_new.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0); transform: scale(.8); opacity: 0;'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += 'transition: '+ time +'s; transform: scale(1); opacity: 1;'; + div_old.style.cssText += 'transition: '+ time +'s;'; + }, 1); + break; + + case 'pop-out': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0); transform: scale(1); opacity: 1;'; + div_new.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0); opacity: 0;'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += 'transition: '+ time +'s; opacity: 1;'; + div_old.style.cssText += 'transition: '+ time +'s; transform: scale(1.7); opacity: 0;'; + }, 1); + break; + + default: + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)'; + div_new.style.cssText += 'overflow: hidden; translate3d(0, 0, 0); opacity: 0;'; + $(div_new).show(); + // -- need a timing function because otherwise not working + window.setTimeout(function() { + div_new.style.cssText += 'transition: '+ time +'s; opacity: 1;'; + div_old.style.cssText += 'transition: '+ time +'s'; + }, 1); + break; + } + + setTimeout(function () { + if (type === 'slide-down') { + $(div_old).css('z-index', '1019'); + $(div_new).css('z-index', '1020'); + } + if (div_new) { + $(div_new).css({ 'opacity': '1' }).css(w2utils.cssPrefix({ + 'transition': '', + 'transform' : '' + })); + } + if (div_old) { + $(div_old).css({ 'opacity': '1' }).css(w2utils.cssPrefix({ + 'transition': '', + 'transform' : '' + })); + } + if (typeof callBack === 'function') callBack(); + }, time * 1000); + } + + function lock (box, msg, spinner) { + var options = {}; + if (typeof msg === 'object') { + options = msg; + } else { + options.msg = msg; + options.spinner = spinner; + } + if (!options.msg && options.msg !== 0) options.msg = ''; + w2utils.unlock(box); + $(box).prepend( + '
'+ + '
' + ); + var $lock = $(box).find('.w2ui-lock'); + var mess = $(box).find('.w2ui-lock-msg'); + if (!options.msg) mess.css({ 'background-color': 'transparent', 'border': '0px' }); + if (options.spinner === true) options.msg = '
' + options.msg; + if (options.opacity != null) $lock.css('opacity', options.opacity); + if (typeof $lock.fadeIn === 'function') { + $lock.fadeIn(200); + mess.html(options.msg).fadeIn(200); + } else { + $lock.show(); + mess.html(options.msg).show(0); + } + } + + function unlock (box, speed) { + if (isInt(speed)) { + $(box).find('.w2ui-lock').fadeOut(speed); + setTimeout(function () { + $(box).find('.w2ui-lock').remove(); + $(box).find('.w2ui-lock-msg').remove(); + }, speed); + } else { + $(box).find('.w2ui-lock').remove(); + $(box).find('.w2ui-lock-msg').remove(); + } + } + + /** + * Used in w2popup, w2grid, w2form, w2layout + * should be called with .call(...) method + */ + + function message(where, options) { + var obj = this, closeTimer, edata; + // var where.path = 'w2popup'; + // var where.title = '.w2ui-popup-title'; + // var where.body = '.w2ui-box'; + $().w2tag(); // hide all tags + if (!options) options = { width: 200, height: 100 }; + if (options.on == null) $.extend(options, w2utils.event); + if (options.width == null) options.width = 200; + if (options.height == null) options.height = 100; + var pWidth = parseInt($(where.box).width()); + var pHeight = parseInt($(where.box).height()); + var titleHeight = parseInt($(where.box).find(where.title).css('height') || 0); + if (options.width > pWidth) options.width = pWidth - 10; + if (options.height > pHeight - titleHeight) options.height = pHeight - 10 - titleHeight; + options.originalWidth = options.width; + options.originalHeight = options.height; + if (parseInt(options.width) < 0) options.width = pWidth + options.width; + if (parseInt(options.width) < 10) options.width = 10; + if (parseInt(options.height) < 0) options.height = pHeight + options.height - titleHeight; + if (parseInt(options.height) < 10) options.height = 10; + if (options.hideOnClick == null) options.hideOnClick = false; + var poptions = $(where.box).data('options') || {}; + if (options.width == null || options.width > poptions.width - 10) { + options.width = poptions.width - 10; + } + if (options.height == null || options.height > poptions.height - titleHeight - 5) { + options.height = poptions.height - titleHeight - 5; // need margin from bottom only + } + // negative value means margin + if (options.originalHeight < 0) options.height = pHeight + options.originalHeight - titleHeight; + if (options.originalWidth < 0) options.width = pWidth + options.originalWidth * 2; // x 2 because there is left and right margin + var head = $(where.box).find(where.title); + + // if some messages are closing, insta close them + var $tmp = $(where.box).find('.w2ui-message.w2ui-closing'); + if ($(where.box).find('.w2ui-message.w2ui-closing').length > 0) { + clearTimeout(closeTimer); + closeCB($tmp, $tmp.data('options') || {}); + } + var msgCount = $(where.box).find('.w2ui-message').length; + // remove message + if ($.trim(options.html) === '' && $.trim(options.body) === '' && $.trim(options.buttons) === '') { + if (msgCount === 0) return; // no messages at all + var $msg = $(where.box).find('#w2ui-message'+ (msgCount-1)); + var options = $msg.data('options') || {}; + // before event + edata = options.trigger({ phase: 'before', type: 'close', target: 'self' }); + if (edata.isCancelled === true) return; + // default behavior + $msg.css(w2utils.cssPrefix({ + 'transition': '0.15s', + 'transform': 'translateY(-' + options.height + 'px)' + })).addClass('w2ui-closing'); + if (msgCount === 1) { + if (this.unlock) { + if (where.param) this.unlock(where.param, 150); else this.unlock(150); + } + } else { + $(where.box).find('#w2ui-message'+ (msgCount-2)).css('z-index', 1500); + } + closeTimer = setTimeout(function () { closeCB($msg, options); }, 150); + + } else { + + if ($.trim(options.body) !== '' || $.trim(options.buttons) !== '') { + options.html = '
'+ (options.body || '') +'
'+ + '
'+ (options.buttons || '') +'
'; + } + // hide previous messages + $(where.box).find('.w2ui-message').css('z-index', 1390); + head.data('old-z-index', head.css('z-index')); + head.css('z-index', 1501); + // add message + $(where.box).find(where.body) + .before(''); + $(where.box).find('#w2ui-message'+ msgCount) + .data('options', options) + .data('prev_focus', $(':focus')); + var display = $(where.box).find('#w2ui-message'+ msgCount).css('display'); + $(where.box).find('#w2ui-message'+ msgCount).css(w2utils.cssPrefix({ + 'transform': (display === 'none' ? 'translateY(-' + options.height + 'px)' : 'translateY(0px)') + })); + if (display === 'none') { + $(where.box).find('#w2ui-message'+ msgCount).show().html(options.html); + options.box = $(where.box).find('#w2ui-message'+ msgCount); + // before event + edata = options.trigger({ phase: 'before', type: 'open', target: 'self' }); + if (edata.isCancelled === true) { + head.css('z-index', head.data('old-z-index')); + $(where.box).find('#w2ui-message'+ msgCount).remove(); + return; + } + // timer needs to animation + setTimeout(function () { + $(where.box).find('#w2ui-message'+ msgCount).css(w2utils.cssPrefix({ + 'transform': (display === 'none' ? 'translateY(0px)' : 'translateY(-' + options.height + 'px)') + })); + }, 1); + // timer for lock + if (msgCount === 0 && this.lock) { + if (where.param) this.lock(where.param); else this.lock(); + } + setTimeout(function() { + // has to be on top of lock + $(where.box).find('#w2ui-message'+ msgCount).css(w2utils.cssPrefix({ 'transition': '0s' })); + // event after + options.trigger($.extend(edata, { phase: 'after' })); + }, 350); + } + } + + function closeCB($msg, options) { + if (edata == null) { + // before event + edata = options.trigger({ phase: 'before', type: 'open', target: 'self' }); + if (edata.isCancelled === true) { + head.css('z-index', head.data('old-z-index')); + $(where.box).find('#w2ui-message'+ msgCount).remove(); + return; + } + } + var $focus = $msg.data('prev_focus'); + $msg.remove(); + if ($focus && $focus.length > 0) { + $focus.focus(); + } else { + if (obj && obj.focus) obj.focus(); + } + head.css('z-index', head.data('old-z-index')); + // event after + options.trigger($.extend(edata, { phase: 'after' })); + } + } + + function getSize (el, type) { + var $el = $(el); + var bwidth = { + left : parseInt($el.css('border-left-width')) || 0, + right : parseInt($el.css('border-right-width')) || 0, + top : parseInt($el.css('border-top-width')) || 0, + bottom : parseInt($el.css('border-bottom-width')) || 0 + }; + var mwidth = { + left : parseInt($el.css('margin-left')) || 0, + right : parseInt($el.css('margin-right')) || 0, + top : parseInt($el.css('margin-top')) || 0, + bottom : parseInt($el.css('margin-bottom')) || 0 + }; + var pwidth = { + left : parseInt($el.css('padding-left')) || 0, + right : parseInt($el.css('padding-right')) || 0, + top : parseInt($el.css('padding-top')) || 0, + bottom : parseInt($el.css('padding-bottom')) || 0 + }; + switch (type) { + case 'top' : return bwidth.top + mwidth.top + pwidth.top; + case 'bottom' : return bwidth.bottom + mwidth.bottom + pwidth.bottom; + case 'left' : return bwidth.left + mwidth.left + pwidth.left; + case 'right' : return bwidth.right + mwidth.right + pwidth.right; + case 'width' : return bwidth.left + bwidth.right + mwidth.left + mwidth.right + pwidth.left + pwidth.right + parseInt($el.width()); + case 'height' : return bwidth.top + bwidth.bottom + mwidth.top + mwidth.bottom + pwidth.top + pwidth.bottom + parseInt($el.height()); + case '+width' : return bwidth.left + bwidth.right + mwidth.left + mwidth.right + pwidth.left + pwidth.right; + case '+height' : return bwidth.top + bwidth.bottom + mwidth.top + mwidth.bottom + pwidth.top + pwidth.bottom; + } + return 0; + } + + function getStrWidth (str, styles) { + var w, html = '
'+ + encodeTags(str) + + '
'; + $('body').append(html); + w = $('#_tmp_width').width(); + $('#_tmp_width').remove(); + return w; + } + + function lang (phrase) { + var translation = this.settings.phrases[phrase]; + if (translation == null) return phrase; else return translation; + } + + function locale (locale, callBack) { + if (!locale) locale = 'en-us'; + + // if the locale is an object, not a string, than we assume it's a + if (typeof locale !== "string" ) { + w2utils.settings = $.extend(true, w2utils.settings, locale); + return; + } + + if (locale.length === 5) locale = 'locale/'+ locale +'.json'; + + // clear phrases from language before + w2utils.settings.phrases = {}; + + // load from the file + $.ajax({ + url : locale, + type : "GET", + dataType : "JSON", + success : function (data, status, xhr) { + w2utils.settings = $.extend(true, w2utils.settings, data); + if (typeof callBack === 'function') callBack(); + }, + error : function (xhr, status, msg) { + console.log('ERROR: Cannot load locale '+ locale); + } + }); + } + + function scrollBarSize () { + if (tmp.scrollBarSize) return tmp.scrollBarSize; + var html = + '
'+ + '
1
'+ + '
'; + $('body').append(html); + tmp.scrollBarSize = 100 - $('#_scrollbar_width > div').width(); + $('#_scrollbar_width').remove(); + if (String(navigator.userAgent).indexOf('MSIE') >= 0) tmp.scrollBarSize = tmp.scrollBarSize / 2; // need this for IE9+ + return tmp.scrollBarSize; + } + + function checkName (params, component) { // was w2checkNameParam + if (!params || params.name == null) { + console.log('ERROR: The parameter "name" is required but not supplied in $().'+ component +'().'); + return false; + } + if (w2ui[params.name] != null) { + console.log('ERROR: The parameter "name" is not unique. There are other objects already created with the same name (obj: '+ params.name +').'); + return false; + } + if (!w2utils.isAlphaNumeric(params.name)) { + console.log('ERROR: The parameter "name" has to be alpha-numeric (a-z, 0-9, dash and underscore). '); + return false; + } + return true; + } + + function checkUniqueId (id, items, itemsDecription, objName) { // was w2checkUniqueId + if (!$.isArray(items)) items = [items]; + for (var i = 0; i < items.length; i++) { + if (items[i].id === id) { + console.log('ERROR: The parameter "id='+ id +'" is not unique within the current '+ itemsDecription +'. (obj: '+ objName +')'); + return false; + } + } + return true; + } + + function parseRoute(route) { + var keys = []; + var path = route + .replace(/\/\(/g, '(?:/') + .replace(/\+/g, '__plus__') + .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional) { + keys.push({ name: key, optional: !! optional }); + slash = slash || ''; + return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' + (optional || ''); + }) + .replace(/([\/.])/g, '\\$1') + .replace(/__plus__/g, '(.+)') + .replace(/\*/g, '(.*)'); + return { + path : new RegExp('^' + path + '$', 'i'), + keys : keys + }; + } + + function cssPrefix(field, value, returnString) { + var css = {}; + var newCSS = {}; + var ret = ''; + if (!$.isPlainObject(field)) { + css[field] = value; + } else { + css = field; + if (value === true) returnString = true; + } + for (var c in css) { + newCSS[c] = css[c]; + newCSS['-webkit-'+c] = css[c]; + newCSS['-moz-'+c] = css[c].replace('-webkit-', '-moz-'); + newCSS['-ms-'+c] = css[c].replace('-webkit-', '-ms-'); + newCSS['-o-'+c] = css[c].replace('-webkit-', '-o-'); + } + if (returnString === true) { + for (var c in newCSS) { + ret += c + ': ' + newCSS[c] + '; '; + } + } else { + ret = newCSS; + } + return ret; + } + + function getCursorPosition(input) { + if (input == null) return null; + var caretOffset = 0; + var doc = input.ownerDocument || input.document; + var win = doc.defaultView || doc.parentWindow; + var sel; + if (input.tagName && input.tagName.toUpperCase() === 'INPUT' && input.selectionStart) { + // standards browser + caretOffset = input.selectionStart; + } else { + if (win.getSelection) { + sel = win.getSelection(); + if (sel.rangeCount > 0) { + var range = sel.getRangeAt(0); + var preCaretRange = range.cloneRange(); + preCaretRange.selectNodeContents(input); + preCaretRange.setEnd(range.endContainer, range.endOffset); + caretOffset = preCaretRange.toString().length; + } + } else if ( (sel = doc.selection) && sel.type !== "Control") { + var textRange = sel.createRange(); + var preCaretTextRange = doc.body.createTextRange(); + preCaretTextRange.moveToElementText(input); + preCaretTextRange.setEndPoint("EndToEnd", textRange); + caretOffset = preCaretTextRange.text.length; + } + } + return caretOffset; + } + + function setCursorPosition(input, pos, posEnd) { + var range = document.createRange(); + var el, sel = window.getSelection(); + if (input == null) return; + for (var i = 0; i < input.childNodes.length; i++) { + var tmp = $(input.childNodes[i]).text(); + if (input.childNodes[i].tagName) { + tmp = $(input.childNodes[i]).html(); + tmp = tmp.replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/ /g, ' '); + } + if (pos <= tmp.length) { + el = input.childNodes[i]; + if (el.childNodes && el.childNodes.length > 0) el = el.childNodes[0]; + if (el.childNodes && el.childNodes.length > 0) el = el.childNodes[0]; + break; + } else { + pos -= tmp.length; + } + } + if (el == null) return; + if (pos > el.length) pos = el.length; + range.setStart(el, pos); + if (posEnd) { + range.setEnd(el, posEnd); + } else { + range.collapse(true); + } + sel.removeAllRanges(); + sel.addRange(range); + } + + function testLocalStorage() { + // test if localStorage is available, see issue #1282 + // original code: https://github.com/Modernizr/Modernizr/blob/master/feature-detects/storage/localstorage.js + var str = 'w2ui_test'; + try { + localStorage.setItem(str, str); + localStorage.removeItem(str); + return true; + } catch (e) { + return false; + } + } + + function parseColor(str) { + if (typeof str !== 'string') return null; else str = str.trim().toUpperCase(); + if (str[0] === '#') str = str.substr(1); + var color = {}; + if (str.length === 3) { + color = { + r: parseInt(str[0] + str[0], 16), + g: parseInt(str[1] + str[1], 16), + b: parseInt(str[2] + str[2], 16), + a: 1 + } + } else if (str.length === 6) { + color = { + r: parseInt(str.substr(0, 2), 16), + g: parseInt(str.substr(2, 2), 16), + b: parseInt(str.substr(4, 2), 16), + a: 1 + } + } else if (str.length === 8) { + color = { + r: parseInt(str.substr(0, 2), 16), + g: parseInt(str.substr(2, 2), 16), + b: parseInt(str.substr(4, 2), 16), + a: Math.round(parseInt(str.substr(6, 2), 16) / 255 * 100) / 100 // alpha channel 0-1 + } + } else if (str.length > 4 && str.substr(0, 4) === 'RGB(') { + var tmp = str.replace('RGB', '').replace(/\(/g, '').replace(/\)/g, '').split(','); + color = { + r: parseInt(tmp[0], 10), + g: parseInt(tmp[1], 10), + b: parseInt(tmp[2], 10), + a: 1 + } + } else if (str.length > 5 && str.substr(0, 5) === 'RGBA(') { + var tmp = str.replace('RGBA', '').replace(/\(/g, '').replace(/\)/g, '').split(','); + color = { + r: parseInt(tmp[0], 10), + g: parseInt(tmp[1], 10), + b: parseInt(tmp[2], 10), + a: parseFloat(tmp[3]) + } + } else { + // word color + return null; + } + return color; + } + + // h=0..360, s=0..100, v=0..100 + function hsv2rgb(h, s, v, a) { + var r, g, b, i, f, p, q, t; + if (arguments.length === 1) { + s = h.s; v = h.v; a = h.a; h = h.h; + } + h = h / 360; + s = s / 100; + v = v / 100; + i = Math.floor(h * 6); + f = h * 6 - i; + p = v * (1 - s); + q = v * (1 - f * s); + t = v * (1 - (1 - f) * s); + switch (i % 6) { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; + } + return { + r: Math.round(r * 255), + g: Math.round(g * 255), + b: Math.round(b * 255), + a: (a != null ? a : 1) + }; + } + + // r=0..255, g=0..255, b=0..255 + function rgb2hsv(r, g, b, a) { + if (arguments.length === 1) { + g = r.g; b = r.b; a = r.a; r = r.r; + } + var max = Math.max(r, g, b), min = Math.min(r, g, b), + d = max - min, + h, + s = (max === 0 ? 0 : d / max), + v = max / 255; + switch (max) { + case min: h = 0; break; + case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break; + case g: h = (b - r) + d * 2; h /= 6 * d; break; + case b: h = (r - g) + d * 4; h /= 6 * d; break; + } + return { + h: Math.round(h * 360), + s: Math.round(s * 100), + v: Math.round(v * 100), + a: (a != null ? a : 1) + } + } + + function tooltip(msg, options) { + var actions, showOn = 'mouseenter', hideOn = 'mouseleave' + options = options || {} + if (options.showOn) { + showOn = options.showOn + delete options.showOn + } + if (options.hideOn) { + hideOn = options.hideOn + delete options.hideOn + } + // base64 is needed to avoid '"<> and other special chars conflicts + actions = 'on'+ showOn +'="$(this).w2tag(w2utils.base64decode(\'' + w2utils.base64encode(msg) + '\'),' + + 'JSON.parse(w2utils.base64decode(\'' + w2utils.base64encode(JSON.stringify(options)) + '\')))"' + + 'on'+ hideOn +'="$(this).w2tag()"' + + return actions + } + + /*! from litejs.com / MIT Licence + https://github.com/litejs/natural-compare-lite/blob/master/index.js */ + /* + * @version 1.4.0 + * @date 2015-10-26 + * @stability 3 - Stable + * @author Lauri Rooden (https://github.com/litejs/natural-compare-lite) + * @license MIT License + */ + function naturalCompare(a, b) { + var i, codeA + , codeB = 1 + , posA = 0 + , posB = 0 + , alphabet = String.alphabet; + + function getCode(str, pos, code) { + if (code) { + for (i = pos; code = getCode(str, i), code < 76 && code > 65;) ++i; + return +str.slice(pos - 1, i); + } + code = alphabet && alphabet.indexOf(str.charAt(pos)); + return code > -1 ? code + 76 : ((code = str.charCodeAt(pos) || 0), code < 45 || code > 127) ? code + : code < 46 ? 65 // - + : code < 48 ? code - 1 + : code < 58 ? code + 18 // 0-9 + : code < 65 ? code - 11 + : code < 91 ? code + 11 // A-Z + : code < 97 ? code - 37 + : code < 123 ? code + 5 // a-z + : code - 63; + } + + + if ((a+="") != (b+="")) for (;codeB;) { + codeA = getCode(a, posA++); + codeB = getCode(b, posB++); + + if (codeA < 76 && codeB < 76 && codeA > 66 && codeB > 66) { + codeA = getCode(a, posA, posA); + codeB = getCode(b, posB, posA = i); + posB = i; + } + + if (codeA != codeB) return (codeA < codeB) ? -1 : 1; + } + return 0; + } +})(jQuery); + +/*********************************************************** +* Formatters object +* --- Primariy used in grid +* +*********************************************************/ + +w2utils.formatters = { + + 'number': function (value, params) { + if (parseInt(params) > 20) params = 20; + if (parseInt(params) < 0) params = 0; + if (value == null || value === '') return ''; + return w2utils.formatNumber(parseFloat(value), params, true); + }, + + 'float': function (value, params) { + return w2utils.formatters['number'](value, params); + }, + + 'int': function (value, params) { + return w2utils.formatters['number'](value, 0); + }, + + 'money': function (value, params) { + if (value == null || value === '') return ''; + var data = w2utils.formatNumber(Number(value), w2utils.settings.currencyPrecision || 2); + return (w2utils.settings.currencyPrefix || '') + data + (w2utils.settings.currencySuffix || ''); + }, + + 'currency': function (value, params) { + return w2utils.formatters['money'](value, params); + }, + + 'percent': function (value, params) { + if (value == null || value === '') return ''; + return w2utils.formatNumber(value, params || 1) + '%'; + }, + + 'size': function (value, params) { + if (value == null || value === '') return ''; + return w2utils.formatSize(parseInt(value)); + }, + + 'date': function (value, params) { + if (params === '') params = w2utils.settings.dateFormat; + if (value == null || value === 0 || value === '') return ''; + var dt = w2utils.isDateTime(value, params, true); + if (dt === false) dt = w2utils.isDate(value, params, true); + return '' + w2utils.formatDate(dt, params) + ''; + }, + + 'datetime': function (value, params) { + if (params === '') params = w2utils.settings.datetimeFormat; + if (value == null || value === 0 || value === '') return ''; + var dt = w2utils.isDateTime(value, params, true); + if (dt === false) dt = w2utils.isDate(value, params, true); + return '' + w2utils.formatDateTime(dt, params) + ''; + }, + + 'time': function (value, params) { + if (params === '') params = w2utils.settings.timeFormat; + if (params === 'h12') params = 'hh:mi pm'; + if (params === 'h24') params = 'h24:mi'; + if (value == null || value === 0 || value === '') return ''; + var dt = w2utils.isDateTime(value, params, true); + if (dt === false) dt = w2utils.isDate(value, params, true); + return '' + w2utils.formatTime(value, params) + ''; + }, + + 'timestamp': function (value, params) { + if (params === '') params = w2utils.settings.datetimeFormat; + if (value == null || value === 0 || value === '') return ''; + var dt = w2utils.isDateTime(value, params, true); + if (dt === false) dt = w2utils.isDate(value, params, true); + return dt.toString ? dt.toString() : ''; + }, + + 'gmt': function (value, params) { + if (params === '') params = w2utils.settings.datetimeFormat; + if (value == null || value === 0 || value === '') return ''; + var dt = w2utils.isDateTime(value, params, true); + if (dt === false) dt = w2utils.isDate(value, params, true); + return dt.toUTCString ? dt.toUTCString() : ''; + }, + + 'age': function (value, params) { + if (value == null || value === 0 || value === '') return ''; + var dt = w2utils.isDateTime(value, null, true); + if (dt === false) dt = w2utils.isDate(value, null, true); + return '' + w2utils.age(value) + (params ? (' ' + params) : '') + ''; + }, + + 'interval': function (value, params) { + if (value == null || value === 0 || value === '') return ''; + return w2utils.interval(value) + (params ? (' ' + params) : ''); + }, + + 'toggle': function (value, params) { + return (value ? 'Yes' : ''); + }, + + 'password': function (value, params) { + var ret = ""; + for (var i=0; i < value.length; i++) { + ret += "*"; + } + return ret; + } +}; + +/*********************************************************** +* Generic Event Object +* --- This object is reused across all other +* --- widgets in w2ui. +* +*********************************************************/ + +w2utils.event = { + + on: function (edata, handler) { + var $ = jQuery; + var scope; + // allow 'eventName.scope' syntax + if (typeof edata === 'string' && edata.indexOf('.') !== -1) { + var tmp = edata.split('.'); + edata = tmp[0]; + scope = tmp[1]; + } + // allow 'eventName:after' syntax + if (typeof edata === 'string' && edata.indexOf(':') !== -1) { + var tmp = edata.split(':'); + if (['complete', 'done'].indexOf(edata[1]) !== -1) edata[1] = 'after'; + edata = { + type : tmp[0], + execute : tmp[1] + }; + if (scope) edata.scope = scope + } + if (!$.isPlainObject(edata)) edata = { type: edata, scope: scope }; + edata = $.extend({ type: null, execute: 'before', target: null, onComplete: null }, edata); + // errors + if (!edata.type) { console.log('ERROR: You must specify event type when calling .on() method of '+ this.name); return; } + if (!handler) { console.log('ERROR: You must specify event handler function when calling .on() method of '+ this.name); return; } + if (!$.isArray(this.handlers)) this.handlers = []; + this.handlers.push({ edata: edata, handler: handler }); + return this; // needed for chaining + }, + + off: function (edata, handler) { + var $ = jQuery; + var scope; + // allow 'eventName.scope' syntax + if (typeof edata === 'string' && edata.indexOf('.') !== -1) { + var tmp = edata.split('.'); + edata = tmp[0]; + scope = tmp[1]; + if (edata === '') edata = '*' + } + // allow 'eventName:after' syntax + if (typeof edata === 'string' && edata.indexOf(':') !== -1) { + var tmp = edata.split(':'); + if (['complete', 'done'].indexOf(edata[1]) !== -1) edata[1] = 'after'; + edata = { + type : tmp[0], + execute : tmp[1] + }; + } + if (!$.isPlainObject(edata)) edata = { type: edata }; + edata = $.extend({}, { type: null, execute: null, target: null, onComplete: null }, edata); + // errors + if (!edata.type && !scope) { console.log('ERROR: You must specify event type when calling .off() method of '+ this.name); return; } + if (!handler) { handler = null; } + // remove handlers + var newHandlers = []; + for (var h = 0, len = this.handlers.length; h < len; h++) { + var t = this.handlers[h]; + if ((t.edata.type === edata.type || edata.type === '*' || (t.edata.scope != null && edata.type == '')) && + (t.edata.target === edata.target || edata.target == null) && + (t.edata.execute === edata.execute || edata.execute == null) && + ((t.handler === handler && handler != null) || (scope != null && t.edata.scope == scope))) + { + // match + } else { + newHandlers.push(t); + } + } + this.handlers = newHandlers; + return this; + }, + + trigger: function (edata) { + var $ = jQuery; + var edata = $.extend({ type: null, phase: 'before', target: null, doneHandlers: [] }, edata, { + isStopped : false, + isCancelled : false, + done : function (handler) { this.doneHandlers.push(handler); }, + preventDefault : function () { this.isCancelled = true; }, + stopPropagation : function () { this.isStopped = true; } + }); + if (edata.phase === 'before') edata.onComplete = null; + var args, fun, tmp; + if (edata.target == null) edata.target = null; + if (!$.isArray(this.handlers)) this.handlers = []; + // process events in REVERSE order + for (var h = this.handlers.length-1; h >= 0; h--) { + var item = this.handlers[h]; + if (item != null && (item.edata.type === edata.type || item.edata.type === '*') && + (item.edata.target === edata.target || item.edata.target == null) && + (item.edata.execute === edata.phase || item.edata.execute === '*' || item.edata.phase === '*')) + { + edata = $.extend({}, item.edata, edata); + // check handler arguments + args = []; + tmp = new RegExp(/\((.*?)\)/).exec(item.handler); + if (tmp) args = tmp[1].split(/\s*,\s*/); + if (args.length === 2) { + item.handler.call(this, edata.target, edata); // old way for back compatibility + } else { + item.handler.call(this, edata); // new way + } + if (edata.isStopped === true || edata.stop === true) return edata; // back compatibility edata.stop === true + } + } + // main object events + var funName = 'on' + edata.type.substr(0,1).toUpperCase() + edata.type.substr(1); + if (edata.phase === 'before' && typeof this[funName] === 'function') { + fun = this[funName]; + // check handler arguments + args = []; + tmp = new RegExp(/\((.*?)\)/).exec(fun); + if (tmp) args = tmp[1].split(/\s*,\s*/); + if (args.length === 2) { + fun.call(this, edata.target, edata); // old way for back compatibility + } else { + fun.call(this, edata); // new way + } + if (edata.isStopped === true || edata.stop === true) return edata; // back compatibility edata.stop === true + } + // item object events + if (edata.object != null && edata.phase === 'before' && + typeof edata.object[funName] === 'function') + { + fun = edata.object[funName]; + // check handler arguments + args = []; + tmp = new RegExp(/\((.*?)\)/).exec(fun); + if (tmp) args = tmp[1].split(/\s*,\s*/); + if (args.length === 2) { + fun.call(this, edata.target, edata); // old way for back compatibility + } else { + fun.call(this, edata); // new way + } + if (edata.isStopped === true || edata.stop === true) return edata; + } + // execute onComplete + if (edata.phase === 'after') { + if (typeof edata.onComplete === 'function') edata.onComplete.call(this, edata); + for (var i = 0; i < edata.doneHandlers.length; i++) { + if (typeof edata.doneHandlers[i] === 'function') { + edata.doneHandlers[i].call(this, edata); + } + } + } + return edata; + } +}; + +/*********************************************************** +* Commonly used plugins +* --- used primarily in grid and form +* +*********************************************************/ + +(function ($) { + + $.fn.w2render = function (name) { + if ($(this).length > 0) { + if (typeof name === 'string' && w2ui[name]) w2ui[name].render($(this)[0]); + if (typeof name === 'object') name.render($(this)[0]); + } + }; + + $.fn.w2destroy = function (name) { + if (!name && this.length > 0) name = this.attr('name'); + if (typeof name === 'string' && w2ui[name]) w2ui[name].destroy(); + if (typeof name === 'object') name.destroy(); + }; + + $.fn.w2marker = function () { + var str = Array.prototype.slice.call(arguments, 0); + if (Array.isArray(str[0])) str = str[0]; + if (str.length === 0 || !str[0]) { // remove marker + return $(this).each(clearMarkedText); + } else { // add marker + return $(this).each(function (index, el) { + clearMarkedText(index, el); + for (var s = 0; s < str.length; s++) { + var tmp = str[s]; + if (typeof tmp !== 'string') tmp = String(tmp); + // escape regex special chars + tmp = tmp.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&").replace(/&/g, '&').replace(//g, '<'); + var regex = new RegExp(tmp + '(?!([^<]+)?>)', "gi"); // only outside tags + el.innerHTML = el.innerHTML.replace(regex, replaceValue); + } + function replaceValue(matched) { // mark new + return '' + matched + ''; + } + }); + } + + function clearMarkedText(index, el) { + while (el.innerHTML.indexOf('') !== -1) { + el.innerHTML = el.innerHTML.replace(/\((.|\n|\r)*)\<\/span\>/ig, '$1'); // unmark + } + } + }; + + // -- w2tag - there can be multiple on screen at a time + + $.fn.w2tag = function (text, options) { + // only one argument + if (arguments.length === 1 && typeof text === 'object') { + options = text; + if (options.html != null) text = options.html; + } + // default options + options = $.extend({ + id : null, // id for the tag, otherwise input id is used + auto : null, // if auto true, then tag will show on mouseEnter and hide on mouseLeave + html : text, // or html + position : 'right|top', // can be left, right, top, bottom + align : 'none', // can be none, left, right (only works for potision: top | bottom) + left : 0, // delta for left coordinate + top : 0, // delta for top coordinate + maxWidth : null, // max width + style : '', // adition style for the tag + css : {}, // add css for input when tag is shown + className : '', // add class bubble + inputClass : '', // add class for input when tag is shown + onShow : null, // callBack when shown + onHide : null, // callBack when hidden + hideOnKeyPress : true, // hide tag if key pressed + hideOnFocus : false, // hide tag on focus + hideOnBlur : false, // hide tag on blur + hideOnClick : false, // hide tag on document click + hideOnChange : true + }, options); + if (options.name != null && options.id == null) options.id = options.name; + + // for backward compatibility + if (options['class'] !== '' && options.inputClass === '') options.inputClass = options['class']; + + // remove all tags + if ($(this).length === 0) { + $('.w2ui-tag').each(function (index, el) { + var tag = $(el).data('w2tag'); + if (tag) tag.hide(); + }); + return; + } + if (options.auto === true || options.showOn != null || options.hideOn != null) { + if (arguments.length == 0 || !text) { + return $(this).each(function (index, el) { + $(el).off('.w2tooltip') + }) + } else { + return $(this).each(function (index, el) { + var showOn = 'mouseenter', hideOn = 'mouseleave' + if (options.showOn) { + showOn = String(options.showOn).toLowerCase() + delete options.showOn + } + if (options.hideOn) { + hideOn = String(options.hideOn).toLowerCase() + delete options.hideOn + } + if (!options.potision) { options.position = 'top|bottom' } + $(el) + .off('.w2tooltip') + .on(showOn + '.w2tooltip', function () { + options.auto = false; + $(this).w2tag(text, options) + }) + .on(hideOn + '.w2tooltip', function () { + $(this).w2tag() + }) + }) + } + } else { + return $(this).each(function (index, el) { + // main object + var tag; + var origID = (options.id ? options.id : el.id); + if (origID == '') { // search for an id + origID = $(el).find('input').attr('id'); + } + if (!origID) { + origID = 'noid'; + } + var tmpID = w2utils.escapeId(origID); + if ($(this).data('w2tag') != null) { + tag = $(this).data('w2tag'); + $.extend(tag.options, options); + } else { + tag = { + id : origID, + attachedTo: el, // element attached to + box : $('#w2ui-tag-' + tmpID), // tag itself + options : $.extend({}, options), + // methods + init : init, // attach events + hide : hide, // hide tag + getPos : getPos, // gets position of tag + isMoved : isMoved, // if called, will adjust position + // internal + tmp : {} // for temp variables + }; + } + // show or hide tag + if (text === '' || text == null) { + tag.hide(); + } else if (tag.box.length !== 0) { + // if already present + tag.box.find('.w2ui-tag-body') + .css(tag.options.css) + .attr('style', tag.options.style) + .addClass(tag.options.className) + .html(tag.options.html); + } else { + tag.tmp.originalCSS = ''; + if ($(tag.attachedTo).length > 0) tag.tmp.originalCSS = $(tag.attachedTo)[0].style.cssText; + var tagStyles = 'white-space: nowrap;' + if (tag.options.maxWidth && w2utils.getStrWidth(text) > tag.options.maxWidth) { + tagStyles = 'width: '+ tag.options.maxWidth + 'px' + } + // insert + $('body').append( + ''); + tag.box = $('#w2ui-tag-' + tmpID); + $(tag.attachedTo).data('w2tag', tag); // make available to element tag attached to + setTimeout(init, 1); + } + return; + + function init() { + tag.box.css('display', 'block'); + if (!tag || !tag.box || !$(tag.attachedTo).offset()) return; + var pos = tag.getPos(); + tag.box.css({ + opacity : '1', + left : pos.left + 'px', + top : pos.top + 'px' + }) + .data('w2tag', tag) + .find('.w2ui-tag-body').addClass(pos['posClass']); + tag.tmp.pos = pos.left + 'x' + pos.top; + + $(tag.attachedTo) + .off('.w2tag') + .css(tag.options.css) + .addClass(tag.options.inputClass); + + + if (tag.options.hideOnKeyPress) { + $(tag.attachedTo).on('keypress.w2tag', tag.hide); + } + if (tag.options.hideOnFocus) { + $(tag.attachedTo).on('focus.w2tag', tag.hide); + } + if (options.hideOnChange) { + if (el.nodeName === 'INPUT') { + $(el).on('change.w2tag', tag.hide); + } else { + $(el).find('input').on('change.w2tag', tag.hide); + } + } + if (tag.options.hideOnBlur) { + $(tag.attachedTo).on('blur.w2tag', tag.hide); + } + if (tag.options.hideOnClick) { + $('body').on('click.w2tag' + (tag.id || ''), tag.hide); + } + if (typeof tag.options.onShow === 'function') { + tag.options.onShow(); + } + isMoved(); + } + + // bind event to hide it + function hide() { + if (tag.box.length <= 0) return; + if (tag.tmp.timer) clearTimeout(tag.tmp.timer); + tag.box.remove(); + if (tag.options.hideOnClick) { + $('body').off('.w2tag' + (tag.id || '')); + } + $(tag.attachedTo).off('.w2tag') + .removeClass(tag.options.inputClass) + .removeData('w2tag'); + // restore original CSS + if ($(tag.attachedTo).length > 0) { + $(tag.attachedTo)[0].style.cssText = tag.tmp.originalCSS; + } + if (typeof tag.options.onHide === 'function') { + tag.options.onHide(); + } + } + + function isMoved(instant) { + // monitor if destroyed + var offset = $(tag.attachedTo).offset(); + if ($(tag.attachedTo).length === 0 || (offset.left === 0 && offset.top === 0) || tag.box.find('.w2ui-tag-body').length === 0) { + tag.hide(); + return; + } + var pos = getPos(); + if (tag.tmp.pos !== pos.left + 'x' + pos.top) { + tag.box + .css(w2utils.cssPrefix({ 'transition': (instant ? '0s' : '.2s') })) + .css({ + left: pos.left + 'px', + top : pos.top + 'px' + }); + tag.tmp.pos = pos.left + 'x' + pos.top; + } + if (tag.tmp.timer) clearTimeout(tag.tmp.timer); + tag.tmp.timer = setTimeout(isMoved, 100); + } + + function getPos() { + var offset = $(tag.attachedTo).offset(); + var posClass = 'w2ui-tag-right'; + var posLeft = parseInt(offset.left + tag.attachedTo.offsetWidth + (tag.options.left ? tag.options.left : 0)); + var posTop = parseInt(offset.top + (tag.options.top ? tag.options.top : 0)); + var tagBody = tag.box.find('.w2ui-tag-body'); + var width = tagBody[0].offsetWidth; + var height = tagBody[0].offsetHeight; + if (typeof tag.options.position === 'string' && tag.options.position.indexOf('|') !== -1) { + tag.options.position = tag.options.position.split('|'); + } + if (tag.options.position === 'top') { + posClass = 'w2ui-tag-top'; + posLeft = parseInt(offset.left + (tag.options.left ? tag.options.left : 0)) - 14; + posTop = parseInt(offset.top + (tag.options.top ? tag.options.top : 0)) - height - 10; + } else if (tag.options.position === 'bottom') { + posClass = 'w2ui-tag-bottom'; + posLeft = parseInt(offset.left + (tag.options.left ? tag.options.left : 0)) - 14; + posTop = parseInt(offset.top + tag.attachedTo.offsetHeight + (tag.options.top ? tag.options.top : 0)) + 10; + } else if (tag.options.position === 'left') { + posClass = 'w2ui-tag-left'; + posLeft = parseInt(offset.left + (tag.options.left ? tag.options.left : 0)) - width - 20; + posTop = parseInt(offset.top + (tag.options.top ? tag.options.top : 0)); + } else if (Array.isArray(tag.options.position)) { + // try to fit the tag on screen in the order defined in the array + var maxWidth = window.innerWidth; + var maxHeight = window.innerHeight; + for (var i = 0; i < tag.options.position.length; i++) { + var pos = tag.options.position[i]; + if (pos === 'right') { + posClass = 'w2ui-tag-right'; + posLeft = parseInt(offset.left + tag.attachedTo.offsetWidth + (tag.options.left ? tag.options.left : 0)); + posTop = parseInt(offset.top + (tag.options.top ? tag.options.top : 0)); + if (posLeft+width <= maxWidth) break; + } else if (pos === 'left') { + posClass = 'w2ui-tag-left'; + posLeft = parseInt(offset.left + (tag.options.left ? tag.options.left : 0)) - width - 20; + posTop = parseInt(offset.top + (tag.options.top ? tag.options.top : 0)); + if (posLeft >= 0) break; + } else if (pos === 'top') { + posClass = 'w2ui-tag-top'; + posLeft = parseInt(offset.left + (tag.options.left ? tag.options.left : 0)) - 14; + posTop = parseInt(offset.top + (tag.options.top ? tag.options.top : 0)) - height - 10; + if(posLeft+width <= maxWidth && posTop >= 0) break; + } else if (pos === 'bottom') { + posClass = 'w2ui-tag-bottom'; + posLeft = parseInt(offset.left + (tag.options.left ? tag.options.left : 0)) - 14; + posTop = parseInt(offset.top + tag.attachedTo.offsetHeight + (tag.options.top ? tag.options.top : 0)) + 10; + if (posLeft+width <= maxWidth && posTop+height <= maxHeight) break; + } + } + if (tagBody.data('posClass') !== posClass) { + tagBody.removeClass('w2ui-tag-right w2ui-tag-left w2ui-tag-top w2ui-tag-bottom') + .addClass(posClass) + .data('posClass', posClass); + } + } + return { left: posLeft, top: posTop, posClass: posClass }; + } + }) + } + } + + // w2overlay - appears under the element, there can be only one at a time + + $.fn.w2overlay = function (html, options) { + var obj = this; + var name = ''; + var defaults = { + name : null, // it not null, then allows multiple concurrent overlays + html : '', // html text to display + align : 'none', // can be none, left, right, both + left : 0, // offset left + top : 0, // offset top + tipLeft : 30, // tip offset left + noTip : false, // if true - no tip will be displayed + selectable : false, + width : 0, // fixed width + height : 0, // fixed height + maxWidth : null, // max width if any + maxHeight : null, // max height if any + contextMenu : false, // if true, it will be opened at mouse position + pageX : null, + pageY : null, + originalEvent : null, + style : '', // additional style for main div + 'class' : '', // additional class name for main div + overlayStyle: '', + onShow : null, // event on show + onHide : null, // event on hide + openAbove : null, // show above control (if not, then as best needed) + tmp : {} + }; + if (arguments.length === 1) { + if (typeof html === 'object') { + options = html; + } else { + options = { html: html }; + } + } + if (arguments.length === 2) options.html = html; + if (!$.isPlainObject(options)) options = {}; + options = $.extend({}, defaults, options); + if (options.name) name = '-' + options.name; + // hide + var tmp_hide; + if (this.length === 0 || options.html === '' || options.html == null) { + if ($('#w2ui-overlay'+ name).length > 0) { + tmp_hide = $('#w2ui-overlay'+ name)[0].hide; + if (typeof tmp_hide === 'function') tmp_hide(); + } else { + $('#w2ui-overlay'+ name).remove(); + } + return $(this); + } + // hide previous if any + if ($('#w2ui-overlay'+ name).length > 0) { + tmp_hide = $('#w2ui-overlay'+ name)[0].hide; + $(document).off('.w2overlay'+ name); + if (typeof tmp_hide === 'function') tmp_hide(); + } + if (obj.length > 0 && (obj[0].tagName == null || obj[0].tagName.toUpperCase() === 'BODY')) options.contextMenu = true; + if (options.contextMenu && options.originalEvent) { + options.pageX = options.originalEvent.pageX; + options.pageY = options.originalEvent.pageY; + } + if (options.contextMenu && (options.pageX == null || options.pageY == null)) { + console.log('ERROR: to display menu at mouse location, pass options.pageX and options.pageY.'); + } + var data_str = '' + if (options.data) { + Object.keys(options.data).forEach(function (item) { + data_str += 'data-'+ item + '="' + options.data[item] +'"' + }) + } + // append + $('body').append( + '' + ); + // init + var div1 = $('#w2ui-overlay'+ name); + var div2 = div1.find(' > div'); + div2.html(options.html); + // pick bg color of first div + var bc = div2.css('background-color'); + if (bc != null && bc !== 'rgba(0, 0, 0, 0)' && bc !== 'transparent') div1.css({ 'background-color': bc, 'border-color': bc }); + + var offset = $(obj).offset() || {}; + div1.data('element', obj.length > 0 ? obj[0] : null) + .data('options', options) + .data('position', offset.left + 'x' + offset.top) + .fadeIn('fast') + .on('click', function (event) { + $('#w2ui-overlay'+ name).data('keepOpen', true); + // if there is label for input, it will produce 2 click events + if (event.target.tagName.toUpperCase() === 'LABEL') event.stopPropagation(); + }) + .on('mousedown', function (event) { + var tmp = event.target.tagName.toUpperCase(); + if (['INPUT', 'TEXTAREA', 'SELECT'].indexOf(tmp) === -1 && !options.selectable) { + event.preventDefault(); + } + }); + div1[0].hide = hide; + div1[0].resize = resize; + + // need time to display + setTimeout(function () { + $(document).off('.w2overlay'+ name).on('click.w2overlay'+ name, hide); + if (typeof options.onShow === 'function') options.onShow(); + resize(); + }, 10); + + monitor(); + return $(this); + + // monitor position + function monitor() { + var tmp = $('#w2ui-overlay'+ name); + if (tmp.data('element') !== obj[0]) return; // it if it different overlay + if (tmp.length === 0) return; + var offset = $(obj).offset() || {}; + var pos = offset.left + 'x' + offset.top; + if (tmp.data('position') !== pos) { + hide(); + } else { + setTimeout(monitor, 250); + } + } + + // click anywhere else hides the drop down + function hide(event) { + if (event && event.button !== 0) return; // only for left click button + var div1 = $('#w2ui-overlay'+ name); + // Allow clicking inside other overlays which belong to the elements inside this overlay + if (event && $($(event.target).closest('.w2ui-overlay').data('element')).closest('.w2ui-overlay')[0] === div1[0]) return; + if (div1.data('keepOpen') === true) { + div1.removeData('keepOpen'); + return; + } + var result; + if (typeof options.onHide === 'function') result = options.onHide(); + if (result === false) return; + div1.remove(); + $(document).off('.w2overlay'+ name); + clearInterval(div1.data('timer')); + } + + function resize() { + var div1 = $('#w2ui-overlay'+ name); + var div2 = div1.find(' > div'); + var menu = $('#w2ui-overlay'+ name +' div.w2ui-menu'); + var pos = {}; + if (menu.length > 0) { + menu.css('overflow-y', 'hidden'); + pos.scrollTop = menu.scrollTop(); + pos.scrollLeft = menu.scrollLeft(); + } + // if goes over the screen, limit height and width + if (div1.length > 0) { + div2.height('auto').width('auto'); + // width/height + var overflowX = false; + var overflowY = false; + var h = div2.height(); + var w = div2.width(); + if (options.width && options.width < w) w = options.width; + if (w < 30) w = 30; + // if content of specific height + if (options.tmp.contentHeight) { + h = parseInt(options.tmp.contentHeight); + div2.height(h); + setTimeout(function () { + var $div = div2.find('div.w2ui-menu'); + if (h > $div.height()) { + div2.find('div.w2ui-menu').css('overflow-y', 'hidden'); + } + }, 1); + setTimeout(function () { + var $div = div2.find('div.w2ui-menu'); + if ($div.css('overflow-y') !== 'auto') $div.css('overflow-y', 'auto'); + }, 10); + } + if (options.tmp.contentWidth && options.align !== 'both') { + w = parseInt(options.tmp.contentWidth); + div2.width(w); + setTimeout(function () { + if (w > div2.find('div.w2ui-menu > table').width()) { + div2.find('div.w2ui-menu > table').css('overflow-x', 'hidden'); + } + }, 1); + setTimeout(function () { + div2.find('div.w2ui-menu > table').css('overflow-x', 'auto'); + }, 10); + } else { + div2.find('div.w2ui-menu').css('width', '100%'); + } + // adjust position + var boxLeft = options.left; + var boxWidth = options.width; + var tipLeft = options.tipLeft; + // alignment + switch (options.align) { + case 'both': + boxLeft = 17; + if (options.width === 0) options.width = w2utils.getSize($(obj), 'width'); + if (options.maxWidth && options.width > options.maxWidth) options.width = options.maxWidth; + break; + case 'left': + boxLeft = 17; + break; + case 'right': + boxLeft = w2utils.getSize($(obj), 'width') - w + 10; + tipLeft = w - 40; + break; + } + if (w === 30 && !boxWidth) boxWidth = 30; else boxWidth = (options.width ? options.width : 'auto'); + var tmp = (w - 17) / 2; + if (boxWidth !== 'auto') tmp = (boxWidth - 17) / 2; + if (tmp < 25) { + boxLeft = 25 - tmp; + tipLeft = Math.floor(tmp); + } + // Y coord + var X, Y, offsetTop; + if (options.contextMenu) { // context menu + X = options.pageX + 8; + Y = options.pageY - 0; + offsetTop = options.pageY; + } else { + var offset = obj.offset() || {}; + X = ((offset.left > 25 ? offset.left : 25) + boxLeft); + Y = (offset.top + w2utils.getSize(obj, 'height') + options.top + 7); + offsetTop = offset.top; + } + div1.css({ + left : X + 'px', + top : Y + 'px', + 'min-width' : boxWidth, + 'min-height': (options.height ? options.height : 'auto') + }); + // $(window).height() - has a problem in FF20 + var offset = div2.offset() || {}; + var maxHeight = window.innerHeight + $(document).scrollTop() - offset.top - 7; + var maxWidth = window.innerWidth + $(document).scrollLeft() - offset.left - 7; + if (options.contextMenu) { // context menu + maxHeight = window.innerHeight + $(document).scrollTop() - options.pageY - 15; + maxWidth = window.innerWidth + $(document).scrollLeft() - options.pageX; + } + + if (((maxHeight > -50 && maxHeight < 210) || options.openAbove === true) && options.openAbove !== false) { + var tipOffset; + // show on top + if (options.contextMenu) { // context menu + maxHeight = options.pageY - 7; + tipOffset = 5; + } else { + maxHeight = offset.top - $(document).scrollTop() - 7; + tipOffset = 24; + } + if (options.maxHeight && maxHeight > options.maxHeight) maxHeight = options.maxHeight; + if (h > maxHeight) { + overflowY = true; + div2.height(maxHeight).width(w).css({ 'overflow-y': 'auto' }); + h = maxHeight; + } + div1.addClass('bottom-arrow'); + div1.css('top', (offsetTop - h - tipOffset + options.top) + 'px'); + div1.find('>style').html( + '#w2ui-overlay'+ name +':before { margin-left: '+ parseInt(tipLeft) +'px; }'+ + '#w2ui-overlay'+ name +':after { margin-left: '+ parseInt(tipLeft) +'px; }' + ); + } else { + // show under + if (options.maxHeight && maxHeight > options.maxHeight) maxHeight = options.maxHeight; + if (h > maxHeight) { + overflowY = true; + div2.height(maxHeight).width(w).css({ 'overflow-y': 'auto' }); + } + div1.addClass('top-arrow'); + div1.find('>style').html( + '#w2ui-overlay'+ name +':before { margin-left: '+ parseInt(tipLeft) +'px; }'+ + '#w2ui-overlay'+ name +':after { margin-left: '+ parseInt(tipLeft) +'px; }' + ); + } + // check width + w = div2.width(); + maxWidth = window.innerWidth + $(document).scrollLeft() - offset.left - 7; + if (options.maxWidth && maxWidth > options.maxWidth) maxWidth = options.maxWidth; + if (w > maxWidth && options.align !== 'both') { + options.align = 'right'; + setTimeout(function () { resize(); }, 1); + } + // don't show tip + if (options.contextMenu || options.noTip) { // context menu + div1.find('>style').html( + '#w2ui-overlay'+ name +':before { display: none; }'+ + '#w2ui-overlay'+ name +':after { display: none; }' + ); + } + // check scroll bar (needed to avoid horizontal scrollbar) + if (overflowY && options.align !== 'both') div2.width(w + w2utils.scrollBarSize() + 2); + } + if (menu.length > 0) { + menu.css('overflow-y', 'auto'); + menu.scrollTop(pos.scrollTop); + menu.scrollLeft(pos.scrollLeft); + } + } + }; + + $.fn.w2menu = function (menu, options) { + /* + ITEM STRUCTURE + item : { + id : null, + text : '', + style : '', + img : '', + icon : '', + count : '', + tooltip : '', + hidden : false, + checked : null, + disabled : false + ... + } + */ + // if items is a function + if (options && typeof options.items === 'function') { + options.items = options.items(); + } + var defaults = { + type : 'normal', // can be normal, radio, check + index : null, // current selected + items : [], + render : null, + msgNoItems : 'No items', + onSelect : null, + hideOnRemove : false, + tmp : {} + }; + var ret; + var obj = this; + var name = ''; + if (menu === 'refresh') { + // if not show - call blur + if ($.fn.w2menuOptions && $.fn.w2menuOptions.name) name = '-' + $.fn.w2menuOptions.name; + if (options.name) name = '-' + options.name; + if ($('#w2ui-overlay'+ name).length > 0) { + options = $.extend($.fn.w2menuOptions, options); + var scrTop = $('#w2ui-overlay'+ name +' div.w2ui-menu').scrollTop(); + $('#w2ui-overlay'+ name +' div.w2ui-menu').html(getMenuHTML()); + $('#w2ui-overlay'+ name +' div.w2ui-menu').scrollTop(scrTop); + mresize(); + } else { + $(this).w2menu(options); + } + } else if (menu === 'refresh-index') { + var $menu = $('#w2ui-overlay'+ name +' div.w2ui-menu'); + var cur = $menu.find('tr[index='+ options.index +']'); + var scrTop = $menu.scrollTop(); + $menu.find('tr.w2ui-selected').removeClass('w2ui-selected'); // clear all + cur.addClass('w2ui-selected'); // select current + // scroll into view + if (cur.length > 0) { + var top = cur[0].offsetTop - 5; // 5 is margin top + var height = $menu.height(); + $menu.scrollTop(scrTop); + if (top < scrTop || top + cur.height() > scrTop + height) { + $menu.animate({ 'scrollTop': top - (height - cur.height() * 2) / 2 }, 200, 'linear'); + } + } + mresize(); + } else { + if (arguments.length === 1) options = menu; else options.items = menu; + if (typeof options !== 'object') options = {}; + options = $.extend({}, defaults, options); + $.fn.w2menuOptions = options; + if (options.name) name = '-' + options.name; + if (typeof options.select === 'function' && typeof options.onSelect !== 'function') options.onSelect = options.select; + if (typeof options.remove === 'function' && typeof options.onRemove !== 'function') options.onRemove = options.remove; + if (typeof options.onRender === 'function' && typeof options.render !== 'function') options.render = options.onRender; + // since only one overlay can exist at a time + $.fn.w2menuClick = function (event, index, parentIndex) { + var keepOpen = false; + var $tr = $(event.target).closest('tr'); + if (event.shiftKey || event.metaKey || event.ctrlKey) { + keepOpen = true; + } + if (parentIndex == null) { + items = options.items + } else { + items = options.items[parentIndex].items + } + if ($(event.target).hasClass('remove')) { + if (typeof options.onRemove === 'function') { + options.onRemove({ + index: index, + parentIndex: parentIndex, + item: items[index], + keepOpen: keepOpen, + originalEvent: event + }); + } + keepOpen = !options.hideOnRemove; + $(event.target).closest('tr').remove(); + mresize(); + } else if ($tr.hasClass('has-sub-menu')) { + keepOpen = true; + if ($tr.hasClass('expanded')) { + items[index].expanded = false; + $tr.removeClass('expanded').addClass('collapsed').next().hide(); + } else { + items[index].expanded = true; + $tr.addClass('expanded').removeClass('collapsed').next().show(); + } + mresize(); + } else if (typeof options.onSelect === 'function') { + var tmp = items; + if (typeof items == 'function') { + tmp = items(options.items[parentIndex]) + } + if (tmp[index].keepOpen != null) { + keepOpen = tmp[index].keepOpen + } + options.onSelect({ + index: index, + parentIndex: parentIndex, + item: tmp[index], + keepOpen: keepOpen, + originalEvent: event + }); + } + // -- hide + if (items[index] == null || items[index].keepOpen !== true) { + var div = $('#w2ui-overlay'+ name); + div.removeData('keepOpen'); + if (div.length > 0 && typeof div[0].hide === 'function' && !keepOpen) { + div[0].hide(); + } + } + }; + $.fn.w2menuDown = function (event, index, parentIndex) { + var items; + var $el = $(event.target).closest('tr'); + var tmp = $($el.get(0)).find('.w2ui-icon'); + if (parentIndex == null) { + items = options.items + } else { + items = options.items[parentIndex].items + } + var item = items[index]; + if ((options.type === 'check' || options.type === 'radio') && item.group !== false + && !$(event.target).hasClass('remove') + && !$(event.target).closest('tr').hasClass('has-sub-menu')) { + item.checked = !item.checked; + if (item.checked) { + if (options.type === 'radio') { + tmp.parents('table').find('.w2ui-icon') // should not be closest, but parents + .removeClass('w2ui-icon-check') + .addClass('w2ui-icon-empty'); + } + if (options.type === 'check' && item.group != null) { + items.forEach(function (sub, ind) { + if (sub.id == item.id) return; + if (sub.group === item.group && sub.checked) { + tmp.closest('table').find('tr[index='+ ind +'] .w2ui-icon') + .removeClass('w2ui-icon-check') + .addClass('w2ui-icon-empty'); + items[ind].checked = false; + } + }); + } + tmp.removeClass('w2ui-icon-empty').addClass('w2ui-icon-check'); + } else if (options.type === 'check' && item.group == null && item.group !== false) { + tmp.removeClass('w2ui-icon-check').addClass('w2ui-icon-empty'); + } + } + // highlight record + $el.parent().find('tr').removeClass('w2ui-selected'); + $el.addClass('w2ui-selected'); + }; + var html = ''; + if (options.search) { + html += + '
'+ + ' '+ + ' '+ + '
'; + options.style += ';background-color: #ECECEC'; + options.index = 0; + for (var i = 0; i < options.items.length; i++) options.items[i].hidden = false; + } + html += (options.topHTML || '') + + '
' + + getMenuHTML() + + '
'; + ret = $(this).w2overlay(html, options); + setTimeout(function () { + $('#w2ui-overlay'+ name +' #menu-search') + .on('keyup', change) + .on('keydown', function (event) { + // cancel tab key + if (event.keyCode === 9) { event.stopPropagation(); event.preventDefault(); } + }); + if (options.search) { + if (['text', 'password'].indexOf($(obj)[0].type) !== -1 || $(obj)[0].tagName.toUpperCase() === 'TEXTAREA') return; + $('#w2ui-overlay'+ name +' #menu-search').focus(); + } + mresize(); + }, 250); + mresize(); + // map functions + var div = $('#w2ui-overlay'+ name); + if (div.length > 0) { + div[0].mresize = mresize; + div[0].change = change; + } + } + return ret; + + function mresize() { + setTimeout(function () { + // show selected + $('#w2ui-overlay'+ name +' tr.w2ui-selected').removeClass('w2ui-selected'); + var cur = $('#w2ui-overlay'+ name +' tr[index='+ options.index +']'); + var scrTop = $('#w2ui-overlay'+ name +' div.w2ui-menu').scrollTop(); + cur.addClass('w2ui-selected'); + if (options.tmp) { + options.tmp.contentHeight = $('#w2ui-overlay'+ name +' table').height() + (options.search ? 50 : 10) + + (parseInt($('#w2ui-overlay'+ name +' .w2ui-menu').css('top')) || 0) // it menu is moved with menuStyle + + (parseInt($('#w2ui-overlay'+ name +' .w2ui-menu').css('bottom')) || 0); // it menu is moved with menuStyle + options.tmp.contentWidth = $('#w2ui-overlay'+ name +' table').width(); + } + if ($('#w2ui-overlay'+ name).length > 0) $('#w2ui-overlay'+ name)[0].resize(); + // scroll into view + if (cur.length > 0) { + var top = cur[0].offsetTop - 5; // 5 is margin top + var el = $('#w2ui-overlay'+ name +' div.w2ui-menu'); + var height = el.height(); + $('#w2ui-overlay'+ name +' div.w2ui-menu').scrollTop(scrTop); + if (top < scrTop || top + cur.height() > scrTop + height) { + $('#w2ui-overlay'+ name +' div.w2ui-menu').animate({ 'scrollTop': top - (height - cur.height() * 2) / 2 }, 200, 'linear'); + } + } + }, 1); + } + + function change(event) { + var search = this.value; + var key = event.keyCode; + var cancel = false; + switch (key) { + case 13: // enter + $('#w2ui-overlay'+ name).remove(); + $.fn.w2menuClick(event, options.index); + break; + case 9: // tab + case 27: // escape + $('#w2ui-overlay'+ name).remove(); + $.fn.w2menuClick(event, -1); + break; + case 38: // up + options.index = w2utils.isInt(options.index) ? parseInt(options.index) : 0; + options.index--; + while (options.index > 0 && options.items[options.index].hidden) options.index--; + if (options.index === 0 && options.items[options.index].hidden) { + while (options.items[options.index] && options.items[options.index].hidden) options.index++; + } + if (options.index < 0) options.index = 0; + cancel = true; + break; + case 40: // down + options.index = w2utils.isInt(options.index) ? parseInt(options.index) : 0; + options.index++; + while (options.index < options.items.length-1 && options.items[options.index].hidden) options.index++; + if (options.index === options.items.length-1 && options.items[options.index].hidden) { + while (options.items[options.index] && options.items[options.index].hidden) options.index--; + } + if (options.index >= options.items.length) options.index = options.items.length - 1; + cancel = true; + break; + } + // filter + if (!cancel) { + var shown = 0; + for (var i = 0; i < options.items.length; i++) { + var item = options.items[i]; + var prefix = ''; + var suffix = ''; + if (['is', 'begins with'].indexOf(options.match) !== -1) prefix = '^'; + if (['is', 'ends with'].indexOf(options.match) !== -1) suffix = '$'; + try { + var re = new RegExp(prefix + search + suffix, 'i'); + if (re.test(item.text) || item.text === '...') item.hidden = false; else item.hidden = true; + } catch (e) {} + // do not show selected items + if (obj.type === 'enum' && $.inArray(item.id, ids) !== -1) item.hidden = true; + if (item.hidden !== true) shown++; + } + options.index = 0; + while (options.index < options.items.length-1 && options.items[options.index].hidden) options.index++; + if (shown <= 0) options.index = -1; + } + $(obj).w2menu('refresh', options); + mresize(); + } + + function getMenuHTML(items, subMenu, expanded, parentIndex) { + if (options.spinner) { + return '
'+ + '
'+ + '
'+ w2utils.lang('Loading...') +'
'+ + '
'; + } + var count = 0; + var menu_html = ''; + var img = null, icon = null; + if (items == null) items = options.items; + if (!Array.isArray(items)) items = [] + for (var f = 0; f < items.length; f++) { + var mitem = items[f]; + if (typeof mitem === 'string') { + mitem = { id: mitem, text: mitem }; + } else { + if (mitem.text != null && mitem.id == null) mitem.id = mitem.text; + if (mitem.text == null && mitem.id != null) mitem.text = mitem.id; + if (mitem.caption != null) mitem.text = mitem.caption; + img = mitem.img; + icon = mitem.icon; + if (img == null) img = null; // img might be undefined + if (icon == null) icon = null; // icon might be undefined + } + if (['radio', 'check'].indexOf(options.type) != -1 && !Array.isArray(mitem.items) && mitem.group !== false) { + if (mitem.checked === true) icon = 'w2ui-icon-check'; else icon = 'w2ui-icon-empty'; + } + if (mitem.hidden !== true) { + var imgd = ''; + var txt = mitem.text; + var subMenu_dsp = ''; + if (typeof options.render === 'function') txt = options.render(mitem, options); + if (typeof txt == 'function') txt = txt(mitem, options) + if (img) imgd = ''; + if (icon) imgd = ''; + // render only if non-empty + if (mitem.type !== 'break' && txt != null && txt !== '' && String(txt).substr(0, 2) != '--') { + var bg = (count % 2 === 0 ? 'w2ui-item-even' : 'w2ui-item-odd'); + if (options.altRows !== true) bg = ''; + var colspan = 1; + if (imgd === '') colspan++; + if (mitem.count == null && mitem.hotkey == null && mitem.remove !== true && mitem.items == null) colspan++; + if (mitem.tooltip == null && mitem.hint != null) mitem.tooltip = mitem.hint; // for backward compatibility + var count_dsp = ''; + if (mitem.remove === true) { + count_dsp = 'X' + } else if (mitem.items != null) { + var _items = [] + if (typeof mitem.items == 'function') { + _items = mitem.items(mitem) + } else if (Array.isArray(mitem.items)) { + _items = mitem.items + } + count_dsp = '' + subMenu_dsp = ''+ + ' '+ + ''; + } else { + if (mitem.count != null) count_dsp += '' + mitem.count + '' + if (mitem.hotkey != null) count_dsp += '' + mitem.hotkey + '' + } + menu_html += + ''+ + (subMenu ? '' : '') + imgd + + ' '+ + ' '+ + ''+ subMenu_dsp; + count++; + } else { + // horizontal line + var divText = txt.replace(/^-+/g, '') + menu_html += ''; + } + } + items[f] = mitem; + } + if (count === 0 && options.msgNoItems) { + menu_html += ''; + } + menu_html += ""; + return menu_html; + } + }; + + $.fn.w2color = function (options, callBack) { + var obj = this; + var $el = $(this); + var el = $el[0]; + // no need to init + if ($el.data('skipInit')) { + $el.removeData('skipInit'); + return; + } + // needed for keyboard navigation + var index = [-1, -1]; + if ($.fn.w2colorPalette == null) { + $.fn.w2colorPalette = [ + ['000000', '333333', '555555', '777777', '888888', '999999', 'AAAAAA', 'CCCCCC', 'DDDDDD', 'EEEEEE', 'F7F7F7', 'FFFFFF'], + ['FF011B', 'FF9838', 'FFC300', 'FFFD59', '86FF14', '14FF7A', '2EFFFC', '2693FF', '006CE7', '9B24F4', 'FF21F5', 'FF0099'], + ['FFEAEA', 'FCEFE1', 'FCF4DC', 'FFFECF', 'EBFFD9', 'D9FFE9', 'E0FFFF', 'E8F4FF', 'ECF4FC', 'EAE6F4', 'FFF5FE', 'FCF0F7'], + ['F4CCCC', 'FCE5CD', 'FFF1C2', 'FFFDA1', 'D5FCB1', 'B5F7D0', 'BFFFFF', 'D6ECFF', 'CFE2F3', 'D9D1E9', 'FFE3FD', 'FFD9F0'], + ['EA9899', 'F9CB9C', 'FFE48C', 'F7F56F', 'B9F77E', '84F0B1', '83F7F7', 'B5DAFF', '9FC5E8', 'B4A7D6', 'FAB9F6', 'FFADDE'], + ['E06666', 'F6B26B', 'DEB737', 'E0DE51', '8FDB48', '52D189', '4EDEDB', '76ACE3', '6FA8DC', '8E7CC3', 'E07EDA', 'F26DBD'], + ['CC0814', 'E69138', 'AB8816', 'B5B20E', '6BAB30', '27A85F', '1BA8A6', '3C81C7', '3D85C6', '674EA7', 'A14F9D', 'BF4990'], + ['99050C', 'B45F17', '80650E', '737103', '395E14', '10783D', '13615E', '094785', '0A5394', '351C75', '780172', '782C5A'] + ]; + } + var pal = $.fn.w2colorPalette; + if (typeof options === 'string') options = { + color: options, + transparent: true + }; + if (options.onSelect == null && callBack != null) options.onSelect = callBack; + // add remove transarent color + if (options.transparent && pal[0][1] == '333333') { + pal[0].splice(1, 1); + pal[0].push(''); + } + if (!options.transparent && pal[0][1] != '333333') { + pal[0].splice(1, 0, '333333'); + pal[0].pop(); + } + if (options.color) options.color = String(options.color).toUpperCase(); + if (typeof options.color === 'string' && options.color.substr(0,1) === '#') options.color = options.color.substr(1); + if (options.fireChange == null) options.fireChange = true; + + if ($('#w2ui-overlay').length === 0) { + $(el).w2overlay(getColorHTML(options), options); + } else { // only refresh contents + $('#w2ui-overlay .w2ui-colors').parent().html(getColorHTML(options)); + $('#w2ui-overlay').show(); + } + // bind events + $('#w2ui-overlay .w2ui-color') + .off('.w2color') + .on('mousedown.w2color', function (event) { + var color = $(event.originalEvent.target).attr('name'); // should not have # + index = $(event.originalEvent.target).attr('index').split(':'); + if (el.tagName.toUpperCase() === 'INPUT') { + if (options.fireChange) $(el).change(); + $(el).next().find('>div').css('background-color', color); + } else { + $(el).data('_color', color); + } + if (typeof options.onSelect === 'function') options.onSelect(color); + }) + .on('mouseup.w2color', function () { + setTimeout(function () { + if ($("#w2ui-overlay").length > 0) $('#w2ui-overlay').removeData('keepOpen')[0].hide(); + }, 10); + }); + $('#w2ui-overlay .color-original') + .off('.w2color') + .on('click.w2color', function (event) { + // restore original color + var tmp = w2utils.parseColor($(event.target).css('background-color')); + if (tmp != null) { + rgb = tmp; + hsv = w2utils.rgb2hsv(rgb); + setColor(hsv); + updateSlides(); + refreshPalette(); + } + }); + $('#w2ui-overlay input') + .off('.w2color') + .on('mousedown.w2color', function (event) { + $('#w2ui-overlay').data('keepOpen', true); + setTimeout(function () { $('#w2ui-overlay').data('keepOpen', true); }, 10); + event.stopPropagation(); + }) + .on('change.w2color', function () { + var $el = $(this); + var val = parseFloat($el.val()); + var max = parseFloat($el.attr('max')); + if (isNaN(val)) val = 0; + if (max > 1) val = parseInt(val); + if (max > 0 && val > max) { + $el.val(max); + val = max; + } + if (val < 0) { + $el.val(0); + val = 0; + } + var name = $el.attr('name'); + var color = {}; + if (['r', 'g', 'b', 'a'].indexOf(name) !== -1) { + rgb[name] = val; + hsv = w2utils.rgb2hsv(rgb); + } else if (['h', 's', 'v'].indexOf(name) !== -1) { + color[name] = val; + } + setColor(color); + updateSlides(); + refreshPalette(); + }); + // advanced color events + var initial; + var hsv, rgb = w2utils.parseColor(options.color); + if (rgb == null) { + rgb = { r: 140, g: 150, b: 160, a: 1 }; + hsv = w2utils.rgb2hsv(rgb); + } + hsv = w2utils.rgb2hsv(rgb); + + var setColor = function (color, silent) { + if (color.h != null) hsv.h = color.h; + if (color.s != null) hsv.s = color.s; + if (color.v != null) hsv.v = color.v; + if (color.a != null) { rgb.a = color.a; hsv.a = color.a; } + rgb = w2utils.hsv2rgb(hsv); + // console.log(rgb) + var newColor = 'rgba('+ rgb.r +','+ rgb.g +','+ rgb.b +','+ rgb.a +')'; + var cl = [ + Number(rgb.r).toString(16).toUpperCase(), + Number(rgb.g).toString(16).toUpperCase(), + Number(rgb.b).toString(16).toUpperCase(), + (Math.round(Number(rgb.a)*255)).toString(16).toUpperCase() + ]; + cl.forEach(function (item, ind) { if (item.length === 1) cl[ind] = '0' + item; }); + newColor = cl[0] + cl[1] + cl[2] + cl[3]; + if (rgb.a === 1) { + newColor = cl[0] + cl[1] + cl[2]; + } + $('#w2ui-overlay .color-preview').css('background-color', '#'+newColor); + $('#w2ui-overlay input').each(function (index, el) { + if (el.name) { + if (rgb[el.name] != null) el.value = rgb[el.name]; + if (hsv[el.name] != null) el.value = hsv[el.name]; + if (el.name === 'a') el.value = rgb.a; + } + }); + if (!silent) { + if (el.tagName.toUpperCase() === 'INPUT') { + $(el).val(newColor).data('skipInit', true); + if (options.fireChange) $(el).change(); + $(el).next().find('>div').css('background-color', '#'+newColor); + } else { + $(el).data('_color', newColor); + } + if (typeof options.onSelect === 'function') options.onSelect(newColor); + } else { + $('#w2ui-overlay .color-original').css('background-color', '#'+newColor); + } + } + var updateSlides = function () { + var $el1 = $('#w2ui-overlay .palette .value1'); + var $el2 = $('#w2ui-overlay .rainbow .value2'); + var $el3 = $('#w2ui-overlay .alpha .value2'); + var offset1 = parseInt($el1.width()) / 2; + var offset2 = parseInt($el2.width()) / 2; + $el1.css({ 'left': hsv.s * 150 / 100 - offset1, 'top': (100 - hsv.v) * 125 / 100 - offset1}); + $el2.css('left', hsv.h/(360/150) - offset2); + $el3.css('left', rgb.a*150 - offset2); + } + var refreshPalette = function() { + var cl = w2utils.hsv2rgb(hsv.h, 100, 100); + var rgb = cl.r + ',' + cl.g + ',' + cl.b; + $('#w2ui-overlay .palette').css('background-image', + 'linear-gradient(90deg, rgba('+ rgb +',0) 0%, rgba(' + rgb + ',1) 100%)'); + } + var mouseDown = function (event) { + var $el = $(this).find('.value1, .value2'); + var offset = parseInt($el.width()) / 2; + if ($el.hasClass('move-x')) $el.css({ left: (event.offsetX - offset) + 'px' }); + if ($el.hasClass('move-y')) $el.css({ top: (event.offsetY - offset) + 'px' }); + initial = { + $el : $el, + x : event.pageX, + y : event.pageY, + width : $el.parent().width(), + height : $el.parent().height(), + left : parseInt($el.css('left')), + top : parseInt($el.css('top')) + }; + mouseMove(event); + $('body').off('.w2color') + .on(mMove, mouseMove) + .on(mUp, mouseUp); + }; + var mouseUp = function(event) { + $('body').off('.w2color'); + }; + var mouseMove = function(event) { + var $el = initial.$el; + var divX = event.pageX - initial.x; + var divY = event.pageY - initial.y; + var newX = initial.left + divX; + var newY = initial.top + divY; + var offset = parseInt($el.width()) / 2; + if (newX < -offset) newX = -offset; + if (newY < -offset) newY = -offset; + if (newX > initial.width - offset) newX = initial.width - offset; + if (newY > initial.height - offset) newY = initial.height - offset + if ($el.hasClass('move-x')) $el.css({ left : newX + 'px' }); + if ($el.hasClass('move-y')) $el.css({ top : newY + 'px' }); + + // move + var name = $el.parent().attr('name'); + var x = parseInt($el.css('left')) + offset; + var y = parseInt($el.css('top')) + offset; + if (name === 'palette') { + setColor({ + s: Math.round(x / initial.width * 100), + v: Math.round(100 - (y / initial.height * 100)) + }); + } + if (name === 'rainbow') { + var h = Math.round(360 / 150 * x); + setColor({ h: h }); + refreshPalette(); + } + if (name === 'alpha') { + setColor({ a: parseFloat(Number(x / 150).toFixed(2)) }); + } + } + if ($.fn._colorAdvanced === true || options.advanced === true) { + $('#w2ui-overlay .w2ui-color-tabs :nth-child(2)').click(); + $('#w2ui-overlay').removeData('keepOpen'); + } + setColor({}, true); + refreshPalette(); + updateSlides(); + + // Events of iOS + var mDown = 'mousedown.w2color'; + var mUp = 'mouseup.w2color'; + var mMove = 'mousemove.w2color'; + if (w2utils.isIOS) { + mDown = 'touchstart.w2color'; + mUp = 'touchend.w2color'; + mMove = 'touchmove.w2color '; + } + $('#w2ui-overlay .palette') + .off('.w2color') + .on('mousedown.w2color', mouseDown); + $('#w2ui-overlay .rainbow') + .off('.w2color') + .on('mousedown.w2color', mouseDown); + $('#w2ui-overlay .alpha') + .off('.w2color') + .on('mousedown.w2color', mouseDown); + + // keyboard navigation + el.nav = function (direction) { + switch (direction) { + case 'up': + index[0]--; + break; + case 'down': + index[0]++; + break; + case 'right': + index[1]++; + break; + case 'left': + index[1]--; + break; + } + if (index[0] < 0) index[0] = 0; + if (index[0] > pal.length - 2) index[0] = pal.length - 2; + if (index[1] < 0) index[1] = 0; + if (index[1] > pal[0].length - 1) index[1] = pal[0].length - 1; + + color = pal[index[0]][index[1]]; + $(el).data('_color', color); + return color; + }; + + function getColorHTML(options) { + var color = options.color, bor; + var html = '
'+ + '
'+ + ''; + for (var i = 0; i < pal.length; i++) { + html += ''; + for (var j = 0; j < pal[i].length; j++) { + if (pal[i][j] === 'FFFFFF') bor = ';border: 1px solid #efefef'; else bor = ''; + html += ''; + if (options.color == pal[i][j]) index = [i, j]; + } + html += ''; + if (i < 2) html += ''; + } + html += '
'+ + '
'+ (options.color == pal[i][j] ? '•' : ' ') + + '
'+ + '
'+ + '
'; + if (true) { + html += ''; + } + html += '
'+ + '
'+ + '
'+ + '
' + (typeof options.html == 'string' ? options.html : '') + '
' + + '
'+ + '
'+ + '
'; + return html; + } + }; +})(jQuery); + +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2grid - grid widget +* - $().w2grid - jQuery wrapper +* - Dependencies: jQuery, w2utils, w2toolbar, w2fields +* +* == TODO == +* - column autosize based on largest content +* - reorder columns/records +* - problem with .set() and arrays, array get extended too, but should be replaced +* - after edit stay on the same record option +* - if supplied array of ids, get should return array of records +* - allow functions in routeData (also add routeData to list/enum) +* - implement global routeData and all elements read from there +* - send parsed URL to the event if there is routeData +* - if you set searchData or sortData and call refresh() it should work +* - add selectType: 'none' so that no selection can be make but with mouse +* - reorder records with frozen columns +* - focus/blur for selectType = cell not display grayed out selection +* - frozen columns + - load more only on the right side + - scrolling on frozen columns is not working only on regular columns +* - copy or large number of records is slow +* - reusable search component (see https://github.com/vitmalina/w2ui/issues/914#issuecomment-107340524) +* - allow enum in inline edit (see https://github.com/vitmalina/w2ui/issues/911#issuecomment-107341193) +* - if record has no recid, then it should be index in the aray (should not be 0) +* +* == KNOWN ISSUES == +* - bug: vs_start = 100 and more then 500 records, when scrolling empty sets +* - row drag and drop has bugs +* - Shift-click/Ctrl-click/Ctrl-Shift-Click selection is not as robust as it should be +* +* == 1.5 changes +* - $('#grid').w2grid() - if called w/o argument then it returns grid object +* - added statusRange : true, +* statusBuffered : false, +* statusRecordID : true, +* statusSelection : true, +* statusResponse : true, +* statusSort : true, +* statusSearch : true, +* - change selectAll() and selectNone() - return time it took +* - added vs_start and vs_extra +* - added update(cells) - updates only data in the grid (or cells) +* - add to docs onColumnDragStart, onColumnDragEnd +* - onSelect and onSelect should fire 1 time for selects with shift or selectAll(), selectNone() +* - record.w2ui.style[field_name] +* - use column field for style: { 1: 'color: red' } +* - added focus(), blur(), onFocus, onBlur +* - search.simple - if false, will not show up in simple search +* - search.operator - default operator to use with search field +* - search.operators - array of operators for the search +* - search.hidden - could not be cleared by the user +* - search.value - only for hidden searches +* - if .search(val) - search all fields +* - refactor reorderRow (not finished) +* - return JSON can now have summary array +* - frozen columns +* - added selectionSave, selectionRestore - for internal use +* - added additional search filter options for int, float, date, time +* - added getLineHTML +* - added lineNumberWidth +* - add searches.style +* - getColumn without params returns fields of all columns +* - getSearch without params returns fields of all searches +* - added column.tooltip +* - added hasFocus, refactored w2utils.keyboard +* - do not clear selection when clicked and it was not in focus +* - added record.w2ui.colspan +* - editable area extends with typing +* - removed onSubmit and onDeleted - now it uses onSave and onDelete +* - column.seachable - can be an object, which will create search +* - added null, not null filters +* - update(cells) - added argument cells +* - scrollIntoView(..., ..., instant) - added third argument +* - added onResizeDblClick +* - added onColumnDblClick +* - implemented showBubble +* - added show.searchAll +* - added show.searchHiddenMsg +* - added w2grid.operators +* - added w2grid.operatorsMap +* - move events into prototype +* - move rec.summary, rec.style, rec.editable -> into rec.w2ui.summary, rec.w2ui.style, rec.w2ui.editable +* - record: { + recid + field1 + ... + fieldN + w2ui: { + colspan: { field: 5, ...} + editable: true/false + hideCheckBox: true/false, + changes: { + field: chagned_value, + .... + }, + children: [ + // similar to records array + // can have sub children + ] + parent_recid: (internally set, id of the parent record, when children are copied to records array) + summary: true/false + style: 'string' - for entire row OR { field: 'string', ...} - per field + class: 'string' - for entire row OR { field: 'string', ...} - per field + } + } +* - added this.show.toolbarInput +* - disableCVS +* - added nestedFields: use field name containing dots as separator to look into objects +* - grid.message +* - added noReset option to localSort() +* - onColumnSelect +* - need to update PHP example +* - added scrollToColumn(field) +* - textSearch: 'begins' (default), 'contains', 'is', ... +* - added refreshBody +* - added response.total = -1 (or not present) to indicate that number of records is unknown +* - message(.., callBack) - added callBack +* - grid.msgEmpty +* - field.render(..., data) -- added last argument which is what grid thinks should be there +* - field.render can return { html, class, style } as an object +* - onSearchOpen (onSearch will have mutli and reset flags) +* - added httpHeaders +* - col.editable can be a function which will be called with the same args as col.render() +* - col.clipboardCopy - display icon to copy to clipboard +* - clipboardCopy - new function on grid level +* - getCellEditable(index, col_ind) -- return an 'editable' descriptor if cell is really editable +* - added stateId +* - rec.w2ui.class (and rec.w2ui.class { fname: '...' }) +* - columnTooltip +* - expendable grids are still working +* - added search.type = 'color' +* - added getFirst +* - added stateColProps +* - added stateColDefaults +* - deprecated search.caption -> search.label +* - deprecated column.caption -> column.text +* - deprecated columnGroup.caption -> columnGroup.text +* - moved a lot of properties into prototype +* - showExtraOnSearch +* - added sortMap, searchMap +* - added column.hideable +* - added updateColumn +* - column.info { + icon : string|function|object, + style : string|function|object, + render : function, + fields : array|object, + showOn : 'mouseover|mouseenter|...', + hideOn : 'mouseout|mouseleave|...', + options : {} - will be passed to w2tag (for example options.potions = 'top') + } +* - added msgDeleteBtn +* - grid.toolbar.item batch +* - order.column +* - fixed select/unselect, not it can take array of ids +* - menuClick - changed parameters +* - column.text can be a function +* - columnGroup.text can be a function +* +************************************************************************/ + +(function ($) { + var w2grid = function(options) { + + // public properties + this.name = null; + this.box = null; // HTML element that hold this element + this.columns = []; // { field, text, size, attr, render, hidden, gridMinWidth, editable } + this.columnGroups = []; // { span: int, text: 'string', master: true/false } + this.records = []; // { recid: int(requied), field1: 'value1', ... fieldN: 'valueN', style: 'string', changes: object } + this.summary = []; // arry of summary records, same structure as records array + this.searches = []; // { type, label, field, inTag, outTag, hidden } + this.sortMap = {}; // remap sort Fields + this.toolbar = {}; // if not empty object; then it is toolbar object + this.ranges = []; + this.menu = []; + this.searchData = []; + this.sortData = []; + this.total = 0; // server total + this.recid = null; // field from records to be used as recid + + // internal + this.last = { + field : '', + label : '', + logic : 'OR', + search : '', + searchIds : [], + selection : { + indexes : [], + columns : {} + }, + multi : false, + scrollTop : 0, + scrollLeft : 0, + colStart : 0, // for column virtual scrolling + colEnd : 0, + sortData : null, + sortCount : 0, + xhr : null, + loaded : false, + range_start : null, + range_end : null, + sel_ind : null, + sel_col : null, + sel_type : null, + edit_col : null, + isSafari : (/^((?!chrome|android).)*safari/i).test(navigator.userAgent) + } + $.extend(true, this, w2obj.grid); + this.show = $.extend(true, {}, w2grid.prototype.show); + this.postData = $.extend(true, {}, w2grid.prototype.postData); + this.routeData = $.extend(true, {}, w2grid.prototype.routeData); + this.httpHeaders = $.extend(true, {}, w2grid.prototype.httpHeaders); + this.buttons = $.extend(true, {}, w2grid.prototype.buttons); + this.operators = $.extend(true, {}, w2grid.prototype.operators); + this.operatorsMap = $.extend(true, {}, w2grid.prototype.operatorsMap); + this.stateColProps = $.extend(true, {}, w2grid.prototype.stateColProps); + this.stateColDefaults = $.extend(true, {}, w2grid.prototype.stateColDefaults); + + $.extend(true, this, options); + } + + // ==================================================== + // -- Registers as a jQuery plugin + + $.fn.w2grid = function(method) { + if ($.isPlainObject(method)) { + // check name parameter + if (!w2utils.checkName(method, 'w2grid')) return; + // remember items + var columns = method.columns; + var columnGroups = method.columnGroups; + var records = method.records; + var searches = method.searches; + var searchData = method.searchData; + var sortData = method.sortData; + // extend items + var object = new w2grid(method); + $.extend(object, { records: [], columns: [], searches: [], sortData: [], searchData: [], handlers: [] }); + + // reassign variables + var p; + if (columns) for (p = 0; p < columns.length; p++) object.columns[p] = $.extend(true, {}, columns[p]); + if (columnGroups) for (p = 0; p < columnGroups.length; p++) object.columnGroups[p] = $.extend(true, {}, columnGroups[p]); + if (searches) for (p = 0; p < searches.length; p++) object.searches[p] = $.extend(true, {}, searches[p]); + if (searchData) for (p = 0; p < searchData.length; p++) object.searchData[p] = $.extend(true, {}, searchData[p]); + if (sortData) for (p = 0; p < sortData.length; p++) object.sortData[p] = $.extend(true, {}, sortData[p]); + + // check if there are records without recid + if (records) for (var r = 0; r < records.length; r++) { + if (records[r][object.recid] != null) { + records[r].recid = records[r][object.recid]; + } + if (records[r].recid == null) { + console.log('ERROR: Cannot add records without recid. (obj: '+ object.name +')'); + return; + } + object.records[r] = $.extend(true, {}, records[r]); + } + // add searches + for (var i = 0; i < object.columns.length; i++) { + var col = object.columns[i]; + var search = col.searchable; + if (search == null || search === false || object.getSearch(col.field) != null) continue; + if ($.isPlainObject(search)) { + object.addSearch($.extend({ field: col.field, label: col.text, type: 'text' }, search)); + } else { + var stype = col.searchable, attr = ''; + if (col.searchable === true) { stype = 'text'; attr = 'size="20"'; } + object.addSearch({ field: col.field, label: col.text, type: stype, attr: attr }); + } + } + // register new object + w2ui[object.name] = object; + // init toolbar + object.initToolbar(); + object.updateToolbar(); + // render if necessary + if ($(this).length !== 0) { + object.render($(this)[0]); + } + return object; + + } else { + var obj = w2ui[$(this).attr('name')]; + if (!obj) return null; + if (arguments.length > 0) { + if (obj[method]) obj[method].apply(obj, Array.prototype.slice.call(arguments, 1)); + return this; + } else { + return obj; + } + } + } + + // ==================================================== + // -- Implementation of core functionality + + w2grid.prototype = { + header : '', + url : '', + limit : 100, + offset : 0, // how many records to skip (for infinite scroll) when pulling from server + postData : {}, + routeData : {}, + httpHeaders : {}, + show: { + header : false, + toolbar : false, + footer : false, + columnHeaders : true, + lineNumbers : false, + orderColumn : false, + expandColumn : false, + selectColumn : false, + emptyRecords : true, + toolbarReload : true, + toolbarColumns : true, + toolbarSearch : true, + toolbarInput : true, + toolbarAdd : false, + toolbarEdit : false, + toolbarDelete : false, + toolbarSave : false, + searchAll : true, + searchHiddenMsg : false, + statusRange : true, + statusBuffered : false, + statusRecordID : true, + statusSelection : true, + statusResponse : true, + statusSort : false, + statusSearch : false, + recordTitles : true, + selectionBorder : true, + skipRecords : true, + saveRestoreState: true + }, + stateId : null, // Custom state name for stateSave, stateRestore and stateReset + hasFocus : false, + autoLoad : true, // for infinite scroll + fixedBody : true, // if false; then grid grows with data + recordHeight : 24, // should be in prototype + lineNumberWidth : null, + keyboard : true, + selectType : 'row', // can be row|cell + multiSearch : true, + multiSelect : true, + multiSort : true, + reorderColumns : false, + reorderRows : false, + showExtraOnSearch : 0, // show extra records before and after on search + markSearch : true, + columnTooltip : 'top|bottom', // can be normal, top, bottom, left, right + disableCVS : false, // disable Column Virtual Scroll + textSearch : 'begins', // default search type for text + nestedFields : true, // use field name containing dots as separator to look into object + vs_start : 150, + vs_extra : 15, + style : '', + method : null, // if defined, then overwrited ajax method + dataType : null, // if defined, then overwrited w2utils.settings.dataType + parser : null, + + // these column properties will be saved in stateSave() + stateColProps: { + text : false, + field : true, + size : true, + min : false, + max : false, + gridMinWidth : false, + sizeCorrected : false, + sizeCalculated : true, + sizeOriginal : true, + sizeType : true, + hidden : true, + sortable : false, + searchable : false, + clipboardCopy : false, + resizable : false, + hideable : false, + attr : false, + style : false, + render : false, + title : false, + editable : false, + frozen : true, + info : false, + }, + + // these are the stateSave() fallback values if the property to save is not a property of the column object + stateColDefaults: { + text : '', // column text + field : '', // field name to map column to a record + size : null, // size of column in px or % + min : 20, // minimum width of column in px + max : null, // maximum width of column in px + gridMinWidth : null, // minimum width of the grid when column is visible + sizeCorrected : null, // read only, corrected size (see explanation below) + sizeCalculated : null, // read only, size in px (see explanation below) + sizeOriginal : null, + sizeType : null, + hidden : false, // indicates if column is hidden + sortable : false, // indicates if column is sortable + searchable : false, // indicates if column is searchable, bool/string: int,float,date,... + clipboardCopy : false, + resizable : true, // indicates if column is resizable + hideable : true, // indicates if column can be hidden + attr : '', // string that will be inside the tag + style : '', // additional style for the td tag + render : null, // string or render function + title : null, // string or function for the title property for the column cells + editable : {}, // editable object if column fields are editable + frozen : false, // indicates if the column is fixed to the left + info : null // info bubble, can be bool/object + }, + + msgDelete : 'Are you sure you want to delete NN records?', + msgDeleteBtn : 'Delete', + msgNotJSON : 'Returned data is not in valid JSON format.', + msgAJAXerror : 'AJAX error. See console for more details.', + msgRefresh : 'Refreshing...', + msgNeedReload : 'Your remote data source record count has changed, reloading from the first record.', + msgEmpty : '', // if not blank, then it is message when server returns no records + + buttons: { + 'reload' : { type: 'button', id: 'w2ui-reload', icon: 'w2ui-icon-reload', tooltip: 'Reload data in the list' }, + 'columns' : { type: 'drop', id: 'w2ui-column-on-off', icon: 'w2ui-icon-columns', tooltip: 'Show/hide columns', arrow: false, html: '' }, + 'search' : { type: 'html', id: 'w2ui-search', + html: '
' + }, + 'search-go': { type: 'drop', id: 'w2ui-search-advanced', icon: 'w2ui-icon-search', text: 'Search', tooltip: 'Open Search Fields' }, + 'add' : { type: 'button', id: 'w2ui-add', text: 'Add New', tooltip: 'Add new record', icon: 'w2ui-icon-plus' }, + 'edit' : { type: 'button', id: 'w2ui-edit', text: 'Edit', tooltip: 'Edit selected record', icon: 'w2ui-icon-pencil', disabled: true }, + 'delete' : { type: 'button', id: 'w2ui-delete', text: 'Delete', tooltip: 'Delete selected records', icon: 'w2ui-icon-cross', disabled: true }, + 'save' : { type: 'button', id: 'w2ui-save', text: 'Save', tooltip: 'Save changed records', icon: 'w2ui-icon-check' } + }, + + operators: { // for search fields + "text" : ['is', 'begins', 'contains', 'ends'], + "number" : ['=', 'between', '>', '<', '>=', '<='], + "date" : ['is', 'between', { oper: 'less', text: 'before'}, { oper: 'more', text: 'after' }], + "list" : ['is'], + "hex" : ['is', 'between'], + "color" : ['is', 'begins', 'contains', 'ends'], + "enum" : ['in', 'not in'] + // -- all possible + // "text" : ['is', 'begins', 'contains', 'ends'], + // "number" : ['is', 'between', 'less:less than', 'more:more than', 'null:is null', 'not null:is not null'], + // "list" : ['is', 'null:is null', 'not null:is not null'], + // "enum" : ['in', 'not in', 'null:is null', 'not null:is not null'] + }, + + operatorsMap: { + "text" : "text", + "int" : "number", + "float" : "number", + "money" : "number", + "currency" : "number", + "percent" : "number", + "hex" : "hex", + "alphanumeric" : "text", + "color" : "color", + "date" : "date", + "time" : "date", + "datetime" : "date", + "list" : "list", + "combo" : "text", + "enum" : "enum", + "file" : "enum", + "select" : "list", + "radio" : "list", + "checkbox" : "list", + "toggle" : "list" + }, + + // events + onAdd : null, + onEdit : null, + onRequest : null, // called on any server event + onLoad : null, + onDelete : null, + onSave : null, + onSelect : null, + onUnselect : null, + onClick : null, + onDblClick : null, + onContextMenu : null, + onMenuClick : null, // when context menu item selected + onColumnClick : null, + onColumnDblClick : null, + onColumnResize : null, + onColumnAutoResize : null, + onSort : null, + onSearch : null, + onSearchOpen : null, + onChange : null, // called when editable record is changed + onRestore : null, // called when editable record is restored + onExpand : null, + onCollapse : null, + onError : null, + onKeydown : null, + onToolbar : null, // all events from toolbar + onColumnOnOff : null, + onCopy : null, + onPaste : null, + onSelectionExtend : null, + onEditField : null, + onRender : null, + onRefresh : null, + onReload : null, + onResize : null, + onDestroy : null, + onStateSave : null, + onStateRestore : null, + onFocus : null, + onBlur : null, + onReorderRow : null, + + add: function (record, first) { + if (!$.isArray(record)) record = [record]; + var added = 0; + for (var i = 0; i < record.length; i++) { + var rec = record[i]; + if (rec[this.recid] != null) { + rec.recid = rec[this.recid]; + } + if (rec.recid == null) { + console.log('ERROR: Cannot add record without recid. (obj: '+ this.name +')'); + continue; + } + if (rec.w2ui && rec.w2ui.summary === true) { + if (first) this.summary.unshift(rec); else this.summary.push(rec); + } else { + if (first) this.records.unshift(rec); else this.records.push(rec); + } + added++; + } + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (!url) { + this.total = this.records.length; + this.localSort(false, true); + this.localSearch(); + // do not call this.refresh(), this is unnecessary, heavy, and messes with the toolbar. + this.refreshBody(); + this.resizeRecords(); + return added; + } + this.refresh(); // ?? should it be reload? + return added; + }, + + find: function (obj, returnIndex) { + if (obj == null) obj = {}; + var recs = []; + var hasDots = false; + // check if property is nested - needed for speed + for (var o in obj) if (String(o).indexOf('.') != -1) hasDots = true; + // look for an item + for (var i = 0; i < this.records.length; i++) { + var match = true; + for (var o in obj) { + var val = this.records[i][o]; + if (hasDots && String(o).indexOf('.') != -1) val = this.parseField(this.records[i], o); + if (obj[o] == 'not-null') { + if (val == null || val === '') match = false; + } else { + if (obj[o] != val) match = false; + } + } + if (match && returnIndex !== true) recs.push(this.records[i].recid); + if (match && returnIndex === true) recs.push(i); + } + return recs; + }, + + set: function (recid, record, noRefresh) { // does not delete existing, but overrides on top of it + if ((typeof recid == 'object') && (recid !== null)) { + noRefresh = record; + record = recid; + recid = null; + } + // update all records + if (recid == null) { + for (var i = 0; i < this.records.length; i++) { + $.extend(true, this.records[i], record); // recid is the whole record + } + if (noRefresh !== true) this.refresh(); + } else { // find record to update + var ind = this.get(recid, true); + if (ind == null) return false; + var isSummary = (this.records[ind] && this.records[ind].recid == recid ? false : true); + if (isSummary) { + $.extend(true, this.summary[ind], record); + } else { + $.extend(true, this.records[ind], record); + } + if (noRefresh !== true) this.refreshRow(recid, ind); // refresh only that record + } + return true; + }, + + get: function (recid, returnIndex) { + // search records + if ($.isArray(recid)) { + var recs = []; + for (var i = 0; i < recid.length; i++) { + var v = this.get(recid[i], returnIndex); + if (v !== null) + recs.push(v); + } + return recs; + } else { + // get() must be fast, implements a cache to bypass loop over all records + // most of the time. + var idCache = this.last.idCache; + if (!idCache) { + this.last.idCache = idCache = {}; + } + var i = idCache[recid]; + if (typeof(i) === "number") { + if (i >= 0 && i < this.records.length && this.records[i].recid == recid) { + if (returnIndex === true) return i; else return this.records[i]; + } + // summary indexes are stored as negative numbers, try them now. + i = ~i; + if (i >= 0 && i < this.summary.length && this.summary[i].recid == recid) { + if (returnIndex === true) return i; else return this.summary[i]; + } + // wrong index returned, clear cache + this.last.idCache = idCache = {}; + } + for (var i = 0; i < this.records.length; i++) { + if (this.records[i].recid == recid) { + idCache[recid] = i; + if (returnIndex === true) return i; else return this.records[i]; + } + } + // search summary + for (var i = 0; i < this.summary.length; i++) { + if (this.summary[i].recid == recid) { + idCache[recid] = ~i; + if (returnIndex === true) return i; else return this.summary[i]; + } + } + return null; + } + }, + + getFirst: function () { + if (this.records.length == 0) return null; + var recid = this.records[0].recid; + var tmp = this.last.searchIds; + if (this.searchData.length > 0) { + if (Array.isArray(tmp) && tmp.length > 0) { + recid = this.records[tmp[0]].recid; + } else { + recid = null; + } + } + return recid; + }, + + remove: function () { + var removed = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.records.length-1; r >= 0; r--) { + if (this.records[r].recid == arguments[a]) { this.records.splice(r, 1); removed++; } + } + for (var r = this.summary.length-1; r >= 0; r--) { + if (this.summary[r].recid == arguments[a]) { this.summary.splice(r, 1); removed++; } + } + } + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (!url) { + this.localSort(false, true); + this.localSearch(); + } + this.refresh(); + return removed; + }, + + addColumn: function (before, columns) { + var added = 0; + if (arguments.length == 1) { + columns = before; + before = this.columns.length; + } else { + if (typeof before == 'string') before = this.getColumn(before, true); + if (before == null) before = this.columns.length; + } + if (!$.isArray(columns)) columns = [columns]; + for (var i = 0; i < columns.length; i++) { + this.columns.splice(before, 0, columns[i]); + // if column is searchable, add search field + if (columns[i].searchable) { + var stype = columns[i].searchable; + var attr = ''; + if (columns[i].searchable === true) { stype = 'text'; attr = 'size="20"'; } + this.addSearch({ field: columns[i].field, label: columns[i].label, type: stype, attr: attr }); + } + before++; + added++; + } + this.refresh(); + return added; + }, + + removeColumn: function () { + var removed = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.columns.length-1; r >= 0; r--) { + if (this.columns[r].field == arguments[a]) { + if (this.columns[r].searchable) this.removeSearch(arguments[a]); + this.columns.splice(r, 1); + removed++; + } + } + } + this.refresh(); + return removed; + }, + + getColumn: function (field, returnIndex) { + // no arguments - return fields of all columns + if (arguments.length === 0) { + var ret = []; + for (var i = 0; i < this.columns.length; i++) ret.push(this.columns[i].field); + return ret; + } + // find column + for (var i = 0; i < this.columns.length; i++) { + if (this.columns[i].field == field) { + if (returnIndex === true) return i; else return this.columns[i]; + } + } + return null; + }, + + updateColumn: function (names, updates) { + var obj = this; + var effected = 0; + names = (Array.isArray(names) ? names : [names]); + names.forEach(function (colName) { + obj.columns.forEach(function (col) { + if (col.field == colName) { + var _updates = $.extend(true, {}, updates); + Object.keys(_updates).forEach(function (key) { + // if it is a function + if (typeof _updates[key] == 'function') { + _updates[key] = _updates[key](col); + } + if (col[key] != _updates[key]) effected++ + }) + $.extend(true, col, _updates) + } + }) + }); + if (effected > 0) { + this.refresh(); // need full refresh due to colgroups not resiging properly + } + return effected; + }, + + toggleColumn: function () { + return this.updateColumn(Array.from(arguments), { hidden: function (col) { return !col.hidden } }); + }, + + showColumn: function () { + return this.updateColumn(Array.from(arguments), { hidden: false }); + }, + + hideColumn: function () { + return this.updateColumn(Array.from(arguments), { hidden: true }); + }, + + addSearch: function (before, search) { + var added = 0; + if (arguments.length == 1) { + search = before; + before = this.searches.length; + } else { + if (typeof before == 'string') before = this.getSearch(before, true); + if (before == null) before = this.searches.length; + } + if (!$.isArray(search)) search = [search]; + for (var i = 0; i < search.length; i++) { + this.searches.splice(before, 0, search[i]); + before++; + added++; + } + this.searchClose(); + return added; + }, + + removeSearch: function () { + var removed = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a]) { this.searches.splice(r, 1); removed++; } + } + } + this.searchClose(); + return removed; + }, + + getSearch: function (field, returnIndex) { + // no arguments - return fields of all searches + if (arguments.length === 0) { + var ret = []; + for (var i = 0; i < this.searches.length; i++) ret.push(this.searches[i].field); + return ret; + } + // find search + for (var i = 0; i < this.searches.length; i++) { + if (this.searches[i].field == field) { + if (returnIndex === true) return i; else return this.searches[i]; + } + } + return null; + }, + + toggleSearch: function () { + var effected = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a]) { + this.searches[r].hidden = !this.searches[r].hidden; + effected++; + } + } + } + this.searchClose(); + return effected; + }, + + showSearch: function () { + var shown = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a] && this.searches[r].hidden !== false) { + this.searches[r].hidden = false; + shown++; + } + } + } + this.searchClose(); + return shown; + }, + + hideSearch: function () { + var hidden = 0; + for (var a = 0; a < arguments.length; a++) { + for (var r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a] && this.searches[r].hidden !== true) { + this.searches[r].hidden = true; + hidden++; + } + } + } + this.searchClose(); + return hidden; + }, + + getSearchData: function (field) { + for (var i = 0; i < this.searchData.length; i++) { + if (this.searchData[i].field == field) return this.searchData[i]; + } + return null; + }, + + localSort: function (silent, noResetRefresh) { + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (url) { + console.log('ERROR: grid.localSort can only be used on local data source, grid.url should be empty.'); + return; + } + if ($.isEmptyObject(this.sortData)) return; + var time = (new Date()).getTime(); + var obj = this; + // process date fields + obj.selectionSave(); + obj.prepareData(); + if (!noResetRefresh) { + obj.reset(); + } + // process sortData + for (var i = 0; i < this.sortData.length; i++) { + var column = this.getColumn(this.sortData[i].field); + if (!column) return; + if (typeof column.render == 'string') { + if (['date', 'age'].indexOf(column.render.split(':')[0]) != -1) { + this.sortData[i]['field_'] = column.field + '_'; + } + if (['time'].indexOf(column.render.split(':')[0]) != -1) { + this.sortData[i]['field_'] = column.field + '_'; + } + } + } + + // prepare paths and process sort + preparePaths(); + this.records.sort(function (a, b) { + return compareRecordPaths(a, b); + }); + cleanupPaths(); + + obj.selectionRestore(noResetRefresh); + time = (new Date()).getTime() - time; + if (silent !== true && obj.show.statusSort) { + setTimeout(function () { + obj.status(w2utils.lang('Sorting took') + ' ' + time/1000 + ' ' + w2utils.lang('sec')); + }, 10); + } + return time; + + // grab paths before sorting for efficiency and because calling obj.get() + // while sorting 'obj.records' is unsafe, at least on webkit + function preparePaths() { + for (var i = 0; i < obj.records.length; i++) { + var rec = obj.records[i]; + if (rec.w2ui && rec.w2ui.parent_recid != null) { + rec.w2ui._path = getRecordPath(rec); + } + } + } + + // cleanup and release memory allocated by preparePaths() + function cleanupPaths() { + for (var i = 0; i < obj.records.length; i++) { + var rec = obj.records[i]; + if (rec.w2ui && rec.w2ui.parent_recid != null) { + rec.w2ui._path = null; + } + } + } + + // compare two paths, from root of tree to given records + function compareRecordPaths(a, b) { + if ((!a.w2ui || a.w2ui.parent_recid == null) && (!b.w2ui || b.w2ui.parent_recid == null)) { + return compareRecords(a, b); // no tree, fast path + } + var pa = getRecordPath(a); + var pb = getRecordPath(b); + for (var i = 0; i < Math.min(pa.length, pb.length); i++) { + var diff = compareRecords(pa[i], pb[i]); + if (diff !== 0) return diff; // different subpath + } + if (pa.length > pb.length) return 1; + if (pa.length < pb.length) return -1; + console.log('ERROR: two paths should not be equal.'); + return 0; + } + + // return an array of all records from root to and including 'rec' + function getRecordPath(rec) { + if (!rec.w2ui || rec.w2ui.parent_recid == null) return [rec]; + if (rec.w2ui._path) + return rec.w2ui._path; + // during actual sort, we should never reach this point + var subrec = obj.get(rec.w2ui.parent_recid); + if (!subrec) { + console.log('ERROR: no parent record: '+rec.w2ui.parent_recid); + return [rec]; + } + return (getRecordPath(subrec).concat(rec)); + } + + // compare two records according to sortData and finally recid + function compareRecords(a, b) { + if (a === b) return 0; // optimize, same object + for (var i = 0; i < obj.sortData.length; i++) { + var fld = obj.sortData[i].field; + var sortFld = (obj.sortData[i].field_) ? obj.sortData[i].field_ : fld; + var aa = a[sortFld]; + var bb = b[sortFld]; + if (String(fld).indexOf('.') != -1) { + aa = obj.parseField(a, sortFld); + bb = obj.parseField(b, sortFld); + } + var col = obj.getColumn(fld); + if (col && col.editable != null) { // for drop editable fields and drop downs + if ($.isPlainObject(aa) && aa.text) aa = aa.text; + if ($.isPlainObject(bb) && bb.text) bb = bb.text; + } + var ret = compareCells(aa, bb, i, obj.sortData[i].direction, col.sortMode || 'default'); + if (ret !== 0) return ret; + } + // break tie for similar records, + // required to have consistent ordering for tree paths + var ret = compareCells(a.recid, b.recid, -1, 'asc'); + return ret; + } + + // compare two values, aa and bb, producing consistent ordering + function compareCells(aa, bb, i, direction, sortMode) { + // if both objects are strictly equal, we're done + if (aa === bb) + return 0; + // all nulls, empty and undefined on bottom + if ((aa == null || aa === "") && (bb != null && bb !== "")) + return 1; + if ((aa != null && aa !== "") && (bb == null || bb === "")) + return -1; + var dir = (direction.toLowerCase() === 'asc') ? 1 : -1; + // for different kind of objects, sort by object type + if (typeof aa != typeof bb) + return (typeof aa > typeof bb) ? dir : -dir; + // for different kind of classes, sort by classes + if (aa.constructor.name != bb.constructor.name) + return (aa.constructor.name > bb.constructor.name) ? dir : -dir; + // if we're dealing with non-null objects, call valueOf(). + // this mean that Date() or custom objects will compare properly. + if (aa && typeof aa == 'object') + aa = aa.valueOf(); + if (bb && typeof bb == 'object') + bb = bb.valueOf(); + // if we're still dealing with non-null objects that have + // a useful Object => String conversion, convert to string. + var defaultToString = {}.toString; + if (aa && typeof aa == 'object' && aa.toString != defaultToString) + aa = String(aa); + if (bb && typeof bb == 'object' && bb.toString != defaultToString) + bb = String(bb); + // do case-insensitive string comparison + if (typeof aa == 'string') + aa = $.trim(aa.toLowerCase()); + if (typeof bb == 'string') + bb = $.trim(bb.toLowerCase()); + + switch (sortMode) { + case 'natural': + sortMode = w2utils.naturalCompare; + break; + } + + if (typeof sortMode == 'function') { + return sortMode(aa,bb) * dir; + } + + // compare both objects + if (aa > bb) + return dir; + if (aa < bb) + return -dir; + return 0; + } + }, + + localSearch: function (silent) { + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (url) { + console.log('ERROR: grid.localSearch can only be used on local data source, grid.url should be empty.'); + return; + } + var time = (new Date()).getTime(); + var obj = this; + var defaultToString = {}.toString; + var duplicateMap = {}; + this.total = this.records.length; + // mark all records as shown + this.last.searchIds = []; + // prepare date/time fields + this.prepareData(); + // hide records that did not match + if (this.searchData.length > 0 && !url) { + this.total = 0; + for (var i = 0; i < this.records.length; i++) { + var rec = this.records[i]; + var match = searchRecord(rec); + if (match) { + if (rec && rec.w2ui) addParent(rec.w2ui.parent_recid); + if (this.showExtraOnSearch > 0) { + var before = this.showExtraOnSearch; + var after = this.showExtraOnSearch; + if (i < before) before = i; + if (i + after > this.records.length) after = this.records.length - i; + if (before > 0) { + for (var j = i - before; j < i; j++) { + if (this.last.searchIds.indexOf(j) < 0) + this.last.searchIds.push(j); + } + } + if (this.last.searchIds.indexOf(i) < 0) this.last.searchIds.push(i); + if (after > 0) { + for (var j = (i + 1) ; j <= (i + after ) ; j++) { + if (this.last.searchIds.indexOf(j) < 0) this.last.searchIds.push(j); + } + } + } else { + this.last.searchIds.push(i); + } + } + } + this.total = this.last.searchIds.length; + } + time = (new Date()).getTime() - time; + if (silent !== true && obj.show.statusSearch) { + setTimeout(function () { + obj.status(w2utils.lang('Search took') + ' ' + time/1000 + ' ' + w2utils.lang('sec')); + }, 10); + } + return time; + + // check if a record (or one of its closed children) matches the search data + function searchRecord(rec) { + var fl = 0; + var orEqual = false; + for (var j = 0; j < obj.searchData.length; j++) { + var sdata = obj.searchData[j]; + var search = obj.getSearch(sdata.field); + if (sdata == null) continue; + if (search == null) search = { field: sdata.field, type: sdata.type }; + var val1b = obj.parseField(rec, search.field); + var val1 = (val1b !== null && val1b !== undefined && + (typeof val1b != "object" || val1b.toString != defaultToString)) ? + String(val1b).toLowerCase() : ""; // do not match a bogus string + if (sdata.value != null) { + if (!$.isArray(sdata.value)) { + var val2 = String(sdata.value).toLowerCase(); + } else { + var val2 = sdata.value[0]; + var val3 = sdata.value[1]; + } + } + switch (sdata.operator) { + case '=': + case 'is': + if (obj.parseField(rec, search.field) == sdata.value) fl++; // do not hide record + else if (search.type == 'date') { + var tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)); + var val1 = w2utils.formatDate(tmp, 'yyyy-mm-dd'); + var val2 = w2utils.formatDate(w2utils.isDate(val2, w2utils.settings.dateFormat, true), 'yyyy-mm-dd'); + if (val1 == val2) fl++; + } + else if (search.type == 'time') { + var tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)); + var val1 = w2utils.formatTime(tmp, 'hh24:mi'); + var val2 = w2utils.formatTime(val2, 'hh24:mi'); + if (val1 == val2) fl++; + } + else if (search.type == 'datetime') { + var tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)); + var val1 = w2utils.formatDateTime(tmp, 'yyyy-mm-dd|hh24:mm:ss'); + var val2 = w2utils.formatDateTime(w2utils.isDateTime(val2, w2utils.settings.datetimeFormat, true), 'yyyy-mm-dd|hh24:mm:ss'); + if (val1 == val2) fl++; + } + break; + case 'between': + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + if (parseFloat(obj.parseField(rec, search.field)) >= parseFloat(val2) && parseFloat(obj.parseField(rec, search.field)) <= parseFloat(val3)) fl++; + } + else if (search.type == 'date') { + var tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)); + var val1 = w2utils.isDate(tmp, w2utils.settings.dateFormat, true); + var val2 = w2utils.isDate(val2, w2utils.settings.dateFormat, true); + var val3 = w2utils.isDate(val3, w2utils.settings.dateFormat, true); + if (val3 != null) val3 = new Date(val3.getTime() + 86400000); // 1 day + if (val1 >= val2 && val1 < val3) fl++; + } + else if (search.type == 'time') { + var val1 = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)); + var val2 = w2utils.isTime(val2, true); + var val3 = w2utils.isTime(val3, true); + val2 = (new Date()).setHours(val2.hours, val2.minutes, val2.seconds ? val2.seconds : 0, 0); + val3 = (new Date()).setHours(val3.hours, val3.minutes, val3.seconds ? val3.seconds : 0, 0); + if (val1 >= val2 && val1 < val3) fl++; + } + else if (search.type == 'datetime') { + var val1 = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)); + var val2 = w2utils.isDateTime(val2, w2utils.settings.datetimeFormat, true); + var val3 = w2utils.isDateTime(val3, w2utils.settings.datetimeFormat, true); + if (val3) val3 = new Date(val3.getTime() + 86400000); // 1 day + if (val1 >= val2 && val1 < val3) fl++; + } + break; + case '<=': + orEqual = true + case '<': + case 'less': + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + var val1 = parseFloat(obj.parseField(rec, search.field)); + var val2 = parseFloat(sdata.value); + if (val1 < val2 || (orEqual && val1 === val2)) fl++; + } + else if (search.type == 'date') { + var tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)); + var val1 = w2utils.isDate(tmp, w2utils.settings.dateFormat, true); + var val2 = w2utils.isDate(val2, w2utils.settings.dateFormat, true); + if (val1 < val2 || (orEqual && val1 === val2)) fl++; + } + else if (search.type == 'time') { + var tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)); + var val1 = w2utils.formatTime(tmp, 'hh24:mi'); + var val2 = w2utils.formatTime(val2, 'hh24:mi'); + if (val1 < val2 || (orEqual && val1 === val2)) fl++; + } + else if (search.type == 'datetime') { + var tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)); + var val1 = w2utils.formatDateTime(tmp, 'yyyy-mm-dd|hh24:mm:ss'); + var val2 = w2utils.formatDateTime(w2utils.isDateTime(val2, w2utils.settings.datetimeFormat, true), 'yyyy-mm-dd|hh24:mm:ss'); + if (val1.length == val2.length && (val1 < val2 || (orEqual && val1 === val2))) fl++; + } + break; + case '>=': + orEqual = true + case '>': + case 'more': + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + var val1 = parseFloat(obj.parseField(rec, search.field)); + var val2 = parseFloat(sdata.value); + if (val1 > val2 || (orEqual && val1 === val2)) fl++; + } + else if (search.type == 'date') { + var tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)); + var val1 = w2utils.isDate(tmp, w2utils.settings.dateFormat, true); + var val2 = w2utils.isDate(val2, w2utils.settings.dateFormat, true); + if (val1 > val2 || (orEqual && val1 === val2)) fl++; + } + else if (search.type == 'time') { + var tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)); + var val1 = w2utils.formatTime(tmp, 'hh24:mi'); + var val2 = w2utils.formatTime(val2, 'hh24:mi'); + if (val1 > val2 || (orEqual && val1 === val2)) fl++; + } + else if (search.type == 'datetime') { + var tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)); + var val1 = w2utils.formatDateTime(tmp, 'yyyy-mm-dd|hh24:mm:ss'); + var val2 = w2utils.formatDateTime(w2utils.isDateTime(val2, w2utils.settings.datetimeFormat, true), 'yyyy-mm-dd|hh24:mm:ss'); + if (val1.length == val2.length && (val1 > val2 || (orEqual && val1 === val2))) fl++; + } + break; + case 'in': + var tmp = sdata.value; + if (sdata.svalue) tmp = sdata.svalue; + if ((tmp.indexOf(w2utils.isFloat(val1b) ? parseFloat(val1b) : val1b) !== -1) || tmp.indexOf(val1) !== -1) fl++; + break; + case 'not in': + var tmp = sdata.value; + if (sdata.svalue) tmp = sdata.svalue; + if (!((tmp.indexOf(w2utils.isFloat(val1b) ? parseFloat(val1b) : val1b) !== -1) || tmp.indexOf(val1) !== -1)) fl++; + break; + case 'begins': + case 'begins with': // need for back compatib. + if (val1.indexOf(val2) === 0) fl++; // do not hide record + break; + case 'contains': + if (val1.indexOf(val2) >= 0) fl++; // do not hide record + break; + case 'null': + if (obj.parseField(rec, search.field) == null) fl++; // do not hide record + break; + case 'not null': + if (obj.parseField(rec, search.field) != null) fl++; // do not hide record + break; + case 'ends': + case 'ends with': // need for back compatib. + var lastIndex = val1.lastIndexOf(val2); + if (lastIndex !== -1 && lastIndex == val1.length - val2.length) fl++; // do not hide record + break; + } + } + if ((obj.last.logic == 'OR' && fl !== 0) || + (obj.last.logic == 'AND' && fl == obj.searchData.length)) + return true; + if (rec.w2ui && rec.w2ui.children && rec.w2ui.expanded !== true) { + // there are closed children, search them too. + for (var r = 0; r < rec.w2ui.children.length; r++) { + var subRec = rec.w2ui.children[r]; + if (searchRecord(subRec)) + return true; + } + } + return false; + } + + // add parents nodes recursively + function addParent(recid) { + if (recid === undefined) + return; + if (duplicateMap[recid]) + return; // already visited + duplicateMap[recid] = true; + var i = obj.get(recid, true); + if (i == null) + return; + if ($.inArray(i, obj.last.searchIds) != -1) + return; + var rec = obj.records[i]; + if (rec && rec.w2ui) + addParent(rec.w2ui.parent_recid); + obj.last.searchIds.push(i); + } + }, + + getRangeData: function (range, extra) { + var rec1 = this.get(range[0].recid, true); + var rec2 = this.get(range[1].recid, true); + var col1 = range[0].column; + var col2 = range[1].column; + + var res = []; + if (col1 == col2) { // one row + for (var r = rec1; r <= rec2; r++) { + var record = this.records[r]; + var dt = record[this.columns[col1].field] || null; + if (extra !== true) { + res.push(dt); + } else { + res.push({ data: dt, column: col1, index: r, record: record }); + } + } + } else if (rec1 == rec2) { // one line + var record = this.records[rec1]; + for (var i = col1; i <= col2; i++) { + var dt = record[this.columns[i].field] || null; + if (extra !== true) { + res.push(dt); + } else { + res.push({ data: dt, column: i, index: rec1, record: record }); + } + } + } else { + for (var r = rec1; r <= rec2; r++) { + var record = this.records[r]; + res.push([]); + for (var i = col1; i <= col2; i++) { + var dt = record[this.columns[i].field]; + if (extra !== true) { + res[res.length-1].push(dt); + } else { + res[res.length-1].push({ data: dt, column: i, index: r, record: record }); + } + } + } + } + return res; + }, + + addRange: function (ranges) { + var added = 0; + if (this.selectType == 'row') return added; + if (!$.isArray(ranges)) ranges = [ranges]; + // if it is selection + for (var i = 0; i < ranges.length; i++) { + if (typeof ranges[i] != 'object') ranges[i] = { name: 'selection' }; + if (ranges[i].name == 'selection') { + if (this.show.selectionBorder === false) continue; + var sel = this.getSelection(); + if (sel.length === 0) { + this.removeRange('selection'); + continue; + } else { + var first = sel[0]; + var last = sel[sel.length-1]; + } + } else { // other range + var first = ranges[i].range[0]; + var last = ranges[i].range[1]; + } + if (first) { + var rg = { + name: ranges[i].name, + range: [{ recid: first.recid, column: first.column }, { recid: last.recid, column: last.column }], + style: ranges[i].style || '' + }; + // add range + var ind = false; + for (var j = 0; j < this.ranges.length; j++) if (this.ranges[j].name == ranges[i].name) { ind = j; break; } + if (ind !== false) { + this.ranges[ind] = rg; + } else { + this.ranges.push(rg); + } + added++; + } + } + this.refreshRanges(); + return added; + }, + + removeRange: function () { + var removed = 0; + for (var a = 0; a < arguments.length; a++) { + var name = arguments[a]; + $('#grid_'+ this.name +'_'+ name).remove(); + $('#grid_'+ this.name +'_f'+ name).remove(); + for (var r = this.ranges.length-1; r >= 0; r--) { + if (this.ranges[r].name == name) { + this.ranges.splice(r, 1); + removed++; + } + } + } + return removed; + }, + + refreshRanges: function () { + if (this.ranges.length === 0) return; + var obj = this; + var time = (new Date()).getTime(); + var rec1 = $('#grid_'+ this.name +'_frecords'); + var rec2 = $('#grid_'+ this.name +'_records'); + for (var i = 0; i < this.ranges.length; i++) { + var rg = this.ranges[i]; + var first = rg.range[0]; + var last = rg.range[1]; + if (first.index == null) first.index = this.get(first.recid, true); + if (last.index == null) last.index = this.get(last.recid, true); + var td1 = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(first.recid) + ' td[col="'+ first.column +'"]'); + var td2 = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(last.recid) + ' td[col="'+ last.column +'"]'); + var td1f = $('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(first.recid) + ' td[col="'+ first.column +'"]'); + var td2f = $('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(last.recid) + ' td[col="'+ last.column +'"]'); + var _lastColumn = last.column; + // adjustment due to column virtual scroll + if (first.column < this.last.colStart && last.column > this.last.colStart) { + td1 = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(first.recid) + ' td[col="start"]'); + } + if (first.column < this.last.colEnd && last.column > this.last.colEnd) { + _lastColumn = '"end"'; + td2 = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(last.recid) + ' td[col="end"]'); + } + // if virtual scrolling kicked in + var index_top = parseInt($('#grid_'+ this.name +'_rec_top').next().attr('index')); + var index_bottom = parseInt($('#grid_'+ this.name +'_rec_bottom').prev().attr('index')); + var index_ftop = parseInt($('#grid_'+ this.name +'_frec_top').next().attr('index')); + var index_fbottom = parseInt($('#grid_'+ this.name +'_frec_bottom').prev().attr('index')); + if (td1.length === 0 && first.index < index_top && last.index > index_top) { + td1 = $('#grid_'+ this.name +'_rec_top').next().find('td[col='+ first.column +']'); + } + if (td2.length === 0 && last.index > index_bottom && first.index < index_bottom) { + td2 = $('#grid_'+ this.name +'_rec_bottom').prev().find('td[col='+ _lastColumn +']'); + } + if (td1f.length === 0 && first.index < index_ftop && last.index > index_ftop) { // frozen + td1f = $('#grid_'+ this.name +'_frec_top').next().find('td[col='+ first.column +']'); + } + if (td2f.length === 0 && last.index > index_fbottom && first.index < index_fbottom) { // frozen + td2f = $('#grid_'+ this.name +'_frec_bottom').prev().find('td[col='+ last.column +']'); + } + + // do not show selection cell if it is editable + var edit = $(this.box).find('#grid_'+ this.name + '_editable'); + var tmp = edit.find('.w2ui-input'); + var tmp1 = tmp.attr('recid'); + var tmp2 = tmp.attr('column'); + if (rg.name == 'selection' && rg.range[0].recid == tmp1 && rg.range[0].column == tmp2) continue; + + // frozen regular columns range + var $range = $('#grid_'+ this.name +'_f'+ rg.name); + if (td1f.length > 0 || td2f.length > 0) { + if ($range.length === 0) { + rec1.append('
'+ + (rg.name == 'selection' ? '
' : '')+ + '
'); + $range = $('#grid_'+ this.name +'_f'+ rg.name); + } else { + $range.attr('style', rg.style); + $range.find('.w2ui-selection-resizer').show(); + } + if (td2f.length === 0) { + td2f = $('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(last.recid) +' td:last-child'); + if (td2f.length === 0) td2f = $('#grid_'+ this.name +'_frec_bottom td:first-child'); + $range.css('border-right', '0px'); + $range.find('.w2ui-selection-resizer').hide(); + } + if (first.recid != null && last.recid != null && td1f.length > 0 && td2f.length > 0) { + var _left = (td1f.position().left - 0 + rec1.scrollLeft()); + var _top = (td1f.position().top - 0 + rec1.scrollTop()); + $range.show().css({ + left : (_left > 0 ? _left : 0) + 'px', + top : (_top > 0 ? _top : 0) + 'px', + width : (td2f.position().left - td1f.position().left + td2f.width() + 2) + 'px', + height : (td2f.position().top - td1f.position().top + td2f.height() + 1) + 'px' + }); + } else { + $range.hide(); + } + } else { + $range.hide(); + } + // regular columns range + var $range = $('#grid_'+ this.name +'_'+ rg.name); + if (td1.length > 0 || td2.length > 0) { + if ($range.length === 0) { + rec2.append('
'+ + (rg.name == 'selection' ? '
' : '')+ + '
'); + $range = $('#grid_'+ this.name +'_'+ rg.name); + } else { + $range.attr('style', rg.style); + } + if (td1.length === 0) { + td1 = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(first.recid) +' td:first-child'); + if (td1.length === 0) td1 = $('#grid_'+ this.name +'_rec_top td:first-child'); + } + if (td2f.length !== 0) { + $range.css('border-left', '0px'); + } + if (first.recid != null && last.recid != null && td1.length > 0 && td2.length > 0) { + var _left = (td1.position().left - 0 + rec2.scrollLeft()); + var _top = (td1.position().top - 0 + rec2.scrollTop()); + $range.show().css({ + left : (_left > 0 ? _left : 0) + 'px', + top : (_top > 0 ? _top : 0) + 'px', + width : (td2.position().left - td1.position().left + td2.width() + 2) + 'px', + height : (td2.position().top - td1.position().top + td2.height() + 1) + 'px' + }); + } else { + $range.hide(); + } + } else { + $range.hide(); + } + } + + // add resizer events + $(this.box).find('.w2ui-selection-resizer') + .off('mousedown').on('mousedown', mouseStart) + .off('dblclick').on('dblclick', function (event) { + var edata = obj.trigger({ phase: 'before', type: 'resizerDblClick', target: obj.name, originalEvent: event }); + if (edata.isCancelled === true) return; + obj.trigger($.extend(edata, { phase: 'after' })); + }); + var edata = { phase: 'before', type: 'selectionExtend', target: obj.name, originalRange: null, newRange: null }; + + return (new Date()).getTime() - time; + + function mouseStart (event) { + var sel = obj.getSelection(); + obj.last.move = { + type : 'expand', + x : event.screenX, + y : event.screenY, + divX : 0, + divY : 0, + recid : sel[0].recid, + column : sel[0].column, + originalRange : [{ recid: sel[0].recid, column: sel[0].column }, { recid: sel[sel.length-1].recid, column: sel[sel.length-1].column }], + newRange : [{ recid: sel[0].recid, column: sel[0].column }, { recid: sel[sel.length-1].recid, column: sel[sel.length-1].column }] + }; + $(document) + .off('.w2ui-' + obj.name) + .on('mousemove.w2ui-' + obj.name, mouseMove) + .on('mouseup.w2ui-' + obj.name, mouseStop); + // do not blur grid + event.preventDefault(); + } + + function mouseMove (event) { + var mv = obj.last.move; + if (!mv || mv.type != 'expand') return; + mv.divX = (event.screenX - mv.x); + mv.divY = (event.screenY - mv.y); + // find new cell + var recid, column; + var tmp = event.originalEvent.target; + if (tmp.tagName.toUpperCase() != 'TD') tmp = $(tmp).parents('td')[0]; + if ($(tmp).attr('col') != null) column = parseInt($(tmp).attr('col')); + tmp = $(tmp).parents('tr')[0]; + recid = $(tmp).attr('recid'); + // new range + if (mv.newRange[1].recid == recid && mv.newRange[1].column == column) return; + var prevNewRange = $.extend({}, mv.newRange); + mv.newRange = [{ recid: mv.recid, column: mv.column }, { recid: recid, column: column }]; + // event before + edata = obj.trigger($.extend(edata, { originalRange: mv.originalRange, newRange : mv.newRange })); + if (edata.isCancelled === true) { + mv.newRange = prevNewRange; + edata.newRange = prevNewRange; + return; + } else { + // default behavior + obj.removeRange('grid-selection-expand'); + obj.addRange({ + name : 'grid-selection-expand', + range : edata.newRange, + style : 'background-color: rgba(100,100,100,0.1); border: 2px dotted rgba(100,100,100,0.5);' + }); + } + } + + function mouseStop (event) { + // default behavior + obj.removeRange('grid-selection-expand'); + delete obj.last.move; + $(document).off('.w2ui-' + obj.name); + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + } + }, + + select: function () { + if (arguments.length === 0) return 0; + var time = (new Date).getTime(); + var selected = 0; + var sel = this.last.selection; + if (!this.multiSelect) this.selectNone(); + // if too manu arguments > 150k, then it errors off + var args = Array.prototype.slice.call(arguments) + if (Array.isArray(args[0])) args = args[0] + // event before + var tmp = { phase: 'before', type: 'select', target: this.name }; + if (args.length == 1) { + tmp.multiple = false; + if ($.isPlainObject(args[0])) { + tmp.recid = args[0].recid; + tmp.column = args[0].column; + } else { + tmp.recid = args[0]; + } + } else { + tmp.multiple = true; + tmp.recids = args; + } + var edata = this.trigger(tmp); + if (edata.isCancelled === true) return 0; + + // default action + if (this.selectType == 'row') { + for (var a = 0; a < args.length; a++) { + var recid = typeof args[a] == 'object' ? args[a].recid : args[a]; + var index = this.get(recid, true); + if (index == null) continue; + var recEl1 = null; + var recEl2 = null; + if (this.searchData.length !== 0 || (index + 1 >= this.last.range_start && index + 1 <= this.last.range_end)) { + recEl1 = $('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)); + recEl2 = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)); + } + if (this.selectType == 'row') { + if (sel.indexes.indexOf(index) != -1) continue; + sel.indexes.push(index); + if (recEl1 && recEl2) { + recEl1.addClass('w2ui-selected').data('selected', 'yes').find('.w2ui-col-number').addClass('w2ui-row-selected'); + recEl2.addClass('w2ui-selected').data('selected', 'yes').find('.w2ui-col-number').addClass('w2ui-row-selected'); + recEl1.find('.w2ui-grid-select-check').prop("checked", true); + } + selected++; + } + } + } else { + // normalize for performance + var new_sel = {}; + for (var a = 0; a < args.length; a++) { + var recid = typeof args[a] == 'object' ? args[a].recid : args[a]; + var column = typeof args[a] == 'object' ? args[a].column : null; + new_sel[recid] = new_sel[recid] || []; + if ($.isArray(column)) { + new_sel[recid] = column; + } else if (w2utils.isInt(column)) { + new_sel[recid].push(column); + } else { + for (var i = 0; i < this.columns.length; i++) { if (this.columns[i].hidden) continue; new_sel[recid].push(parseInt(i)); } + } + } + // add all + var col_sel = []; + for (var recid in new_sel) { + var index = this.get(recid, true); + if (index == null) continue; + var recEl1 = null; + var recEl2 = null; + if (index + 1 >= this.last.range_start && index + 1 <= this.last.range_end) { + recEl1 = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)); + recEl2 = $('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)); + } + var s = sel.columns[index] || []; + // default action + if (sel.indexes.indexOf(index) == -1) { + sel.indexes.push(index); + } + // anly only those that are new + for (var t = 0; t < new_sel[recid].length; t++) { + if (s.indexOf(new_sel[recid][t]) == -1) s.push(new_sel[recid][t]); + } + s.sort(function(a, b) { return a-b; }); // sort function must be for numerical sort + for (var t = 0; t < new_sel[recid].length; t++) { + var col = new_sel[recid][t]; + if (col_sel.indexOf(col) == -1) col_sel.push(col); + if (recEl1) { + recEl1.find('#grid_'+ this.name +'_data_'+ index +'_'+ col).addClass('w2ui-selected'); + recEl1.find('.w2ui-col-number').addClass('w2ui-row-selected'); + recEl1.data('selected', 'yes'); + recEl1.find('.w2ui-grid-select-check').prop("checked", true); + } + if (recEl2) { + recEl2.find('#grid_'+ this.name +'_data_'+ index +'_'+ col).addClass('w2ui-selected'); + recEl2.find('.w2ui-col-number').addClass('w2ui-row-selected'); + recEl2.data('selected', 'yes'); + recEl2.find('.w2ui-grid-select-check').prop("checked", true); + } + selected++; + } + // save back to selection object + sel.columns[index] = s; + } + // select columns (need here for speed) + for (var c = 0; c < col_sel.length; c++) { + $(this.box).find('#grid_'+ this.name +'_column_'+ col_sel[c] +' .w2ui-col-header').addClass('w2ui-col-selected'); + } + } + // need to sort new selection for speed + sel.indexes.sort(function(a, b) { return a-b; }); + // all selected? + var areAllSelected = (this.records.length > 0 && sel.indexes.length == this.records.length), + areAllSearchedSelected = (sel.indexes.length > 0 && this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length); + if (areAllSelected || areAllSearchedSelected) { + $('#grid_'+ this.name +'_check_all').prop('checked', true); + } else { + $('#grid_'+ this.name +'_check_all').prop('checked', false); + } + this.status(); + this.addRange('selection'); + this.updateToolbar(sel, areAllSelected); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + return selected; + }, + + unselect: function () { + var unselected = 0; + var sel = this.last.selection; + // if too manu arguments > 150k, then it errors off + var args = Array.prototype.slice.call(arguments) + if (Array.isArray(args[0])) args = args[0] + // event before + var tmp = { phase: 'before', type: 'unselect', target: this.name }; + if (args.length == 1) { + tmp.multiple = false; + if ($.isPlainObject(args[0])) { + tmp.recid = args[0].recid; + tmp.column = args[0].column; + } else { + tmp.recid = args[0]; + } + } else { + tmp.multiple = true; + tmp.recids = args + } + var edata = this.trigger(tmp); + if (edata.isCancelled === true) return 0; + + for (var a = 0; a < args.length; a++) { + var recid = typeof args[a] == 'object' ? args[a].recid : args[a]; + var record = this.get(recid); + if (record == null) continue; + var index = this.get(record.recid, true); + var recEl1 = $('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)); + var recEl2 = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)); + if (this.selectType == 'row') { + if (sel.indexes.indexOf(index) == -1) continue; + // default action + sel.indexes.splice(sel.indexes.indexOf(index), 1); + recEl1.removeClass('w2ui-selected w2ui-inactive').removeData('selected').find('.w2ui-col-number').removeClass('w2ui-row-selected'); + recEl2.removeClass('w2ui-selected w2ui-inactive').removeData('selected').find('.w2ui-col-number').removeClass('w2ui-row-selected'); + if (recEl1.length != 0) { + recEl1[0].style.cssText = 'height: '+ this.recordHeight +'px; ' + recEl1.attr('custom_style'); + recEl2[0].style.cssText = 'height: '+ this.recordHeight +'px; ' + recEl2.attr('custom_style'); + } + recEl1.find('.w2ui-grid-select-check').prop("checked", false); + unselected++; + } else { + var col = args[a].column; + if (!w2utils.isInt(col)) { // unselect all columns + var cols = []; + for (var i = 0; i < this.columns.length; i++) { if (this.columns[i].hidden) continue; cols.push({ recid: recid, column: i }); } + return this.unselect(cols); + } + var s = sel.columns[index]; + if (!$.isArray(s) || s.indexOf(col) == -1) continue; + // default action + s.splice(s.indexOf(col), 1); + $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)).find(' > td[col='+ col +']').removeClass('w2ui-selected w2ui-inactive'); + $('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)).find(' > td[col='+ col +']').removeClass('w2ui-selected w2ui-inactive'); + // check if any row/column still selected + var isColSelected = false; + var isRowSelected = false; + var tmp = this.getSelection(); + for (var i = 0; i < tmp.length; i++) { + if (tmp[i].column == col) isColSelected = true; + if (tmp[i].recid == recid) isRowSelected = true; + } + if (!isColSelected) { + $(this.box).find('.w2ui-grid-columns td[col='+ col +'] .w2ui-col-header, .w2ui-grid-fcolumns td[col='+ col +'] .w2ui-col-header').removeClass('w2ui-col-selected'); + } + if (!isRowSelected) { + $('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)).find('.w2ui-col-number').removeClass('w2ui-row-selected'); + } + unselected++; + if (s.length === 0) { + delete sel.columns[index]; + sel.indexes.splice(sel.indexes.indexOf(index), 1); + recEl1.removeData('selected'); + recEl1.find('.w2ui-grid-select-check').prop("checked", false); + recEl2.removeData('selected'); + } + } + } + // all selected? + var areAllSelected = (this.records.length > 0 && sel.indexes.length == this.records.length), + areAllSearchedSelected = (sel.indexes.length > 0 && this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length); + if (areAllSelected || areAllSearchedSelected) { + $('#grid_'+ this.name +'_check_all').prop('checked', true); + } else { + $('#grid_'+ this.name +'_check_all').prop('checked', false); + } + // show number of selected + this.status(); + this.addRange('selection'); + this.updateToolbar(sel, areAllSelected); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + return unselected; + }, + + selectAll: function () { + var time = (new Date()).getTime(); + if (this.multiSelect === false) return; + // event before + var edata = this.trigger({ phase: 'before', type: 'select', target: this.name, all: true }); + if (edata.isCancelled === true) return; + // default action + var url = (typeof this.url != 'object' ? this.url : this.url.get); + var sel = this.last.selection; + var cols = []; + for (var i = 0; i < this.columns.length; i++) cols.push(i); + // if local data source and searched + sel.indexes = []; + if (!url && this.searchData.length !== 0) { + // local search applied + for (var i = 0; i < this.last.searchIds.length; i++) { + sel.indexes.push(this.last.searchIds[i]); + if (this.selectType != 'row') sel.columns[this.last.searchIds[i]] = cols.slice(); // .slice makes copy of the array + } + } else { + var buffered = this.records.length; + if (this.searchData.length != 0 && !url) buffered = this.last.searchIds.length; + for (var i = 0; i < buffered; i++) { + sel.indexes.push(i); + if (this.selectType != 'row') sel.columns[i] = cols.slice(); // .slice makes copy of the array + } + } + // add selected class + if (this.selectType == 'row') { + $(this.box).find('.w2ui-grid-records tr').not('.w2ui-empty-record') + .addClass('w2ui-selected').data('selected', 'yes').find('.w2ui-col-number').addClass('w2ui-row-selected'); + $(this.box).find('.w2ui-grid-frecords tr').not('.w2ui-empty-record') + .addClass('w2ui-selected').data('selected', 'yes').find('.w2ui-col-number').addClass('w2ui-row-selected'); + $(this.box).find('input.w2ui-grid-select-check').prop('checked', true); + } else { + $(this.box).find('.w2ui-grid-columns td .w2ui-col-header, .w2ui-grid-fcolumns td .w2ui-col-header').addClass('w2ui-col-selected'); + $(this.box).find('.w2ui-grid-records tr .w2ui-col-number').addClass('w2ui-row-selected'); + $(this.box).find('.w2ui-grid-records tr').not('.w2ui-empty-record') + .find('.w2ui-grid-data').not('.w2ui-col-select').addClass('w2ui-selected').data('selected', 'yes'); + $(this.box).find('.w2ui-grid-frecords tr .w2ui-col-number').addClass('w2ui-row-selected'); + $(this.box).find('.w2ui-grid-frecords tr').not('.w2ui-empty-record') + .find('.w2ui-grid-data').not('.w2ui-col-select').addClass('w2ui-selected').data('selected', 'yes'); + $(this.box).find('input.w2ui-grid-select-check').prop('checked', true); + } + // enable/disable toolbar buttons + var sel = this.getSelection(true); + if (sel.length == 1) this.toolbar.enable('w2ui-edit'); else this.toolbar.disable('w2ui-edit'); + if (sel.length >= 1) this.toolbar.enable('w2ui-delete'); else this.toolbar.disable('w2ui-delete'); + this.addRange('selection'); + $('#grid_'+ this.name +'_check_all').prop('checked', true); + this.status(); + this.updateToolbar({ indexes: sel }, true); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + selectNone: function () { + var time = (new Date()).getTime(); + // event before + var edata = this.trigger({ phase: 'before', type: 'unselect', target: this.name, all: true }); + if (edata.isCancelled === true) return; + // default action + var sel = this.last.selection; + // remove selected class + if (this.selectType == 'row') { + $(this.box).find('.w2ui-grid-records tr.w2ui-selected').removeClass('w2ui-selected w2ui-inactive').removeData('selected') + .find('.w2ui-col-number').removeClass('w2ui-row-selected'); + $(this.box).find('.w2ui-grid-frecords tr.w2ui-selected').removeClass('w2ui-selected w2ui-inactive').removeData('selected') + .find('.w2ui-col-number').removeClass('w2ui-row-selected'); + $(this.box).find('input.w2ui-grid-select-check').prop('checked', false); + } else { + $(this.box).find('.w2ui-grid-columns td .w2ui-col-header, .w2ui-grid-fcolumns td .w2ui-col-header').removeClass('w2ui-col-selected'); + $(this.box).find('.w2ui-grid-records tr .w2ui-col-number').removeClass('w2ui-row-selected'); + $(this.box).find('.w2ui-grid-frecords tr .w2ui-col-number').removeClass('w2ui-row-selected'); + $(this.box).find('.w2ui-grid-data.w2ui-selected').removeClass('w2ui-selected w2ui-inactive').removeData('selected'); + $(this.box).find('input.w2ui-grid-select-check').prop('checked', false); + } + sel.indexes = []; + sel.columns = {}; + this.toolbar.disable('w2ui-edit', 'w2ui-delete'); + this.removeRange('selection'); + $('#grid_'+ this.name +'_check_all').prop('checked', false); + this.status(); + this.updateToolbar(sel, false); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + updateToolbar: function (sel, areAllSelected) { + var obj = this + var cnt = sel && sel.indexes ? sel.indexes.length : 0 + this.toolbar.items.forEach(function (item) { + _checkItem(item, '') + if (Array.isArray(item.items)) { + item.items.forEach(function (it) { + _checkItem(it, item.id + ':') + }) + } + }) + + function _checkItem(item, prefix) { + if (item.batch === 0) { + if (cnt > 0) obj.toolbar.enable(prefix + item.id); else obj.toolbar.disable(prefix + item.id) + } + if (item.batch != null && !isNaN(item.batch) && item.batch > 0) { + if (cnt == item.batch) obj.toolbar.enable(prefix + item.id); else obj.toolbar.disable(prefix + item.id) + } + if (typeof item.batch == 'function') { + item.batch(obj.selectType == 'cell' ? sel : (sel ? sel.indexes : null)) + } + } + }, + + getSelection: function (returnIndex) { + var ret = []; + var sel = this.last.selection; + if (this.selectType == 'row') { + for (var i = 0; i < sel.indexes.length; i++) { + if (!this.records[sel.indexes[i]]) continue; + if (returnIndex === true) ret.push(sel.indexes[i]); else ret.push(this.records[sel.indexes[i]].recid); + } + return ret; + } else { + for (var i = 0; i < sel.indexes.length; i++) { + var cols = sel.columns[sel.indexes[i]]; + if (!this.records[sel.indexes[i]]) continue; + for (var j = 0; j < cols.length; j++) { + ret.push({ recid: this.records[sel.indexes[i]].recid, index: parseInt(sel.indexes[i]), column: cols[j] }); + } + } + return ret; + } + }, + + search: function (field, value) { + var obj = this; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + var searchData = []; + var last_multi = this.last.multi; + var last_logic = this.last.logic; + var last_field = this.last.field; + var last_search = this.last.search; + var hasHiddenSearches = false; + // add hidden searches + for (var i = 0; i < this.searches.length; i++) { + if (!this.searches[i].hidden || this.searches[i].value == null) continue; + searchData.push({ + field : this.searches[i].field, + operator : this.searches[i].operator || 'is', + type : this.searches[i].type, + value : this.searches[i].value || '' + }); + hasHiddenSearches = true; + } + // 1: search() - advanced search (reads from popup) + if (arguments.length === 0) { + last_search = ''; + // advanced search + for (var i = 0; i < this.searches.length; i++) { + var search = this.searches[i]; + var operator = $('#grid_'+ this.name + '_operator_'+ i).val(); + var field1 = $('#grid_'+ this.name + '_field_'+ i); + var field2 = $('#grid_'+ this.name + '_field2_'+ i); + var value1 = field1.val(); + var value2 = field2.val(); + var svalue = null; + var text = null; + + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + var fld1 = field1.data('w2field'); + var fld2 = field2.data('w2field'); + if (fld1) value1 = fld1.clean(value1); + if (fld2) value2 = fld2.clean(value2); + } + if (['list', 'enum'].indexOf(search.type) != -1) { + value1 = field1.data('selected') || {}; + if ($.isArray(value1)) { + svalue = []; + for (var j = 0; j < value1.length; j++) { + svalue.push(w2utils.isFloat(value1[j].id) ? parseFloat(value1[j].id) : String(value1[j].id).toLowerCase()); + delete value1[j].hidden; + } + if ($.isEmptyObject(value1)) value1 = ''; + } else { + text = value1.text || ''; + value1 = value1.id || ''; + } + } + if ((value1 !== '' && value1 != null) || (value2 != null && value2 !== '')) { + var tmp = { + field : search.field, + type : search.type, + operator : operator + }; + if (operator == 'between') { + $.extend(tmp, { value: [value1, value2] }); + } else if (operator == 'in' && typeof value1 == 'string') { + $.extend(tmp, { value: value1.split(',') }); + } else if (operator == 'not in' && typeof value1 == 'string') { + $.extend(tmp, { value: value1.split(',') }); + } else { + $.extend(tmp, { value: value1 }); + } + if (svalue) $.extend(tmp, { svalue: svalue }); + if (text) $.extend(tmp, { text: text }); + + // conver date to unix time + try { + if (search.type == 'date' && operator == 'between') { + tmp.value[0] = value1; // w2utils.isDate(value1, w2utils.settings.dateFormat, true).getTime(); + tmp.value[1] = value2; // w2utils.isDate(value2, w2utils.settings.dateFormat, true).getTime(); + } + if (search.type == 'date' && operator == 'is') { + tmp.value = value1; // w2utils.isDate(value1, w2utils.settings.dateFormat, true).getTime(); + } + } catch (e) { + + } + searchData.push(tmp); + last_multi = true; // if only hidden searches, then do not set + } + } + last_logic = 'AND'; + } + // 2: search(field, value) - regular search + if (typeof field == 'string') { + // if only one argument - search all + if (arguments.length == 1) { + value = field; + field = 'all'; + } + last_field = field; + last_search = value; + last_multi = false; + last_logic = (hasHiddenSearches ? 'AND' : 'OR'); + // loop through all searches and see if it applies + if (value != null) { + if (field.toLowerCase() == 'all') { + // if there are search fields loop thru them + if (this.searches.length > 0) { + for (var i = 0; i < this.searches.length; i++) { + var search = this.searches[i]; + if (search.type == 'text' || (search.type == 'alphanumeric' && w2utils.isAlphaNumeric(value)) + || (search.type == 'int' && w2utils.isInt(value)) || (search.type == 'float' && w2utils.isFloat(value)) + || (search.type == 'percent' && w2utils.isFloat(value)) || ((search.type == 'hex' || search.type == 'color') && w2utils.isHex(value)) + || (search.type == 'currency' && w2utils.isMoney(value)) || (search.type == 'money' && w2utils.isMoney(value)) + || (search.type == 'date' && w2utils.isDate(value)) || (search.type == 'time' && w2utils.isTime(value)) + || (search.type == 'datetime' && w2utils.isDateTime(value)) || (search.type == 'datetime' && w2utils.isDate(value)) + || (search.type == 'enum' && w2utils.isAlphaNumeric(value)) || (search.type == 'list' && w2utils.isAlphaNumeric(value)) + ) { + var tmp = { + field : search.field, + type : search.type, + operator : (search.operator != null ? search.operator : (search.type == 'text' ? this.textSearch : 'is')), + value : value + }; + if ($.trim(value) != '') searchData.push(tmp); + } + // range in global search box + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1 && $.trim(String(value)).split('-').length == 2) { + var t = $.trim(String(value)).split('-'); + var tmp = { + field : search.field, + type : search.type, + operator : (search.operator != null ? search.operator : 'between'), + value : [t[0], t[1]] + }; + searchData.push(tmp); + } + // lists fiels + if (['list', 'enum'].indexOf(search.type) != -1) { + var new_values = []; + if (search.options == null) search.options = {}; + if (!Array.isArray(search.options.items)) search.options.items = []; + for (var j = 0; j < search.options.items; j++) { + var tmp = search.options.items[j]; + try { + var re = new RegExp(value, 'i'); + if (re.test(tmp)) new_values.push(j); + if (tmp.text && re.test(tmp.text)) new_values.push(tmp.id); + } catch (e) {} + } + if (new_values.length > 0) { + var tmp = { + field : search.field, + type : search.type, + operator : (search.operator != null ? search.operator : 'in'), + value : new_values + }; + searchData.push(tmp); + } + } + } + } else { + // no search fields, loop thru columns + for (var i = 0; i < this.columns.length; i++) { + var tmp = { + field : this.columns[i].field, + type : 'text', + operator : this.textSearch, + value : value + }; + searchData.push(tmp); + } + } + } else { + var el = $('#grid_'+ this.name +'_search_all'); + var search = this.getSearch(field); + if (search == null) search = { field: field, type: 'text' }; + if (search.field == field) this.last.label = search.label; + if (value !== '') { + var op = this.textSearch; + var val = value; + if (['date', 'time', 'datetime'].indexOf(search.type) != -1) op = 'is'; + if (['list', 'enum'].indexOf(search.type) != -1) { + op = 'is'; + var tmp = el.data('selected'); + if (tmp && !$.isEmptyObject(tmp)) val = tmp.id; else val = ''; + } + if (search.type == 'int' && value !== '') { + op = 'is'; + if (String(value).indexOf('-') != -1) { + var tmp = value.split('-'); + if (tmp.length == 2) { + op = 'between'; + val = [parseInt(tmp[0]), parseInt(tmp[1])]; + } + } + if (String(value).indexOf(',') != -1) { + var tmp = value.split(','); + op = 'in'; + val = []; + for (var i = 0; i < tmp.length; i++) val.push(tmp[i]); + } + } + if (search.operator != null) op = search.operator; + var tmp = { + field : search.field, + type : search.type, + operator : op, + value : val + }; + searchData.push(tmp); + } + } + } + } + // 3: search([ { field, value, [operator,] [type] }, { field, value, [operator,] [type] } ], logic) - submit whole structure + if ($.isArray(field)) { + var logic = 'AND'; + if (typeof value == 'string') { + logic = value.toUpperCase(); + if (logic != 'OR' && logic != 'AND') logic = 'AND'; + } + last_search = ''; + last_multi = true; + last_logic = logic; + for (var i = 0; i < field.length; i++) { + var data = field[i]; + if (typeof data.value == 'number') data.operator = 'is'; + if (typeof data.value == 'string') data.operator = this.textSearch; + if ($.isArray(data.value)) data.operator = 'in' + // merge current field and search if any + searchData.push(data); + } + } + // event before + var edata = this.trigger({ + phase: 'before', type: 'search', target: this.name, multi: (arguments.length === 0 ? true : false), + searchField: (field ? field : 'multi'), searchValue: (field ? value : 'multi'), + searchData: searchData, searchLogic: last_logic }); + if (edata.isCancelled === true) return; + // default action + this.searchData = edata.searchData; + this.last.field = last_field; + this.last.search = last_search; + this.last.multi = last_multi; + this.last.logic = edata.searchLogic; + this.last.scrollTop = 0; + this.last.scrollLeft = 0; + this.last.selection.indexes = []; + this.last.selection.columns = {}; + // -- clear all search field + this.searchClose(); + // apply search + if (url) { + this.last.xhr_offset = 0; + this.reload(); + } else { + // local search + this.localSearch(); + this.refresh(); + } + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + searchOpen: function () { + if (!this.box) return; + if (this.searches.length === 0) return; + var obj = this; + var it = obj.toolbar.get('w2ui-search-advanced'); + var btn = '#tb_'+ obj.toolbar.name +'_item_'+ w2utils.escapeId(it.id) +' table.w2ui-button'; + // event before + var edata = this.trigger({ phase: 'before', type: 'searchOpen', target: this.name }); + if (edata.isCancelled === true) { + setTimeout(function () { obj.toolbar.uncheck('w2ui-search-advanced'); }, 1); + return; + } + // show search + $('#tb_'+ this.name +'_toolbar_item_w2ui-search-advanced').w2overlay({ + html : this.getSearchesHTML(), + name : this.name + '-searchOverlay', + left : -10, + 'class' : 'w2ui-grid-searches', + onShow : function () { + obj.initSearches(); + $('#w2ui-overlay-'+ obj.name +'-searchOverlay .w2ui-grid-searches').data('grid-name', obj.name); + var sfields = $('#w2ui-overlay-'+ this.name +'-searchOverlay .w2ui-grid-searches *[rel=search]'); + if (sfields.length > 0) sfields[0].focus(); + if (!it.checked) { + it.checked = true; + $(btn).addClass('checked'); + } + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + }, + onHide: function () { + it.checked = false; + $(btn).removeClass('checked'); + } + }); + }, + + searchClose: function () { + var obj = this; + if (!this.box) return; + if (this.searches.length === 0) return; + if (this.toolbar) this.toolbar.uncheck('w2ui-search-advanced', 'w2ui-column-on-off'); + // hide search + $().w2overlay({ name: this.name + '-searchOverlay' }); + }, + + searchReset: function (noRefresh) { + var searchData = []; + var hasHiddenSearches = false; + // add hidden searches + for (var i = 0; i < this.searches.length; i++) { + if (!this.searches[i].hidden || this.searches[i].value == null) continue; + searchData.push({ + field : this.searches[i].field, + operator : this.searches[i].operator || 'is', + type : this.searches[i].type, + value : this.searches[i].value || '' + }); + hasHiddenSearches = true; + } + // event before + var edata = this.trigger({ phase: 'before', type: 'search', reset: true, target: this.name, searchData: searchData }); + if (edata.isCancelled === true) return; + // default action + this.searchData = edata.searchData; + this.last.search = ''; + this.last.logic = (hasHiddenSearches ? 'AND' : 'OR'); + // --- do not reset to All Fields (I think) + if (this.searches.length > 0) { + if (!this.multiSearch || !this.show.searchAll) { + var tmp = 0; + while (tmp < this.searches.length && (this.searches[tmp].hidden || this.searches[tmp].simple === false)) tmp++; + if (tmp >= this.searches.length) { + // all searches are hidden + this.last.field = ''; + this.last.label = ''; + } else { + this.last.field = this.searches[tmp].field; + this.last.label = this.searches[tmp].label; + } + } else { + this.last.field = 'all'; + this.last.label = w2utils.lang('All Fields'); + } + } + this.last.multi = false; + this.last.xhr_offset = 0; + // reset scrolling position + this.last.scrollTop = 0; + this.last.scrollLeft = 0; + this.last.selection.indexes = []; + this.last.selection.columns = {}; + // -- clear all search field + this.searchClose(); + $('#grid_'+ this.name +'_search_all').val('').removeData('selected'); + // apply search + if (!noRefresh) this.reload(); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + searchShowFields: function (forceHide) { + var obj = this; + var el = $('#grid_'+ this.name +'_search_all'); + if (forceHide === true) { + $(el).w2overlay({ name: obj.name + '-searchFields' }); + return; + } + var html = '
'; + for (var s = -1; s < this.searches.length; s++) { + var search = this.searches[s]; + var sField = (search ? search.field : null); + var column = this.getColumn(sField); + var disable = false; + var msg = 'This column is hidden'; + if (this.show.searchHiddenMsg == true && s != -1 && (column == null || (column.hidden === true && column.hideable !== false))) { + disable = true; + if (column == null) { + msg = 'This column does not exist' + } + } + if (s == -1) { // -1 is All Fields search + if (!this.multiSearch || !this.show.searchAll) continue; + search = { field: 'all', label: w2utils.lang('All Fields') }; + } else { + if (column != null && column.hideable === false) continue; + // don't show hidden searches + if (this.searches[s].hidden === true || this.searches[s].simple === false) continue; + } + if (search.label == null && search.caption != null) { + console.log('NOTICE: grid search.caption property is deprecated, please use search.label. Search ->', search); + search.label = search.caption; + } + + html += ''+ + ' '+ + ' '+ + ''; + } + html += "
'+ + ' '+ + ' '+ search.label +'
"; + + var overName = obj.name + '-searchFields'; + if ($('#w2ui-overlay-'+ overName).length == 1) html = ''; // hide if visible + // need timer otherwise does nto show with list type + setTimeout(function () { + $(el).w2overlay({ html: html, name: overName, left: -10 }); + }, 1); + }, + + initAllField: function (field, value) { + var el = $('#grid_'+ this.name +'_search_all'); + if (field == 'all') { + var search = { field: 'all', label: w2utils.lang('All Fields') }; + el.w2field('clear'); + // el.change(); // triggering change will cause grid calling remote url twice + } else { + var search = this.getSearch(field); + if (search == null) return; + var st = search.type; + if (['enum', 'select'].indexOf(st) != -1) st = 'list'; + el.w2field(st, $.extend({}, search.options, { suffix: '', autoFormat: false, selected: value })); + if (['list', 'enum', 'date', 'time', 'datetime'].indexOf(search.type) != -1) { + this.last.search = ''; + this.last.item = ''; + el.val(''); + $('#grid_'+ this.name +'_searchClear').hide() + } + } + // update field + if (this.last.search != '') { + this.last.label = search.label; + this.search(search.field, this.last.search); + } else { + this.last.field = search.field; + this.last.label = search.label; + } + el.attr('placeholder', w2utils.lang(search.label || search.caption || search.field)); + $().w2overlay({ name: this.name + '-searchFields' }); + }, + + // clears records and related params + clear: function (noRefresh) { + this.total = 0; + this.records = []; + this.summary = []; + this.last.xhr_offset = 0; // need this for reload button to work on remote data set + this.last.idCache = {}; // optimization to free memory + this.reset(true); + // refresh + if (!noRefresh) this.refresh(); + }, + + // clears scroll position, selection, ranges + reset: function (noRefresh) { + // position + this.last.scrollTop = 0; + this.last.scrollLeft = 0; + this.last.selection = { indexes: [], columns: {} }; + this.last.range_start = null; + this.last.range_end = null; + // additional + $('#grid_'+ this.name +'_records').prop('scrollTop', 0); + // refresh + if (!noRefresh) this.refresh(); + }, + + skip: function (offset, callBack) { + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (url) { + this.offset = parseInt(offset); + if (this.offset > this.total) this.offset = this.total - this.limit; + if (this.offset < 0 || !w2utils.isInt(this.offset)) this.offset = 0; + this.clear(true); + this.reload(callBack); + } else { + console.log('ERROR: grid.skip() can only be called when you have remote data source.'); + } + }, + + load: function (url, callBack) { + if (url == null) { + console.log('ERROR: You need to provide url argument when calling .load() method of "'+ this.name +'" object.'); + return; + } + // default action + this.clear(true); + this.request('get', {}, url, callBack); + }, + + reload: function (callBack) { + var grid = this; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + grid.selectionSave(); + if (url) { + // need to remember selection (not just last.selection object) + this.load(url, function () { + grid.selectionRestore(); + if (typeof callBack == 'function') callBack(); + }); + } else { + this.reset(true); + this.localSearch(); + this.selectionRestore(); + if (typeof callBack == 'function') callBack({ status: 'success' }); + } + }, + + request: function (cmd, add_params, url, callBack) { + if (add_params == null) add_params = {}; + if (url == '' || url == null) url = this.url; + if (url == '' || url == null) return; + // build parameters list + if (!w2utils.isInt(this.offset)) this.offset = 0; + if (!w2utils.isInt(this.last.xhr_offset)) this.last.xhr_offset = 0; + // add list params + var params = { + limit : this.limit, + offset : parseInt(this.offset) + parseInt(this.last.xhr_offset), + searchLogic : this.last.logic, + search: this.searchData.map(function (search) { + var _search = $.extend({}, search); + if (this.searchMap && this.searchMap[_search.field]) _search.field = this.searchMap[_search.field]; + return _search; + }.bind(this)), + sort: this.sortData.map(function (sort) { + var _sort = $.extend({}, sort); + if (this.sortMap && this.sortMap[_sort.field]) _sort.field = this.sortMap[_sort.field]; + return _sort; + }.bind(this)) + } + if (this.searchData.length === 0) { + delete params['search']; + delete params['searchLogic']; + } + if (this.sortData.length === 0) { + delete params['sort']; + } + // append other params + $.extend(params, this.postData); + $.extend(params, add_params); + // other actions + if (cmd == 'delete' || cmd == 'save') { + delete params['limit']; + delete params['offset']; + params['action'] = cmd + if (cmd == 'delete') { + params[this.recid || 'recid'] = this.getSelection(); + } + } + // event before + if (cmd == 'get') { + var edata = this.trigger({ phase: 'before', type: 'request', target: this.name, url: url, postData: params, httpHeaders: this.httpHeaders }); + if (edata.isCancelled === true) { if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); return; } + } else { + var edata = { url: url, postData: params, httpHeaders: this.httpHeaders }; + } + // call server to get data + var obj = this; + if (this.last.xhr_offset === 0) { + this.lock(w2utils.lang(this.msgRefresh), true); + } + if (this.last.xhr) try { this.last.xhr.abort(); } catch (e) {} + // URL + url = (typeof edata.url != 'object' ? edata.url : edata.url.get); + if (cmd == 'save' && typeof edata.url == 'object') url = edata.url.save; + if (cmd == 'delete' && typeof edata.url == 'object') url = edata.url.remove; + // process url with routeData + if (!$.isEmptyObject(obj.routeData)) { + var info = w2utils.parseRoute(url); + if (info.keys.length > 0) { + for (var k = 0; k < info.keys.length; k++) { + if (obj.routeData[info.keys[k].name] == null) continue; + url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), obj.routeData[info.keys[k].name]); + } + } + } + // ajax options + var ajaxOptions = { + type : 'GET', + url : url, + data : edata.postData, + headers : edata.httpHeaders, + dataType : 'json' // expected data type from server + }; + + var dataType = this.dataType || w2utils.settings.dataType + switch (dataType) { + case 'HTTP': + ajaxOptions.data = (typeof ajaxOptions.data == 'object' ? String($.param(ajaxOptions.data, false)).replace(/%5B/g, '[').replace(/%5D/g, ']') : ajaxOptions.data); + break; + case 'HTTPJSON': + ajaxOptions.data = { request: JSON.stringify(ajaxOptions.data) }; + break; + case 'RESTFULL': + ajaxOptions.type = 'GET'; + if (cmd == 'save') ajaxOptions.type = 'PUT'; // so far it is always update + if (cmd == 'delete') ajaxOptions.type = 'DELETE'; + ajaxOptions.data = (typeof ajaxOptions.data == 'object' ? String($.param(ajaxOptions.data, false)).replace(/%5B/g, '[').replace(/%5D/g, ']') : ajaxOptions.data); + break; + case 'RESTFULLJSON': + ajaxOptions.type = 'GET'; + if (cmd == 'save') ajaxOptions.type = 'PUT'; // so far it is always update + if (cmd == 'delete') ajaxOptions.type = 'DELETE'; + ajaxOptions.data = JSON.stringify(ajaxOptions.data); + ajaxOptions.contentType = 'application/json'; + break; + case 'JSON': + ajaxOptions.type = 'POST'; + ajaxOptions.data = JSON.stringify(ajaxOptions.data); + ajaxOptions.contentType = 'application/json'; + break; + } + if (this.method) ajaxOptions.type = this.method; + + this.last.xhr_cmd = cmd; + this.last.xhr_start = (new Date()).getTime(); + this.last.loaded = false; + this.last.xhr = $.ajax(ajaxOptions) + .done(function (data, status, xhr) { + obj.requestComplete(status, xhr, cmd, callBack); + }) + .fail(function (xhr, status, error) { + // trigger event + var errorObj = { status: status, error: error, rawResponseText: xhr.responseText }; + var edata2 = obj.trigger({ phase: 'before', type: 'error', error: errorObj, xhr: xhr }); + if (edata2.isCancelled === true) return; + // default behavior + if (status != 'abort') { // it can be aborted by the grid itself + var data; + try { data = typeof xhr.responseJSON === 'object' ? xhr.responseJSON : $.parseJSON(xhr.responseText); } catch (e) {} + console.log('ERROR: Server communication failed.', + '\n EXPECTED:', { status: 'success', total: 5, records: [{ recid: 1, field: 'value' }] }, + '\n OR:', { status: 'error', message: 'error message' }, + '\n RECEIVED:', typeof data == 'object' ? data : xhr.responseText); + obj.requestComplete('error', xhr, cmd, callBack); + } + // event after + obj.trigger($.extend(edata2, { phase: 'after' })); + }); + if (cmd == 'get') { + // event after + this.trigger($.extend(edata, { phase: 'after' })); + } + }, + + requestComplete: function(status, xhr, cmd, callBack) { + var obj = this; + this.unlock(); + setTimeout(function () { + if (obj.show.statusResponse) { + obj.status(w2utils.lang('Server Response') + ' ' + ((new Date()).getTime() - obj.last.xhr_start)/1000 +' ' + w2utils.lang('sec')); + } + }, 10); + this.last.pull_more = false; + this.last.pull_refresh = true; + + // event before + var event_name = 'load'; + if (this.last.xhr_cmd == 'save') event_name = 'save'; + if (this.last.xhr_cmd == 'delete') event_name = 'delete'; + var edata = this.trigger({ phase: 'before', target: this.name, type: event_name, xhr: xhr, status: status }); + if (edata.isCancelled === true) { + if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); + return; + } + // parse server response + var data; + if (status != 'error') { + // default action + if (typeof obj.parser == 'function') { + data = obj.parser(xhr.responseJSON); + if (typeof data != 'object') { + console.log('ERROR: Your parser did not return proper object'); + } + } else { + data = xhr.responseJSON; + if (data == null) { + data = { + status : 'error', + message : w2utils.lang(this.msgNotJSON), + responseText : xhr.responseText + }; + } else if (Array.isArray(data)) { + // if it is plain array, assume these are records + data = { + status : 'success', + records : data, + total : data.length + } + } + } + if (Array.isArray(data.records)) { + // make sure each record has recid + data.records.forEach(function (rec, ind) { + if (obj.recid) { + rec.recid = obj.parseField(rec, obj.recid); + } + if (rec.recid == null) { + rec.recid = 'recid-' + ind; + } + }) + } + if (data['status'] == 'error') { + obj.error(data['message']); + } else { + if (cmd == 'get') { + if (data.total == null) data.total = -1; + if (data.records == null) { + data.records = []; + } + if (data.records.length == this.limit) { + var loaded = this.records.length + data.records.length + this.last.xhr_hasMore = (loaded == this.total ? false : true); + } else { + this.last.xhr_hasMore = false; + this.total = this.offset + this.last.xhr_offset + data.records.length; + } + if (!this.last.xhr_hasMore) { + // if no morerecords, then hide spinner + $('#grid_'+ this.name +'_rec_more, #grid_'+ this.name +'_frec_more').hide() + } + if (this.last.xhr_offset === 0) { + this.records = []; + this.summary = []; + if (w2utils.isInt(data.total)) this.total = parseInt(data.total); + } else { + if (data.total != -1 && parseInt(data.total) != parseInt(this.total)) { + this.message(w2utils.lang(this.msgNeedReload), function () { + delete this.last.xhr_offset; + this.reload(); + }.bind(this)); + return; + } + } + // records + if (data.records) { + for (var r = 0; r < data.records.length; r++) { + this.records.push(data.records[r]); + } + } + // summary records (if any) + if (data.summary) { + this.summary = []; + for (var r = 0; r < data.summary.length; r++) { + this.summary.push(data.summary[r]); + } + } + } + if (cmd == 'delete') { + this.reset(); // unselect old selections + this.reload(); + return; + } + } + } else { + data = { + status : 'error', + message : w2utils.lang(this.msgAJAXerror), + responseText : xhr.responseText + }; + obj.error(w2utils.lang(this.msgAJAXerror)); + } + // event after + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (!url) { + this.localSort(); + this.localSearch(); + } + this.total = parseInt(this.total); + // do not refresh if loading on infinite scroll + if (this.last.xhr_offset === 0) { + this.refresh(); + } else { + this.scroll(); + this.resize(); + } + // call back + if (typeof callBack == 'function') callBack(data); // need to be befor event:after + // after event + this.trigger($.extend(edata, { phase: 'after' })); + this.last.loaded = true; + }, + + error: function (msg) { + var obj = this; + // let the management of the error outside of the grid + var edata = this.trigger({ target: this.name, type: 'error', message: msg , xhr: this.last.xhr }); + if (edata.isCancelled === true) { + if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); + return; + } + this.message(msg); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + getChanges: function (recordsBase) { + var changes = []; + if (typeof recordsBase == 'undefined') { + var recordsBase = this.records; + } + + for (var r = 0; r < recordsBase.length; r++) { + var rec = recordsBase[r]; + if (rec.w2ui) { + if (rec.w2ui.changes != null) { + var obj = {} + obj[this.recid || 'recid'] = rec.recid + changes.push($.extend(true, obj, rec.w2ui.changes)); + } + + // recursively look for changes in non-expanded children + if (rec.w2ui.expanded !== true && rec.w2ui.children && rec.w2ui.children.length) { + $.merge(changes, this.getChanges(rec.w2ui.children)) + } + } + } + return changes; + }, + + mergeChanges: function () { + var changes = this.getChanges(); + for (var c = 0; c < changes.length; c++) { + var record = this.get(changes[c].recid); + for (var s in changes[c]) { + if (s == 'recid') continue; // do not allow to change recid + if (typeof changes[c][s] === "object") changes[c][s] = changes[c][s].text; + try { + if (s.indexOf('.') != -1) { + eval("record['" + s.replace(/\./g, "']['") + "'] = changes[c][s]") + } else { + record[s] = changes[c][s]; + } + } catch (e) { + console.log('ERROR: Cannot merge. ', e.message || '', e); + } + if (record.w2ui) delete record.w2ui.changes; + } + } + this.refresh(); + }, + + // =================================================== + // -- Action Handlers + + save: function (callBack) { + var obj = this; + var changes = this.getChanges(); + var url = (typeof this.url != 'object' ? this.url : this.url.save); + // event before + var edata = this.trigger({ phase: 'before', target: this.name, type: 'save', changes: changes }); + if (edata.isCancelled === true) { + if (url && typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); + return; + } + if (url) { + this.request('save', { 'changes' : edata.changes }, null, + function (data) { + if (data.status !== 'error') { + // only merge changes, if save was successful + obj.mergeChanges(); + } + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + // call back + if (typeof callBack == 'function') callBack(data); + } + ); + } else { + this.mergeChanges(); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + } + }, + + editField: function (recid, column, value, event) { + var obj = this; + if (this.last.inEditMode === true) { // already editing + if (event.keyCode == 13) { + var index = this.last._edit.index; + var column = this.last._edit.column; + var recid = this.last._edit.recid; + this.editChange({ type: 'custom', value: this.last._edit.value }, this.get(recid, true), column, event); + var next = event.shiftKey ? this.prevRow(index, column) : this.nextRow(index, column); + if (next != null && next != index) { + setTimeout(function () { + if (obj.selectType != 'row') { + obj.selectNone(); + obj.select({ recid: obj.records[next].recid, column: column }); + } else { + obj.editField(obj.records[next].recid, column, null, event); + } + }, 1); + } + this.last.inEditMode = false; + } else { + // when 2 chars entered fast + var $input = $(this.box).find('div.w2ui-edit-box .w2ui-input'); + if ($input.length > 0 && $input[0].tagName == 'DIV') { + $input.text($input.text() + value); + w2utils.setCursorPosition($input[0], $input.text().length); + } + } + return; + } + var index = obj.get(recid, true); + var edit = obj.getCellEditable(index, column); + if (!edit) return; + var rec = obj.records[index]; + var col = obj.columns[column]; + var prefix = (col.frozen === true ? '_f' : '_'); + if (['enum', 'file'].indexOf(edit.type) != -1) { + console.log('ERROR: input types "enum" and "file" are not supported in inline editing.'); + return; + } + // event before + var edata = obj.trigger({ phase: 'before', type: 'editField', target: obj.name, recid: recid, column: column, value: value, + index: index, originalEvent: event }); + if (edata.isCancelled === true) return; + value = edata.value; + // default behaviour + this.last.inEditMode = true; + this.last._edit = { value: value, index: index, column: column, recid: recid }; + this.selectNone(); + this.select({ recid: recid, column: column }); + if (['checkbox', 'check'].indexOf(edit.type) != -1) return; + // create input element + var tr = $('#grid_'+ obj.name + prefix +'rec_' + w2utils.escapeId(recid)); + var el = tr.find('[col='+ column +'] > div'); + // clear previous if any + $(this.box).find('div.w2ui-edit-box').remove(); + // for spreadsheet - insert into selection + if (this.selectType != 'row') { + $('#grid_'+ this.name + prefix + 'selection') + .attr('id', 'grid_'+ this.name + '_editable') + .removeClass('w2ui-selection') + .addClass('w2ui-edit-box') + .prepend('
') + .find('.w2ui-selection-resizer') + .remove(); + el = $('#grid_'+ this.name + '_editable >div:first-child'); + } + if (edit.inTag == null) edit.inTag = ''; + if (edit.outTag == null) edit.outTag = ''; + if (edit.style == null) edit.style = ''; + if (edit.items == null) edit.items = []; + var val = (rec.w2ui && rec.w2ui.changes && rec.w2ui.changes[col.field] != null ? w2utils.stripTags(rec.w2ui.changes[col.field]) : w2utils.stripTags(rec[col.field])); + if (val == null) val = ''; + var old_value = (typeof val != 'object' ? val : ''); + if (edata.old_value != null) old_value = edata.old_value; + if (value != null) val = value; + var addStyle = (col.style != null ? col.style + ';' : ''); + if (typeof col.render == 'string' && ['number', 'int', 'float', 'money', 'percent', 'size'].indexOf(col.render.split(':')[0]) != -1) { + addStyle += 'text-align: right;'; + } + // normalize items + if (edit.items.length > 0 && !$.isPlainObject(edit.items[0])) { + edit.items = w2obj.field.prototype.normMenu(edit.items); + } + switch (edit.type) { + + case 'select': + var html = ''; + for (var i = 0; i < edit.items.length; i++) { + html += ''; + } + el.addClass('w2ui-editable') + .html('' + edit.outTag); + setTimeout(function () { + el.find('select') + .on('change', function (event) { + delete obj.last.move; + }) + .on('blur', function (event) { + if ($(this).data('keep-open') == true) return; + obj.editChange.call(obj, this, index, column, event); + }); + }, 10); + break; + + case 'div': + var $tmp = tr.find('[col='+ column +'] > div'); + var font = 'font-family: '+ $tmp.css('font-family') + '; font-size: '+ $tmp.css('font-size') + ';'; + el.addClass('w2ui-editable') + .html('
' + edit.outTag); + if (value == null) el.find('div.w2ui-input').text(typeof val != 'object' ? val : ''); + // add blur listener + var input = el.find('div.w2ui-input').get(0); + setTimeout(function () { + var tmp = input; + $(tmp).on('blur', function (event) { + if ($(this).data('keep-open') == true) return; + obj.editChange.call(obj, tmp, index, column, event); + }); + }, 10); + if (value != null) $(input).text(typeof val != 'object' ? val : ''); + break; + + default: + var $tmp = tr.find('[col='+ column +'] > div'); + var font = 'font-family: '+ $tmp.css('font-family') + '; font-size: '+ $tmp.css('font-size'); + el.addClass('w2ui-editable') + .html('' + edit.outTag); + // issue #499 + if (edit.type == 'number') { + val = w2utils.formatNumber(val); + } + if (edit.type == 'date') { + val = w2utils.formatDate(w2utils.isDate(val, edit.format, true) || new Date(), edit.format); + } + if (value == null) el.find('input').val(typeof val != 'object' ? val : ''); + // init w2field + var input = el.find('input').get(0); + $(input).w2field(edit.type, $.extend(edit, { selected: val })); + // add blur listener + setTimeout(function () { + var tmp = input; + if (edit.type == 'list') { + tmp = $($(input).data('w2field').helpers.focus).find('input'); + if (typeof val != 'object' && val != '') tmp.val(val).css({ opacity: 1 }).prev().css({ opacity: 1 }); + el.find('input').on('change', function (event) { + obj.editChange.call(obj, input, index, column, event); + }); + } + $(tmp).on('blur', function (event) { + if ($(this).data('keep-open') == true) return; + obj.editChange.call(obj, input, index, column, event); + }); + }, 10); + if (value != null) $(input).val(typeof val != 'object' ? val : ''); + } + + setTimeout(function () { + if (!obj.last.inEditMode) return; + el.find('input, select, div.w2ui-input') + .data('old_value', old_value) + .on('mousedown', function (event) { + event.stopPropagation(); + }) + .on('click', function (event) { + if (edit.type == 'div') { + expand.call(el.find('div.w2ui-input')[0], null); + } else { + expand.call(el.find('input, select')[0], null); + } + }) + .on('paste', function (event) { + // clean paste to be plain text + var e = event.originalEvent; + event.preventDefault(); + var text = e.clipboardData.getData("text/plain"); + document.execCommand("insertHTML", false, text); + }) + .on('keydown', function (event) { + var el = this; + var val = (el.tagName.toUpperCase() == 'DIV' ? $(el).text() : $(el).val()); + switch (event.keyCode) { + case 8: // backspace; + if (edit.type == 'list' && !$(input).data('w2field')) { // cancel backspace when deleting element + event.preventDefault(); + } + break; + case 9: + case 13: + event.preventDefault(); + break; + case 37: + if (w2utils.getCursorPosition(el) === 0) { + event.preventDefault(); + } + break; + case 39: + if (w2utils.getCursorPosition(el) == val.length) { + w2utils.setCursorPosition(el, val.length); + event.preventDefault(); + } + break; + } + // need timeout so, this handler is executed last + setTimeout(function () { + switch (event.keyCode) { + case 9: // tab + var next_rec = recid; + var next_col = event.shiftKey ? obj.prevCell(index, column, true) : obj.nextCell(index, column, true); + // next or prev row + if (next_col == null) { + var tmp = event.shiftKey ? obj.prevRow(index, column) : obj.nextRow(index, column); + if (tmp != null && tmp != index) { + next_rec = obj.records[tmp].recid; + // find first editable row + for (var c = 0; c < obj.columns.length; c++) { + var edit = obj.getCellEditable(index, c); + if (edit != null && ['checkbox', 'check'].indexOf(edit.type) == -1) { + next_col = parseInt(c); + if (!event.shiftKey) break; + } + } + } + + } + if (next_rec === false) next_rec = recid; + if (next_col == null) next_col = column; + // init new or same record + el.blur(); + setTimeout(function () { + if (obj.selectType != 'row') { + obj.selectNone(); + obj.select({ recid: next_rec, column: next_col }); + } else { + obj.editField(next_rec, next_col, null, event); + } + }, 1); + if (event.preventDefault) event.preventDefault(); + break; + + case 13: // enter + el.blur(); + var next = event.shiftKey ? obj.prevRow(index, column) : obj.nextRow(index, column); + if (next != null && next != index) { + setTimeout(function () { + if (obj.selectType != 'row') { + obj.selectNone(); + obj.select({ recid: obj.records[next].recid, column: column }); + } else { + obj.editField(obj.records[next].recid, column, null, event); + } + }, 1); + } + if (el.tagName.toUpperCase() == 'DIV') { + event.preventDefault(); + } + break; + + case 27: // escape + var old = obj.parseField(rec, col.field); + if (rec.w2ui && rec.w2ui.changes && rec.w2ui.changes[col.field] != null) old = rec.w2ui.changes[col.field]; + if ($(el).data('old_value') != null) old = $(el).data('old_value'); + if (el.tagName.toUpperCase() == 'DIV') { + $(el).text(old != null ? old : ''); + } else { + el.value = old != null ? old : ''; + } + el.blur(); + setTimeout(function () { obj.select({ recid: recid, column: column }); }, 1); + break; + } + // if input too small - expand + expand.call(el, event); + }, 1); + }) + .on('keyup', function (event) { + expand.call(this, event); + }); + // focus and select + setTimeout(function () { + if (!obj.last.inEditMode) return; + var tmp = el.find('.w2ui-input'); + var len = ($(tmp).val() != null ? $(tmp).val().length : 0); + if (edit.type == 'div') len = $(tmp).text().length; + if (tmp.length > 0) { + tmp.focus(); + clearTimeout(obj.last.kbd_timer); // keep focus + if (tmp[0].tagName.toUpperCase() != 'SELECT') w2utils.setCursorPosition(tmp[0], len); + tmp[0].resize = expand; + expand.call(tmp[0], null); + } + }, 50); + // event after + obj.trigger($.extend(edata, { phase: 'after', input: el.find('input, select, div.w2ui-input') })); + }, 5); // needs to be 5-10 + return; + + function expand(event) { + try { + var val = (this.tagName.toUpperCase() == 'DIV' ? $(this).text() : this.value); + var $sel = $('#grid_'+ obj.name + '_editable'); + var style = 'font-family: '+ $(this).css('font-family') + '; font-size: '+ $(this).css('font-size') + '; white-space: pre;'; + var width = w2utils.getStrWidth(val, style); + if (width + 20 > $sel.width()) { + $sel.width(width + 20); + } + } catch (e) { + } + } + }, + + editChange: function (el, index, column, event) { + var obj = this; + // keep focus + setTimeout(function () { + var $input = $(obj.box).find('#grid_'+ obj.name + '_focus'); + if (!$input.is(':focus')) $input.focus(); + }, 10); + // all other fields + var summary = index < 0; + index = index < 0 ? -index - 1 : index; + var records = summary ? this.summary : this.records; + var rec = records[index]; + var col = this.columns[column]; + var tr = $('#grid_'+ this.name + (col.frozen === true ? '_frec_' : '_rec_') + w2utils.escapeId(rec.recid)); + var new_val = (el.tagName && el.tagName.toUpperCase() == 'DIV' ? $(el).text() : el.value); + var old_val = this.parseField(rec, col.field); + var tmp = $(el).data('w2field'); + if (tmp) { + if (tmp.type == 'list') new_val = $(el).data('selected'); + if ($.isEmptyObject(new_val) || new_val == null) new_val = ''; + if (!$.isPlainObject(new_val)) new_val = tmp.clean(new_val); + } + if (el.type == 'checkbox') { + if (rec.w2ui && rec.w2ui.editable === false) el.checked = !el.checked; + new_val = el.checked; + } + // change/restore event + var edata = { + phase: 'before', type: 'change', target: this.name, input_id: el.id, recid: rec.recid, index: index, column: column, + originalEvent: (event.originalEvent ? event.originalEvent : event), + value_new: new_val, + value_previous: (rec.w2ui && rec.w2ui.changes && rec.w2ui.changes.hasOwnProperty(col.field) ? rec.w2ui.changes[col.field]: old_val), + value_original: old_val + }; + if ($(event.target).data('old_value') != null) edata.value_previous = $(event.target).data('old_value'); + // if (old_val == null) old_val = ''; -- do not uncomment, error otherwise + while (true) { + new_val = edata.value_new; + if ((typeof new_val != 'object' && String(old_val) != String(new_val)) || + (typeof new_val == 'object' && new_val && new_val.id != old_val && (typeof old_val != 'object' || old_val == null || new_val.id != old_val.id))) { + // change event + edata = this.trigger($.extend(edata, { type: 'change', phase: 'before' })); + if (edata.isCancelled !== true) { + if (new_val !== edata.value_new) { + // re-evaluate the type of change to be made + continue; + } + // default action + rec.w2ui = rec.w2ui || {}; + rec.w2ui.changes = rec.w2ui.changes || {}; + rec.w2ui.changes[col.field] = edata.value_new; + // event after + this.trigger($.extend(edata, { phase: 'after' })); + } + } else { + // restore event + edata = this.trigger($.extend(edata, { type: 'restore', phase: 'before' })); + if (edata.isCancelled !== true) { + if (new_val !== edata.value_new) { + // re-evaluate the type of change to be made + continue; + } + // default action + if (rec.w2ui && rec.w2ui.changes) delete rec.w2ui.changes[col.field]; + if (rec.w2ui && $.isEmptyObject(rec.w2ui.changes)) delete rec.w2ui.changes; + // event after + this.trigger($.extend(edata, { phase: 'after' })); + } + } + break; + } + // refresh cell + var cell = $(tr).find('[col='+ column +']'); + if (!summary) { + if (rec.w2ui && rec.w2ui.changes && rec.w2ui.changes[col.field] != null) { + cell.addClass('w2ui-changed'); + } else { + cell.removeClass('w2ui-changed'); + } + // update cell data + cell.replaceWith(this.getCellHTML(index, column, summary)); + } + // remove + $(this.box).find('div.w2ui-edit-box').remove(); + // enable/disable toolbar search button + if (this.show.toolbarSave) { + if (this.getChanges().length > 0) this.toolbar.enable('w2ui-save'); else this.toolbar.disable('w2ui-save'); + } + obj.last.inEditMode = false; + }, + + "delete": function (force) { + var time = (new Date()).getTime(); + var obj = this; + // event before + var edata = this.trigger({ phase: 'before', target: this.name, type: 'delete', force: force }); + if (force) this.message(); // close message + if (edata.isCancelled === true) return; + force = edata.force; + // hide all tooltips + setTimeout(function () { $().w2tag(); }, 20); + // default action + var recs = this.getSelection(); + if (recs.length === 0) return; + if (this.msgDelete != '' && !force) { + this.message({ + width : 380, + height : 170, + body : '
' + + w2utils.lang(obj.msgDelete).replace('NN', recs.length).replace('records', (recs.length == 1 ? 'record' : 'records')) + + '
', + buttons : (w2utils.settings.macButtonOrder + ? '' + + '' + : '' + + '' + ), + onOpen: function (event) { + var inputs = $(this.box).find('input, textarea, select, button'); + inputs.off('.message') + .on('blur.message', function (evt) { + // last input + if (inputs.index(evt.target) + 1 === inputs.length) { + inputs.get(0).focus(); + evt.preventDefault(); + } + }) + .on('keydown.message', function (evt) { + if (evt.keyCode == 27) obj.message(); // esc + }); + setTimeout(function () { + $(this.box).find('.w2ui-btn.btn-default').focus(); + clearTimeout(obj.last.kbd_timer); + }.bind(this), 50); + } + }); + return; + } + // call delete script + var url = (typeof this.url != 'object' ? this.url : this.url.remove); + if (url) { + this.request('delete'); + } else { + if (typeof recs[0] != 'object') { + this.selectNone(); + this.remove.apply(this, recs); + } else { + // clear cells + for (var r = 0; r < recs.length; r++) { + var fld = this.columns[recs[r].column].field; + var ind = this.get(recs[r].recid, true); + var rec = this.records[ind]; + if (ind != null && fld != 'recid') { + this.records[ind][fld] = ''; + if (rec.w2ui && rec.w2ui.changes) delete rec.w2ui.changes[fld]; + // -- style should not be deleted + // if (rec.style != null && $.isPlainObject(rec.style) && rec.style[recs[r].column]) { + // delete rec.style[recs[r].column]; + // } + } + } + this.update(); + } + } + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + click: function (recid, event) { + var time = (new Date()).getTime(); + var column = null; + var obj = this; + if (this.last.cancelClick == true || (event && event.altKey)) return; + if ((typeof recid == 'object') && (recid !== null)) { + column = recid.column; + recid = recid.recid; + } + if (event == null) event = {}; + // check for double click + if (time - parseInt(this.last.click_time) < 350 && this.last.click_recid == recid && event.type == 'click') { + this.dblClick(recid, event); + return; + } + // hide bubble + if (this.last.bubbleEl) { + $(this.last.bubbleEl).w2tag(); + this.last.bubbleEl = null; + } + this.last.click_time = time; + var last_recid = this.last.click_recid; + this.last.click_recid = recid; + // column user clicked on + if (column == null && event.target) { + var tmp = event.target; + if (tmp.tagName.toUpperCase() != 'TD') tmp = $(tmp).parents('td')[0]; + if ($(tmp).attr('col') != null) column = parseInt($(tmp).attr('col')); + } + // event before + var edata = this.trigger({ phase: 'before', target: this.name, type: 'click', recid: recid, column: column, originalEvent: event }); + if (edata.isCancelled === true) return; + // default action + var obj = this; + var sel = this.getSelection(); + $('#grid_'+ this.name +'_check_all').prop("checked", false); + var ind = this.get(recid, true); + var record = this.records[ind]; + var selectColumns = []; + obj.last.sel_ind = ind; + obj.last.sel_col = column; + obj.last.sel_recid = recid; + obj.last.sel_type = 'click'; + // multi select with shif key + if (event.shiftKey && sel.length > 0 && obj.multiSelect) { + if (sel[0].recid) { + var start = this.get(sel[0].recid, true); + var end = this.get(recid, true); + if (column > sel[0].column) { + var t1 = sel[0].column; + var t2 = column; + } else { + var t1 = column; + var t2 = sel[0].column; + } + for (var c = t1; c <= t2; c++) selectColumns.push(c); + } else { + var start = this.get(last_recid, true); + var end = this.get(recid, true); + } + var sel_add = []; + if (start > end) { var tmp = start; start = end; end = tmp; } + var url = (typeof this.url != 'object' ? this.url : this.url.get); + for (var i = start; i <= end; i++) { + if (this.searchData.length > 0 && !url && $.inArray(i, this.last.searchIds) == -1) continue; + if (this.selectType == 'row') { + sel_add.push(this.records[i].recid); + } else { + for (var sc = 0; sc < selectColumns.length; sc++) { + sel_add.push({ recid: this.records[i].recid, column: selectColumns[sc] }); + } + } + //sel.push(this.records[i].recid); + } + this.select(sel_add); + } else { + var last = this.last.selection; + var flag = (last.indexes.indexOf(ind) != -1 ? true : false); + var fselect = false; + // if clicked on the checkbox + if ($(event.target).parents('td').hasClass('w2ui-col-select')) fselect = true; + // clear other if necessary + if (((!event.ctrlKey && !event.shiftKey && !event.metaKey && !fselect) || !this.multiSelect) && !this.showSelectColumn) { + if (this.selectType != 'row' && $.inArray(column, last.columns[ind]) == -1) flag = false; + if (sel.length > 300) this.selectNone(); else this.unselect(sel); + if (flag === true && sel.length == 1) { + this.unselect({ recid: recid, column: column }); + } else { + this.select({ recid: recid, column: column }); + } + } else { + var isChecked = $(event.target).parents('tr').find('.w2ui-grid-select-check').is(':checked'); + if (this.selectType != 'row' && $.inArray(column, last.columns[ind]) == -1 && !isChecked) flag = false; + if (flag === true) { + this.unselect({ recid: recid, column: column }); + } else { + this.select({ recid: recid, column: column }); + } + } + } + this.status(); + obj.initResize(); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + columnClick: function (field, event) { + // event before + var edata = this.trigger({ phase: 'before', type: 'columnClick', target: this.name, field: field, originalEvent: event }); + if (edata.isCancelled === true) return; + // default behaviour + if (this.selectType == 'row') { + var column = this.getColumn(field); + if (column && column.sortable) this.sort(field, null, (event && (event.ctrlKey || event.metaKey) ? true : false) ); + if (edata.field == 'line-number') { + if (this.getSelection().length >= this.records.length) { + this.selectNone(); + } else { + this.selectAll(); + } + } + } else { + if (event.altKey){ + var column = this.getColumn(field); + if (column && column.sortable) this.sort(field, null, (event && (event.ctrlKey || event.metaKey) ? true : false) ); + } + // select entire column + if (edata.field == 'line-number') { + if (this.getSelection().length >= this.records.length) { + this.selectNone(); + } else { + this.selectAll(); + } + } else { + if (!event.shiftKey && !event.metaKey && !event.ctrlKey) { + this.selectNone(); + } + var tmp = this.getSelection(); + var column = this.getColumn(edata.field, true); + var sel = []; + var cols = []; + // check if there was a selection before + if (tmp.length != 0 && event.shiftKey) { + var start = column; + var end = tmp[0].column; + if (start > end) { + start = tmp[0].column; + end = column; + } + for (var i=start; i<=end; i++) cols.push(i); + } else { + cols.push(column); + } + var edata = this.trigger({ phase: 'before', type: 'columnSelect', target: this.name, columns: cols }); + if (edata.isCancelled !== true) { + for (var i = 0; i < this.records.length; i++) { + sel.push({ recid: this.records[i].recid, column: cols }); + } + this.select(sel); + } + this.trigger($.extend(edata, { phase: 'after' })); + } + } + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + columnDblClick: function (field, event) { + // event before + var edata = this.trigger({ phase: 'before', type: 'columnDblClick', target: this.name, field: field, originalEvent: event }); + if (edata.isCancelled === true) return; + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + focus: function (event) { + var obj = this; + // event before + var edata = this.trigger({ phase: 'before', type: 'focus', target: this.name, originalEvent: event }); + if (edata.isCancelled === true) return false; + // default behaviour + this.hasFocus = true; + $(this.box).removeClass('w2ui-inactive').find('.w2ui-inactive').removeClass('w2ui-inactive'); + setTimeout(function () { + var $input = $(obj.box).find('#grid_'+ obj.name + '_focus'); + if (!$input.is(':focus')) $input.focus(); + }, 10); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + blur: function (event) { + // event before + var edata = this.trigger({ phase: 'before', type: 'blur', target: this.name, originalEvent: event }); + if (edata.isCancelled === true) return false; + // default behaviour + this.hasFocus = false; + $(this.box).addClass('w2ui-inactive').find('.w2ui-selected').addClass('w2ui-inactive'); + $(this.box).find('.w2ui-selection').addClass('w2ui-inactive'); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + keydown: function (event) { + // this method is called from w2utils + var obj = this; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (obj.keyboard !== true) return; + // trigger event + var edata = obj.trigger({ phase: 'before', type: 'keydown', target: obj.name, originalEvent: event }); + if (edata.isCancelled === true) return; + // default behavior + if ($(this.box).find('>.w2ui-message').length > 0) { + // if there are messages + if (event.keyCode == 27) this.message(); + return + } + var empty = false; + var records = $('#grid_'+ obj.name +'_records'); + var sel = obj.getSelection(); + if (sel.length === 0) empty = true; + var recid = sel[0] || null; + var columns = []; + var recid2 = sel[sel.length-1]; + if (typeof recid == 'object' && recid != null) { + recid = sel[0].recid; + columns = []; + var ii = 0; + while (true) { + if (!sel[ii] || sel[ii].recid != recid) break; + columns.push(sel[ii].column); + ii++; + } + recid2 = sel[sel.length-1].recid; + } + var ind = obj.get(recid, true); + var ind2 = obj.get(recid2, true); + var rec = obj.get(recid); + var recEL = $('#grid_'+ obj.name +'_rec_'+ (ind != null ? w2utils.escapeId(obj.records[ind].recid) : 'none')); + var cancel = false; + var key = event.keyCode; + var shiftKey = event.shiftKey; + + switch (key) { + case 8: // backspace + case 46: // delete + // delete if button is visible + obj["delete"](); + cancel = true; + event.stopPropagation(); + break; + + case 27: // escape + obj.selectNone(); + cancel = true; + break; + + case 65: // cmd + A + if (!event.metaKey && !event.ctrlKey) break; + obj.selectAll(); + cancel = true; + break; + + case 13: // enter + // if expandable columns - expand it + if (this.selectType == 'row' && obj.show.expandColumn === true) { + if (recEL.length <= 0) break; + obj.toggle(recid, event); + cancel = true; + } else { // or enter edit + for (var c = 0; c < this.columns.length; c++) { + var edit = this.getCellEditable(ind, c); + if (edit) { + columns.push(parseInt(c)); + break; + } + } + // edit last column that was edited + if (this.selectType == 'row' && this.last._edit && this.last._edit.column) { + columns = [this.last._edit.column]; + } + if (columns.length > 0) { + obj.editField(recid, columns[0], null, event); + cancel = true; + } + } + break; + + case 37: // left + if (empty) { // no selection + selectTopRecord(); + break; + } + if (this.selectType == 'row') { + if (recEL.length <= 0) break; + var tmp = this.records[ind].w2ui || {}; + if (tmp && tmp.parent_recid != null && (!Array.isArray(tmp.children) || tmp.children.length === 0 || !tmp.expanded)) { + obj.unselect(recid); + obj.collapse(tmp.parent_recid, event); + obj.select(tmp.parent_recid); + } else { + obj.collapse(recid, event); + } + } else { + var prev = obj.prevCell(ind, columns[0]); + if (!shiftKey && prev == null) { + this.selectNone(); + prev = 0; + } + if (prev != null) { + if (shiftKey && obj.multiSelect) { + if (tmpUnselect()) return; + var tmp = []; + var newSel = []; + var unSel = []; + if (columns.indexOf(this.last.sel_col) === 0 && columns.length > 1) { + for (var i = 0; i < sel.length; i++) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid); + unSel.push({ recid: sel[i].recid, column: columns[columns.length-1] }); + } + obj.unselect(unSel); + obj.scrollIntoView(ind, columns[columns.length-1], true); + } else { + for (var i = 0; i < sel.length; i++) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid); + newSel.push({ recid: sel[i].recid, column: prev }); + } + obj.select(newSel); + obj.scrollIntoView(ind, prev, true); + } + } else { + event.metaKey = false; + obj.click({ recid: recid, column: prev }, event); + obj.scrollIntoView(ind, prev, true); + } + } else { + // if selected more then one, then select first + if (!shiftKey) { + if (sel.length > 1) { + obj.selectNone(); + } else { + for (var s = 1; s < sel.length; s++) obj.unselect(sel[s]); + } + } + } + } + cancel = true; + break; + + case 39: // right + if (empty) { + selectTopRecord(); + break; + } + if (this.selectType == 'row') { + if (recEL.length <= 0) break; + obj.expand(recid, event); + } else { + var next = obj.nextCell(ind, columns[columns.length-1]); // columns is an array of selected columns + if (!shiftKey && next == null) { + this.selectNone(); + next = this.columns.length-1; + } + if (next != null) { + if (shiftKey && key == 39 && obj.multiSelect) { + if (tmpUnselect()) return; + var tmp = []; + var newSel = []; + var unSel = []; + if (columns.indexOf(this.last.sel_col) == columns.length-1 && columns.length > 1) { + for (var i = 0; i < sel.length; i++) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid); + unSel.push({ recid: sel[i].recid, column: columns[0] }); + } + obj.unselect(unSel); + obj.scrollIntoView(ind, columns[0], true); + } else { + for (var i = 0; i < sel.length; i++) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid); + newSel.push({ recid: sel[i].recid, column: next }); + } + obj.select(newSel); + obj.scrollIntoView(ind, next, true); + } + } else { + event.metaKey = false; + obj.click({ recid: recid, column: next }, event); + obj.scrollIntoView(ind, next, true); + } + } else { + // if selected more then one, then select first + if (!shiftKey) { + if (sel.length > 1) { + obj.selectNone(); + } else { + for (var s = 0; s < sel.length-1; s++) obj.unselect(sel[s]); + } + } + } + } + cancel = true; + break; + + case 38: // up + if (empty) selectTopRecord(); + if (recEL.length <= 0) break; + // move to the previous record + var prev = obj.prevRow(ind, columns[0]); + if (!shiftKey && prev == null) { + if (this.searchData.length != 0 && !url) { + prev = this.last.searchIds[0]; + } else { + prev = 0; + } + } + if (prev != null) { + if (shiftKey && obj.multiSelect) { // expand selection + if (tmpUnselect()) return; + if (obj.selectType == 'row') { + if (obj.last.sel_ind > prev && obj.last.sel_ind != ind2) { + obj.unselect(obj.records[ind2].recid); + } else { + obj.select(obj.records[prev].recid); + } + } else { + if (obj.last.sel_ind > prev && obj.last.sel_ind != ind2) { + prev = ind2; + var tmp = []; + for (var c = 0; c < columns.length; c++) tmp.push({ recid: obj.records[prev].recid, column: columns[c] }); + obj.unselect(tmp); + } else { + var tmp = []; + for (var c = 0; c < columns.length; c++) tmp.push({ recid: obj.records[prev].recid, column: columns[c] }); + obj.select(tmp); + } + } + } else { // move selected record + if (sel.length > 300) this.selectNone(); else this.unselect(sel); + obj.click({ recid: obj.records[prev].recid, column: columns[0] }, event); + } + obj.scrollIntoView(prev); + if (event.preventDefault) event.preventDefault(); + } else { + // if selected more then one, then select first + if (!shiftKey) { + if (sel.length > 1) { + obj.selectNone(); + } else { + for (var s = 1; s < sel.length; s++) obj.unselect(sel[s]); + } + } + } + break; + + case 40: // down + if (empty) selectTopRecord(); + if (recEL.length <= 0) break; + // move to the next record + var next = obj.nextRow(ind2, columns[0]); + if (!shiftKey && next == null) { + if (this.searchData.length != 0 && !url) { + next = this.last.searchIds[this.last.searchIds.length - 1]; + } else { + next = this.records.length - 1; + } + } + if (next != null) { + if (shiftKey && obj.multiSelect) { // expand selection + if (tmpUnselect()) return; + if (obj.selectType == 'row') { + if (this.last.sel_ind < next && this.last.sel_ind != ind) { + obj.unselect(obj.records[ind].recid); + } else { + obj.select(obj.records[next].recid); + } + } else { + if (this.last.sel_ind < next && this.last.sel_ind != ind) { + next = ind; + var tmp = []; + for (var c = 0; c < columns.length; c++) tmp.push({ recid: obj.records[next].recid, column: columns[c] }); + obj.unselect(tmp); + } else { + var tmp = []; + for (var c = 0; c < columns.length; c++) tmp.push({ recid: obj.records[next].recid, column: columns[c] }); + obj.select(tmp); + } + } + } else { // move selected record + if (sel.length > 300) this.selectNone(); else this.unselect(sel); + obj.click({ recid: obj.records[next].recid, column: columns[0] }, event); + } + obj.scrollIntoView(next); + cancel = true; + } else { + // if selected more then one, then select first + if (!shiftKey) { + if (sel.length > 1) { + obj.selectNone(); + } else { + for (var s = 0; s < sel.length-1; s++) obj.unselect(sel[s]); + } + } + } + break; + + // copy & paste + + case 17: // ctrl key + case 91: // cmd key + // SLOW: 10k records take 7.0 + if (empty) break; + // in Safari need to copy to buffer on cmd or ctrl key (otherwise does not work) + if (obj.last.isSafari) { + obj.last.copy_event = obj.copy(false, event); + $('#grid_'+ obj.name + '_focus').val(obj.last.copy_event.text).select(); + } + break; + + case 67: // - c + // this fill trigger event.onComplete + if (event.metaKey || event.ctrlKey) { + if (obj.last.isSafari) { + obj.copy(obj.last.copy_event, event); + } else { + obj.last.copy_event = obj.copy(false, event); + $('#grid_'+ obj.name + '_focus').val(obj.last.copy_event.text).select(); + obj.copy(obj.last.copy_event, event); + } + } + break; + + case 88: // x - cut + if (empty) break; + if (event.ctrlKey || event.metaKey) { + if (obj.last.isSafari) { + obj.copy(obj.last.copy_event, event); + } else { + obj.last.copy_event = obj.copy(false, event); + $('#grid_'+ obj.name + '_focus').val(obj.last.copy_event.text).select(); + obj.copy(obj.last.copy_event, event); + } + } + break; + } + var tmp = [32, 187, 189, 192, 219, 220, 221, 186, 222, 188, 190, 191]; // other typable chars + for (var i=48; i<=111; i++) tmp.push(i); // 0-9,a-z,A-Z,numpad + if (tmp.indexOf(key) != -1 && !event.ctrlKey && !event.metaKey && !cancel) { + if (columns.length === 0) columns.push(0); + cancel = false; + // move typed key into edit + setTimeout(function () { + var focus = $('#grid_'+ obj.name + '_focus'); + var key = focus.val(); + focus.val(''); + obj.editField(recid, columns[0], key, event); + }, 1); + } + if (cancel) { // cancel default behaviour + if (event.preventDefault) event.preventDefault(); + } + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + + function selectTopRecord() { + var ind = Math.floor(records[0].scrollTop / obj.recordHeight) + 1; + if (!obj.records[ind] || ind < 2) ind = 0; + obj.select({ recid: obj.records[ind].recid, column: 0}); + } + + function tmpUnselect () { + if (obj.last.sel_type != 'click') return false; + if (obj.selectType != 'row') { + obj.last.sel_type = 'key'; + if (sel.length > 1) { + for (var s = 0; s < sel.length; s++) { + if (sel[s].recid == obj.last.sel_recid && sel[s].column == obj.last.sel_col) { + sel.splice(s, 1); + break; + } + } + obj.unselect(sel); + return true; + } + return false; + } else { + obj.last.sel_type = 'key'; + if (sel.length > 1) { + sel.splice(sel.indexOf(obj.records[obj.last.sel_ind].recid), 1); + obj.unselect(sel); + return true; + } + return false; + } + } + }, + + scrollIntoView: function (ind, column, instant) { + var buffered = this.records.length; + if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length; + if (buffered === 0) return; + if (ind == null) { + var sel = this.getSelection(); + if (sel.length === 0) return; + if ($.isPlainObject(sel[0])) { + ind = sel[0].index; + column = sel[0].column; + } else { + ind = this.get(sel[0], true); + } + } + var records = $('#grid_'+ this.name +'_records'); + // if all records in view + var len = this.last.searchIds.length; + if (len > 0) ind = this.last.searchIds.indexOf(ind); // if search is applied + + // vertical + if (records.height() < this.recordHeight * (len > 0 ? len : buffered) && records.length > 0) { + // scroll to correct one + var t1 = Math.floor(records[0].scrollTop / this.recordHeight); + var t2 = t1 + Math.floor(records.height() / this.recordHeight); + if (ind == t1) { + if (instant === true) { + records.prop({ 'scrollTop': records.scrollTop() - records.height() / 1.3 }); + } else { + records.stop(); + records.animate({ 'scrollTop': records.scrollTop() - records.height() / 1.3 }, 250, 'linear'); + } + } + if (ind == t2) { + if (instant === true) { + records.prop({ 'scrollTop': records.scrollTop() + records.height() / 1.3 }); + } else { + records.stop(); + records.animate({ 'scrollTop': records.scrollTop() + records.height() / 1.3 }, 250, 'linear'); + } + } + if (ind < t1 || ind > t2) { + if (instant === true) { + records.prop({ 'scrollTop': (ind - 1) * this.recordHeight }); + } else { + records.stop(); + records.animate({ 'scrollTop': (ind - 1) * this.recordHeight }, 250, 'linear'); + } + } + } + + // horizontal + if (column != null) { + var x1 = 0; + var x2 = 0; + var sb = w2utils.scrollBarSize(); + for (var i = 0; i <= column; i++) { + var col = this.columns[i]; + if (col.frozen || col.hidden) continue; + x1 = x2; + x2 += parseInt(col.sizeCalculated); + } + if (records.width() < x2 - records.scrollLeft()) { // right + if (instant === true) { + records.prop({ 'scrollLeft': x1 - sb }); + } else { + records.animate({ 'scrollLeft': x1 - sb }, 250, 'linear'); + } + } else if (x1 < records.scrollLeft()) { // left + if (instant === true) { + records.prop({ 'scrollLeft': x2 - records.width() + sb * 2 }); + } else { + records.animate({ 'scrollLeft': x2 - records.width() + sb * 2 }, 250, 'linear'); + } + } + } + }, + + dblClick: function (recid, event) { + // find columns + var column = null; + if ((typeof recid == 'object') && (recid !== null)) { + column = recid.column; + recid = recid.recid; + } + if (event == null) event = {}; + // column user clicked on + if (column == null && event.target) { + var tmp = event.target; + if (tmp.tagName.toUpperCase() != 'TD') tmp = $(tmp).parents('td')[0]; + column = parseInt($(tmp).attr('col')); + } + var index = this.get(recid, true); + var rec = this.records[index]; + // event before + var edata = this.trigger({ phase: 'before', target: this.name, type: 'dblClick', recid: recid, column: column, originalEvent: event }); + if (edata.isCancelled === true) return; + // default action + this.selectNone(); + var edit = this.getCellEditable(index, column); + if (edit) { + this.editField(recid, column, null, event); + } else { + this.select({ recid: recid, column: column }); + if (this.show.expandColumn || (rec.w2ui && Array.isArray(rec.w2ui.children))) this.toggle(recid); + } + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + contextMenu: function (recid, column, event) { + var obj = this; + if (obj.last.userSelect == 'text') return; + if (event == null) event = { offsetX: 0, offsetY: 0, target: $('#grid_'+ obj.name +'_rec_'+ recid)[0] }; + if (event.offsetX == null) { + event.offsetX = event.layerX - event.target.offsetLeft; + event.offsetY = event.layerY - event.target.offsetTop; + } + if (w2utils.isFloat(recid)) recid = parseFloat(recid); + var sel = this.getSelection(); + if (this.selectType == 'row') { + if (sel.indexOf(recid) == -1) obj.click(recid); + } else { + var $tmp = $(event.target); + if ($tmp[0].tagName.toUpperCase() != 'TD') $tmp = $(event.target).parents('td'); + var selected = false; + column = $tmp.attr('col'); + // check if any selected sel in the right row/column + for (var i=0; i 0) { + $(obj.box).find(event.target) + .w2menu(obj.menu, { + originalEvent: event, + contextMenu: true, + onSelect: function (event) { + obj.menuClick(recid, event); + } + } + ); + } + // cancel event + if (event.preventDefault) event.preventDefault(); + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + }, + + menuClick: function (recid, event) { + var obj = this; + // event before + var edata = obj.trigger({ + phase: 'before', type: 'menuClick', target: obj.name, + originalEvent: event.originalEvent, menuEvent: event, + recid: recid, menuIndex: event.index, menuItem: event.item + }); + if (edata.isCancelled === true) return; + // default action + // -- empty + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + }, + + toggle: function (recid) { + var rec = this.get(recid); + rec.w2ui = rec.w2ui || {}; + if (rec.w2ui.expanded === true) return this.collapse(recid); else return this.expand(recid); + }, + + expand: function (recid) { + var obj = this; + var ind = this.get(recid, true); + var rec = this.records[ind]; + rec.w2ui = rec.w2ui || {}; + var id = w2utils.escapeId(recid); + var children = rec.w2ui.children; + if (Array.isArray(children)) { + if (rec.w2ui.expanded === true || children.length === 0) return false; // already shown + var edata = this.trigger({ phase: 'before', type: 'expand', target: this.name, recid: recid }); + if (edata.isCancelled === true) return false; + rec.w2ui.expanded = true; + children.forEach(function (child) { + child.w2ui = child.w2ui || {}; + child.w2ui.parent_recid = rec.recid; + if (child.w2ui.children == null) child.w2ui.children = []; + }); + this.records.splice.apply(this.records, [ind + 1, 0].concat(children)); + this.total += children.length; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (!url) { + this.localSort(true, true); + if (this.searchData.length > 0) { + this.localSearch(true); + } + } + this.refresh(); + this.trigger($.extend(edata, { phase: 'after' })); + } else { + if ($('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').length > 0 || this.show.expandColumn !== true) return false; + if (rec.w2ui.expanded == 'none') return false; + // insert expand row + $('#grid_'+ this.name +'_rec_'+ id).after( + ''+ + ' '+ + '
'+ + ' '+ + ' '+ + ''); + + $('#grid_'+ this.name +'_frec_'+ id).after( + ''+ + (this.show.lineNumbers ? '' : '') + + ' '+ + '
'+ + ' '+ + ''); + + // event before + var edata = this.trigger({ phase: 'before', type: 'expand', target: this.name, recid: recid, + box_id: 'grid_'+ this.name +'_rec_'+ recid +'_expanded', fbox_id: 'grid_'+ this.name +'_frec_'+ id +'_expanded' }); + if (edata.isCancelled === true) { + $('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').remove(); + $('#grid_'+ this.name +'_frec_'+ id +'_expanded_row').remove(); + return false; + } + // expand column + var row1 = $(this.box).find('#grid_'+ this.name +'_rec_'+ recid +'_expanded'); + var row2 = $(this.box).find('#grid_'+ this.name +'_frec_'+ recid +'_expanded'); + var innerHeight = row1.find('> div:first-child').height(); + if (row1.height() < innerHeight) { + row1.css({ height: innerHeight + 'px' }); + } + if (row2.height() < innerHeight) { + row2.css({ height: innerHeight + 'px' }); + } + // default action + $('#grid_'+ this.name +'_rec_'+ id).attr('expanded', 'yes').addClass('w2ui-expanded'); + $('#grid_'+ this.name +'_frec_'+ id).attr('expanded', 'yes').addClass('w2ui-expanded'); + // $('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').show(); + $('#grid_'+ this.name +'_cell_'+ this.get(recid, true) +'_expand div').html('-'); + rec.w2ui.expanded = true; + // event after + this.trigger($.extend(edata, { phase: 'after' })); + this.resizeRecords(); + } + return true; + }, + + collapse: function (recid) { + var obj = this; + var ind = this.get(recid, true); + var rec = this.records[ind]; + rec.w2ui = rec.w2ui || {}; + var id = w2utils.escapeId(recid); + var children = rec.w2ui.children; + if (Array.isArray(children)) { + if (rec.w2ui.expanded !== true) return false; // already hidden + var edata = this.trigger({ phase: 'before', type: 'collapse', target: this.name, recid: recid }); + if (edata.isCancelled === true) return false; + clearExpanded(rec); + var stops = []; + for (var r = rec; r != null; r = this.get(r.w2ui.parent_recid)) + stops.push(r.w2ui.parent_recid); + // stops contains 'undefined' plus the ID of all nodes in the path from 'rec' to the tree root + var start = ind + 1; + var end = start; + while (true) { + if (this.records.length <= end + 1 || this.records[end+1].w2ui == null || + stops.indexOf(this.records[end+1].w2ui.parent_recid) >= 0) { + break; + } + end++; + } + this.records.splice(start, end - start + 1); + this.total -= end - start + 1; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (!url) { + if (this.searchData.length > 0) { + this.localSearch(true); + } + } + this.refresh(); + obj.trigger($.extend(edata, { phase: 'after' })); + } else { + if ($('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').length === 0 || this.show.expandColumn !== true) return false; + // event before + var edata = this.trigger({ phase: 'before', type: 'collapse', target: this.name, recid: recid, + box_id: 'grid_'+ this.name +'_rec_'+ id +'_expanded', fbox_id: 'grid_'+ this.name +'_frec_'+ id +'_expanded' }); + if (edata.isCancelled === true) return false; + // default action + $('#grid_'+ this.name +'_rec_'+ id).removeAttr('expanded').removeClass('w2ui-expanded'); + $('#grid_'+ this.name +'_frec_'+ id).removeAttr('expanded').removeClass('w2ui-expanded'); + $('#grid_'+ this.name +'_cell_'+ this.get(recid, true) +'_expand div').html('+'); + $('#grid_'+ obj.name +'_rec_'+ id +'_expanded').css('height', '0px'); + $('#grid_'+ obj.name +'_frec_'+ id +'_expanded').css('height', '0px'); + setTimeout(function () { + $('#grid_'+ obj.name +'_rec_'+ id +'_expanded_row').remove(); + $('#grid_'+ obj.name +'_frec_'+ id +'_expanded_row').remove(); + rec.w2ui.expanded = false; + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + obj.resizeRecords(); + }, 300); + } + return true; + + function clearExpanded(rec) { + rec.w2ui.expanded = false; + for (var i = 0; i < rec.w2ui.children.length; i++) { + var subRec = rec.w2ui.children[i]; + if (subRec.w2ui.expanded) { + clearExpanded(subRec); + } + } + } + }, + + sort: function (field, direction, multiField) { // if no params - clears sort + // event before + var edata = this.trigger({ phase: 'before', type: 'sort', target: this.name, field: field, direction: direction, multiField: multiField }); + if (edata.isCancelled === true) return; + // check if needed to quit + if (field != null) { + // default action + var sortIndex = this.sortData.length; + for (var s = 0; s < this.sortData.length; s++) { + if (this.sortData[s].field == field) { sortIndex = s; break; } + } + if (direction == null) { + if (this.sortData[sortIndex] == null) { + direction = 'asc'; + } else { + if(this.sortData[sortIndex].direction == null) { + this.sortData[sortIndex].direction = ''; + } + switch (this.sortData[sortIndex].direction.toLowerCase()) { + case 'asc' : direction = 'desc'; break; + case 'desc' : direction = 'asc'; break; + default : direction = 'asc'; break; + } + } + } + if (this.multiSort === false) { this.sortData = []; sortIndex = 0; } + if (multiField != true) { this.sortData = []; sortIndex = 0; } + // set new sort + if (this.sortData[sortIndex] == null) this.sortData[sortIndex] = {}; + this.sortData[sortIndex].field = field; + this.sortData[sortIndex].direction = direction; + } else { + this.sortData = []; + } + // if local + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (!url) { + this.localSort(true, true); + if (this.searchData.length > 0) this.localSearch(true); + // reset vertical scroll + this.last.scrollTop = 0; + $('#grid_'+ this.name +'_records').prop('scrollTop', 0); + // event after + this.trigger($.extend(edata, { phase: 'after', direction: direction })); + this.refresh(); + } else { + // event after + this.trigger($.extend(edata, { phase: 'after', direction: direction })); + this.last.xhr_offset = 0; + this.reload(); + } + }, + + copy: function (flag, oEvent) { + if ($.isPlainObject(flag)) { + // event after + this.trigger($.extend(flag, { phase: 'after' })); + return flag.text; + } + // generate text to copy + var sel = this.getSelection(); + if (sel.length === 0) return ''; + var text = ''; + if (typeof sel[0] == 'object') { // cell copy + // find min/max column + var minCol = sel[0].column; + var maxCol = sel[0].column; + var recs = []; + for (var s = 0; s < sel.length; s++) { + if (sel[s].column < minCol) minCol = sel[s].column; + if (sel[s].column > maxCol) maxCol = sel[s].column; + if (recs.indexOf(sel[s].index) == -1) recs.push(sel[s].index); + } + recs.sort(function(a, b) { return a-b; }); // sort function must be for numerical sort + for (var r = 0 ; r < recs.length; r++) { + var ind = recs[r]; + for (var c = minCol; c <= maxCol; c++) { + var col = this.columns[c]; + if (col.hidden === true) continue; + text += this.getCellCopy(ind, c) + '\t'; + } + text = text.substr(0, text.length-1); // remove last \t + text += '\n'; + } + } else { // row copy + // copy headers + for (var c = 0; c < this.columns.length; c++) { + var col = this.columns[c]; + if (col.hidden === true) continue; + var colName = (col.text ? col.text : col.field); + if (col.text && col.text.length < 3 && col.tooltip) colName = col.tooltip; // if column name is less then 3 char and there is tooltip - use it + text += '"' + w2utils.stripTags(colName) + '"\t'; + } + text = text.substr(0, text.length-1); // remove last \t + text += '\n'; + // copy selected text + for (var s = 0; s < sel.length; s++) { + var ind = this.get(sel[s], true); + for (var c = 0; c < this.columns.length; c++) { + var col = this.columns[c]; + if (col.hidden === true) continue; + text += '"' + this.getCellCopy(ind, c) + '"\t'; + } + text = text.substr(0, text.length-1); // remove last \t + text += '\n'; + } + } + text = text.substr(0, text.length - 1); + + // if called without params + if (flag == null) { + // before event + var edata = this.trigger({ phase: 'before', type: 'copy', target: this.name, text: text, + cut: (oEvent.keyCode == 88 ? true : false), originalEvent: oEvent }); + if (edata.isCancelled === true) return ''; + text = edata.text; + // event after + this.trigger($.extend(edata, { phase: 'after' })); + return text; + } else if (flag === false) { // only before event + // before event + var edata = this.trigger({ phase: 'before', type: 'copy', target: this.name, text: text, + cut: (oEvent.keyCode == 88 ? true : false), originalEvent: oEvent }); + if (edata.isCancelled === true) return ''; + text = edata.text; + return edata; + } + }, + + /** + * Gets value to be copied to the clipboard + * @param ind index of the record + * @param col_ind index of the column + * @returns the displayed value of the field's record associated with the cell + */ + getCellCopy: function(ind, col_ind) { + return w2utils.stripTags(this.getCellHTML(ind, col_ind)); + }, + + paste: function (text) { + var sel = this.getSelection(); + var ind = this.get(sel[0].recid, true); + var col = sel[0].column; + // before event + var edata = this.trigger({ phase: 'before', type: 'paste', target: this.name, text: text, index: ind, column: col }); + if (edata.isCancelled === true) return; + text = edata.text; + // default action + if (this.selectType == 'row' || sel.length === 0) { + console.log('ERROR: You can paste only if grid.selectType = \'cell\' and when at least one cell selected.'); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + return; + } + var newSel = []; + var text = text.split('\n'); + for (var t = 0; t < text.length; t++) { + var tmp = text[t].split('\t'); + var cnt = 0; + var rec = this.records[ind]; + var cols = []; + if (rec == null) continue; + for (var dt = 0; dt < tmp.length; dt++) { + if (!this.columns[col + cnt]) continue; + this.setCellPaste(rec, col + cnt, tmp[dt]); + cols.push(col + cnt); + cnt++; + } + for (var c = 0; c < cols.length; c++) newSel.push({ recid: rec.recid, column: cols[c] }); + ind++; + } + this.selectNone(); + this.select(newSel); + this.refresh(); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + /** + * Sets record field using clipboard text + * @param rec record + * @param col_ind column index + * @param paste sub part of the pasted text + */ + setCellPaste: function(rec, col_ind, paste) { + var field = this.columns[col_ind].field; + rec.w2ui = rec.w2ui || {}; + rec.w2ui.changes = rec.w2ui.changes || {}; + rec.w2ui.changes[field] = paste; + }, + + // ================================================== + // --- Common functions + + resize: function () { + var obj = this; + var time = (new Date()).getTime(); + // make sure the box is right + if (!this.box || $(this.box).attr('name') != this.name) return; + // determine new width and height + $(this.box).find('> div.w2ui-grid-box') + .css('width', $(this.box).width()) + .css('height', $(this.box).height()); + // event before + var edata = this.trigger({ phase: 'before', type: 'resize', target: this.name }); + if (edata.isCancelled === true) return; + // resize + obj.resizeBoxes(); + obj.resizeRecords(); + if (obj.toolbar && obj.toolbar.resize) obj.toolbar.resize(); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + update: function (cells) { + var time = (new Date()).getTime(); + if (this.box == null) return 0; + if (cells == null) { + for (var index = this.last.range_start - 1; index <= this.last.range_end - 1; index++) { + if (index < 0) continue; + var rec = this.records[index] || {}; + if (!rec.w2ui) rec.w2ui = {}; + for (var column = 0; column < this.columns.length; column++) { + var row = $(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(rec.recid)); + var cell = $(this.box).find('#grid_'+ this.name + '_data_'+ index +'_'+ column); + cell.replaceWith(this.getCellHTML(index, column, false)); + cell = $(this.box).find('#grid_'+ this.name + '_data_'+ index +'_'+ column); // need to reselect as it was replaced + // assign style + if (rec.w2ui.style != null && !$.isEmptyObject(rec.w2ui.style)) { + if (typeof rec.w2ui.style == 'string') { + row.attr('style', rec.w2ui.style); + } + if ($.isPlainObject(rec.w2ui.style) && typeof rec.w2ui.style[column] == 'string') { + cell.attr('style', rec.w2ui.style[column]); + } + } else { + cell.attr('style', ''); + } + // assign class + if (rec.w2ui.class != null && !$.isEmptyObject(rec.w2ui.class)) { + if (typeof rec.w2ui.class == 'string') { + row.addClass(rec.w2ui.class); + } + if ($.isPlainObject(rec.w2ui.class) && typeof rec.w2ui.class[column] == 'string') { + cell.addClass(rec.w2ui.class[column]); + } + } + } + } + + } else { + + for (var i = 0; i < cells.length; i++) { + var index = cells[i].index; + var column = cells[i].column; + if (index < 0) continue; + if (index == null || column == null) { + console.log('ERROR: Wrong argument for grid.update(cells), cells should be [{ index: X, column: Y }, ...]'); + continue; + } + var rec = this.records[index] || {}; + var row = $(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(rec.recid)); + var cell = $(this.box).find('#grid_'+ this.name + '_data_'+ index +'_'+ column); + if (!rec.w2ui) rec.w2ui = {}; + cell.replaceWith(this.getCellHTML(index, column, false)); + cell = $(this.box).find('#grid_'+ this.name + '_data_'+ index +'_'+ column); // need to reselect as it was replaced + // assign style + if (rec.w2ui.style != null && !$.isEmptyObject(rec.w2ui.style)) { + if (typeof rec.w2ui.style == 'string') { + row.attr('style', rec.w2ui.style); + } + if ($.isPlainObject(rec.w2ui.style) && typeof rec.w2ui.style[column] == 'string') { + cell.attr('style', rec.w2ui.style[column]); + } + } else { + cell.attr('style', ''); + } + // assign class + if (rec.w2ui.class != null && !$.isEmptyObject(rec.w2ui.class)) { + if (typeof rec.w2ui.class == 'string') { + row.addClass(rec.w2ui.class); + } + if ($.isPlainObject(rec.w2ui.class) && typeof rec.w2ui.class[column] == 'string') { + cell.addClass(rec.w2ui.class[column]); + } + } + } + } + return (new Date()).getTime() - time; + }, + + refreshCell: function (recid, field) { + var index = this.get(recid, true); + var col_ind = this.getColumn(field, true); + var isSummary = (this.records[index] && this.records[index].recid == recid ? false : true); + var cell = $(this.box).find((isSummary ? '.w2ui-grid-summary ' : '') + '#grid_'+ this.name + '_data_'+ index +'_'+ col_ind); + if (cell.length == 0) return false; + // set cell html and changed flag + cell.replaceWith(this.getCellHTML(index, col_ind, isSummary)); + }, + + refreshRow: function (recid, ind) { + var tr1 = $(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)); + var tr2 = $(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)); + if (tr1.length > 0) { + if (ind == null) ind = this.get(recid, true); + var line = tr1.attr('line'); + var isSummary = (this.records[ind] && this.records[ind].recid == recid ? false : true); + // if it is searched, find index in search array + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (this.searchData.length > 0 && !url) for (var s = 0; s < this.last.searchIds.length; s++) if (this.last.searchIds[s] == ind) ind = s; + var rec_html = this.getRecordHTML(ind, line, isSummary); + $(tr1).replaceWith(rec_html[0]); + $(tr2).replaceWith(rec_html[1]); + // apply style to row if it was changed in render functions + var st = (this.records[ind].w2ui ? this.records[ind].w2ui.style : ''); + if (typeof st == 'string') { + var tr1 = $(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)); + var tr2 = $(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)); + tr1.attr('custom_style', st); + tr2.attr('custom_style', st); + if (tr1.hasClass('w2ui-selected')) { + st = st.replace('background-color', 'none'); + } + tr1[0].style.cssText = 'height: '+ this.recordHeight + 'px;' + st; + tr2[0].style.cssText = 'height: '+ this.recordHeight + 'px;' + st; + } + if (isSummary) { + this.resize(); + } + } + }, + + refresh: function () { + var obj = this; + var time = (new Date()).getTime(); + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (this.total <= 0 && !url && this.searchData.length === 0) { + this.total = this.records.length; + } + this.toolbar.disable('w2ui-edit', 'w2ui-delete'); + if (!this.box) return; + // event before + var edata = this.trigger({ phase: 'before', target: this.name, type: 'refresh' }); + if (edata.isCancelled === true) return; + // -- header + if (this.show.header) { + $('#grid_'+ this.name +'_header').html(this.header +' ').show(); + } else { + $('#grid_'+ this.name +'_header').hide(); + } + // -- toolbar + if (this.show.toolbar) { + // if select-collumn is checked - no toolbar refresh + if (this.toolbar && this.toolbar.get('w2ui-column-on-off') && this.toolbar.get('w2ui-column-on-off').checked) { + // no action + } else { + $('#grid_'+ this.name +'_toolbar').show(); + // refresh toolbar all but search field + if (typeof this.toolbar == 'object') { + var tmp = this.toolbar.items; + for (var t = 0; t < tmp.length; t++) { + if (tmp[t].id == 'w2ui-search' || tmp[t].type == 'break') continue; + this.toolbar.refresh(tmp[t].id); + } + } + } + } else { + $('#grid_'+ this.name +'_toolbar').hide(); + } + // -- make sure search is closed + this.searchClose(); + // search placeholder + var el = $('#grid_'+ obj.name +'_search_all'); + if (!this.multiSearch && this.last.field == 'all' && this.searches.length > 0) { + this.last.field = this.searches[0].field; + this.last.label = this.searches[0].label; + } + for (var s = 0; s < this.searches.length; s++) { + if (this.searches[s].field == this.last.field) this.last.label = this.searches[s].label; + } + if (this.last.multi) { + el.attr('placeholder', '[' + w2utils.lang('Multiple Fields') + ']'); + el.w2field('clear'); + } else { + el.attr('placeholder', w2utils.lang(this.last.label)); + } + if (el.val() != this.last.search) { + var val = this.last.search; + var tmp = el.data('w2field'); + if (tmp) val = tmp.format(val); + el.val(val); + } + + // -- body + obj.refreshBody(); + + // -- footer + if (this.show.footer) { + $('#grid_'+ this.name +'_footer').html(this.getFooterHTML()).show(); + } else { + $('#grid_'+ this.name +'_footer').hide(); + } + // show/hide clear search link + var $clear = $('#grid_'+ this.name +'_searchClear'); + $clear.hide(); + this.searchData.some(function (item) { + var tmp = obj.getSearch(item.field); + if (obj.last.multi || (tmp && !tmp.hidden && ['list', 'enum'].indexOf(tmp.type) == -1)) { + $clear.show(); + return true; + } + }); + // all selected? + var sel = this.last.selection, + areAllSelected = (this.records.length > 0 && sel.indexes.length == this.records.length), + areAllSearchedSelected = (sel.indexes.length > 0 && this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length); + if (areAllSelected || areAllSearchedSelected) { + $('#grid_'+ this.name +'_check_all').prop('checked', true); + } else { + $('#grid_'+ this.name +'_check_all').prop('checked', false); + } + // show number of selected + this.status(); + // collapse all records + var rows = obj.find({ 'w2ui.expanded': true }, true); + for (var r = 0; r < rows.length; r++) { + var tmp = obj.records[rows[r]].w2ui; + if (tmp && !Array.isArray(tmp.children)) { + tmp.expanded = false; + } + } + // mark selection + if (obj.markSearch) { + setTimeout(function () { + // mark all search strings + var search = []; + for (var s = 0; s < obj.searchData.length; s++) { + var sdata = obj.searchData[s]; + var fld = obj.getSearch(sdata.field); + if (!fld || fld.hidden) continue; + var ind = obj.getColumn(sdata.field, true); + search.push({ field: sdata.field, search: sdata.value, col: ind }); + } + if (search.length > 0) { + search.forEach(function (item) { + $(obj.box).find('td[col="'+ item.col +'"]').not('.w2ui-head').w2marker(item.search); + }); + } + }, 50); + } + // enable/disable toolbar search button + if (this.show.toolbarSave) { + if (this.getChanges().length > 0) this.toolbar.enable('w2ui-save'); else this.toolbar.disable('w2ui-save'); + } + // event after + this.trigger($.extend(edata, { phase: 'after' })); + obj.resize(); + obj.addRange('selection'); + setTimeout(function () { // allow to render first + obj.resize(); // needed for horizontal scroll to show (do not remove) + obj.scroll(); + }, 1); + + if ( obj.reorderColumns && !obj.last.columnDrag ) { + obj.last.columnDrag = obj.initColumnDrag(); + } else if ( !obj.reorderColumns && obj.last.columnDrag ) { + obj.last.columnDrag.remove(); + } + return (new Date()).getTime() - time; + }, + + refreshBody: function () { + // -- separate summary + var obj = this, + tmp = this.find({ 'w2ui.summary': true }, true); + if (tmp.length > 0) { + for (var t = 0; t < tmp.length; t++) this.summary.push(this.records[tmp[t]]); + for (var t = tmp.length-1; t >= 0; t--) this.records.splice(tmp[t], 1); + } + + // -- body + this.scroll(); // need to calculate virtual scolling for columns + var recHTML = this.getRecordsHTML(); + var colHTML = this.getColumnsHTML(); + var bodyHTML = + '
'+ + recHTML[0] + + '
'+ + '
' + + recHTML[1] + + '
'+ + '
'+ + // Columns need to be after to be able to overlap + '
'+ + ' '+ colHTML[0] +'
'+ + '
'+ + '
'+ + ' '+ colHTML[1] +'
'+ + '
'; + + var gridBody = $('#grid_'+ this.name +'_body', obj.box).html(bodyHTML), + records = $('#grid_'+ this.name +'_records', obj.box); + var frecords = $('#grid_'+ this.name +'_frecords', obj.box); + var self = this; + if (this.selectType == 'row') { + records + .on('mouseover mouseout', 'tr', function(event) { + $('#grid_'+ self.name +'_frec_' + w2utils.escapeId($(this).attr('recid'))).toggleClass('w2ui-record-hover', event.type == 'mouseover') + }) + frecords + .on('mouseover mouseout', 'tr', function(event) { + $('#grid_'+ self.name +'_rec_' + w2utils.escapeId($(this).attr('recid'))).toggleClass('w2ui-record-hover', event.type == 'mouseover') + }) + } + if(w2utils.isIOS) + records.add(frecords) + .on('click', 'tr', function(ev) { + self.dblClick($(this).attr('recid'), ev); + }) + else + records.add(frecords) + .on('click', 'tr', function(ev) { + self.click($(this).attr('recid'), ev); + }) + .on('contextmenu', 'tr', function(ev) { + self.contextMenu($(this).attr('recid'), null, ev); + }) + + // enable scrolling on frozen records, + gridBody.data('scrolldata', { lastTime: 0, lastDelta: 0, time: 0 }) + .find('.w2ui-grid-frecords') + .on("mousewheel DOMMouseScroll ", function(event) { + event.preventDefault(); + + var e = event.originalEvent, + scrolldata = gridBody.data('scrolldata'), + recordsContainer = $(this).siblings('.w2ui-grid-records').addBack().filter('.w2ui-grid-records'), + amount = typeof e.wheelDelta != null ? e.wheelDelta * -1 / 120 : (e.detail || e.deltaY) / 3, // normalizing scroll speed + newScrollTop = recordsContainer.scrollTop(); + + scrolldata.time = +new Date(); + + if (scrolldata.lastTime < scrolldata.time - 150) { + scrolldata.lastDelta = 0; + } + + scrolldata.lastTime = scrolldata.time; + scrolldata.lastDelta += amount; + + if (Math.abs(scrolldata.lastDelta) < 1) { + amount = 0; + } else { + amount = Math.round(scrolldata.lastDelta); + } + gridBody.data('scrolldata', scrolldata); + + // make scroll amount dependent on visible rows + amount *= (Math.round(records.height() / obj.recordHeight) - 1) * obj.recordHeight / 4; + recordsContainer.stop().animate({ 'scrollTop': newScrollTop + amount }, 250, 'linear'); + }); + + if (this.records.length === 0 && this.msgEmpty) { + $('#grid_'+ this.name +'_body') + .append('
'+ this.msgEmpty +'
'); + } else if ($('#grid_'+ this.name +'_empty_msg').length > 0) { + $('#grid_'+ this.name +'_empty_msg').remove(); + } + // show summary records + if (this.summary.length > 0) { + var sumHTML = this.getSummaryHTML(); + $('#grid_'+ this.name +'_fsummary').html(sumHTML[0]).show(); + $('#grid_'+ this.name +'_summary').html(sumHTML[1]).show(); + } else { + $('#grid_'+ this.name +'_fsummary').hide(); + $('#grid_'+ this.name +'_summary').hide(); + } + }, + + render: function (box) { + var obj = this; + var time = (new Date()).getTime(); + if (box != null) { + if ($(this.box).find('#grid_'+ this.name +'_body').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-grid w2ui-inactive') + .html(''); + } + this.box = box; + } + if (!this.box) return; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + // event before + var edata = this.trigger({ phase: 'before', target: this.name, type: 'render', box: box }); + if (edata.isCancelled === true) return; + // reset needed if grid existed + this.reset(true); + // --- default search field + if (!this.last.field) { + if (!this.multiSearch || !this.show.searchAll) { + var tmp = 0; + while (tmp < this.searches.length && (this.searches[tmp].hidden || this.searches[tmp].simple === false)) tmp++; + if (tmp >= this.searches.length) { + // all searches are hidden + this.last.field = ''; + this.last.label = ''; + } else { + this.last.field = this.searches[tmp].field; + this.last.label = this.searches[tmp].label; + } + } else { + this.last.field = 'all'; + this.last.label = w2utils.lang('All Fields'); + } + } + // insert elements + $(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-grid w2ui-inactive') + .html('
'+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'+ + ' '+ + ' '+ // readonly needed on android not to open keyboard + '
'); + if (this.selectType != 'row') $(this.box).addClass('w2ui-ss'); + if ($(this.box).length > 0) $(this.box)[0].style.cssText += this.style; + // init toolbar + this.initToolbar(); + if (this.toolbar != null) this.toolbar.render($('#grid_'+ this.name +'_toolbar')[0]); + // reinit search_all + if (this.last.field && this.last.field != 'all') { + var sd = this.searchData; + setTimeout(function () { obj.initAllField(obj.last.field, (sd.length == 1 ? sd[0].value : null)); }, 1); + } + // init footer + $('#grid_'+ this.name +'_footer').html(this.getFooterHTML()); + // refresh + if (!this.last.state) this.last.state = this.stateSave(true); // initial default state + this.stateRestore(); + if (url) { this.clear(); this.refresh(); } // show empty grid (need it) - should it be only for remote data source + // if hidden searches - apply it + var hasHiddenSearches = false; + for (var i = 0; i < this.searches.length; i++) { + if (this.searches[i].hidden) { hasHiddenSearches = true; break; } + } + if (hasHiddenSearches) { + this.searchReset(false); // will call reload + if (!url) setTimeout(function () { obj.searchReset(); }, 1); + } else { + this.reload(); + } + // focus + $(this.box).find('#grid_'+ this.name + '_focus') + .on('focus', function (event) { + clearTimeout(obj.last.kbd_timer); + if (!obj.hasFocus) obj.focus(); + }) + .on('blur', function (event) { + clearTimeout(obj.last.kbd_timer); + obj.last.kbd_timer = setTimeout(function () { + if (obj.hasFocus) { obj.blur(); } + }, 100); // need this timer to be 100 ms + }) + .on('paste', function (event) { + var cd = (event.originalEvent.clipboardData ? event.originalEvent.clipboardData : null) + if (cd && cd.types && cd.types.indexOf('text/plain') != -1) { + event.preventDefault() + var text = cd.getData('text/plain'); + if (text.indexOf('\r') != -1 && text.indexOf('\n') == -1) { + text = text.replace(/\r/g, '\n'); + } + w2ui[obj.name].paste(text); + } else { + // for older browsers + var el = this; + setTimeout(function () { w2ui[obj.name].paste(el.value); el.value = ''; }, 1) + } + }) + .on('keydown', function (event) { + w2ui[obj.name].keydown.call(w2ui[obj.name], event); + }); + // init mouse events for mouse selection + var edataCol; // event for column select + $(this.box).off('mousedown').on('mousedown', mouseStart); + this.updateToolbar() + // event after + this.trigger($.extend(edata, { phase: 'after' })); + // attach to resize event + if ($('.w2ui-layout').length === 0) { // if there is layout, it will send a resize event + $(window) + .off('resize.w2ui-'+ obj.name) + .on('resize.w2ui-'+ obj.name, function (event) { + if (w2ui[obj.name] == null) { + $(window).off('resize.w2ui-'+ obj.name) + } else { + w2ui[obj.name].resize(); + } + }); + } + return (new Date()).getTime() - time; + + function mouseStart (event) { + if (event.which != 1) return; // if not left mouse button + // restore css user-select + if (obj.last.userSelect == 'text') { + delete obj.last.userSelect; + $(obj.box).find('.w2ui-grid-body').css(w2utils.cssPrefix('user-select', 'none')); + } + // regular record select + if (obj.selectType == 'row' && ($(event.target).parents().hasClass('w2ui-head') || $(event.target).hasClass('w2ui-head'))) return; + if (obj.last.move && obj.last.move.type == 'expand') return; + // if altKey - alow text selection + if (event.altKey) { + $(obj.box).find('.w2ui-grid-body').css(w2utils.cssPrefix('user-select', 'text')); + obj.selectNone(); + obj.last.move = { type: 'text-select' }; + obj.last.userSelect = 'text'; + } else { + var tmp = event.target; + var pos = { + x: event.offsetX - 10, + y: event.offsetY - 10 + } + var tmps = false; + while (tmp) { + if (tmp.classList && tmp.classList.contains('w2ui-grid')) break; + if (tmp.tagName && tmp.tagName.toUpperCase() == 'TD') tmps = true; + if (tmp.tagName && tmp.tagName.toUpperCase() != 'TR' && tmps == true) { + pos.x += tmp.offsetLeft; + pos.y += tmp.offsetTop; + } + tmp = tmp.parentNode; + } + + obj.last.move = { + x : event.screenX, + y : event.screenY, + divX : 0, + divY : 0, + focusX : pos.x, + focusY : pos.y, + recid : $(event.target).parents('tr').attr('recid'), + column : parseInt(event.target.tagName.toUpperCase() == 'TD' ? $(event.target).attr('col') : $(event.target).parents('td').attr('col')), + type : 'select', + ghost : false, + start : true + }; + if (obj.last.move.recid == null) obj.last.move.type = 'select-column'; + // set focus to grid + var target = event.target; + var $input = $(obj.box).find('#grid_'+ obj.name + '_focus'); + // move input next to cursor so screen does not jump + if (obj.last.move) { + var sLeft = obj.last.move.focusX; + var sTop = obj.last.move.focusY; + var $owner = $(target).parents('table').parent(); + if ($owner.hasClass('w2ui-grid-records') || $owner.hasClass('w2ui-grid-frecords') + || $owner.hasClass('w2ui-grid-columns') || $owner.hasClass('w2ui-grid-fcolumns') + || $owner.hasClass('w2ui-grid-summary')) { + sLeft = obj.last.move.focusX - $(obj.box).find('#grid_'+ obj.name +'_records').scrollLeft(); + sTop = obj.last.move.focusY - $(obj.box).find('#grid_'+ obj.name +'_records').scrollTop(); + } + if ($(target).hasClass('w2ui-grid-footer') || $(target).parents('div.w2ui-grid-footer').length > 0) { + sTop = $(obj.box).find('#grid_'+ obj.name +'_footer').position().top; + } + // if clicked on toolbar + if ($owner.hasClass('w2ui-scroll-wrapper') && $owner.parent().hasClass('w2ui-toolbar')) { + sLeft = obj.last.move.focusX - $owner.scrollLeft(); + } + $input.css({ + left: sLeft - 10, + top : sTop + }); + } + // if toolbar input is clicked + setTimeout(function () { + if (['INPUT', 'TEXTAREA', 'SELECT'].indexOf(target.tagName.toUpperCase()) != -1) { + $(target).focus(); + } else { + if (!$input.is(':focus')) $input.focus(); + } + }, 50); + // disable click select for this condition + if (!obj.multiSelect && !obj.reorderRows && obj.last.move.type == 'drag') { + delete obj.last.move; + } + } + if (obj.reorderRows == true) { + var el = event.target; + if (el.tagName.toUpperCase() != 'TD') el = $(el).parents('td')[0]; + if ($(el).hasClass('w2ui-col-number') || $(el).hasClass('w2ui-col-order')) { + obj.selectNone(); + obj.last.move.reorder = true; + // supress hover + var eColor = $(obj.box).find('.w2ui-even.w2ui-empty-record').css('background-color'); + var oColor = $(obj.box).find('.w2ui-odd.w2ui-empty-record').css('background-color'); + $(obj.box).find('.w2ui-even td').not('.w2ui-col-number').css('background-color', eColor); + $(obj.box).find('.w2ui-odd td').not('.w2ui-col-number').css('background-color', oColor); + // display empty record and ghost record + var mv = obj.last.move; + if (!mv.ghost) { + var row = $('#grid_'+ obj.name + '_rec_'+ mv.recid); + var tmp = row.parents('table').find('tr:first-child').clone(); + mv.offsetY = event.offsetY; + mv.from = mv.recid; + mv.pos = row.position(); + mv.ghost = $(row).clone(true); + mv.ghost.removeAttr('id'); + row.find('td').remove(); + row.append('
'); + var recs = $(obj.box).find('.w2ui-grid-records'); + recs.append('
'); + recs.append('
'); + $('#grid_'+ obj.name + '_ghost').append(tmp).append(mv.ghost); + } + var ghost = $('#grid_'+ obj.name + '_ghost'); + var recs = $(obj.box).find('.w2ui-grid-records'); + ghost.css({ + top : mv.pos.top + recs.scrollTop(), + left : mv.pos.left, + "border-top" : '1px solid #aaa', + "border-bottom" : '1px solid #aaa' + }); + } else { + obj.last.move.reorder = false; + } + } + $(document) + .on('mousemove.w2ui-' + obj.name, mouseMove) + .on('mouseup.w2ui-' + obj.name, mouseStop); + // needed when grid grids are nested, see issue #1275 + event.stopPropagation(); + } + + function mouseMove (event) { + var mv = obj.last.move; + if (!mv || ['select', 'select-column'].indexOf(mv.type) == -1) return; + mv.divX = (event.screenX - mv.x); + mv.divY = (event.screenY - mv.y); + if (Math.abs(mv.divX) <= 1 && Math.abs(mv.divY) <= 1) return; // only if moved more then 1px + obj.last.cancelClick = true; + if (obj.reorderRows == true && obj.last.move.reorder) { + var recs = $(obj.box).find('.w2ui-grid-records'); + var tmp = $(event.target).parents('tr'); + var recid = tmp.attr('recid'); + if (recid == '-none-') recid = 'bottom'; + if (recid != mv.from) { + var row1 = $('#grid_'+ obj.name + '_rec_'+ mv.recid); + var row2 = $('#grid_'+ obj.name + '_rec_'+ recid); + $(obj.box).find('.insert-before'); + row2.addClass('insert-before'); + // MOVABLE GHOST + // if (event.screenY - mv.lastY < 0) row1.after(row2); else row2.after(row1); + mv.lastY = event.screenY; + mv.to = recid; + // line to insert before + var pos = row2.position() + var ghost_line = $('#grid_'+ obj.name + '_ghost_line'); + if (pos) { + ghost_line.css({ + top : pos.top + recs.scrollTop(), + left : mv.pos.left, + 'border-top': '2px solid #769EFC' + }); + } else { + ghost_line.css({ + 'border-top': '2px solid transparent' + }); + } + } + var ghost = $('#grid_'+ obj.name + '_ghost'); + ghost.css({ + top : mv.pos.top + mv.divY + recs.scrollTop(), + left : mv.pos.left + }); + return; + } + if (mv.start && mv.recid) { + obj.selectNone(); + mv.start = false; + } + var newSel= []; + var recid = (event.target.tagName.toUpperCase() == 'TR' ? $(event.target).attr('recid') : $(event.target).parents('tr').attr('recid')); + if (recid == null) { + // select by dragging columns + if (obj.selectType == 'row') return; + if (obj.last.move && obj.last.move.type == 'select') return; + var col = parseInt($(event.target).parents('td').attr('col')); + if (isNaN(col)) { + obj.removeRange('column-selection'); + $(obj.box).find('.w2ui-grid-columns .w2ui-col-header, .w2ui-grid-fcolumns .w2ui-col-header').removeClass('w2ui-col-selected'); + $(obj.box).find('.w2ui-col-number').removeClass('w2ui-row-selected'); + delete mv.colRange; + } else { + // add all columns in between + var newRange = col + '-' + col; + if (mv.column < col) newRange = mv.column + '-' + col; + if (mv.column > col) newRange = col + '-' + mv.column; + // array of selected columns + var cols = []; + var tmp = newRange.split('-'); + for (var ii = parseInt(tmp[0]); ii <= parseInt(tmp[1]); ii++) { + cols.push(ii) + } + if (mv.colRange != newRange) { + edataCol = obj.trigger({ phase: 'before', type: 'columnSelect', target: obj.name, columns: cols, isCancelled: false }); // initial isCancelled + if (edataCol.isCancelled !== true) { + if (mv.colRange == null) obj.selectNone(); + // highlight columns + var tmp = newRange.split('-'); + $(obj.box).find('.w2ui-grid-columns .w2ui-col-header, .w2ui-grid-fcolumns .w2ui-col-header').removeClass('w2ui-col-selected'); + for (var j = parseInt(tmp[0]); j <= parseInt(tmp[1]); j++) { + $(obj.box).find('#grid_'+ obj.name +'_column_' + j + ' .w2ui-col-header').addClass('w2ui-col-selected'); + } + $(obj.box).find('.w2ui-col-number').not('.w2ui-head').addClass('w2ui-row-selected'); + // show new range + mv.colRange = newRange; + obj.removeRange('column-selection'); + obj.addRange({ + name : 'column-selection', + range : [{ recid: obj.records[0].recid, column: tmp[0] }, { recid: obj.records[obj.records.length-1].recid, column: tmp[1] }], + style : 'background-color: rgba(90, 145, 234, 0.1)' + }); + } + } + } + + } else { // regular selection + + var ind1 = obj.get(mv.recid, true); + // this happens when selection is started on summary row + if (ind1 == null || (obj.records[ind1] && obj.records[ind1].recid != mv.recid)) return; + var ind2 = obj.get(recid, true); + // this happens when selection is extended into summary row (a good place to implement scrolling) + if (ind2 == null) return; + var col1 = parseInt(mv.column); + var col2 = parseInt(event.target.tagName.toUpperCase() == 'TD' ? $(event.target).attr('col') : $(event.target).parents('td').attr('col')); + if (isNaN(col1) && isNaN(col2)) { // line number select entire record + col1 = 0; + col2 = obj.columns.length-1; + } + if (ind1 > ind2) { var tmp = ind1; ind1 = ind2; ind2 = tmp; } + // check if need to refresh + var tmp = 'ind1:'+ ind1 +',ind2;'+ ind2 +',col1:'+ col1 +',col2:'+ col2; + if (mv.range == tmp) return; + mv.range = tmp; + for (var i = ind1; i <= ind2; i++) { + if (obj.last.searchIds.length > 0 && obj.last.searchIds.indexOf(i) == -1) continue; + if (obj.selectType != 'row') { + if (col1 > col2) { var tmp = col1; col1 = col2; col2 = tmp; } + var tmp = []; + for (var c = col1; c <= col2; c++) { + if (obj.columns[c].hidden) continue; + newSel.push({ recid: obj.records[i].recid, column: parseInt(c) }); + } + } else { + newSel.push(obj.records[i].recid); + } + } + if (obj.selectType != 'row') { + var sel = obj.getSelection(); + // add more items + var tmp = []; + for (var ns = 0; ns < newSel.length; ns++) { + var flag = false; + for (var s = 0; s < sel.length; s++) if (newSel[ns].recid == sel[s].recid && newSel[ns].column == sel[s].column) flag = true; + if (!flag) tmp.push({ recid: newSel[ns].recid, column: newSel[ns].column }); + } + obj.select(tmp); + // remove items + var tmp = []; + for (var s = 0; s < sel.length; s++) { + var flag = false; + for (var ns = 0; ns < newSel.length; ns++) if (newSel[ns].recid == sel[s].recid && newSel[ns].column == sel[s].column) flag = true; + if (!flag) tmp.push({ recid: sel[s].recid, column: sel[s].column }); + } + obj.unselect(tmp); + } else { + if (obj.multiSelect) { + var sel = obj.getSelection(); + for (var ns = 0; ns < newSel.length; ns++) { + if (sel.indexOf(newSel[ns]) == -1) obj.select(newSel[ns]); // add more items + } + for (var s = 0; s < sel.length; s++) { + if (newSel.indexOf(sel[s]) == -1) obj.unselect(sel[s]); // remove items + } + } + } + } + } + + function mouseStop (event) { + var mv = obj.last.move; + setTimeout(function () { delete obj.last.cancelClick; }, 1); + if ($(event.target).parents().hasClass('.w2ui-head') || $(event.target).hasClass('.w2ui-head')) return; + if (mv && ['select', 'select-column'].indexOf(mv.type) != -1) { + if (mv.colRange != null && edataCol.isCancelled !== true) { + var tmp = mv.colRange.split('-'); + var sel = []; + for (var i = 0; i < obj.records.length; i++) { + var cols = [] + for (var j = parseInt(tmp[0]); j <= parseInt(tmp[1]); j++) cols.push(j); + sel.push({ recid: obj.records[i].recid, column: cols }); + } + obj.removeRange('column-selection'); + obj.trigger($.extend(edataCol, { phase: 'after' })); + obj.select(sel); + } + if (obj.reorderRows == true && obj.last.move.reorder) { + // event + var edata = obj.trigger({ phase: 'before', target: obj.name, type: 'reorderRow', recid: mv.from, moveAfter: mv.to }); + if (edata.isCancelled === true) { + $('#grid_'+ obj.name + '_ghost').remove(); + $('#grid_'+ obj.name + '_ghost_line').remove(); + obj.refresh(); + delete obj.last.move; + return; + } + // default behavior + var ind1 = obj.get(mv.from, true); + var ind2 = obj.get(mv.to, true); + if (mv.to == 'bottom') ind2 = obj.records.length; // end of list + var tmp = obj.records[ind1]; + // swap records + if (ind1 != null && ind2 != null) { + obj.records.splice(ind1, 1); + if (ind1 > ind2) { + obj.records.splice(ind2, 0, tmp); + } else { + obj.records.splice(ind2 - 1, 0, tmp); + } + } + $('#grid_'+ obj.name + '_ghost').remove(); + $('#grid_'+ obj.name + '_ghost_line').remove(); + obj.refresh(); + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + } + } + delete obj.last.move; + $(document).off('.w2ui-' + obj.name); + } + }, + + destroy: function () { + // event before + var edata = this.trigger({ phase: 'before', target: this.name, type: 'destroy' }); + if (edata.isCancelled === true) return; + // remove all events + $(this.box).off(); + // clean up + if (typeof this.toolbar == 'object' && this.toolbar.destroy) this.toolbar.destroy(); + if ($(this.box).find('#grid_'+ this.name +'_body').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-grid w2ui-inactive') + .html(''); + } + delete w2ui[this.name]; + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + // =========================================== + // --- Internal Functions + + initColumnOnOff: function () { + if (!this.show.toolbarColumns) return; + var obj = this; + // line number + var col_html = '
'+ + ''+ + ''+ + ' '+ + ' '+ + ''; + // columns + for (var c = 0; c < this.columns.length; c++) { + var col = this.columns[c]; + var tmp = this.columns[c].text; + if (col.hideable === false) continue; + if (!tmp && this.columns[c].tooltip) tmp = this.columns[c].tooltip; + if (!tmp) tmp = '- column '+ (parseInt(c) + 1) +' -'; + col_html += + ''+ + ' '+ + ' '+ + ''; + } + var url = (typeof this.url != 'object' ? this.url : this.url.get); + // devider + if ((url && obj.show.skipRecords) || obj.show.saveRestoreState) { + col_html += ''; + } + // skip records + if (url && obj.show.skipRecords) { + col_html += + ''; + } + // save/restore state + if (obj.show.saveRestoreState) { + col_html += ''+ + ''; + } + col_html += "
'+ + ' '+ + ' '+ + ' '+ + '
'+ + ' '+ + ' '+ + ' '+ + '
'+ + '
'+ w2utils.lang('Skip') + + ' '+ w2utils.lang('Records')+ + '
'+ + '
'+ + '
'+ w2utils.lang('Save Grid State') + '
'+ + '
'+ + '
'+ w2utils.lang('Restore Default State') + '
'+ + '
"; + this.toolbar.get('w2ui-column-on-off').html = col_html; + }, + + /** + * + * @param box, grid object + * @returns {{remove: Function}} contains a closure around all events to ensure they are removed from the dom + */ + initColumnDrag: function ( box ) { + //throw error if using column groups + if ( this.columnGroups && this.columnGroups.length ) throw 'Draggable columns are not currently supported with column groups.'; + + var obj = this, + _dragData = {}; + _dragData.lastInt = null; + _dragData.pressed = false; + _dragData.timeout = null;_dragData.columnHead = null; + + //attach original event listener + $(obj.box).on('mousedown', dragColStart); + $(obj.box).on('mouseup', catchMouseup); + + function catchMouseup(){ + _dragData.pressed = false; + clearTimeout( _dragData.timeout ); + } + /** + * + * @param event, mousedown + * @returns {boolean} false, preventsDefault + */ + function dragColStart ( event ) { + if ( _dragData.timeout ) clearTimeout( _dragData.timeout ); + var self = this; + _dragData.pressed = true; + + _dragData.timeout = setTimeout(function(){ + // When dragging a column for reordering, a quick release and a secondary + // click may result in a bug where the column is ghosted to the screen, + // but can no longer be docked back into the header. It simply floats and you + // can no longer interact with it. + // The erronius event thats fired will have _dragData.numberPreColumnsPresent === 0 + // populated, wheras a valid event will not. + // if we see the erronius event, dont allow that second click to register, which results + // in the floating column remaining under the mouse's control. + if ( !_dragData.pressed || _dragData.numberPreColumnsPresent === 0 ) return; + + var edata, + columns, + selectedCol, + origColumn, + origColumnNumber, + invalidPreColumns = [ 'w2ui-col-number', 'w2ui-col-expand', 'w2ui-col-select' ], + invalidPostColumns = [ 'w2ui-head-last' ], + invalidColumns = invalidPreColumns.concat( invalidPostColumns ), + preColumnsSelector = '.w2ui-col-number, .w2ui-col-expand, .w2ui-col-select', + preColHeadersSelector = '.w2ui-head.w2ui-col-number, .w2ui-head.w2ui-col-expand, .w2ui-head.w2ui-col-select'; + + // do nothing if it is not a header + if ( !$( event.originalEvent.target ).parents().hasClass( 'w2ui-head' ) ) return; + + // do nothing if it is an invalid column + for ( var i = 0, l = invalidColumns.length; i < l; i++ ){ + if ( $( event.originalEvent.target ).parents().hasClass( invalidColumns[ i ] ) ) return; + } + + _dragData.numberPreColumnsPresent = $( obj.box ).find( preColHeadersSelector ).length; + + //start event for drag start + _dragData.columnHead = origColumn = $( event.originalEvent.target ).parents( '.w2ui-head' ); + _dragData.originalPos = origColumnNumber = parseInt( origColumn.attr( 'col' ), 10); + edata = obj.trigger({ type: 'columnDragStart', phase: 'before', originalEvent: event, origColumnNumber: origColumnNumber, target: origColumn[0] }); + if ( edata.isCancelled === true ) return false; + + columns = _dragData.columns = $( obj.box ).find( '.w2ui-head:not(.w2ui-head-last)' ); + + //add events + $( document ).on( 'mouseup', dragColEnd ); + $( document ).on( 'mousemove', dragColOver ); + + //_dragData.columns.css({ overflow: 'visible' }).children( 'div' ).css({ overflow: 'visible' }); + + //configure and style ghost image + _dragData.ghost = $( self ).clone( true ); + + //hide other elements on ghost except the grid body + $( _dragData.ghost ).find( '[col]:not([col="' + _dragData.originalPos + '"]), .w2ui-toolbar, .w2ui-grid-header' ).remove(); + $( _dragData.ghost ).find( preColumnsSelector ).remove(); + $( _dragData.ghost ).find( '.w2ui-grid-body' ).css({ top: 0 }); + + selectedCol = $( _dragData.ghost ).find( '[col="' + _dragData.originalPos + '"]' ); + $( document.body ).append( _dragData.ghost ); + + $( _dragData.ghost ).css({ + width: 0, + height: 0, + margin: 0, + position: 'fixed', + zIndex: 999999, + opacity: 0 + }).addClass( '.w2ui-grid-ghost' ).animate({ + width: selectedCol.width(), + height: $(obj.box).find('.w2ui-grid-body:first').height(), + left : event.pageX, + top : event.pageY, + opacity: 0.8 + }, 0 ); + + //establish current offsets + _dragData.offsets = []; + for ( var i = 0, l = columns.length; i < l; i++ ) { + _dragData.offsets.push( $( columns[ i ] ).offset().left ); + } + + //conclude event + obj.trigger( $.extend( edata, { phase: 'after' } ) ); + }, 150 );//end timeout wrapper + } + + function dragColOver ( event ) { + if ( !_dragData.pressed ) return; + + var cursorX = event.originalEvent.pageX, + cursorY = event.originalEvent.pageY, + offsets = _dragData.offsets, + lastWidth = $( '.w2ui-head:not(.w2ui-head-last)' ).width(); + + _dragData.targetInt = Math.max(_dragData.numberPreColumnsPresent,targetIntersection( cursorX, offsets, lastWidth )); + + markIntersection( _dragData.targetInt ); + trackGhost( cursorX, cursorY ); + } + + function dragColEnd ( event ) { + _dragData.pressed = false; + + var edata, + target, + selected, + columnConfig, + targetColumn, + ghosts = $( '.w2ui-grid-ghost' ); + + //start event for drag start + edata = obj.trigger({ type: 'columnDragEnd', phase: 'before', originalEvent: event, target: _dragData.columnHead[0] }); + if ( edata.isCancelled === true ) return false; + + selected = obj.columns[ _dragData.originalPos ]; + columnConfig = obj.columns; + targetColumn = $( _dragData.columns[ Math.min(_dragData.lastInt, _dragData.columns.length - 1) ] ); + target = (_dragData.lastInt < _dragData.columns.length) ? parseInt(targetColumn.attr('col')) : columnConfig.length; + + if ( target !== _dragData.originalPos + 1 && target !== _dragData.originalPos && targetColumn && targetColumn.length ) { + $( _dragData.ghost ).animate({ + top: $( obj.box ).offset().top, + left: targetColumn.offset().left, + width: 0, + height: 0, + opacity: 0.2 + }, 300, function(){ + $( this ).remove(); + ghosts.remove(); + }); + + columnConfig.splice( target, 0, $.extend( {}, selected ) ); + columnConfig.splice( columnConfig.indexOf( selected ), 1); + + } else { + $( _dragData.ghost ).remove(); + ghosts.remove(); + } + + //_dragData.columns.css({ overflow: '' }).children( 'div' ).css({ overflow: '' }); + + $( document ).off( 'mouseup', dragColEnd ); + $( document ).off( 'mousemove', dragColOver ); + if ( _dragData.marker ) _dragData.marker.remove(); + _dragData = {}; + + obj.refresh(); + + //conclude event + obj.trigger( $.extend( edata, { phase: 'after', targetColumnNumber: target - 1 } ) ); + } + + function markIntersection( intersection ){ + if ( !_dragData.marker && !_dragData.markerLeft ) { + _dragData.marker = $('
' + + '
' + + '
' + + '
'); + _dragData.markerLeft = $('
' + + '
' + + '
' + + '
'); + } + + if ( !_dragData.lastInt || _dragData.lastInt !== intersection ){ + _dragData.lastInt = intersection; + _dragData.marker.remove(); + _dragData.markerLeft.remove(); + $('.w2ui-head').removeClass('w2ui-col-intersection'); + + //if the current intersection is greater than the number of columns add the marker to the end of the last column only + if ( intersection >= _dragData.columns.length ) { + $( _dragData.columns[ _dragData.columns.length - 1 ] ).children( 'div:last' ).append( _dragData.marker.addClass( 'right' ).removeClass( 'left' ) ); + $( _dragData.columns[ _dragData.columns.length - 1 ] ).addClass('w2ui-col-intersection'); + } else if ( intersection <= _dragData.numberPreColumnsPresent ) { + //if the current intersection is on the column numbers place marker on first available column only + $( _dragData.columns[ _dragData.numberPreColumnsPresent ] ).prepend( _dragData.marker.addClass( 'left' ).removeClass( 'right' ) ).css({ position: 'relative' }); + $( _dragData.columns[ _dragData.numberPreColumnsPresent ] ).prev().addClass('w2ui-col-intersection'); + } else { + //otherwise prepend the marker to the targeted column and append it to the previous column + $( _dragData.columns[intersection] ).children( 'div:last' ).prepend( _dragData.marker.addClass( 'left' ).removeClass( 'right' ) ); + $( _dragData.columns[intersection] ).prev().children( 'div:last' ).append( _dragData.markerLeft.addClass( 'right' ).removeClass( 'left' ) ).css({ position: 'relative' }); + $( _dragData.columns[intersection - 1] ).addClass('w2ui-col-intersection'); + } + } + } + + function targetIntersection( cursorX, offsets, lastWidth ){ + if ( cursorX <= offsets[0] ) { + return 0; + } else if ( cursorX >= offsets[offsets.length - 1] + lastWidth ) { + return offsets.length; + } else { + for ( var i = 0, l = offsets.length; i < l; i++ ) { + var thisOffset = offsets[ i ]; + var nextOffset = offsets[ i + 1 ] || offsets[ i ] + lastWidth; + var midpoint = ( nextOffset - offsets[ i ]) / 2 + offsets[ i ]; + + if ( cursorX > thisOffset && cursorX <= midpoint ) { + return i; + } else if ( cursorX > midpoint && cursorX <= nextOffset ) { + return i + 1; + } + } + return intersection; + } + } + + function trackGhost( cursorX, cursorY ){ + $( _dragData.ghost ).css({ + left: cursorX - 10, + top: cursorY - 10 + }); + } + + //return an object to remove drag if it has ever been enabled + return { + remove: function(){ + $( obj.box ).off( 'mousedown', dragColStart ); + $( obj.box ).off( 'mouseup', catchMouseup ); + $( obj.box ).find( '.w2ui-head' ).removeAttr( 'draggable' ); + obj.last.columnDrag = false; + } + }; + }, + + columnOnOff: function (event, field) { + var $el = $(event.target).parents('tr').find('.w2ui-column-check'); + // event before + var edata = this.trigger({ phase: 'before', target: this.name, type: 'columnOnOff', field: field, originalEvent: event }); + if (edata.isCancelled === true) return; + // regular processing + var obj = this; + var hide = (!event.shiftKey && !event.metaKey && !event.ctrlKey && !$(event.target).hasClass('w2ui-column-check')); + // collapse expanded rows + var rows = obj.find({ 'w2ui.expanded': true }, true); + for (var r = 0; r < rows.length; r++) { + var tmp = this.records[r].w2ui; + if (tmp && !Array.isArray(tmp.children)) { + this.records[r].w2ui.expanded = false; + } + } + // show/hide + if (field == 'line-numbers') { + this.show.lineNumbers = !this.show.lineNumbers; + if (this.show.lineNumbers) { + $el.addClass('w2ui-icon-check').removeClass('w2ui-icon-empty'); + } else { + $el.addClass('w2ui-icon-empty').removeClass('w2ui-icon-check'); + } + this.refreshBody(); + this.resizeRecords(); + } else { + var col = this.getColumn(field); + if (col.hidden) { + $el.addClass('w2ui-icon-check').removeClass('w2ui-icon-empty'); + setTimeout(function () { + obj.showColumn(col.field); + }, hide ? 0 : 50); + } else { + $el.addClass('w2ui-icon-empty').removeClass('w2ui-icon-check'); + setTimeout(function () { + obj.hideColumn(col.field); + }, hide ? 0 : 50); + } + } + if (hide) { + setTimeout(function () { + $().w2overlay({ name: obj.name + '_toolbar' }); + }, 40); + } + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + scrollToColumn: function (field) { + if (field == null) + return; + var sWidth = 0; + var found = false; + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + if (col.field == field) { + found = true; + break; + } + if (col.frozen || col.hidden) + continue; + var cSize = parseInt(col.sizeCalculated ? col.sizeCalculated : col.size); + sWidth += cSize; + } + if (!found) + return; + this.last.scrollLeft = sWidth+1; + this.scroll(); + }, + + initToolbar: function () { + var obj = this; + // -- if toolbar is true + if (this.toolbar['render'] == null) { + var tmp_items = this.toolbar.items || []; + this.toolbar.items = []; + this.toolbar = $().w2toolbar($.extend(true, {}, this.toolbar, { name: this.name +'_toolbar', owner: this })); + + // ============================================= + // ------ Toolbar Generic buttons + + if (this.show.toolbarReload) { + this.toolbar.items.push($.extend(true, {}, this.buttons['reload'])); + } + if (this.show.toolbarColumns) { + this.toolbar.items.push($.extend(true, {}, this.buttons['columns'])); + } + if (this.show.toolbarReload || this.show.toolbarColumns) { + this.toolbar.items.push({ type: 'break', id: 'w2ui-break0' }); + } + if (this.show.toolbarInput) { + var html = + ''; + this.toolbar.items.push({ type: 'html', id: 'w2ui-search', html: html }); + } + if (this.show.toolbarSearch && this.multiSearch && this.searches.length > 0) { + this.toolbar.items.push($.extend(true, {}, this.buttons['search-go'])); + } + if ((this.show.toolbarSearch || this.show.toolbarInput) + && (this.show.toolbarAdd || this.show.toolbarEdit || this.show.toolbarDelete || this.show.toolbarSave)) { + this.toolbar.items.push({ type: 'break', id: 'w2ui-break1' }); + } + if (this.show.toolbarAdd && Array.isArray(tmp_items) + && tmp_items.map(function (item) { return item.id }).indexOf(this.buttons['add'].id) == -1) { + this.toolbar.items.push($.extend(true, {}, this.buttons['add'])); + } + if (this.show.toolbarEdit && Array.isArray(tmp_items) + && tmp_items.map(function (item) { return item.id }).indexOf(this.buttons['edit'].id) == -1) { + this.toolbar.items.push($.extend(true, {}, this.buttons['edit'])); + } + if (this.show.toolbarDelete && Array.isArray(tmp_items) + && tmp_items.map(function (item) { return item.id }).indexOf(this.buttons['delete'].id) == -1) { + this.toolbar.items.push($.extend(true, {}, this.buttons['delete'])); + } + if (this.show.toolbarSave && Array.isArray(tmp_items) + && tmp_items.map(function (item) { return item.id }).indexOf(this.buttons['save'].id) == -1) { + if (this.show.toolbarAdd || this.show.toolbarDelete || this.show.toolbarEdit) { + this.toolbar.items.push({ type: 'break', id: 'w2ui-break2' }); + } + this.toolbar.items.push($.extend(true, {}, this.buttons['save'])); + } + // add original buttons + if (tmp_items) for (var i = 0; i < tmp_items.length; i++) this.toolbar.items.push(tmp_items[i]); + + // ============================================= + // ------ Toolbar onClick processing + + var obj = this; + this.toolbar.on('click', function (event) { + var edata = obj.trigger({ phase: 'before', type: 'toolbar', target: event.target, originalEvent: event }); + if (edata.isCancelled === true) return; + var id = event.target; + switch (id) { + case 'w2ui-reload': + var edata2 = obj.trigger({ phase: 'before', type: 'reload', target: obj.name }); + if (edata2.isCancelled === true) return false; + obj.reload(); + obj.trigger($.extend(edata2, { phase: 'after' })); + break; + case 'w2ui-column-on-off': + obj.initColumnOnOff(); + obj.initResize(); + obj.resize(); + break; + case 'w2ui-search-advanced': + var it = this.get(id); + if (it.checked) { + obj.searchClose(); + } else { + obj.searchOpen(); + } + // need to cancel event in order to user custom searchOpen/close functions + obj.toolbar.tooltipHide('w2ui-search-advanced'); + event.preventDefault(); + break; + case 'w2ui-add': + // events + var edata2 = obj.trigger({ phase: 'before', target: obj.name, type: 'add', recid: null }); + if (edata2.isCancelled === true) return false; + obj.trigger($.extend(edata2, { phase: 'after' })); + // hide all tooltips + setTimeout(function () { $().w2tag(); }, 20); + break; + case 'w2ui-edit': + var sel = obj.getSelection(); + var recid = null; + if (sel.length == 1) recid = sel[0]; + // events + var edata2 = obj.trigger({ phase: 'before', target: obj.name, type: 'edit', recid: recid }); + if (edata2.isCancelled === true) return false; + obj.trigger($.extend(edata2, { phase: 'after' })); + // hide all tooltips + setTimeout(function () { $().w2tag(); }, 20); + break; + case 'w2ui-delete': + obj["delete"](); + break; + case 'w2ui-save': + obj.save(); + break; + } + // no default action + obj.trigger($.extend(edata, { phase: 'after' })); + }) + this.toolbar.on('refresh', function (event) { + if (event.target == 'w2ui-search') { + var sd = obj.searchData; + setTimeout(function () { + obj.initAllField(obj.last.field, (sd.length == 1 ? sd[0].value : null)); + }, 1); + } + }) + } + }, + + initResize: function () { + var obj = this; + //if (obj.resizing === true) return; + $(this.box).find('.w2ui-resizer') + .off('.grid-col-resize') + .on('click.grid-col-resize', function (event) { + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + if (event.preventDefault) event.preventDefault(); + }) + .on('mousedown.grid-col-resize', function (event) { + if (!event) event = window.event; + obj.resizing = true; + obj.last.tmp = { + x : event.screenX, + y : event.screenY, + gx : event.screenX, + gy : event.screenY, + col : parseInt($(this).attr('name')) + }; + // find tds that will be resized + obj.last.tmp.tds = $('#grid_'+ obj.name +'_body table tr:first-child td[col='+ obj.last.tmp.col +']') + + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + if (event.preventDefault) event.preventDefault(); + // fix sizes + for (var c = 0; c < obj.columns.length; c++) { + if (obj.columns[c].hidden) continue; + if (obj.columns[c].sizeOriginal == null) obj.columns[c].sizeOriginal = obj.columns[c].size; + obj.columns[c].size = obj.columns[c].sizeCalculated; + } + var edata = { phase: 'before', type: 'columnResize', target: obj.name, column: obj.last.tmp.col, field: obj.columns[obj.last.tmp.col].field }; + edata = obj.trigger($.extend(edata, { resizeBy: 0, originalEvent: event })); + // set move event + var timer; + var mouseMove = function (event) { + if (obj.resizing != true) return; + if (!event) event = window.event; + // event before + edata = obj.trigger($.extend(edata, { resizeBy: (event.screenX - obj.last.tmp.gx), originalEvent: event })); + if (edata.isCancelled === true) { edata.isCancelled = false; return; } + // default action + obj.last.tmp.x = (event.screenX - obj.last.tmp.x); + obj.last.tmp.y = (event.screenY - obj.last.tmp.y); + var newWidth =(parseInt(obj.columns[obj.last.tmp.col].size) + obj.last.tmp.x) + 'px' + obj.columns[obj.last.tmp.col].size = newWidth; + if (timer) clearTimeout(timer) + timer = setTimeout(function () { + obj.resizeRecords() + obj.scroll() + }, 100) + // quick resize + obj.last.tmp.tds.css({ width: newWidth }) + // reset + obj.last.tmp.x = event.screenX; + obj.last.tmp.y = event.screenY; + }; + var mouseUp = function (event) { + delete obj.resizing; + $(document).off('.grid-col-resize'); + obj.resizeRecords(); + obj.scroll(); + // event after + obj.trigger($.extend(edata, { phase: 'after', originalEvent: event })); + }; + + $(document) + .off('.grid-col-resize') + .on('mousemove.grid-col-resize', mouseMove) + .on('mouseup.grid-col-resize', mouseUp); + }) + .on('dblclick.grid-col-resize', function(event) { + var colId = parseInt($(this).attr('name')), + col = obj.columns[colId], + maxDiff = 0; + + if (col.autoResize === false) { + return true; + } + + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + if (event.preventDefault) event.preventDefault(); + $('.w2ui-grid-records td[col="' + colId + '"] > div', obj.box).each(function() { + var thisDiff = this.offsetWidth - this.scrollWidth; + + if (thisDiff < maxDiff) { + maxDiff = thisDiff - 3; // 3px buffer needed for Firefox + } + }); + + // event before + var edata = { phase: 'before', type: 'columnAutoResize', target: obj.name, column: col, field: col.field }; + edata = obj.trigger($.extend(edata, { resizeBy: Math.abs(maxDiff), originalEvent: event })); + if (edata.isCancelled === true) { edata.isCancelled = false; return; } + + if (maxDiff < 0) { + col.size = Math.min(parseInt(col.size) + Math.abs(maxDiff), col.max || Infinity) + 'px'; + obj.resizeRecords(); + obj.resizeRecords(); // Why do we have to call it twice in order to show the scrollbar? + obj.scroll(); + } + + // event after + obj.trigger($.extend(edata, { phase: 'after', originalEvent: event })); + }) + .each(function (index, el) { + var td = $(el).parent(); + $(el).css({ + "height" : td.height(), + "margin-left" : (td.width() - 3) + 'px' + }); + }); + }, + + resizeBoxes: function () { + // elements + var header = $('#grid_'+ this.name +'_header'); + var toolbar = $('#grid_'+ this.name +'_toolbar'); + var fsummary = $('#grid_'+ this.name +'_fsummary'); + var summary = $('#grid_'+ this.name +'_summary'); + var footer = $('#grid_'+ this.name +'_footer'); + var body = $('#grid_'+ this.name +'_body'); + + if (this.show.header) { + header.css({ + top: '0px', + left: '0px', + right: '0px' + }); + } + + if (this.show.toolbar) { + toolbar.css({ + top: ( 0 + (this.show.header ? w2utils.getSize(header, 'height') : 0) ) + 'px', + left: '0px', + right: '0px' + }); + } + if (this.summary.length > 0) { + fsummary.css({ + bottom: ( 0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0) ) + 'px' + }); + summary.css({ + bottom: ( 0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0) ) + 'px', + right: '0px' + }); + } + if (this.show.footer) { + footer.css({ + bottom: '0px', + left: '0px', + right: '0px' + }); + } + body.css({ + top: ( 0 + (this.show.header ? w2utils.getSize(header, 'height') : 0) + (this.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0) ) + 'px', + bottom: ( 0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0) + (this.summary.length > 0 ? w2utils.getSize(summary, 'height') : 0) ) + 'px', + left: '0px', + right: '0px' + }); + }, + + resizeRecords: function () { + var obj = this; + // remove empty records + $(this.box).find('.w2ui-empty-record').remove(); + // -- Calculate Column size in PX + var box = $(this.box); + var grid = $(this.box).find('> div.w2ui-grid-box'); + var header = $('#grid_'+ this.name +'_header'); + var toolbar = $('#grid_'+ this.name +'_toolbar'); + var summary = $('#grid_'+ this.name +'_summary'); + var fsummary = $('#grid_'+ this.name +'_fsummary'); + var footer = $('#grid_'+ this.name +'_footer'); + var body = $('#grid_'+ this.name +'_body'); + var columns = $('#grid_'+ this.name +'_columns'); + var fcolumns = $('#grid_'+ this.name +'_fcolumns'); + var records = $('#grid_'+ this.name +'_records'); + var frecords = $('#grid_'+ this.name +'_frecords'); + var scroll1 = $('#grid_'+ this.name +'_scroll1'); + var lineNumberWidth = String(this.total).length * 8 + 10; + if (lineNumberWidth < 34) lineNumberWidth = 34; // 3 digit width + if (this.lineNumberWidth != null) lineNumberWidth = this.lineNumberWidth; + + var bodyOverflowX = false; + var bodyOverflowY = false; + var sWidth = 0; + for (var i = 0; i < obj.columns.length; i++) { + if (obj.columns[i].frozen || obj.columns[i].hidden) continue; + var cSize = parseInt(obj.columns[i].sizeCalculated ? obj.columns[i].sizeCalculated : obj.columns[i].size); + sWidth += cSize; + } + if (records.width() < sWidth) bodyOverflowX = true; + if (body.height() - columns.height() < $(records).find('>table').height() + (bodyOverflowX ? w2utils.scrollBarSize() : 0)) bodyOverflowY = true; + + // body might be expanded by data + if (!this.fixedBody) { + // allow it to render records, then resize + var calculatedHeight = w2utils.getSize(columns, 'height') + + w2utils.getSize($('#grid_'+ obj.name +'_records table'), 'height') + + (bodyOverflowX ? w2utils.scrollBarSize() : 0); + obj.height = calculatedHeight + + w2utils.getSize(grid, '+height') + + (obj.show.header ? w2utils.getSize(header, 'height') : 0) + + (obj.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0) + + (summary.css('display') != 'none' ? w2utils.getSize(summary, 'height') : 0) + + (obj.show.footer ? w2utils.getSize(footer, 'height') : 0); + grid.css('height', obj.height); + body.css('height', calculatedHeight); + box.css('height', w2utils.getSize(grid, 'height') + w2utils.getSize(box, '+height')); + } else { + // fixed body height + var calculatedHeight = grid.height() + - (this.show.header ? w2utils.getSize(header, 'height') : 0) + - (this.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0) + - (summary.css('display') != 'none' ? w2utils.getSize(summary, 'height') : 0) + - (this.show.footer ? w2utils.getSize(footer, 'height') : 0); + body.css('height', calculatedHeight); + } + + var buffered = this.records.length; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (this.searchData.length != 0 && !url) buffered = this.last.searchIds.length; + // apply overflow + if (!this.fixedBody) { bodyOverflowY = false; } + if (bodyOverflowX || bodyOverflowY) { + columns.find('> table > tbody > tr:nth-child(1) td.w2ui-head-last').css('width', w2utils.scrollBarSize()).show(); + records.css({ + top: ((this.columnGroups.length > 0 && this.show.columns ? 1 : 0) + w2utils.getSize(columns, 'height')) +'px', + "-webkit-overflow-scrolling": "touch", + "overflow-x": (bodyOverflowX ? 'auto' : 'hidden'), + "overflow-y": (bodyOverflowY ? 'auto' : 'hidden') + }); + } else { + columns.find('> table > tbody > tr:nth-child(1) td.w2ui-head-last').hide(); + records.css({ + top: ((this.columnGroups.length > 0 && this.show.columns ? 1 : 0) + w2utils.getSize(columns, 'height')) +'px', + overflow: 'hidden' + }); + if (records.length > 0) { this.last.scrollTop = 0; this.last.scrollLeft = 0; } // if no scrollbars, always show top + } + if (bodyOverflowX) { + frecords.css('margin-bottom', w2utils.scrollBarSize()); + scroll1.show(); + } else { + frecords.css('margin-bottom', 0); + scroll1.hide(); + } + frecords.css({ overflow: 'hidden', top: records.css('top') }); + if (this.show.emptyRecords && !bodyOverflowY) { + var max = Math.floor(records.height() / this.recordHeight) - 1; + var leftover = 0; + if (records[0]) leftover = records[0].scrollHeight - max * this.recordHeight; + if (leftover >= this.recordHeight) { + leftover -= this.recordHeight; + max++; + } + if (this.fixedBody) { + for (var di = buffered; di < max; di++) { + addEmptyRow(di, this.recordHeight, this); + } + addEmptyRow(max, leftover, this); + } + } + + function addEmptyRow(row, height, grid) { + var html1 = ''; + var html2 = ''; + var htmlp = ''; + html1 += ''; + html2 += ''; + if (grid.show.lineNumbers) html1 += ''; + if (grid.show.selectColumn) html1 += ''; + if (grid.show.expandColumn) html1 += ''; + html2 += ''; + if (grid.show.orderColumn) html2 += ''; + for (var j = 0; j < grid.columns.length; j++) { + var col = grid.columns[j]; + if ((col.hidden || j < grid.last.colStart || j > grid.last.colEnd) && !col.frozen) continue; + htmlp = ''; + if (col.frozen) html1 += htmlp; else html2 += htmlp; + } + html1 += ' '; + html2 += ' '; + $('#grid_'+ grid.name +'_frecords > table').append(html1); + $('#grid_'+ grid.name +'_records > table').append(html2); + } + if (body.length > 0) { + var width_max = parseInt(body.width()) + - (bodyOverflowY ? w2utils.scrollBarSize() : 0) + - (this.show.lineNumbers ? lineNumberWidth : 0) + // - (this.show.orderColumn ? 26 : 0) + - (this.show.selectColumn ? 26 : 0) + - (this.show.expandColumn ? 26 : 0) + - 1; // left is 1xp due to border width + var width_box = width_max; + var percent = 0; + // gridMinWidth processing + var restart = false; + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + if (col.gridMinWidth > 0) { + if (col.gridMinWidth > width_box && col.hidden !== true) { + col.hidden = true; + restart = true; + } + if (col.gridMinWidth < width_box && col.hidden === true) { + col.hidden = false; + restart = true; + } + } + } + if (restart === true) { + this.refresh(); + return; + } + // assign PX column s + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + if (col.hidden) continue; + if (String(col.size).substr(String(col.size).length-2).toLowerCase() == 'px') { + width_max -= parseFloat(col.size); + this.columns[i].sizeCalculated = col.size; + this.columns[i].sizeType = 'px'; + } else { + percent += parseFloat(col.size); + this.columns[i].sizeType = '%'; + delete col.sizeCorrected; + } + } + // if sum != 100% -- reassign proportionally + if (percent != 100 && percent > 0) { + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + if (col.hidden) continue; + if (col.sizeType == '%') { + col.sizeCorrected = Math.round(parseFloat(col.size) * 100 * 100 / percent) / 100 + '%'; + } + } + } + // calculate % columns + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + if (col.hidden) continue; + if (col.sizeType == '%') { + if (this.columns[i].sizeCorrected != null) { + // make it 1px smaller, so margin of error can be calculated correctly + this.columns[i].sizeCalculated = Math.floor(width_max * parseFloat(col.sizeCorrected) / 100) - 1 + 'px'; + } else { + // make it 1px smaller, so margin of error can be calculated correctly + this.columns[i].sizeCalculated = Math.floor(width_max * parseFloat(col.size) / 100) - 1 + 'px'; + } + } + } + } + // fix margin of error that is due percentage calculations + var width_cols = 0; + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + if (col.hidden) continue; + if (col.min == null) col.min = 20; + if (parseInt(col.sizeCalculated) < parseInt(col.min)) col.sizeCalculated = col.min + 'px'; + if (parseInt(col.sizeCalculated) > parseInt(col.max)) col.sizeCalculated = col.max + 'px'; + width_cols += parseInt(col.sizeCalculated); + } + var width_diff = parseInt(width_box) - parseInt(width_cols); + if (width_diff > 0 && percent > 0) { + var i = 0; + while (true) { + var col = this.columns[i]; + if (col == null) { i = 0; continue; } + if (col.hidden || col.sizeType == 'px') { i++; continue; } + col.sizeCalculated = (parseInt(col.sizeCalculated) + 1) + 'px'; + width_diff--; + if (width_diff === 0) break; + i++; + } + } else if (width_diff > 0) { + columns.find('> table > tbody > tr:nth-child(1) td.w2ui-head-last').css('width', w2utils.scrollBarSize()).show(); + } + + // find width of frozen columns + var fwidth = 1; + if (this.show.lineNumbers) fwidth += lineNumberWidth; + if (this.show.selectColumn) fwidth += 26; + // if (this.show.orderColumn) fwidth += 26; + if (this.show.expandColumn) fwidth += 26; + for (var i = 0; i < this.columns.length; i++) { + if (this.columns[i].hidden) continue; + if (this.columns[i].frozen) fwidth += parseInt(this.columns[i].sizeCalculated); + } + fcolumns.css('width', fwidth); + frecords.css('width', fwidth); + fsummary.css('width', fwidth); + scroll1.css('width', fwidth); + columns.css('left', fwidth); + records.css('left', fwidth); + summary.css('left', fwidth); + + // resize columns + columns.find('> table > tbody > tr:nth-child(1) td') + .add(fcolumns.find('> table > tbody > tr:nth-child(1) td')) + .each(function (index, el) { + // line numbers + if ($(el).hasClass('w2ui-col-number')) { + $(el).css('width', lineNumberWidth); + } + // records + var ind = $(el).attr('col'); + if (ind != null) { + if (ind == 'start') { + var width = 0; + for (var i = 0; i < obj.last.colStart; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue; + width += parseInt(obj.columns[i].sizeCalculated); + } + $(el).css('width', width + 'px'); + } + if (obj.columns[ind]) $(el).css('width', obj.columns[ind].sizeCalculated); + } + // last column + if ($(el).hasClass('w2ui-head-last')) { + if (obj.last.colEnd + 1 < obj.columns.length) { + var width = 0; + for (var i = obj.last.colEnd + 1; i < obj.columns.length; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue; + width += parseInt(obj.columns[i].sizeCalculated); + } + $(el).css('width', width + 'px'); + } else { + $(el).css('width', w2utils.scrollBarSize() + (width_diff > 0 && percent === 0 ? width_diff : 0) + 'px'); + } + } + }); + // if there are column groups - hide first row (needed for sizing) + if (columns.find('> table > tbody > tr').length == 3) { + columns.find('> table > tbody > tr:nth-child(1) td') + .add(fcolumns.find('> table > tbody > tr:nth-child(1) td')) + .html('').css({ + 'height' : '0px', + 'border' : '0px', + 'padding': '0px', + 'margin' : '0px' + }); + } + // resize records + records.find('> table > tbody > tr:nth-child(1) td') + .add(frecords.find('> table > tbody > tr:nth-child(1) td')) + .each(function (index, el) { + // line numbers + if ($(el).hasClass('w2ui-col-number')) { + $(el).css('width', lineNumberWidth); + } + // records + var ind = $(el).attr('col'); + if (ind != null) { + if (ind == 'start') { + var width = 0; + for (var i = 0; i < obj.last.colStart; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue; + width += parseInt(obj.columns[i].sizeCalculated); + } + $(el).css('width', width + 'px'); + } + if (obj.columns[ind]) $(el).css('width', obj.columns[ind].sizeCalculated); + } + // last column + if ($(el).hasClass('w2ui-grid-data-last') && $(el).parents('.w2ui-grid-frecords').length === 0) { // not in frecords + if (obj.last.colEnd + 1 < obj.columns.length) { + var width = 0; + for (var i = obj.last.colEnd + 1; i < obj.columns.length; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue; + width += parseInt(obj.columns[i].sizeCalculated); + } + $(el).css('width', width + 'px'); + } else { + $(el).css('width', (width_diff > 0 && percent === 0 ? width_diff : 0) + 'px'); + } + } + }); + // resize summary + summary.find('> table > tbody > tr:nth-child(1) td') + .add(fsummary.find('> table > tbody > tr:nth-child(1) td')) + .each(function (index, el) { + // line numbers + if ($(el).hasClass('w2ui-col-number')) { + $(el).css('width', lineNumberWidth); + } + // records + var ind = $(el).attr('col'); + if (ind != null) { + if (ind == 'start') { + var width = 0; + for (var i = 0; i < obj.last.colStart; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue; + width += parseInt(obj.columns[i].sizeCalculated); + } + $(el).css('width', width + 'px'); + } + if (obj.columns[ind]) $(el).css('width', obj.columns[ind].sizeCalculated); + } + // last column + if ($(el).hasClass('w2ui-grid-data-last') && $(el).parents('.w2ui-grid-frecords').length === 0) { // not in frecords + $(el).css('width', w2utils.scrollBarSize() + (width_diff > 0 && percent === 0 ? width_diff : 0) + 'px'); + } + }); + this.initResize(); + this.refreshRanges(); + // apply last scroll if any + if ((this.last.scrollTop || this.last.scrollLeft) && records.length > 0) { + columns.prop('scrollLeft', this.last.scrollLeft); + records.prop('scrollTop', this.last.scrollTop); + records.prop('scrollLeft', this.last.scrollLeft); + } + }, + + getSearchesHTML: function () { + var obj = this; + var html = ''; + var showBtn = false; + for (var i = 0; i < this.searches.length; i++) { + var s = this.searches[i]; + s.type = String(s.type).toLowerCase(); + if (s.hidden) continue; + var btn = ''; + if (showBtn == false) { + btn = ''; + showBtn = true; + } + if (s.inTag == null) s.inTag = ''; + if (s.outTag == null) s.outTag = ''; + if (s.style == null) s.style = ''; + if (s.type == null) s.type = 'text'; + if (s.label == null && s.caption != null) { + console.log('NOTICE: grid search.caption property is deprecated, please use search.label. Search ->', s) + s.label = s.caption; + } + var operator = + ''; + + html += ''+ + ' ' + + ' ' + + ' '+ + ' ' + + ''; + } + html += ''+ + ' '+ + '
'+ btn +''+ (s.label || '') +''+ operator +''; + + switch (s.type) { + case 'text': + case 'alphanumeric': + case 'hex': + case 'color': + case 'list': + case 'combo': + case 'enum': + var tmpStyle = 'width: 250px;'; + if (['hex', 'color'].indexOf(s.type) != -1) tmpStyle = 'width: 90px;'; + html += ''; + break; + + case 'int': + case 'float': + case 'money': + case 'currency': + case 'percent': + case 'date': + case 'time': + case 'datetime': + var tmpStyle = 'width: 90px;'; + if (s.type == 'datetime') tmpStyle = 'width: 140px;'; + html += ''+ + ''; + break; + + case 'select': + html += ''; + break; + + } + html += s.outTag + + '
'+ + '
'+ + ' '+ + ' '+ + '
'+ + '
'; + return html; + + function getOperators(type, fieldOperators) { + var html = ''; + var operators = obj.operators[obj.operatorsMap[type]]; + if (fieldOperators != null) operators = fieldOperators; + for (var i = 0; i < operators.length; i++) { + var oper = operators[i]; + var text = oper; + if (Array.isArray(oper)) { + text = oper[1]; + oper = oper[0]; + if (text == null) text = oper; + } else if ($.isPlainObject(oper)) { + text = oper.text; + oper = oper.oper; + } + html += '\n'; + } + return html; + } + }, + + initOperator: function (el, search_ind) { + var obj = this; + var search = obj.searches[search_ind]; + var range = $('#grid_'+ obj.name + '_range_'+ search_ind); + var fld1 = $('#grid_'+ obj.name +'_field_'+ search_ind); + var fld2 = fld1.parent().find('span input'); + fld1.show(); + range.hide(); + switch ($(el).val()) { + case 'between': + range.show(); + fld2.w2field(search.type, search.options); + break; + case 'not null': + case 'null': + fld1.hide(); + fld1.val('1'); // need to insert something for search to activate + fld1.change(); + break; + } + }, + + initSearches: function () { + var obj = this; + // init searches + for (var s = 0; s < this.searches.length; s++) { + var search = this.searches[s]; + var sdata = this.getSearchData(search.field); + search.type = String(search.type).toLowerCase(); + var operators = obj.operators[obj.operatorsMap[search.type]]; + if (search.operators) operators = search.operators; + var operator = operators[0]; // default operator + if ($.isPlainObject(operator)) operator = operator.oper; + if (typeof search.options != 'object') search.options = {}; + if (search.type == 'text') operator = this.textSearch; + // only accept search.operator if it is valid + for (var i = 0; i < operators.length; i++) { + var oper = operators[i]; + if ($.isPlainObject(oper)) oper = oper.oper; + if (search.operator == oper) { + operator = search.operator; + break; + } + } + // init types + switch (search.type) { + case 'text': + case 'alphanumeric': + $('#grid_'+ this.name +'_field_' + s).w2field(search.type, search.options); + break; + + case 'int': + case 'float': + case 'hex': + case 'color': + case 'money': + case 'currency': + case 'percent': + case 'date': + case 'time': + case 'datetime': + $('#grid_'+ this.name +'_field_'+s).w2field(search.type, search.options); + $('#grid_'+ this.name +'_field2_'+s).w2field(search.type, search.options); + setTimeout(function () { // convert to date if it is number + $('#grid_'+ obj.name +'_field_'+s).keydown(); + $('#grid_'+ obj.name +'_field2_'+s).keydown(); + }, 1); + break; + + case 'list': + case 'combo': + case 'enum': + var options = search.options; + if (search.type == 'list') options.selected = {}; + if (search.type == 'enum') options.selected = []; + if (sdata) options.selected = sdata.value; + $('#grid_'+ this.name +'_field_'+s).w2field(search.type, $.extend({ openOnFocus: true }, options)); + if (sdata && sdata.text != null) $('#grid_'+ this.name +'_field_'+s).data('selected', {id: sdata.value, text: sdata.text}); + break; + + case 'select': + // build options + var options = ''; + for (var i = 0; i < search.options.items.length; i++) { + var si = search.options.items[i]; + if ($.isPlainObject(search.options.items[i])) { + var val = si.id; + var txt = si.text; + if (val == null && si.value != null) val = si.value; + if (txt == null && si.text != null) txt = si.text; + if (val == null) val = ''; + options += ''; + } else { + options += ''; + } + } + $('#grid_'+ this.name +'_field_'+s).html(options); + break; + } + if (sdata != null) { + if (sdata.type == 'int' && ['in', 'not in'].indexOf(sdata.operator) != -1) { + $('#grid_'+ this.name +'_field_'+ s).w2field('clear').val(sdata.value); + } + $('#grid_'+ this.name +'_operator_'+ s).val(sdata.operator).trigger('change'); + if (!$.isArray(sdata.value)) { + if (sdata.value != null) $('#grid_'+ this.name +'_field_'+ s).val(sdata.value).trigger('change'); + } else { + if (['in', 'not in'].indexOf(sdata.operator) != -1) { + $('#grid_'+ this.name +'_field_'+ s).val(sdata.value).trigger('change'); + } else { + $('#grid_'+ this.name +'_field_'+ s).val(sdata.value[0]).trigger('change'); + $('#grid_'+ this.name +'_field2_'+ s).val(sdata.value[1]).trigger('change'); + } + } + } else { + $('#grid_'+ this.name +'_operator_'+s).val(operator).trigger('change'); + } + } + // add on change event + $('#w2ui-overlay-'+ this.name +'-searchOverlay .w2ui-grid-searches *[rel=search]').on('keypress', function (evnt) { + if (evnt.keyCode == 13) { + obj.search(); + $().w2overlay({ name: obj.name + '-searchOverlay' }); + } + }); + }, + + getColumnsHTML: function () { + var obj = this; + var html1 = ''; + var html2 = ''; + if (this.show.columnHeaders) { + if (this.columnGroups.length > 0) { + var tmp1 = getColumns(true); + var tmp2 = getGroups(); + var tmp3 = getColumns(false); + html1 = tmp1[0] + tmp2[0] + tmp3[0]; + html2 = tmp1[1] + tmp2[1] + tmp3[1]; + } else { + var tmp = getColumns(true); + html1 = tmp[0]; + html2 = tmp[1]; + } + } + return [html1, html2]; + + function getGroups () { + var html1 = ''; + var html2 = ''; + var tmpf = ''; + // add empty group at the end + var tmp = obj.columnGroups.length - 1; + if (obj.columnGroups[tmp].text == null && obj.columnGroups[tmp].caption != null) { + console.log('NOTICE: grid columnGroup.caption property is deprecated, please use columnGroup.text. Group -> ', obj.columnGroups[tmp]); + obj.columnGroups[tmp].text = obj.columnGroups[tmp].caption; + } + if (obj.columnGroups[obj.columnGroups.length-1].text != '') obj.columnGroups.push({ text: '' }); + + if (obj.show.lineNumbers) { + html1 += ''+ + '
 
'+ + ''; + } + if (obj.show.selectColumn) { + html1 += ''+ + '
 
'+ + ''; + } + if (obj.show.expandColumn) { + html1 += ''+ + '
 
'+ + ''; + } + var ii = 0; + html2 += ''; + if (obj.show.orderColumn) { + html2 += ''+ + '
 
'+ + ''; + } + for (var i=0; i', col); + col.text = col.caption; + } + var colspan = 0; + for (var jj = ii; jj < ii + colg.span; jj++) { + if (obj.columns[jj] && !obj.columns[jj].hidden) { + colspan++; + } + } + if (i == obj.columnGroups.length-1) { + colspan = 100; // last column + } + if (colspan <= 0) { + // do nothing here, all columns in the group are hidden. + } else if (colg.master === true) { + var sortStyle = ''; + for (var si = 0; si < obj.sortData.length; si++) { + if (obj.sortData[si].field == col.field) { + if ((obj.sortData[si].direction || '').toLowerCase() === 'asc') sortStyle = 'w2ui-sort-up'; + if ((obj.sortData[si].direction || '').toLowerCase() === 'desc') sortStyle = 'w2ui-sort-down'; + } + } + var resizer = ""; + if (col.resizable !== false) { + resizer = '
'; + } + var text = (typeof col.text == 'function' ? col.text(col) : col.text); + tmpf = ''+ + resizer + + '
'+ + '
'+ + (!text ? ' ' : text) + + '
'+ + ''; + if (col && col.frozen) html1 += tmpf; else html2 += tmpf; + } else { + var gText = (typeof colg.text == 'function' ? colg.text(colg) : colg.text); + tmpf = ''+ + '
'+ + (!gText ? ' ' : gText) + + '
'+ + ''; + if (col && col.frozen) html1 += tmpf; else html2 += tmpf; + } + ii += colg.span; + } + html1 += ''; // need empty column for border-right + html2 += ''; + return [html1, html2]; + } + + function getColumns (master) { + var html1 = ''; + var html2 = ''; + if (obj.show.lineNumbers) { + html1 += ''+ + '
#
'+ + ''; + } + if (obj.show.selectColumn) { + html1 += ''+ + '
'+ + ' '+ + '
'+ + ''; + } + if (obj.show.expandColumn) { + html1 += ''+ + '
 
'+ + ''; + } + var ii = 0; + var id = 0; + var colg; + html2 += ''; + if (obj.show.orderColumn) { + html2 += ''+ + '
 
'+ + ''; + } + for (var i = 0; i < obj.columns.length; i++) { + var col = obj.columns[i]; + if (col.text == null && col.caption != null) { + console.log('NOTICE: grid column.caption property is deprecated, please use column.text. Column -> ', col); + col.text = col.caption; + } + if (col.size == null) col.size = '100%'; + if (i == id) { // always true on first iteration + colg = obj.columnGroups[ii++] || {}; + id = id + colg.span; + } + if ((i < obj.last.colStart || i > obj.last.colEnd) && !col.frozen) + continue; + if (col.hidden) + continue; + if (colg.master !== true || master) { // grouping of columns + var colCellHTML = obj.getColumnCellHTML(i); + if (col && col.frozen) html1 += colCellHTML; else html2 += colCellHTML; + } + } + html1 += '
 
'; + html2 += '
 
'; + html1 += ''; + html2 += ''; + return [html1, html2]; + } + }, + + getColumnCellHTML: function (i) { + var col = this.columns[i]; + if (col == null) return ''; + // reorder style + var reorderCols = (this.reorderColumns && (!this.columnGroups || !this.columnGroups.length)) ? ' w2ui-reorder-cols-head ' : ''; + // sort style + var sortStyle = ''; + for (var si = 0; si < this.sortData.length; si++) { + if (this.sortData[si].field == col.field) { + if ((this.sortData[si].direction || '').toLowerCase() === 'asc') sortStyle = 'w2ui-sort-up'; + if ((this.sortData[si].direction || '').toLowerCase() === 'desc') sortStyle = 'w2ui-sort-down'; + } + } + // col selected + var tmp = this.last.selection.columns; + var selected = false; + for (var t in tmp) { + for (var si = 0; si < tmp[t].length; si++) { + if (tmp[t][si] == i) selected = true; + } + } + var text = (typeof col.text == 'function' ? col.text(col) : col.text); + var html = ''+ + (col.resizable !== false ? '
' : '') + + '
'+ + '
'+ + (!text ? ' ' : text) + + '
'+ + ''; + + return html + }, + + columnTooltipShow: function (ind) { + if (this.columnTooltip == 'normal') return; + var $el = $(this.box).find('#grid_'+ this.name + '_column_'+ ind); + var item = this.columns[ind]; + var pos = this.columnTooltip; + $el.prop('_mouse_over', true); + setTimeout(function () { + if ($el.prop('_mouse_over') === true && $el.prop('_mouse_tooltip') !== true) { + $el.prop('_mouse_tooltip', true); + // show tooltip + $el.w2tag(item.tooltip, { position: pos, top: 5 }); + } + }, 1); + }, + + columnTooltipHide: function (ind) { + if (this.columnTooltip == 'normal') return; + var $el = $(this.box).find('#grid_'+ this.name + '_column_'+ ind); + var item = this.columns[ind]; + $el.removeProp('_mouse_over'); + setTimeout(function () { + if ($el.prop('_mouse_over') !== true && $el.prop('_mouse_tooltip') === true) { + $el.removeProp('_mouse_tooltip'); + // hide tooltip + $el.w2tag(); + } + }, 1); + }, + + getRecordsHTML: function () { + var buffered = this.records.length; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (this.searchData.length != 0 && !url) buffered = this.last.searchIds.length; + // larger number works better with chrome, smaller with FF. + if (buffered > this.vs_start) this.last.show_extra = this.vs_extra; else this.last.show_extra = this.vs_start; + var records = $('#grid_'+ this.name +'_records'); + var limit = Math.floor((records.height() || 0) / this.recordHeight) + this.last.show_extra + 1; + if (!this.fixedBody || limit > buffered) limit = buffered; + // always need first record for resizing purposes + var rec_html = this.getRecordHTML(-1, 0); + var html1 = '' + rec_html[0]; + var html2 = '
' + rec_html[1]; + // first empty row with height + html1 += ''+ + ' '+ + ''; + html2 += ''+ + ' '+ + ''; + for (var i = 0; i < limit; i++) { + rec_html = this.getRecordHTML(i, i+1); + html1 += rec_html[0]; + html2 += rec_html[1]; + } + var h2 = (buffered - limit) * this.recordHeight; + html1 += '' + + ' '+ + ''+ + ''+ + ' '+ + ''+ + '
'; + html2 += '' + + ' '+ + ''+ + ''+ + ' '+ + ''+ + ''; + this.last.range_start = 0; + this.last.range_end = limit; + return [html1, html2]; + }, + + getSummaryHTML: function () { + if (this.summary.length === 0) return; + var rec_html = this.getRecordHTML(-1, 0); // need this in summary too for colspan to work properly + var html1 = '' + rec_html[0]; + var html2 = '
' + rec_html[1]; + for (var i = 0; i < this.summary.length; i++) { + rec_html = this.getRecordHTML(i, i+1, true); + html1 += rec_html[0]; + html2 += rec_html[1]; + } + html1 += '
'; + html2 += ''; + return [html1, html2]; + }, + + scroll: function (event) { + var time = (new Date()).getTime(); + var obj = this; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + var records = $('#grid_'+ this.name +'_records'); + var frecords = $('#grid_'+ this.name +'_frecords'); + // sync scroll positions + if (event) { + var sTop = event.target.scrollTop; + var sLeft = event.target.scrollLeft; + obj.last.scrollTop = sTop; + obj.last.scrollLeft = sLeft; + $('#grid_'+ obj.name +'_columns')[0].scrollLeft = sLeft; + $('#grid_'+ obj.name +'_summary')[0].scrollLeft = sLeft; + frecords[0].scrollTop = sTop; + } + // hide bubble + if (this.last.bubbleEl) { + $(this.last.bubbleEl).w2tag(); + this.last.bubbleEl = null; + } + // column virtual scroll + var colStart = null; + var colEnd = null; + if (obj.disableCVS || obj.columnGroups.length > 0) { + // disable virtual scroll + colStart = 0; + colEnd = obj.columns.length - 1; + } else { + var sWidth = records.width(); + var cLeft = 0; + for (var i = 0; i < obj.columns.length; i++) { + if (obj.columns[i].frozen || obj.columns[i].hidden) continue; + var cSize = parseInt(obj.columns[i].sizeCalculated ? obj.columns[i].sizeCalculated : obj.columns[i].size); + if (cLeft + cSize + 30 > obj.last.scrollLeft && colStart == null) colStart = i; + if (cLeft + cSize - 30 > obj.last.scrollLeft + sWidth && colEnd == null) colEnd = i; + cLeft += cSize; + } + if (colEnd == null) colEnd = obj.columns.length - 1; + } + if (colStart != null) { + if (colStart < 0) colStart = 0; + if (colEnd < 0) colEnd = 0; + if (colStart == colEnd) { + if (colStart > 0) colStart--; else colEnd++; // show at least one column + } + // --------- + if (colStart != obj.last.colStart || colEnd != obj.last.colEnd) { + var $box = $(obj.box); + var deltaStart = Math.abs(colStart - obj.last.colStart); + var deltaEnd = Math.abs(colEnd - obj.last.colEnd) + // add/remove columns for small jumps + if (deltaStart < 5 && deltaEnd < 5) { + var $cfirst = $box.find('.w2ui-grid-columns #grid_'+ obj.name +'_column_start'); + var $clast = $box.find('.w2ui-grid-columns .w2ui-head-last'); + var $rfirst = $box.find('#grid_'+ obj.name +'_records .w2ui-grid-data-spacer'); + var $rlast = $box.find('#grid_'+ obj.name +'_records .w2ui-grid-data-last'); + var $sfirst = $box.find('#grid_'+ obj.name +'_summary .w2ui-grid-data-spacer'); + var $slast = $box.find('#grid_'+ obj.name +'_summary .w2ui-grid-data-last'); + // remove on left + if (colStart > obj.last.colStart) { + for (var i = obj.last.colStart; i < colStart; i++) { + $box.find('#grid_'+ obj.name +'_columns #grid_'+ obj.name +'_column_'+ i).remove(); // column + $box.find('#grid_'+ obj.name +'_records td[col="'+ i +'"]').remove(); // record + $box.find('#grid_'+ obj.name +'_summary td[col="'+ i +'"]').remove(); // summary + } + } + // remove on right + if (colEnd < obj.last.colEnd) { + for (var i = obj.last.colEnd; i > colEnd; i--) { + $box.find('#grid_'+ obj.name +'_columns #grid_'+ obj.name +'_column_'+ i).remove(); // column + $box.find('#grid_'+ obj.name +'_records td[col="'+ i +'"]').remove(); // record + $box.find('#grid_'+ obj.name +'_summary td[col="'+ i +'"]').remove(); // summary + } + } + // add on left + if (colStart < obj.last.colStart) { + for (var i = obj.last.colStart - 1; i >= colStart; i--) { + if (obj.columns[i] && (obj.columns[i].frozen || obj.columns[i].hidden)) continue; + $cfirst.after(obj.getColumnCellHTML(i)); // column + // record + $rfirst.each(function (ind, el) { + var index = $(el).parent().attr('index'); + var td = ''; // width column + if (index != null) td = obj.getCellHTML(parseInt(index), i, false); + $(el).after(td); + }); + // summary + $sfirst.each(function (ind, el) { + var index = $(el).parent().attr('index'); + var td = ''; // width column + if (index != null) td = obj.getCellHTML(parseInt(index), i, true); + $(el).after(td); + }); + } + } + // add on right + if (colEnd > obj.last.colEnd) { + for (var i = obj.last.colEnd + 1; i <= colEnd; i++) { + if (obj.columns[i] && (obj.columns[i].frozen || obj.columns[i].hidden)) continue; + $clast.before(obj.getColumnCellHTML(i)); // column + // record + $rlast.each(function (ind, el) { + var index = $(el).parent().attr('index'); + var td = ''; // width column + if (index != null) td = obj.getCellHTML(parseInt(index), i, false); + $(el).before(td); + }); + // summary + $slast.each(function (ind, el) { + var index = $(el).parent().attr('index') || -1; + var td = obj.getCellHTML(parseInt(index), i, true); + $(el).before(td); + }); + } + } + obj.last.colStart = colStart; + obj.last.colEnd = colEnd; + obj.resizeRecords(); + } else { + obj.last.colStart = colStart; + obj.last.colEnd = colEnd; + // dot not just call obj.refresh(); + var colHTML = this.getColumnsHTML(); + var recHTML = this.getRecordsHTML(); + var sumHTML = this.getSummaryHTML(); + var $columns = $box.find('#grid_'+ this.name +'_columns'); + var $records = $box.find('#grid_'+ this.name +'_records'); + var $frecords = $box.find('#grid_'+ this.name +'_frecords'); + var $summary = $box.find('#grid_'+ this.name +'_summary'); + $columns.find('tbody').html(colHTML[1]); + $frecords.html(recHTML[0]); + $records.prepend(recHTML[1]); + if (sumHTML != null) $summary.html(sumHTML[1]); + // need timeout to clean up (otherwise scroll problem) + setTimeout(function () { + $records.find('> table').not('table:first-child').remove(); + if ($summary[0]) $summary[0].scrollLeft = obj.last.scrollLeft; + }, 1); + obj.resizeRecords(); + } + } + } + // perform virtual scroll + var buffered = this.records.length; + if (buffered > this.total && this.total !== -1) buffered = this.total; + if (this.searchData.length != 0 && !url) buffered = this.last.searchIds.length; + if (buffered === 0 || records.length === 0 || records.height() === 0) return; + if (buffered > this.vs_start) this.last.show_extra = this.vs_extra; else this.last.show_extra = this.vs_start; + // update footer + var t1 = Math.round(records[0].scrollTop / this.recordHeight + 1); + var t2 = t1 + (Math.round(records.height() / this.recordHeight) - 1); + if (t1 > buffered) t1 = buffered; + if (t2 >= buffered - 1) t2 = buffered; + $('#grid_'+ this.name + '_footer .w2ui-footer-right').html( + (obj.show.statusRange + ? w2utils.formatNumber(this.offset + t1) + '-' + w2utils.formatNumber(this.offset + t2) + + (this.total != -1 ? ' ' + w2utils.lang('of') + ' ' + w2utils.formatNumber(this.total) : '') + : '') + + (url && obj.show.statusBuffered ? ' ('+ w2utils.lang('buffered') + ' '+ w2utils.formatNumber(buffered) + + (this.offset > 0 ? ', skip ' + w2utils.formatNumber(this.offset) : '') + ')' : '') + ); + // only for local data source, else no extra records loaded + if (!url && (!this.fixedBody || (this.total != -1 && this.total <= this.vs_start))) return; + // regular processing + var start = Math.floor(records[0].scrollTop / this.recordHeight) - this.last.show_extra; + var end = start + Math.floor(records.height() / this.recordHeight) + this.last.show_extra * 2 + 1; + // var div = start - this.last.range_start; + if (start < 1) start = 1; + if (end > this.total && this.total != -1) end = this.total; + var tr1 = records.find('#grid_'+ this.name +'_rec_top'); + var tr2 = records.find('#grid_'+ this.name +'_rec_bottom'); + var tr1f = frecords.find('#grid_'+ this.name +'_frec_top'); + var tr2f = frecords.find('#grid_'+ this.name +'_frec_bottom'); + // if row is expanded + if (String(tr1.next().prop('id')).indexOf('_expanded_row') != -1) { + tr1.next().remove(); + tr1f.next().remove(); + } + if (this.total > end && String(tr2.prev().prop('id')).indexOf('_expanded_row') != -1) { + tr2.prev().remove(); + tr2f.prev().remove(); + } + var first = parseInt(tr1.next().attr('line')); + var last = parseInt(tr2.prev().attr('line')); + //$('#log').html('buffer: '+ this.buffered +' start-end: ' + start + '-'+ end + ' ===> first-last: ' + first + '-' + last); + if (first < start || first == 1 || this.last.pull_refresh) { // scroll down + if (end <= last + this.last.show_extra - 2 && end != this.total) return; + this.last.pull_refresh = false; + // remove from top + while (true) { + var tmp1 = frecords.find('#grid_'+ this.name +'_frec_top').next(); + var tmp2 = records.find('#grid_'+ this.name +'_rec_top').next(); + if (tmp2.attr('line') == 'bottom') break; + if (parseInt(tmp2.attr('line')) < start) { tmp1.remove(); tmp2.remove(); } else break; + } + // add at bottom + var tmp = records.find('#grid_'+ this.name +'_rec_bottom').prev(); + var rec_start = tmp.attr('line'); + if (rec_start == 'top') rec_start = start; + for (var i = parseInt(rec_start) + 1; i <= end; i++) { + if (!this.records[i-1]) continue; + var tmp2 = this.records[i-1].w2ui; + if (tmp2 && !Array.isArray(tmp2.children)) { + tmp2.expanded = false; + } + var rec_html = this.getRecordHTML(i-1, i); + tr2.before(rec_html[1]); + tr2f.before(rec_html[0]); + } + markSearch(); + setTimeout(function() { obj.refreshRanges(); }, 0); + } else { // scroll up + if (start >= first - this.last.show_extra + 2 && start > 1) return; + // remove from bottom + while (true) { + var tmp1 = frecords.find('#grid_'+ this.name +'_frec_bottom').prev(); + var tmp2 = records.find('#grid_'+ this.name +'_rec_bottom').prev(); + if (tmp2.attr('line') == 'top') break; + if (parseInt(tmp2.attr('line')) > end) { tmp1.remove(); tmp2.remove(); } else break; + } + // add at top + var tmp = records.find('#grid_'+ this.name +'_rec_top').next(); + var rec_start = tmp.attr('line'); + if (rec_start == 'bottom') rec_start = end; + for (var i = parseInt(rec_start) - 1; i >= start; i--) { + if (!this.records[i-1]) continue; + var tmp2 = this.records[i-1].w2ui; + if (tmp2 && !Array.isArray(tmp2.children)) { + tmp2.expanded = false; + } + var rec_html = this.getRecordHTML(i-1, i); + tr1.after(rec_html[1]); + tr1f.after(rec_html[0]); + } + markSearch(); + setTimeout(function() { obj.refreshRanges(); }, 0); + } + // first/last row size + var h1 = (start - 1) * obj.recordHeight; + var h2 = (buffered - end) * this.recordHeight; + if (h2 < 0) h2 = 0; + tr1.css('height', h1 + 'px'); + tr1f.css('height', h1 + 'px'); + tr2.css('height', h2 + 'px'); + tr2f.css('height', h2 + 'px'); + obj.last.range_start = start; + obj.last.range_end = end; + // load more if needed + var s = Math.floor(records[0].scrollTop / this.recordHeight); + var e = s + Math.floor(records.height() / this.recordHeight); + if (e + 10 > buffered && this.last.pull_more !== true && (buffered < this.total - this.offset || (this.total == -1 && this.last.xhr_hasMore))) { + if (this.autoLoad === true) { + this.last.pull_more = true; + this.last.xhr_offset += this.limit; + this.request('get'); + } + // scroll function + var more = $('#grid_'+ this.name +'_rec_more, #grid_'+ this.name +'_frec_more'); + more.show() + .eq(1) // only main table + .off('.load-more') + .on('click.load-more', function () { + // show spinner + $(this).find('td').html('
'); + // load more + obj.last.pull_more = true; + obj.last.xhr_offset += obj.limit; + obj.request('get'); + }) + .find('td') + .html(obj.autoLoad + ? '
' + : '
'+ w2utils.lang('Load') + ' ' + obj.limit + ' ' + w2utils.lang('More') + '...
' + ) + } + + function markSearch() { + // mark search + if (!obj.markSearch) return; + clearTimeout(obj.last.marker_timer); + obj.last.marker_timer = setTimeout(function () { + // mark all search strings + var search = []; + for (var s = 0; s < obj.searchData.length; s++) { + var sdata = obj.searchData[s]; + var fld = obj.getSearch(sdata.field); + if (!fld || fld.hidden) continue; + var ind = obj.getColumn(sdata.field, true); + search.push({ field: sdata.field, search: sdata.value, col: ind }); + } + if (search.length > 0) { + search.forEach(function (item) { + $(obj.box).find('td[col="'+ item.col +'"]').not('.w2ui-head').w2marker(item.search); + }); + } + }, 50); + } + }, + + getRecordHTML: function (ind, lineNum, summary) { + var tmph = ''; + var rec_html1 = ''; + var rec_html2 = ''; + var sel = this.last.selection; + var record; + // first record needs for resize purposes + if (ind == -1) { + rec_html1 += ''; + rec_html2 += ''; + if (this.show.lineNumbers) rec_html1 += ''; + if (this.show.selectColumn) rec_html1 += ''; + if (this.show.expandColumn) rec_html1 += ''; + rec_html2 += ''; + if (this.show.orderColumn) rec_html2 += ''; + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + tmph = ''; + if (col.frozen && !col.hidden) { + rec_html1 += tmph; + } else { + if (col.hidden || i < this.last.colStart || i > this.last.colEnd) continue; + rec_html2 += tmph; + } + } + rec_html1 += ''; + rec_html2 += ''; + rec_html1 += ''; + rec_html2 += ''; + return [rec_html1, rec_html2]; + } + // regular record + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (summary !== true) { + if (this.searchData.length > 0 && !url) { + if (ind >= this.last.searchIds.length) return ''; + ind = this.last.searchIds[ind]; + record = this.records[ind]; + } else { + if (ind >= this.records.length) return ''; + record = this.records[ind]; + } + } else { + if (ind >= this.summary.length) return ''; + record = this.summary[ind]; + } + if (!record) return ''; + if (record.recid == null && this.recid != null) { + var rid = this.parseField(record, this.recid) + if (rid != null) record.recid = rid; + } + var id = w2utils.escapeId(record.recid); + var isRowSelected = false; + if (sel.indexes.indexOf(ind) != -1) isRowSelected = true; + var rec_style = (record.w2ui ? record.w2ui.style : ''); + if (rec_style == null || typeof rec_style != 'string') rec_style = ''; + var rec_class = (record.w2ui ? record.w2ui.class : ''); + if (rec_class == null || typeof rec_class != 'string') rec_class = ''; + // render TR + rec_html1 += ''; + rec_html2 += ''; + if (this.show.lineNumbers) { + rec_html1 += ''+ + (summary !== true ? this.getLineHTML(lineNum, record) : '') + + ''; + } + if (this.show.selectColumn) { + var hideCB = false; + if (record && record.w2ui && record.w2ui.hideCheckBox === true) hideCB = true; + rec_html1 += + ''+ + (summary !== true && !(record.w2ui && record.w2ui.hideCheckBox === true) ? + '
'+ + ' '+ + '
' + : + '' ) + + ''; + } + if (this.show.expandColumn) { + var tmp_img = ''; + if (record.w2ui && record.w2ui.expanded === true) tmp_img = '-'; else tmp_img = '+'; + if (record.w2ui && record.w2ui.expanded == 'none') tmp_img = ''; + if (record.w2ui && record.w2ui.expanded == 'spinner') tmp_img = '
'; + rec_html1 += + ''+ + (summary !== true ? + '
'+ + ' '+ tmp_img +'
' + : + '' ) + + ''; + } + // insert empty first column + rec_html2 += ''; + if (this.show.orderColumn) { + rec_html2 += + ''+ + (summary !== true ? '
 
' : '' ) + + ''; + } + var col_ind = 0; + var col_skip = 0; + while (true) { + var col_span = 1; + var col = this.columns[col_ind]; + if (col == null) break; + if (col.hidden) { + col_ind++; + if (col_skip > 0) col_skip--; + continue; + } + if (col_skip > 0) { + col_ind++; + if (this.columns[col_ind] == null) break; + record.w2ui.colspan[this.columns[col_ind-1].field] = 0; // need it for other methods + col_skip--; + continue; + } else if (record.w2ui) { + var tmp1 = record.w2ui.colspan; + var tmp2 = this.columns[col_ind].field; + if (tmp1 && tmp1[tmp2] === 0) { + delete tmp1[tmp2]; // if no longer colspan then remove 0 + } + } + // column virtual scroll + if ((col_ind < this.last.colStart || col_ind > this.last.colEnd) && !col.frozen) { + col_ind++; + continue; + } + if (record.w2ui) { + if (typeof record.w2ui.colspan == 'object') { + var span = parseInt(record.w2ui.colspan[col.field]) || null; + if (span > 1) { + // if there are hidden columns, then no colspan on them + var hcnt = 0; + for (var i = col_ind; i < col_ind + span; i++) { + if (i >= this.columns.length) break; + if (this.columns[i].hidden) hcnt++; + } + col_span = span - hcnt; + col_skip = span - 1; + } + } + } + var rec_cell = this.getCellHTML(ind, col_ind, summary, col_span); + if (col.frozen) rec_html1 += rec_cell; else rec_html2 += rec_cell; + col_ind++; + } + rec_html1 += ''; + rec_html2 += ''; + rec_html1 += ''; + rec_html2 += ''; + return [rec_html1, rec_html2]; + }, + + getLineHTML: function(lineNum) { + return '
' + lineNum + '
'; + }, + + getCellHTML: function (ind, col_ind, summary, col_span) { + var obj = this; + var col = this.columns[col_ind]; + if (col == null) return ''; + var record = (summary !== true ? this.records[ind] : this.summary[ind]); + var data = (ind !== -1 ? this.getCellValue(ind, col_ind, summary) : ''); + var edit = (ind !== -1 ? this.getCellEditable(ind, col_ind) : ''); + var style = 'max-height: '+ parseInt(this.recordHeight) +'px;' + (col.clipboardCopy ? 'margin-right: 20px' : ''); + var isChanged = !summary && record && record.w2ui && record.w2ui.changes && record.w2ui.changes[col.field] != null; + var addStyle = ''; + var addClass = ''; + var sel = this.last.selection; + var isRowSelected = false; + var infoBubble = ''; + if (sel.indexes.indexOf(ind) != -1) isRowSelected = true; + if (col_span == null) { + if (record && record.w2ui && record.w2ui.colspan && record.w2ui.colspan[col.field]) { + col_span = record.w2ui.colspan[col.field]; + } else { + col_span = 1; + } + } + // expand icon + if (col_ind === 0 && record && record.w2ui && Array.isArray(record.w2ui.children)) { + var level = 0; + var subrec = this.get(record.w2ui.parent_recid, true); + while (true) { + if (subrec != null) { + level++ + var tmp = this.records[subrec].w2ui; + if (tmp != null && tmp.parent_recid != null) { + subrec = this.get(tmp.parent_recid, true); + } else { + break; + } + } else { + break; + } + } + if (record.w2ui.parent_recid){ + for (var i = 0; i < level; i++) { + infoBubble += ''; + } + } + infoBubble += ''; + } + // info bubble + if (col.info === true) col.info = {}; + if (col.info != null) { + var infoIcon = 'w2ui-icon-info'; + if (typeof col.info.icon == 'function') { + infoIcon = col.info.icon(record); + } else if (typeof col.info.icon == 'object') { + infoIcon = col.info.icon[this.parseField(record, col.field)] || '' + } else if (typeof col.info.icon == 'string') { + infoIcon = col.info.icon; + } + var infoStyle = col.info.style || ''; + if (typeof col.info.style == 'function') { + infoStyle = col.info.style(record); + } else if (typeof col.info.style == 'object') { + infoStyle = col.info.style[this.parseField(record, col.field)] || ''; + } else if (typeof col.info.style == 'string') { + infoStyle = col.info.style; + } + infoBubble += ''; + } + if (col.render != null && ind !== -1) { + if (typeof col.render == 'function') { + var html = col.render.call(this, record, ind, col_ind, data); + if (html != null && typeof html == 'object') { + data = $.trim(html.html || ''); + addClass = html.class || ''; + addStyle = html.style || ''; + } else { + data = $.trim(html); + } + if (data.length < 4 || data.substr(0, 4).toLowerCase() != '' + infoBubble + String(data) + ''; + } + } + // if it is an object + if (typeof col.render == 'object') { + var dsp = col.render[data]; + if (dsp == null || dsp === '') dsp = data; + data = '
' + infoBubble + String(dsp) + '
'; + } + // formatters + if (typeof col.render == 'string') { + var t = col.render.toLowerCase().indexOf(':'); + var tmp = []; + if (t == -1) { + tmp[0] = col.render.toLowerCase(); + tmp[1] = ''; + } else { + tmp[0] = col.render.toLowerCase().substr(0, t); + tmp[1] = col.render.toLowerCase().substr(t+1); + } + // formatters + var func = w2utils.formatters[tmp[0]]; + if (col.options && col.options.autoFormat === false) { + func = null; + } + data = (typeof func == 'function' ? func(data, tmp[1], record) : ''); + data = '
' + infoBubble + String(data) + '
'; + } + } else { + // if editable checkbox + if (edit && ['checkbox', 'check'].indexOf(edit.type) != -1) { + var changeInd = summary ? -(ind + 1) : ind; + style += 'text-align: center;'; + data = ''; + infoBubble = ''; + } + data = '
' + infoBubble + String(data) + '
'; + } + if (data == null) data = ''; + // --> cell TD + if (typeof col.render == 'string') { + var tmp = col.render.toLowerCase().split(':'); + if (['number', 'int', 'float', 'money', 'currency', 'percent', 'size'].indexOf(tmp[0]) != -1) addStyle += 'text-align: right;'; + } + if (record && record.w2ui) { + if (typeof record.w2ui.style == 'object') { + if (typeof record.w2ui.style[col_ind] == 'string') addStyle += record.w2ui.style[col_ind] + ';'; + if (typeof record.w2ui.style[col.field] == 'string') addStyle += record.w2ui.style[col.field] + ';'; + } + if (typeof record.w2ui.class == 'object') { + if (typeof record.w2ui.class[col_ind] == 'string') addClass += record.w2ui.class[col_ind] + ' '; + if (typeof record.w2ui.class[col.field] == 'string') addClass += record.w2ui.class[col.field] + ' '; + } + } + var isCellSelected = false; + if (isRowSelected && $.inArray(col_ind, sel.columns[ind]) != -1) isCellSelected = true; + var clipboardTxt = (typeof col.clipboardCopy == 'string' ? col.clipboardCopy : 'Copy to clipboard') + var clipboardIcon = '' + // data + data = ' 1 ? 'colspan="'+ col_span + '"' : '') + + '>' + data + (w2utils.stripTags(data) != '' && col.clipboardCopy && clipboardTxt ? clipboardIcon : '') +''; + // summary top row + if (ind === -1 && summary === true) { + data = ' 1 ? 'colspan="'+ col_span + '"' : '') + + '>'; + } + return data; + + function getTitle(cellData){ + var title = ""; + if (obj.show.recordTitles) { + if (col.title != null) { + if (typeof col.title == 'function') title = col.title.call(obj, record, ind, col_ind); + if (typeof col.title == 'string') title = col.title; + } else { + title = w2utils.stripTags(String(cellData).replace(/"/g, "''")); + } + } + return (title != null) ? String(title) : ""; + } + }, + + clipboardCopy: function (ind, col_ind) { + var rec = this.records[ind] + var col = this.columns[col_ind] + var txt = (col ? this.parseField(rec, col.field) : ''); + if (typeof col.clipboardCopy == 'function') { + txt = col.clipboardCopy(rec) + } + $('#grid_' + this.name + '_focus').text(txt).select(); + document.execCommand('copy'); + }, + + showBubble: function (ind, col_ind) { + var html = ''; + var info = this.columns[col_ind].info; + var rec = this.records[ind]; + var el = $(this.box).find('#grid_'+ this.name +'_data_'+ ind +'_'+ col_ind + ' .w2ui-info'); + if (this.last.bubbleEl) $(this.last.bubbleEl).w2tag(); + this.last.bubbleEl = el; + // if no fields defined - show all + if (info.fields == null) { + info.fields = []; + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + info.fields.push(col.field + (typeof col.render == 'string' ? ':' + col.render : '')); + } + } + var fields = info.fields; + if (typeof fields == 'function') { + fields = fields(rec, ind, col_ind); // custom renderer + } + // generate html + if (typeof info.render == 'function') { + html = info.render(rec, ind, col_ind); + + } else if ($.isArray(fields)) { + // display mentioned fields + html = ''; + for (var i = 0; i < fields.length; i++) { + var tmp = String(fields[i]).split(':'); + if (tmp[0] == '' || tmp[0] == '-' || tmp[0] == '--' || tmp[0] == '---') { + html += ''; + continue; + } + var col = this.getColumn(tmp[0]); + if (col == null) col = { field: tmp[0], caption: tmp[0] }; // if not found in columns + var val = (col ? this.parseField(rec, col.field) : ''); + if (tmp.length > 1) { + if (w2utils.formatters[tmp[1]]) { + val = w2utils.formatters[tmp[1]](val, tmp[2] || null, rec); + } else { + console.log('ERROR: w2utils.formatters["'+ tmp[1] + '"] does not exists.') + } + } + if (info.showEmpty !== true && (val == null || val == '')) continue; + if (info.maxLength != null && typeof val == 'string' && val.length > info.maxLength) val = val.substr(0, info.maxLength) + '...'; + html += ''; + } + html += '
' + col.text + '' + ((val === 0 ? '0' : val) || '') + '
'; + } else if ($.isPlainObject(fields)) { + // display some fields + html = ''; + for (var caption in fields) { + var fld = fields[caption]; + if (fld == '' || fld == '-' || fld == '--' || fld == '---') { + html += ''; + continue; + } + var tmp = String(fld).split(':'); + var col = this.getColumn(tmp[0]); + if (col == null) col = { field: tmp[0], caption: tmp[0] }; // if not found in columns + var val = (col ? this.parseField(rec, col.field) : ''); + if (tmp.length > 1) { + if (w2utils.formatters[tmp[1]]) { + val = w2utils.formatters[tmp[1]](val, tmp[2] || null, rec); + } else { + console.log('ERROR: w2utils.formatters["'+ tmp[1] + '"] does not exists.') + } + } + if (typeof fld == 'function') { + val = fld(rec, ind, col_ind); + } + if (info.showEmpty !== true && (val == null || val == '')) continue; + if (info.maxLength != null && typeof val == 'string' && val.length > info.maxLength) val = val.substr(0, info.maxLength) + '...'; + html += ''; + } + html += '
' + caption + '' + (val || '') + '
'; + } + $(el).w2tag($.extend({ + html : html, + left : -4, + position : 'bottom|top', + className : 'w2ui-info-bubble', + style : '', + hideOnClick : true + }, info.options || {})); + }, + + // return null or the editable object if the given cell is editable + getCellEditable: function (ind, col_ind) { + var col = this.columns[col_ind]; + var rec = this.records[ind]; + if (!rec || !col) return null; + var edit = (rec.w2ui ? rec.w2ui.editable : null); + if (edit === false) return null; + if (edit == null || edit === true) { + edit = (col ? col.editable : null); + if (typeof(edit) === 'function') { + var data = this.getCellValue(ind, col_ind, false); + // same arguments as col.render() + edit = edit.call(this, rec, ind, col_ind, data); + } + } + return edit; + }, + + getCellValue: function (ind, col_ind, summary) { + var col = this.columns[col_ind]; + var record = (summary !== true ? this.records[ind] : this.summary[ind]); + var data = this.parseField(record, col.field); + if (record && record.w2ui && record.w2ui.changes && record.w2ui.changes[col.field] != null) { + data = record.w2ui.changes[col.field]; + } + if ($.isPlainObject(data) /*&& col.editable*/) { //It can be an object btw + if (col.options && col.options.items) { + val=col.options.items.find(function(item){ return item.id==data.id}); + if (val) data=val.text; + else data=data.id; + } else { + if (data.text != null) data = data.text; + if (data.id != null) data = data.id; + } + } + if (data == null) data = ''; + return data; + }, + + getFooterHTML: function () { + return '
'+ + ' '+ + ' '+ + ' '+ + '
'; + }, + + status: function (msg) { + if (msg != null) { + $('#grid_'+ this.name +'_footer').find('.w2ui-footer-left').html(msg); + } else { + // show number of selected + var msgLeft = ''; + var sel = this.getSelection(); + if (sel.length > 0) { + if (this.show.statusSelection && sel.length > 1) { + msgLeft = String(sel.length).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,") + ' ' + w2utils.lang('selected'); + } + if (this.show.statusRecordID && sel.length == 1) { + var tmp = sel[0]; + if (typeof tmp == 'object') tmp = tmp.recid + ', '+ w2utils.lang('Column') +': '+ tmp.column; + msgLeft = w2utils.lang('Record ID') + ': '+ tmp + ' '; + } + } + $('#grid_'+ this.name +'_footer .w2ui-footer-left').html(msgLeft); + // toolbar + if (sel.length == 1) this.toolbar.enable('w2ui-edit'); else this.toolbar.disable('w2ui-edit'); + if (sel.length >= 1) this.toolbar.enable('w2ui-delete'); else this.toolbar.disable('w2ui-delete'); + } + }, + + lock: function (msg, showSpinner) { + var obj = this; + var args = Array.prototype.slice.call(arguments, 0); + args.unshift(this.box); + setTimeout(function () { + // hide empty msg if any + $(obj.box).find('#grid_'+ obj.name +'_empty_msg').remove(); + w2utils.lock.apply(window, args); + }, 10); + }, + + unlock: function (speed) { + var box = this.box; + setTimeout(function () { + // do not unlock if there is a message + if ($(box).find('.w2ui-message').not('.w2ui-closing').length > 0) return; + w2utils.unlock(box, speed); + }, 25); // needed timer so if server fast, it will not flash + }, + + stateSave: function (returnOnly) { + var obj = this; + if (!w2utils.hasLocalStorage) return null; + var state = { + columns : [], + show : $.extend({}, this.show), + last : { + search : this.last.search, + multi : this.last.multi, + logic : this.last.logic, + label : this.last.label, + field : this.last.field, + scrollTop : this.last.scrollTop, + scrollLeft : this.last.scrollLeft + }, + sortData : [], + searchData : [] + }; + var prop_val; + for (var i = 0; i < this.columns.length; i++) { + var col = obj.columns[i]; + var col_save_obj = {}; + // iterate properties to save + Object.keys(obj.stateColProps).forEach(function(prop, idx) { + if(obj.stateColProps[prop]){ + // check if the property is defined on the column + if(col[prop] !== undefined){ + prop_val = col[prop]; + } else { + // use fallback or null + prop_val = obj.stateColDefaults[prop] || null; + } + col_save_obj[prop] = prop_val; + } + }); + state.columns.push(col_save_obj); + } + for (var i = 0; i < this.sortData.length; i++) state.sortData.push($.extend({}, this.sortData[i])); + for (var i = 0; i < this.searchData.length; i++) state.searchData.push($.extend({}, this.searchData[i])); + // save into local storage + if (returnOnly !== true) { + // event before + var edata = this.trigger({ phase: 'before', type: 'stateSave', target: this.name, state: state }); + if (edata.isCancelled === true) { if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); return; } + try { + var savedState = $.parseJSON(localStorage.w2ui || '{}'); + if (!savedState) savedState = {}; + if (!savedState.states) savedState.states = {}; + savedState.states[(this.stateId || this.name)] = state; + localStorage.w2ui = JSON.stringify(savedState); + } catch (e) { + delete localStorage.w2ui; + return null; + } + // event after + this.trigger($.extend(edata, { phase: 'after' })); + } + return state; + }, + + stateRestore: function (newState) { + var obj = this; + var url = (typeof this.url != 'object' ? this.url : this.url.get); + if (!newState) { + // read it from local storage + try { + if (!w2utils.hasLocalStorage) return false; + var tmp = $.parseJSON(localStorage.w2ui || '{}'); + if (!tmp) tmp = {}; + if (!tmp.states) tmp.states = {}; + newState = tmp.states[(this.stateId || this.name)]; + } catch (e) { + delete localStorage.w2ui; + return null; + } + } + // event before + var edata = this.trigger({ phase: 'before', type: 'stateRestore', target: this.name, state: newState }); + if (edata.isCancelled === true) { if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); return; } + // default behavior + if ($.isPlainObject(newState)) { + $.extend(this.show, newState.show); + $.extend(this.last, newState.last); + var sTop = this.last.scrollTop; + var sLeft = this.last.scrollLeft; + for (var c = 0; c < newState.columns.length; c++) { + var tmp = newState.columns[c]; + var col_index = this.getColumn(tmp.field, true); + if (col_index !== null) { + $.extend(this.columns[col_index], tmp); + // restore column order from saved state + if (c !== col_index) this.columns.splice(c, 0, this.columns.splice(col_index, 1)[0]); + } + } + this.sortData.splice(0, this.sortData.length); + for (var c = 0; c < newState.sortData.length; c++) this.sortData.push(newState.sortData[c]); + this.searchData.splice(0, this.searchData.length); + for (var c = 0; c < newState.searchData.length; c++) this.searchData.push(newState.searchData[c]); + // apply sort and search + setTimeout(function () { + // needs timeout as records need to be populated + // ez 10.09.2014 this --> + if (!url) { + if (obj.sortData.length > 0) obj.localSort(); + if (obj.searchData.length > 0) obj.localSearch(); + } + obj.last.scrollTop = sTop; + obj.last.scrollLeft = sLeft; + obj.refresh(); + }, 1); + } + // event after + this.trigger($.extend(edata, { phase: 'after' })); + return true; + }, + + stateReset: function () { + var obj = this; + this.stateRestore(this.last.state); + // remove from local storage + if (w2utils.hasLocalStorage) { + try { + var tmp = $.parseJSON(localStorage.w2ui || '{}'); + if (tmp.states && tmp.states[(this.stateId || this.name)]) { + delete tmp.states[(this.stateId || this.name)]; + } + localStorage.w2ui = JSON.stringify(tmp); + } catch (e) { + delete localStorage.w2ui; + return null; + } + } + }, + + parseField: function (obj, field) { + if (this.nestedFields) { + var val = ''; + try { // need this to make sure no error in fields + val = obj; + var tmp = String(field).split('.'); + for (var i = 0; i < tmp.length; i++) { + val = val[tmp[i]]; + } + } catch (event) { + val = ''; + } + return val; + } else { + return obj ? obj[field] : ''; + } + }, + + prepareData: function () { + var obj = this; + + // loops thru records and prepares date and time objects + for (var r = 0; r < this.records.length; r++) { + var rec = this.records[r]; + prepareRecord(rec); + } + + // prepare date and time objects for the 'rec' record and its closed children + function prepareRecord(rec) { + for (var c = 0; c < obj.columns.length; c++) { + var column = obj.columns[c]; + if (rec[column.field] == null || typeof column.render != 'string') continue; + // number + if (['number', 'int', 'float', 'money', 'currency', 'percent'].indexOf(column.render.split(':')[0]) != -1) { + if (typeof rec[column.field] != 'number') rec[column.field] = parseFloat(rec[column.field]); + } + // date + if (['date', 'age'].indexOf(column.render.split(':')[0]) != -1) { + if (!rec[column.field + '_']) { + var dt = rec[column.field]; + if (w2utils.isInt(dt)) dt = parseInt(dt); + rec[column.field + '_'] = new Date(dt); + } + } + // time + if (['time'].indexOf(column.render) != -1) { + if (w2utils.isTime(rec[column.field])) { // if string + var tmp = w2utils.isTime(rec[column.field], true); + var dt = new Date(); + dt.setHours(tmp.hours, tmp.minutes, (tmp.seconds ? tmp.seconds : 0), 0); // sets hours, min, sec, mills + if (!rec[column.field + '_']) rec[column.field + '_'] = dt; + } else { // if date object + var tmp = rec[column.field]; + if (w2utils.isInt(tmp)) tmp = parseInt(tmp); + var tmp = (tmp != null ? new Date(tmp) : new Date()); + var dt = new Date(); + dt.setHours(tmp.getHours(), tmp.getMinutes(), tmp.getSeconds(), 0); // sets hours, min, sec, mills + if (!rec[column.field + '_']) rec[column.field + '_'] = dt; + } + } + } + + if (rec.w2ui && rec.w2ui.children && rec.w2ui.expanded !== true) { + // there are closed children, prepare them too. + for (var r = 0; r < rec.w2ui.children.length; r++) { + var subRec = rec.w2ui.children[r]; + prepareRecord(subRec); + } + } + } + }, + + nextCell: function (index, col_ind, editable) { + var check = col_ind + 1; + if (check >= this.columns.length) return null; + var tmp = this.records[index].w2ui; + var ccol = this.columns[col_ind]; + // if (tmp && tmp.colspan[ccol.field]) check += parseInt(tmp.colspan[ccol.field]) -1; // colspan of a column + var col = this.columns[check]; + var span = (tmp && tmp.colspan && !isNaN(tmp.colspan[col.field]) ? parseInt(tmp.colspan[col.field]) : 1); + if (col == null) return null; + if (col && col.hidden || span === 0) return this.nextCell(index, check, editable); + if (editable) { + var edit = this.getCellEditable(index, col_ind); + if (edit == null || ['checkbox', 'check'].indexOf(edit.type) != -1) { + return this.nextCell(index, check, editable); + } + } + return check; + }, + + prevCell: function (index, col_ind, editable) { + var check = col_ind - 1; + if (check < 0) return null; + var tmp = this.records[index].w2ui; + var col = this.columns[check]; + var span = (tmp && tmp.colspan && !isNaN(tmp.colspan[col.field]) ? parseInt(tmp.colspan[col.field]) : 1); + if (col == null) return null; + if (col && col.hidden || span === 0) return this.prevCell(index, check, editable); + if (editable) { + var edit = this.getCellEditable(index, col_ind); + if (edit == null || ['checkbox', 'check'].indexOf(edit.type) != -1) { + return this.prevCell(index, check, editable); + } + } + return check; + }, + + nextRow: function (ind, col_ind) { + var sids = this.last.searchIds; + var ret = null; + if ((ind + 1 < this.records.length && sids.length === 0) // if there are more records + || (sids.length > 0 && ind < sids[sids.length-1])) { + ind++; + if (sids.length > 0) while (true) { + if ($.inArray(ind, sids) != -1 || ind > this.records.length) break; + ind++; + } + // colspan + var tmp = this.records[ind].w2ui; + var col = this.columns[col_ind]; + var span = (tmp && tmp.colspan && col != null && !isNaN(tmp.colspan[col.field]) ? parseInt(tmp.colspan[col.field]) : 1); + if (span === 0) { + ret = this.nextRow(ind, col_ind); + } else { + ret = ind; + } + } + return ret; + }, + + prevRow: function (ind, col_ind) { + var sids = this.last.searchIds; + var ret = null; + if ((ind > 0 && sids.length === 0) // if there are more records + || (sids.length > 0 && ind > sids[0])) { + ind--; + if (sids.length > 0) while (true) { + if ($.inArray(ind, sids) != -1 || ind < 0) break; + ind--; + } + // colspan + var tmp = this.records[ind].w2ui; + var col = this.columns[col_ind]; + var span = (tmp && tmp.colspan && col != null && !isNaN(tmp.colspan[col.field]) ? parseInt(tmp.colspan[col.field]) : 1); + if (span === 0) { + ret = this.prevRow(ind, col_ind); + } else { + ret = ind; + } + } + return ret; + }, + + selectionSave: function () { + this.last._selection = this.getSelection(); + return this.last._selection; + }, + + selectionRestore: function (noRefresh) { + var time = (new Date()).getTime(); + this.last.selection = { indexes: [], columns: {} }; + var sel = this.last.selection; + var lst = this.last._selection; + if (lst) for (var i = 0; i < lst.length; i++) { + if ($.isPlainObject(lst[i])) { + // selectType: cell + var tmp = this.get(lst[i].recid, true); + if (tmp != null) { + if (sel.indexes.indexOf(tmp) == -1) sel.indexes.push(tmp); + if (!sel.columns[tmp]) sel.columns[tmp] = []; + sel.columns[tmp].push(lst[i].column); + } + } else { + // selectType: row + var tmp = this.get(lst[i], true); + if (tmp != null) sel.indexes.push(tmp); + } + } + delete this.last._selection; + if (noRefresh !== true) this.refresh(); + return (new Date()).getTime() - time; + }, + + message: function(options, callBack) { + if (typeof options == 'string') { + options = { + width : (options.length < 300 ? 350 : 550), + height : (options.length < 300 ? 170: 250), + body : '
' + options + '
', + buttons : '', + onOpen : function (event) { + setTimeout(function () { + $(this.box).find('.w2ui-btn').focus(); + }, 25); + }, + onClose: function (even) { + if (typeof callBack == 'function') callBack(); + } + }; + } + w2utils.message.call(this, { + box : this.box, + path : 'w2ui.' + this.name, + title : '.w2ui-grid-header:visible', + body : '.w2ui-grid-box' + }, options); + } + } + + $.extend(w2grid.prototype, w2utils.event); + w2obj.grid = w2grid; +})(jQuery); + +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2layout - layout widget +* - $().w2layout - jQuery wrapper +* - Dependencies: jQuery, w2utils, w2toolbar, w2tabs +* +* == changes +* - negative values for left, right panel +* - onResize for layout as well as onResizing +* - panel.callBack - one time +* - layout.html().replaced(function () {}) +* +* == NICE TO HAVE == +* - onResize for the panel +* - add more panel title positions (left=rotated, right=rotated, bottom) +* - bug: when you assign content before previous transition completed. +* +************************************************************************/ + +(function ($) { + var w2layout = function (options) { + this.box = null; // DOM Element that holds the element + this.name = null; // unique name for w2ui + this.panels = []; + this.tmp = {}; + this.padding = 1; // panel padding + this.resizer = 4; // resizer width or height + this.style = ''; + + $.extend(true, this, w2obj.layout, options); + }; + + var w2panels = ['top', 'left', 'main', 'preview', 'right', 'bottom']; + + // ==================================================== + // -- Registers as a jQuery plugin + + $.fn.w2layout = function(method) { + if ($.isPlainObject(method)) { + // check name parameter + if (!w2utils.checkName(method, 'w2layout')) return; + var panels = method.panels || []; + var object = new w2layout(method); + $.extend(object, { handlers: [], panels: [] }); + // add defined panels + for (var p = 0, len = panels.length; p < len; p++) { + object.panels[p] = $.extend(true, {}, w2layout.prototype.panel, panels[p]); + if ($.isPlainObject(object.panels[p].tabs) || $.isArray(object.panels[p].tabs)) initTabs(object, panels[p].type); + if ($.isPlainObject(object.panels[p].toolbar) || $.isArray(object.panels[p].toolbar)) initToolbar(object, panels[p].type); + } + // add all other panels + for (var p1 = 0; p1 < w2panels.length; p1++) { + if (object.get(w2panels[p1]) != null) continue; + object.panels.push($.extend(true, {}, w2layout.prototype.panel, { type: w2panels[p1], hidden: (w2panels[p1] !== 'main'), size: 50 })); + } + w2ui[object.name] = object; + if ($(this).length > 0) { + object.render($(this)[0]); + } + return object; + + } else { + var obj = w2ui[$(this).attr('name')]; + if (!obj) return null; + if (arguments.length > 0) { + if (obj[method]) obj[method].apply(obj, Array.prototype.slice.call(arguments, 1)); + return this; + } else { + return obj; + } + } + + function initTabs(object, panel, tabs) { + var pan = object.get(panel); + if (pan != null && tabs == null) tabs = pan.tabs; + if (pan == null || tabs == null) return false; + // instanciate tabs + if ($.isArray(tabs)) tabs = { tabs: tabs }; + $().w2destroy(object.name + '_' + panel + '_tabs'); // destroy if existed + pan.tabs = $().w2tabs($.extend({}, tabs, { owner: object, name: object.name + '_' + panel + '_tabs' })); + pan.show.tabs = true; + return true; + } + + function initToolbar(object, panel, toolbar) { + var pan = object.get(panel); + if (pan != null && toolbar == null) toolbar = pan.toolbar; + if (pan == null || toolbar == null) return false; + // instanciate toolbar + if ($.isArray(toolbar)) toolbar = { items: toolbar }; + $().w2destroy(object.name + '_' + panel + '_toolbar'); // destroy if existed + pan.toolbar = $().w2toolbar($.extend({}, toolbar, { owner: object, name: object.name + '_' + panel + '_toolbar' })); + pan.show.toolbar = true; + return true; + } + }; + + // ==================================================== + // -- Implementation of core functionality + + w2layout.prototype = { + onShow : null, + onHide : null, + onResizing : null, + onResizerClick: null, + onRender : null, + onRefresh : null, + onContent : null, + onResize : null, + onDestroy : null, + + // default setting for a panel + panel: { + type : null, // left, right, top, bottom + title : '', + size : 100, // width or height depending on panel name + minSize : 20, + maxSize : false, + hidden : false, + resizable : false, + overflow : 'auto', + style : '', + content : '', // can be String or Object with .render(box) method + tabs : null, + toolbar : null, + width : null, // read only + height : null, // read only + show : { + toolbar : false, + tabs : false + }, + callBack : null, // function to call when content is overwritten + onRefresh : null, + onShow : null, + onHide : null + }, + + // alias for content + content: function (panel, data, transition) { + console.log('NOTICE: layout.content method is deprecated, please use layout.html() instead'); + return this.html(panel, data, transition); + }, + + html: function (panel, data, transition) { + var obj = this; + var p = this.get(panel); + var promise = { + panel : panel, + html : p.content, + error : false, + cancelled : false, + removed : function (callBack) { + if (typeof callBack == 'function') { + p.callBack = callBack + } + } + } + if (typeof p.callBack == 'function') { + p.callBack({ panel: panel, content: p.content, new_content: data, transition: transition || 'none' }); + p.callBack = null; // this is one time call back only + } + // if it is CSS panel + if (panel == 'css') { + $('#layout_'+ obj.name +'_panel_css').html(''); + promise.status = true; + return promise; + } + if (p == null) { + console.log('ERROR: incorrect panel name. Panel name can be main, left, right, top, bottom, preview or css') + promise.error = true; + return promise; + } + if (data == null) { + return promise; + } + // event before + var edata = this.trigger({ phase: 'before', type: 'content', target: panel, object: p, content: data, transition: transition }); + if (edata.isCancelled === true) { + promise.cancelled = true; + return promise; + } + + if (data instanceof jQuery) { + console.log('ERROR: You can not pass jQuery object to w2layout.content() method'); + return promise; + } + var pname = '#layout_'+ this.name + '_panel_'+ p.type; + var current = $(pname + '> .w2ui-panel-content'); + var panelTop = 0; + if (current.length > 0) { + $(pname).scrollTop(0); + panelTop = $(current).position().top; + } + if (p.content === '') { + p.content = data; + this.refresh(panel); + } else { + p.content = data; + if (!p.hidden) { + if (transition != null && transition !== '') { + // apply transition + var div1 = $(pname + '> .w2ui-panel-content'); + div1.after('
'); + var div2 = $(pname + '> .w2ui-panel-content.new-panel'); + div1.css('top', panelTop); + div2.css('top', panelTop); + if (typeof data == 'object') { + data.box = div2[0]; // do not do .render(box); + data.render(); + } else { + div2.html(data); + } + w2utils.transition(div1[0], div2[0], transition, function () { + div1.remove(); + div2.removeClass('new-panel'); + div2.css('overflow', p.overflow); + // make sure only one content left + $(pname + '> .w2ui-panel-content').slice(1).remove() + // IE Hack + obj.resize(); + if (window.navigator.userAgent.indexOf('MSIE') != -1) setTimeout(function () { obj.resize(); }, 100); + }); + } + } + this.refresh(panel); + } + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + // IE Hack + obj.resize(); + if (window.navigator.userAgent.indexOf('MSIE') != -1) setTimeout(function () { obj.resize(); }, 100); + return promise; + }, + + message: function(panel, options) { + var obj = this; + if (typeof options == 'string') { + options = { + width : (options.length < 300 ? 350 : 550), + height : (options.length < 300 ? 170: 250), + body : '
' + options + '
', + buttons : '', + onOpen : function (event) { + setTimeout(function () { + $(this.box).find('.w2ui-btn').focus(); + }, 25); + } + }; + } + var p = this.get(panel); + var $el = $('#layout_'+ this.name + '_panel_'+ p.type); + var oldOverflow = $el.css('overflow'); + var oldOnClose; + if (options) { + if (options.onClose) oldOnClose = options.onClose; + options.onClose = function (event) { + if (typeof oldOnClose == 'function') oldOnClose(event); + event.done(function () { + $('#layout_'+ obj.name + '_panel_'+ p.type).css('overflow', oldOverflow); + }); + }; + } + $('#layout_'+ this.name + '_panel_'+ p.type).css('overflow', 'hidden'); + w2utils.message.call(this, { + box : $('#layout_'+ this.name + '_panel_'+ p.type), + param : panel, + path : 'w2ui.' + this.name, + title : '.w2ui-panel-title:visible', + body : '.w2ui-panel-content' + }, options); + }, + + load: function (panel, url, transition, onLoad) { + var obj = this; + if (panel == 'css') { + $.get(url, function (data, status, xhr) { // should always be $.get as it is template + obj.html(panel, xhr.responseText); + if (onLoad) onLoad(); + }); + return true; + } + if (this.get(panel) != null) { + $.get(url, function (data, status, xhr) { // should always be $.get as it is template + obj.html(panel, xhr.responseText, transition); + if (onLoad) onLoad(); + // IE Hack + obj.resize(); + if (window.navigator.userAgent.indexOf('MSIE') != -1) setTimeout(function () { obj.resize(); }, 100); + }); + return true; + } + return false; + }, + + sizeTo: function (panel, size, instant) { + var obj = this; + var pan = obj.get(panel); + if (pan == null) return false; + // resize + $(obj.box).find(' > div > .w2ui-panel') + .css(w2utils.cssPrefix('transition', (instant !== true ? '.2s' : '0s'))); + setTimeout(function () { + obj.set(panel, { size: size }); + }, 1); + // clean + setTimeout(function () { + $(obj.box).find(' > div > .w2ui-panel').css(w2utils.cssPrefix('transition', '0s')); + obj.resize(); + }, 500); + return true; + }, + + show: function (panel, immediate) { + var obj = this; + // event before + var edata = this.trigger({ phase: 'before', type: 'show', target: panel, object: this.get(panel), immediate: immediate }); + if (edata.isCancelled === true) return; + + var p = obj.get(panel); + if (p == null) return false; + p.hidden = false; + if (immediate === true) { + $('#layout_'+ obj.name +'_panel_'+panel).css({ 'opacity': '1' }); + obj.trigger($.extend(edata, { phase: 'after' })); + obj.resize(); + } else { + // resize + $('#layout_'+ obj.name +'_panel_'+panel).css({ 'opacity': '0' }); + $(obj.box).find(' > div > .w2ui-panel').css(w2utils.cssPrefix('transition', '.2s')); + setTimeout(function () { obj.resize(); }, 1); + // show + setTimeout(function() { + $('#layout_'+ obj.name +'_panel_'+ panel).css({ 'opacity': '1' }); + }, 250); + // clean + setTimeout(function () { + $(obj.box).find(' > div > .w2ui-panel').css(w2utils.cssPrefix('transition', '0s')); + obj.trigger($.extend(edata, { phase: 'after' })); + obj.resize(); + }, 500); + } + return true; + }, + + hide: function (panel, immediate) { + var obj = this; + // event before + var edata = this.trigger({ phase: 'before', type: 'hide', target: panel, object: this.get(panel), immediate: immediate }); + if (edata.isCancelled === true) return; + + var p = obj.get(panel); + if (p == null) return false; + p.hidden = true; + if (immediate === true) { + $('#layout_'+ obj.name +'_panel_'+panel).css({ 'opacity': '0' }); + obj.trigger($.extend(edata, { phase: 'after' })); + obj.resize(); + } else { + // hide + $(obj.box).find(' > div > .w2ui-panel').css(w2utils.cssPrefix('transition', '.2s')); + $('#layout_'+ obj.name +'_panel_'+panel).css({ 'opacity': '0' }); + setTimeout(function () { obj.resize(); }, 1); + // clean + setTimeout(function () { + $(obj.box).find(' > div > .w2ui-panel').css(w2utils.cssPrefix('transition', '0s')); + obj.trigger($.extend(edata, { phase: 'after' })); + obj.resize(); + }, 500); + } + return true; + }, + + toggle: function (panel, immediate) { + var p = this.get(panel); + if (p == null) return false; + if (p.hidden) return this.show(panel, immediate); else return this.hide(panel, immediate); + }, + + set: function (panel, options) { + var ind = this.get(panel, true); + if (ind == null) return false; + $.extend(this.panels[ind], options); + // refresh only when content changed + if (options.content != null || options.resizable != null) { + this.refresh(panel); + } + // show/hide resizer + this.resize(); // resize is needed when panel size is changed + return true; + }, + + get: function (panel, returnIndex) { + for (var p = 0; p < this.panels.length; p++) { + if (this.panels[p].type == panel) { + if (returnIndex === true) return p; else return this.panels[p]; + } + } + return null; + }, + + el: function (panel) { + var el = $('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-content'); + if (el.length != 1) return null; + return el[0]; + }, + + hideToolbar: function (panel) { + var pan = this.get(panel); + if (!pan) return; + pan.show.toolbar = false; + $('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-toolbar').hide(); + this.resize(); + }, + + showToolbar: function (panel) { + var pan = this.get(panel); + if (!pan) return; + pan.show.toolbar = true; + $('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-toolbar').show(); + this.resize(); + }, + + toggleToolbar: function (panel) { + var pan = this.get(panel); + if (!pan) return; + if (pan.show.toolbar) this.hideToolbar(panel); else this.showToolbar(panel); + }, + + assignToolbar: function (panel, toolbar) { + if (typeof toolbar == 'string' && w2ui[toolbar] != null) toolbar = w2ui[toolbar]; + var pan = this.get(panel); + pan.toolbar = toolbar; + var tmp = $(this.box).find(panel +'> .w2ui-panel-toolbar'); + if (pan.toolbar != null) { + if (tmp.find('[name='+ pan.toolbar.name +']').length === 0) { + tmp.w2render(pan.toolbar); + } else if (pan.toolbar != null) { + pan.toolbar.refresh(); + } + toolbar.owner = this; + this.showToolbar(panel); + this.refresh(panel); + } else { + tmp.html(''); + this.hideToolbar(panel); + } + }, + + hideTabs: function (panel) { + var pan = this.get(panel); + if (!pan) return; + pan.show.tabs = false; + $('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-tabs').hide(); + this.resize(); + }, + + showTabs: function (panel) { + var pan = this.get(panel); + if (!pan) return; + pan.show.tabs = true; + $('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-tabs').show(); + this.resize(); + }, + + toggleTabs: function (panel) { + var pan = this.get(panel); + if (!pan) return; + if (pan.show.tabs) this.hideTabs(panel); else this.showTabs(panel); + }, + + render: function (box) { + var obj = this; + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + var time = (new Date()).getTime(); + // event before + var edata = obj.trigger({ phase: 'before', type: 'render', target: obj.name, box: box }); + if (edata.isCancelled === true) return; + + if (box != null) { + if ($(obj.box).find('#layout_'+ obj.name +'_panel_main').length > 0) { + $(obj.box) + .removeAttr('name') + .removeClass('w2ui-layout') + .html(''); + } + obj.box = box; + } + if (!obj.box) return false; + $(obj.box) + .attr('name', obj.name) + .addClass('w2ui-layout') + .html('
'); + if ($(obj.box).length > 0) $(obj.box)[0].style.cssText += obj.style; + // create all panels + for (var p1 = 0; p1 < w2panels.length; p1++) { + var pan = obj.get(w2panels[p1]); + var html = '
'+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'; + $(obj.box).find(' > div').append(html); + // tabs are rendered in refresh() + } + $(obj.box).find(' > div') + .append('
'); + obj.refresh(); // if refresh is not called here, the layout will not be available right after initialization + // process event + obj.trigger($.extend(edata, { phase: 'after' })); + // reinit events + setTimeout(function () { // needed this timeout to allow browser to render first if there are tabs or toolbar + initEvents(); + obj.resize(); + }, 0); + return (new Date()).getTime() - time; + + function initEvents() { + obj.tmp.events = { + resize : function (event) { + if (w2ui[obj.name] == null) { + $(window).off('resize.w2ui-'+ obj.name); + } else { + w2ui[obj.name].resize(); + } + }, + resizeStart : resizeStart, + mouseMove : resizeMove, + mouseUp : resizeStop + }; + $(window).on('resize.w2ui-'+ obj.name, obj.tmp.events.resize); + } + + function resizeStart(type, evnt) { + if (!obj.box) return; + if (!evnt) evnt = window.event; + $(document).off('mousemove', obj.tmp.events.mouseMove).on('mousemove', obj.tmp.events.mouseMove); + $(document).off('mouseup', obj.tmp.events.mouseUp).on('mouseup', obj.tmp.events.mouseUp); + obj.tmp.resize = { + type : type, + x : evnt.screenX, + y : evnt.screenY, + diff_x : 0, + diff_y : 0, + value : 0 + }; + // lock all panels + for (var p1 = 0; p1 < w2panels.length; p1++) { + var $tmp = $(obj.el(w2panels[p1])).parent().find('.w2ui-lock'); + if ($tmp.length > 0) { + $tmp.attr('locked', 'previous'); + } else { + obj.lock(w2panels[p1], { opacity: 0 }); + } + } + if (type == 'left' || type == 'right') { + obj.tmp.resize.value = parseInt($('#layout_'+ obj.name +'_resizer_'+ type)[0].style.left); + } + if (type == 'top' || type == 'preview' || type == 'bottom') { + obj.tmp.resize.value = parseInt($('#layout_'+ obj.name +'_resizer_'+ type)[0].style.top); + } + } + + function resizeStop(evnt) { + if (!obj.box) return; + if (!evnt) evnt = window.event; + $(document).off('mousemove', obj.tmp.events.mouseMove); + $(document).off('mouseup', obj.tmp.events.mouseUp); + if (obj.tmp.resize == null) return; + // unlock all panels + for (var p1 = 0; p1 < w2panels.length; p1++) { + var $tmp = $(obj.el(w2panels[p1])).parent().find('.w2ui-lock'); + if ($tmp.attr('locked') == 'previous') { + $tmp.removeAttr('locked'); + } else { + obj.unlock(w2panels[p1]); + } + } + // set new size + if (obj.tmp.diff_x !== 0 || obj.tmp.resize.diff_y !== 0) { // only recalculate if changed + var ptop = obj.get('top'); + var pbottom = obj.get('bottom'); + var panel = obj.get(obj.tmp.resize.type); + var height = parseInt($(obj.box).height()); + var width = parseInt($(obj.box).width()); + var str = String(panel.size); + var ns, nd; + switch (obj.tmp.resize.type) { + case 'top': + ns = parseInt(panel.sizeCalculated) + obj.tmp.resize.diff_y; + nd = 0; + break; + case 'bottom': + ns = parseInt(panel.sizeCalculated) - obj.tmp.resize.diff_y; + nd = 0; + break; + case 'preview': + ns = parseInt(panel.sizeCalculated) - obj.tmp.resize.diff_y; + nd = (ptop && !ptop.hidden ? ptop.sizeCalculated : 0) + + (pbottom && !pbottom.hidden ? pbottom.sizeCalculated : 0); + break; + case 'left': + ns = parseInt(panel.sizeCalculated) + obj.tmp.resize.diff_x; + nd = 0; + break; + case 'right': + ns = parseInt(panel.sizeCalculated) - obj.tmp.resize.diff_x; + nd = 0; + break; + } + // set size + if (str.substr(str.length-1) == '%') { + panel.size = Math.floor(ns * 100 / (panel.type == 'left' || panel.type == 'right' ? width : height - nd) * 100) / 100 + '%'; + } else { + if (String(panel.size).substr(0, 1) == '-') { + panel.size = parseInt(panel.size) - panel.sizeCalculated + ns; + } else { + panel.size = ns; + } + } + obj.resize(); + } + $('#layout_'+ obj.name + '_resizer_'+ obj.tmp.resize.type).removeClass('active'); + delete obj.tmp.resize; + } + + function resizeMove(evnt) { + if (!obj.box) return; + if (!evnt) evnt = window.event; + if (obj.tmp.resize == null) return; + var panel = obj.get(obj.tmp.resize.type); + // event before + var tmp = obj.tmp.resize; + var edata = obj.trigger({ phase: 'before', type: 'resizing', target: obj.name, object: panel, originalEvent: evnt, + panel: tmp ? tmp.type : 'all', diff_x: tmp ? tmp.diff_x : 0, diff_y: tmp ? tmp.diff_y : 0 }); + if (edata.isCancelled === true) return; + + var p = $('#layout_'+ obj.name + '_resizer_'+ tmp.type); + var resize_x = (evnt.screenX - tmp.x); + var resize_y = (evnt.screenY - tmp.y); + var mainPanel = obj.get('main'); + + if (!p.hasClass('active')) p.addClass('active'); + + switch (tmp.type) { + case 'left': + if (panel.minSize - resize_x > panel.width) { + resize_x = panel.minSize - panel.width; + } + if (panel.maxSize && (panel.width + resize_x > panel.maxSize)) { + resize_x = panel.maxSize - panel.width; + } + if (mainPanel.minSize + resize_x > mainPanel.width) { + resize_x = mainPanel.width - mainPanel.minSize; + } + break; + + case 'right': + if (panel.minSize + resize_x > panel.width) { + resize_x = panel.width - panel.minSize; + } + if (panel.maxSize && (panel.width - resize_x > panel.maxSize)) { + resize_x = panel.width - panel.maxSize; + } + if (mainPanel.minSize - resize_x > mainPanel.width) { + resize_x = mainPanel.minSize - mainPanel.width; + } + break; + + case 'top': + if (panel.minSize - resize_y > panel.height) { + resize_y = panel.minSize - panel.height; + } + if (panel.maxSize && (panel.height + resize_y > panel.maxSize)) { + resize_y = panel.maxSize - panel.height; + } + if (mainPanel.minSize + resize_y > mainPanel.height) { + resize_y = mainPanel.height - mainPanel.minSize; + } + break; + + case 'preview': + case 'bottom': + if (panel.minSize + resize_y > panel.height) { + resize_y = panel.height - panel.minSize; + } + if (panel.maxSize && (panel.height - resize_y > panel.maxSize)) { + resize_y = panel.height - panel.maxSize; + } + if (mainPanel.minSize - resize_y > mainPanel.height) { + resize_y = mainPanel.minSize - mainPanel.height; + } + break; + } + tmp.diff_x = resize_x; + tmp.diff_y = resize_y; + + switch (tmp.type) { + case 'top': + case 'preview': + case 'bottom': + tmp.diff_x = 0; + if (p.length > 0) p[0].style.top = (tmp.value + tmp.diff_y) + 'px'; + break; + + case 'left': + case 'right': + tmp.diff_y = 0; + if (p.length > 0) p[0].style.left = (tmp.value + tmp.diff_x) + 'px'; + break; + } + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + } + }, + + refresh: function (panel) { + var obj = this; + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + if (panel == null) panel = null; + var time = (new Date()).getTime(); + // event before + var edata = obj.trigger({ phase: 'before', type: 'refresh', target: (panel != null ? panel : obj.name), object: obj.get(panel) }); + if (edata.isCancelled === true) return; + // obj.unlock(panel); + if (typeof panel == 'string') { + var p = obj.get(panel); + if (p == null) return; + var pname = '#layout_'+ obj.name + '_panel_'+ p.type; + var rname = '#layout_'+ obj.name +'_resizer_'+ p.type; + // apply properties to the panel + $(pname).css({ display: p.hidden ? 'none' : 'block' }); + if (p.resizable) $(rname).show(); else $(rname).hide(); + // insert content + if (typeof p.content == 'object' && typeof p.content.render === 'function') { + p.content.box = $(pname +'> .w2ui-panel-content')[0]; + setTimeout(function () { + // need to remove unnecessary classes + if ($(pname +'> .w2ui-panel-content').length > 0) { + $(pname +'> .w2ui-panel-content') + .removeClass() + .removeAttr('name') + .addClass('w2ui-panel-content') + .css('overflow', p.overflow)[0].style.cssText += ';' + p.style; + } + if (p.content && typeof p.content.render == 'function') { + p.content.render(); // do not do .render(box); + } + }, 1); + } else { + // need to remove unnecessary classes + if ($(pname +'> .w2ui-panel-content').length > 0) { + $(pname +'> .w2ui-panel-content') + .removeClass() + .removeAttr('name') + .addClass('w2ui-panel-content') + .html(p.content) + .css('overflow', p.overflow)[0].style.cssText += ';' + p.style; + } + } + // if there are tabs and/or toolbar - render it + var tmp = $(obj.box).find(pname +'> .w2ui-panel-tabs'); + if (p.show.tabs) { + if (tmp.find('[name='+ p.tabs.name +']').length === 0 && p.tabs != null) tmp.w2render(p.tabs); else p.tabs.refresh(); + } else { + tmp.html('').removeClass('w2ui-tabs').hide(); + } + tmp = $(obj.box).find(pname +'> .w2ui-panel-toolbar'); + if (p.show.toolbar) { + if (tmp.find('[name='+ p.toolbar.name +']').length === 0 && p.toolbar != null) tmp.w2render(p.toolbar); else p.toolbar.refresh(); + } else { + tmp.html('').removeClass('w2ui-toolbar').hide(); + } + // show title + tmp = $(obj.box).find(pname +'> .w2ui-panel-title'); + if (p.title) { + tmp.html(p.title).show(); + } else { + tmp.html('').hide(); + } + } else { + if ($('#layout_'+ obj.name +'_panel_main').length === 0) { + obj.render(); + return; + } + obj.resize(); + // refresh all of them + for (var p1 = 0; p1 < this.panels.length; p1++) { obj.refresh(this.panels[p1].type); } + } + obj.trigger($.extend(edata, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + resize: function () { + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + if (!this.box) return false; + var time = (new Date()).getTime(); + // event before + var tmp = this.tmp.resize; + var edata = this.trigger({ phase: 'before', type: 'resize', target: this.name, + panel: tmp ? tmp.type : 'all', diff_x: tmp ? tmp.diff_x : 0, diff_y: tmp ? tmp.diff_y : 0 }); + if (edata.isCancelled === true) return; + if (this.padding < 0) this.padding = 0; + + // layout itself + var width = parseInt($(this.box).width()); + var height = parseInt($(this.box).height()); + $(this.box).find(' > div').css({ + width : width + 'px', + height : height + 'px' + }); + var obj = this; + // panels + var pmain = this.get('main'); + var pprev = this.get('preview'); + var pleft = this.get('left'); + var pright = this.get('right'); + var ptop = this.get('top'); + var pbottom = this.get('bottom'); + var smain = true; // main always on + var sprev = (pprev != null && pprev.hidden !== true ? true : false); + var sleft = (pleft != null && pleft.hidden !== true ? true : false); + var sright = (pright != null && pright.hidden !== true ? true : false); + var stop = (ptop != null && ptop.hidden !== true ? true : false); + var sbottom = (pbottom != null && pbottom.hidden !== true ? true : false); + var l, t, w, h, e; + // calculate % + for (var p = 0; p < w2panels.length; p++) { + if (w2panels[p] === 'main') continue; + tmp = this.get(w2panels[p]); + if (!tmp) continue; + var str = String(tmp.size || 0); + if (str.substr(str.length-1) == '%') { + var tmph = height; + if (tmp.type == 'preview') { + tmph = tmph - + (ptop && !ptop.hidden ? ptop.sizeCalculated : 0) - + (pbottom && !pbottom.hidden ? pbottom.sizeCalculated : 0); + } + tmp.sizeCalculated = parseInt((tmp.type == 'left' || tmp.type == 'right' ? width : tmph) * parseFloat(tmp.size) / 100); + } else { + tmp.sizeCalculated = parseInt(tmp.size); + } + tmp.sizeCalculated = Math.max(tmp.sizeCalculated, parseInt(tmp.minSize)); + } + // negative size + if (String(pright.size).substr(0, 1) == '-') { + if (sleft && String(pleft.size).substr(0, 1) == '-') { + console.log('ERROR: you cannot have both left panel.size and right panel.size be negative.'); + } else { + pright.sizeCalculated = width - (sleft ? pleft.sizeCalculated : 0) + parseInt(pright.size); + } + } + if (String(pleft.size).substr(0, 1) == '-') { + if (sright && pright.size.substr(0, 1) == '-') { + console.log('ERROR: you cannot have both left panel.size and right panel.size be negative.'); + } else { + pleft.sizeCalculated = width - (sright ? pright.sizeCalculated : 0) + parseInt(pleft.size); + } + } + // top if any + if (ptop != null && ptop.hidden !== true) { + l = 0; + t = 0; + w = width; + h = ptop.sizeCalculated; + $('#layout_'+ this.name +'_panel_top').css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }).show(); + ptop.width = w; + ptop.height = h; + // resizer + if (ptop.resizable) { + t = ptop.sizeCalculated - (this.padding === 0 ? this.resizer : 0); + h = (this.resizer > this.padding ? this.resizer : this.padding); + $('#layout_'+ this.name +'_resizer_top').show().css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ns-resize' + }).off('mousedown').on('mousedown', function (event) { + // event before + var edata = obj.trigger({ phase: 'before', type: 'resizerClick', target: 'top', originalEvent: event }); + if (edata.isCancelled === true) return; + // default action + w2ui[obj.name].tmp.events.resizeStart('top', event); + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + return false; + }); + } + } else { + $('#layout_'+ this.name +'_panel_top').hide(); + $('#layout_'+ this.name +'_resizer_top').hide(); + } + // left if any + if (pleft != null && pleft.hidden !== true) { + l = 0; + t = 0 + (stop ? ptop.sizeCalculated + this.padding : 0); + w = pleft.sizeCalculated; + h = height - (stop ? ptop.sizeCalculated + this.padding : 0) - + (sbottom ? pbottom.sizeCalculated + this.padding : 0); + e = $('#layout_'+ this.name +'_panel_left'); + if (window.navigator.userAgent.indexOf('MSIE') != -1 && e.length > 0 && e[0].clientHeight < e[0].scrollHeight) w += 17; // IE hack + e.css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }).show(); + pleft.width = w; + pleft.height = h; + // resizer + if (pleft.resizable) { + l = pleft.sizeCalculated - (this.padding === 0 ? this.resizer : 0); + w = (this.resizer > this.padding ? this.resizer : this.padding); + $('#layout_'+ this.name +'_resizer_left').show().css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ew-resize' + }).off('mousedown').on('mousedown', function (event) { + // event before + var edata = obj.trigger({ phase: 'before', type: 'resizerClick', target: 'left', originalEvent: event }); + if (edata.isCancelled === true) return; + // default action + w2ui[obj.name].tmp.events.resizeStart('left', event); + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + return false; + }); + } + } else { + $('#layout_'+ this.name +'_panel_left').hide(); + $('#layout_'+ this.name +'_resizer_left').hide(); + } + // right if any + if (pright != null && pright.hidden !== true) { + l = width - pright.sizeCalculated; + t = 0 + (stop ? ptop.sizeCalculated + this.padding : 0); + w = pright.sizeCalculated; + h = height - (stop ? ptop.sizeCalculated + this.padding : 0) - + (sbottom ? pbottom.sizeCalculated + this.padding : 0); + $('#layout_'+ this.name +'_panel_right').css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }).show(); + pright.width = w; + pright.height = h; + // resizer + if (pright.resizable) { + l = l - this.padding; + w = (this.resizer > this.padding ? this.resizer : this.padding); + $('#layout_'+ this.name +'_resizer_right').show().css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ew-resize' + }).off('mousedown').on('mousedown', function (event) { + // event before + var edata = obj.trigger({ phase: 'before', type: 'resizerClick', target: 'right', originalEvent: event }); + if (edata.isCancelled === true) return; + // default action + w2ui[obj.name].tmp.events.resizeStart('right', event); + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + return false; + }); + } + } else { + $('#layout_'+ this.name +'_panel_right').hide(); + $('#layout_'+ this.name +'_resizer_right').hide(); + } + // bottom if any + if (pbottom != null && pbottom.hidden !== true) { + l = 0; + t = height - pbottom.sizeCalculated; + w = width; + h = pbottom.sizeCalculated; + $('#layout_'+ this.name +'_panel_bottom').css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }).show(); + pbottom.width = w; + pbottom.height = h; + // resizer + if (pbottom.resizable) { + t = t - (this.padding === 0 ? 0 : this.padding); + h = (this.resizer > this.padding ? this.resizer : this.padding); + $('#layout_'+ this.name +'_resizer_bottom').show().css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ns-resize' + }).off('mousedown').on('mousedown', function (event) { + // event before + var edata = obj.trigger({ phase: 'before', type: 'resizerClick', target: 'bottom', originalEvent: event }); + if (edata.isCancelled === true) return; + // default action + w2ui[obj.name].tmp.events.resizeStart('bottom', event); + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + return false; + }); + } + } else { + $('#layout_'+ this.name +'_panel_bottom').hide(); + $('#layout_'+ this.name +'_resizer_bottom').hide(); + } + // main - always there + l = 0 + (sleft ? pleft.sizeCalculated + this.padding : 0); + t = 0 + (stop ? ptop.sizeCalculated + this.padding : 0); + w = width - (sleft ? pleft.sizeCalculated + this.padding : 0) - + (sright ? pright.sizeCalculated + this.padding: 0); + h = height - (stop ? ptop.sizeCalculated + this.padding : 0) - + (sbottom ? pbottom.sizeCalculated + this.padding : 0) - + (sprev ? pprev.sizeCalculated + this.padding : 0); + e = $('#layout_'+ this.name +'_panel_main'); + if (window.navigator.userAgent.indexOf('MSIE') != -1 && e.length > 0 && e[0].clientHeight < e[0].scrollHeight) w += 17; // IE hack + e.css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }); + pmain.width = w; + pmain.height = h; + + // preview if any + if (pprev != null && pprev.hidden !== true) { + l = 0 + (sleft ? pleft.sizeCalculated + this.padding : 0); + t = height - (sbottom ? pbottom.sizeCalculated + this.padding : 0) - pprev.sizeCalculated; + w = width - (sleft ? pleft.sizeCalculated + this.padding : 0) - + (sright ? pright.sizeCalculated + this.padding : 0); + h = pprev.sizeCalculated; + e = $('#layout_'+ this.name +'_panel_preview'); + if (window.navigator.userAgent.indexOf('MSIE') != -1 && e.length > 0 && e[0].clientHeight < e[0].scrollHeight) w += 17; // IE hack + e.css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }).show(); + pprev.width = w; + pprev.height = h; + // resizer + if (pprev.resizable) { + t = t - (this.padding === 0 ? 0 : this.padding); + h = (this.resizer > this.padding ? this.resizer : this.padding); + $('#layout_'+ this.name +'_resizer_preview').show().css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ns-resize' + }).off('mousedown').on('mousedown', function (event) { + // event before + var edata = obj.trigger({ phase: 'before', type: 'resizerClick', target: 'preview', originalEvent: event }); + if (edata.isCancelled === true) return; + // default action + w2ui[obj.name].tmp.events.resizeStart('preview', event); + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + return false; + }); + } + } else { + $('#layout_'+ this.name +'_panel_preview').hide(); + $('#layout_'+ this.name +'_resizer_preview').hide(); + } + + // display tabs and toolbar if needed + for (var p1 = 0; p1 < w2panels.length; p1++) { + var pan = this.get(w2panels[p1]); + var tmp2 = '#layout_'+ this.name +'_panel_'+ w2panels[p1] +' > .w2ui-panel-'; + var tabHeight = 0; + if (pan) { + if (pan.title) { + tabHeight += w2utils.getSize($(tmp2 + 'title').css({ top: tabHeight + 'px', display: 'block' }), 'height'); + } + if (pan.show.tabs) { + if (pan.tabs != null && w2ui[this.name +'_'+ w2panels[p1] +'_tabs']) w2ui[this.name +'_'+ w2panels[p1] +'_tabs'].resize(); + tabHeight += w2utils.getSize($(tmp2 + 'tabs').css({ top: tabHeight + 'px', display: 'block' }), 'height'); + } + if (pan.show.toolbar) { + if (pan.toolbar != null && w2ui[this.name +'_'+ w2panels[p1] +'_toolbar']) w2ui[this.name +'_'+ w2panels[p1] +'_toolbar'].resize(); + tabHeight += w2utils.getSize($(tmp2 + 'toolbar').css({ top: tabHeight + 'px', display: 'block' }), 'height'); + } + } + $(tmp2 + 'content').css({ display: 'block' }).css({ top: tabHeight + 'px' }); + } + // send resize to all objects + clearTimeout(this._resize_timer); + this._resize_timer = setTimeout(function () { + for (var e in w2ui) { + if (typeof w2ui[e].resize == 'function') { + // sent to all none-layouts + if (w2ui[e].panels == null) w2ui[e].resize(); + // only send to nested layouts + var parent = $(w2ui[e].box).parents('.w2ui-layout'); + if (parent.length > 0 && parent.attr('name') == obj.name) w2ui[e].resize(); + } + } + }, 100); + this.trigger($.extend(edata, { phase: 'after' })); + return (new Date()).getTime() - time; + }, + + destroy: function () { + // event before + var edata = this.trigger({ phase: 'before', type: 'destroy', target: this.name }); + if (edata.isCancelled === true) return; + if (w2ui[this.name] == null) return false; + // clean up + if ($(this.box).find('#layout_'+ this.name +'_panel_main').length > 0) { + $(this.box) + .removeAttr('name') + .removeClass('w2ui-layout') + .html(''); + } + delete w2ui[this.name]; + // event after + this.trigger($.extend(edata, { phase: 'after' })); + if (this.tmp.events && this.tmp.events.resize) $(window).off('resize', this.tmp.events.resize); + return true; + }, + + lock: function (panel, msg, showSpinner) { + if (w2panels.indexOf(panel) == -1) { + console.log('ERROR: First parameter needs to be the a valid panel name.'); + return; + } + var args = Array.prototype.slice.call(arguments, 0); + args[0] = '#layout_'+ this.name + '_panel_' + panel; + w2utils.lock.apply(window, args); + }, + + unlock: function (panel, speed) { + if (w2panels.indexOf(panel) == -1) { + console.log('ERROR: First parameter needs to be the a valid panel name.'); + return; + } + var nm = '#layout_'+ this.name + '_panel_' + panel; + w2utils.unlock(nm, speed); + } + }; + + $.extend(w2layout.prototype, w2utils.event); + w2obj.layout = w2layout; +})(jQuery); + +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2popup - popup widget +* - $().w2popup - jQuery wrapper +* - Dependencies: jQuery, w2utils +* +* == changes +* - added onMove event +* - w2prompt.options.ok_class, cancel_class +* - w2confirm.options.onOpen, w2confirm.options.onClose +* - w2prompt.options.onOpen, w2prompt.options.onClose +* - w2popup.actions, w2popup.action, w2popup.onAction +* - w2popup.onMsgOpen, w2popup.onMsgClose +* - options.multiple +* +* == NICE TO HAVE == +* - hide overlay on esc +* - make popup width/height in % +* +************************************************************************/ + +var w2popup = {}; + +(function ($) { + + // ==================================================== + // -- Registers as a jQuery plugin + + $.fn.w2popup = function(method, options) { + if (method == null) { + options = {}; + method = 'open'; + } + if ($.isPlainObject(method)) { + options = method; + method = 'open'; + } + method = method.toLowerCase(); + if (method === 'load' && typeof options === 'string') { + options = $.extend({ url: options }, arguments.length > 2 ? arguments[2] : {}); + } + if (method === 'open' && options.url != null) method = 'load'; + options = options || {}; + // load options from markup + var dlgOptions = {}; + if ($(this).length > 0 && method == 'open') { + if ($(this).find('div[rel=title], div[rel=body], div[rel=buttons]').length > 0) { + // remember previous tempalte + if ($('#w2ui-popup').length > 0) { + var tmp = $('#w2ui-popup').data('options'); + w2popup._prev = { + template : w2popup._template, + title : tmp.title, + body : tmp.body, + buttons : tmp.buttons + }; + } + w2popup._template = this; + + if ($(this).find('div[rel=title]').length > 0) { + dlgOptions['title'] = $(this).find('div[rel=title]'); + } + if ($(this).find('div[rel=body]').length > 0) { + dlgOptions['body'] = $(this).find('div[rel=body]'); + dlgOptions['style'] = $(this).find('div[rel=body]')[0].style.cssText; + } + if ($(this).find('div[rel=buttons]').length > 0) { + dlgOptions['buttons'] = $(this).find('div[rel=buttons]'); + } + } else { + dlgOptions['title'] = ' '; + dlgOptions['body'] = $(this).html(); + } + if (parseInt($(this).css('width')) !== 0) dlgOptions['width'] = parseInt($(this).css('width')); + //if the popup will have a title bar, we must add the height of title bar to the popup height + var hasTitlebar = options.title || (options.showClose || options.showClose === undefined) || (options.showMax || options.showMax === undefined) ; + if (parseInt($(this).css('height')) !== 0) dlgOptions['height'] = parseInt($(this).css('height')) + (hasTitlebar?32:0); + } + // show popup + return w2popup[method]($.extend({}, dlgOptions, options)); + }; + + // ==================================================== + // -- Implementation of core functionality (SINGLETON) + + w2popup = { + defaults: { + title : '', + body : '', + buttons : '', + style : '', + color : '#000', + opacity : 0.4, + speed : 0.3, + modal : false, + maximized : false, + keyboard : true, // will close popup on esc if not modal + width : 500, + height : 300, + showClose : true, + showMax : false, + transition: null, + multiple : false // if popup already open, opens as a message + }, + status : 'closed', // string that describes current status + handlers : [], + onOpen : null, + onClose : null, + onMax : null, + onMin : null, + onToggle : null, + onKeydown : null, + + open: function (options) { + var obj = this; + var orig_options = $.extend(true, {}, options); + if (w2popup.status == 'closing') { + setTimeout(function () { obj.open.call(obj, options); }, 100); + return; + } + // get old options and merge them + var old_options = $('#w2ui-popup').data('options'); + var options = $.extend({}, this.defaults, old_options, { title: '', body : '', buttons: '' }, options, { maximized: false }); + // need timer because popup might not be open + setTimeout(function () { $('#w2ui-popup').data('options', options); }, 100); + // if new - reset event handlers + if ($('#w2ui-popup').length === 0) { + // w2popup.handlers = []; // if commented, allows to add w2popup.on() for all + w2popup.onMax = null; + w2popup.onMin = null; + w2popup.onToggle = null; + w2popup.onOpen = null; + w2popup.onClose = null; + w2popup.onKeydown = null; + w2popup.onAction = null; + } + if (options.onOpen) w2popup.onOpen = options.onOpen; + if (options.onClose) w2popup.onClose = options.onClose; + if (options.onMax) w2popup.onMax = options.onMax; + if (options.onMin) w2popup.onMin = options.onMin; + if (options.onToggle) w2popup.onToggle = options.onToggle; + if (options.onKeydown) w2popup.onKeydown = options.onKeydown; + if (options.onAction) w2popup.onAction = options.onAction; + options.width = parseInt(options.width); + options.height = parseInt(options.height); + + var maxW, maxH; + if (window.innerHeight == undefined) { + maxW = parseInt(document.documentElement.offsetWidth); + maxH = parseInt(document.documentElement.offsetHeight); + if (w2utils.engine === 'IE7') { maxW += 21; maxH += 4; } + } else { + maxW = parseInt(window.innerWidth); + maxH = parseInt(window.innerHeight); + } + if (maxW - 10 < options.width) options.width = maxW - 10; + if (maxH - 10 < options.height) options.height = maxH - 10; + var top = (maxH - options.height) / 2 * 0.6; + var left = (maxW - options.width) / 2; + + // convert action arrays into buttons + if (options.actions != null) { + options.buttons = ''; + Object.keys(options.actions).forEach(function (action) { + var handler = options.actions[action]; + if (typeof handler == 'function') { + options.buttons += '' + } + if (typeof handler == 'object') { + options.buttons += '' + } + if (typeof handler == 'string') { + options.buttons += handler + } + }); + } + + // check if message is already displayed + if ($('#w2ui-popup').length === 0) { + // trigger event + var edata = this.trigger({ phase: 'before', type: 'open', target: 'popup', options: options, present: false }); + if (edata.isCancelled === true) return; + w2popup.status = 'opening'; + // output message + w2popup.lockScreen(options); + var btn = ''; + if (options.showClose) { + btn += '
Close
'; + } + if (options.showMax) { + btn += '
Max
'; + } + // first insert just body + var msg = '
'; + $('body').append(msg); + // parse rel=* + var parts = $('#w2ui-popup'); + if (parts.find('div[rel=title], div[rel=body], div[rel=buttons]').length > 0) { + // title + var tmp = parts.find('div[rel=title]'); + if (tmp.length > 0) { options.title = tmp.html(); tmp.remove(); } + // buttons + var tmp = parts.find('div[rel=buttons]'); + if (tmp.length > 0) { options.buttons = tmp.html(); tmp.remove(); } + // body + var tmp = parts.find('div[rel=body]'); + if (tmp.length > 0) options.body = tmp.html(); else options.body = parts.html(); + } + // then content + var msg = '
' + btn + '
'+ + '
'+ + '
' + + '
'+ + '
'+ + '
'+ + ''; // this is needed to keep focus in popup + $('#w2ui-popup').html(msg); + + if (options.title) $('#w2ui-popup .w2ui-popup-title').append(options.title); + if (options.buttons) $('#w2ui-popup .w2ui-popup-buttons').append(options.buttons); + if (options.body) $('#w2ui-popup .w2ui-popup-body').append(options.body); + + // allow element to render + setTimeout(function () { + $('#w2ui-popup') + .css(w2utils.cssPrefix({ + 'transition': options.speed + 's opacity, ' + options.speed + 's -webkit-transform' + })) + .removeClass("w2ui-popup-opening"); + obj.focus(); + }, 1); + // clean transform + setTimeout(function () { + $('#w2ui-popup').css(w2utils.cssPrefix('transform', '')); + }, options.speed * 1000); + // event after + w2popup.status = 'open'; + obj.trigger($.extend(edata, { phase: 'after' })); + + } else if (options.multiple === true) { + // popup is not compatible with w2popup.message + w2popup.message(orig_options) + } else { + // if was from template and now not + if (w2popup._prev == null && w2popup._template != null) obj.restoreTemplate(); + + // trigger event + var edata = this.trigger({ phase: 'before', type: 'open', target: 'popup', options: options, present: true }); + if (edata.isCancelled === true) return; + // check if size changed + w2popup.status = 'opening'; + if (old_options != null) { + if (!old_options.maximized && (old_options['width'] != options['width'] || old_options['height'] != options['height'])) { + w2popup.resize(options.width, options.height); + } + options.prevSize = options.width + 'px:' + options.height + 'px'; + options.maximized = old_options.maximized; + } + // show new items + var cloned = $('#w2ui-popup .w2ui-box').clone(); + cloned.removeClass('w2ui-box').addClass('w2ui-box-temp').find('.w2ui-popup-body').empty().append(options.body); + // parse rel=* + if (typeof options.body == 'string' && cloned.find('div[rel=title], div[rel=body], div[rel=buttons]').length > 0) { + // title + var tmp = cloned.find('div[rel=title]'); + if (tmp.length > 0) { options['title'] = tmp.html(); tmp.remove(); } + // buttons + var tmp = cloned.find('div[rel=buttons]'); + if (tmp.length > 0) { options['buttons'] = tmp.html(); tmp.remove(); } + // body + var tmp = cloned.find('div[rel=body]'); + if (tmp.length > 0) options['body'] = tmp.html(); else options['body'] = cloned.html(); + // set proper body + cloned.html(options.body); + } + $('#w2ui-popup .w2ui-box').after(cloned); + + if (options.buttons) { + $('#w2ui-popup .w2ui-popup-buttons').show().html('').append(options.buttons); + $('#w2ui-popup .w2ui-popup-body').removeClass('w2ui-popup-no-buttons'); + $('#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp').css('bottom', ''); + } else { + $('#w2ui-popup .w2ui-popup-buttons').hide().html(''); + $('#w2ui-popup .w2ui-popup-body').addClass('w2ui-popup-no-buttons'); + $('#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp').css('bottom', '0px'); + } + if (options.title) { + $('#w2ui-popup .w2ui-popup-title') + .show() + .html((options.showClose ? '
Close
' : '') + + (options.showMax ? '
Max
' : '')) + .append(options.title); + $('#w2ui-popup .w2ui-popup-body').removeClass('w2ui-popup-no-title'); + $('#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp').css('top', ''); + } else { + $('#w2ui-popup .w2ui-popup-title').hide().html(''); + $('#w2ui-popup .w2ui-popup-body').addClass('w2ui-popup-no-title'); + $('#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp').css('top', '0px'); + } + // transition + var div_old = $('#w2ui-popup .w2ui-box')[0]; + var div_new = $('#w2ui-popup .w2ui-box-temp')[0]; + w2utils.transition(div_old, div_new, options.transition, function () { + // clean up + obj.restoreTemplate(); + $(div_old).remove(); + $(div_new).removeClass('w2ui-box-temp').addClass('w2ui-box'); + var $body = $(div_new).find('.w2ui-popup-body'); + if ($body.length == 1) $body[0].style.cssText = options.style; + // remove max state + $('#w2ui-popup').data('prev-size', null); + // focus on first button + obj.focus(); + }); + // call event onOpen + w2popup.status = 'open'; + obj.trigger($.extend(edata, { phase: 'after' })); + } + + // save new options + options._last_focus = $(':focus'); + // keyboard events + if (options.keyboard) $(document).on('keydown', this.keydown); + + // initialize move + var tmp = { + resizing : false, + mvMove : mvMove, + mvStop : mvStop + }; + $('#w2ui-popup .w2ui-popup-title').on('mousedown', function (event) { + if (!w2popup.get().maximized) mvStart(event); + }); + + return this; + + // handlers + function mvStart(evnt) { + if (!evnt) evnt = window.event; + w2popup.status = 'moving'; + tmp.resizing = true; + tmp.isLocked = $('#w2ui-popup > .w2ui-lock').length == 1 ? true : false; + tmp.x = evnt.screenX; + tmp.y = evnt.screenY; + tmp.pos_x = $('#w2ui-popup').position().left; + tmp.pos_y = $('#w2ui-popup').position().top; + if (!tmp.isLocked) w2popup.lock({ opacity: 0 }); + $(document).on('mousemove', tmp.mvMove); + $(document).on('mouseup', tmp.mvStop); + if (evnt.stopPropagation) evnt.stopPropagation(); else evnt.cancelBubble = true; + if (evnt.preventDefault) evnt.preventDefault(); else return false; + } + + function mvMove(evnt) { + if (tmp.resizing != true) return; + if (!evnt) evnt = window.event; + tmp.div_x = evnt.screenX - tmp.x; + tmp.div_y = evnt.screenY - tmp.y; + // trigger event + var edata = w2popup.trigger({ phase: 'before', type: 'move', target: 'popup', div_x: tmp.div_x, div_y: tmp.div_y }); + if (edata.isCancelled === true) return; + // default behavior + $('#w2ui-popup').css(w2utils.cssPrefix({ + 'transition': 'none', + 'transform' : 'translate3d('+ tmp.div_x +'px, '+ tmp.div_y +'px, 0px)' + })); + // event after + w2popup.trigger($.extend(edata, { phase: 'after'})); + } + + function mvStop(evnt) { + if (tmp.resizing != true) return; + if (!evnt) evnt = window.event; + w2popup.status = 'open'; + tmp.div_x = (evnt.screenX - tmp.x); + tmp.div_y = (evnt.screenY - tmp.y); + $('#w2ui-popup').css({ + 'left': (tmp.pos_x + tmp.div_x) + 'px', + 'top' : (tmp.pos_y + tmp.div_y) + 'px' + }).css(w2utils.cssPrefix({ + 'transition': 'none', + 'transform' : 'translate3d(0px, 0px, 0px)' + })); + tmp.resizing = false; + $(document).off('mousemove', tmp.mvMove); + $(document).off('mouseup', tmp.mvStop); + if (!tmp.isLocked) w2popup.unlock(); + } + }, + + action: function (action, msgId) { + var obj = this; + var options = $('#w2ui-popup').data('options'); + if (msgId != null) { + options = $('#w2ui-message' + msgId).data('options'); + obj = { + parent: this, + options: options, + close: function () { + w2popup.message({ msgId: msgId }) + } + } + } + var act = options.actions[action]; + var click = act; + if ($.isPlainObject(act) && act.onClick) click = act.onClick; + // event before + var edata = this.trigger({ phase: 'before', target: action, msgId: msgId, type: 'action', action: act, originalEvent: event }); + if (edata.isCancelled === true) return; + // default actions + if (typeof click === 'function') click.call(obj, event); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + keydown: function (event) { + var options = $('#w2ui-popup').data('options'); + if (options && !options.keyboard) return; + // trigger event + var edata = w2popup.trigger({ phase: 'before', type: 'keydown', target: 'popup', options: options, originalEvent: event }); + if (edata.isCancelled === true) return; + // default behavior + switch (event.keyCode) { + case 27: + event.preventDefault(); + if ($('#w2ui-popup .w2ui-message').length > 0) w2popup.message(); else w2popup.close(); + break; + } + // event after + w2popup.trigger($.extend(edata, { phase: 'after'})); + }, + + close: function (options) { + var obj = this; + var options = $.extend({}, $('#w2ui-popup').data('options'), options); + if ($('#w2ui-popup').length === 0 || this.status == 'closed') return; + if (this.status == 'opening') { + setTimeout(function () { w2popup.close(); }, 100); + return; + } + // trigger event + var edata = this.trigger({ phase: 'before', type: 'close', target: 'popup', options: options }); + if (edata.isCancelled === true) return; + // default behavior + w2popup.status = 'closing'; + $('#w2ui-popup') + .css(w2utils.cssPrefix({ + 'transition': options.speed + 's opacity, ' + options.speed + 's -webkit-transform' + })) + .addClass("w2ui-popup-closing"); + w2popup.unlockScreen(options); + setTimeout(function () { + // return template + obj.restoreTemplate(); + $('#w2ui-popup').remove(); + w2popup.status = 'closed'; + // restore active + if (options._last_focus && options._last_focus.length > 0) options._last_focus.focus(); + // event after + obj.trigger($.extend(edata, { phase: 'after'})); + }, options.speed * 1000); + // remove keyboard events + if (options.keyboard) $(document).off('keydown', this.keydown); + }, + + toggle: function () { + var obj = this; + var options = $('#w2ui-popup').data('options'); + // trigger event + var edata = this.trigger({ phase: 'before', type: 'toggle', target: 'popup', options: options }); + if (edata.isCancelled === true) return; + // defatul action + if (options.maximized === true) w2popup.min(); else w2popup.max(); + // event after + setTimeout(function () { + obj.trigger($.extend(edata, { phase: 'after'})); + }, (options.speed * 1000) + 50); + }, + + max: function () { + var obj = this; + var options = $('#w2ui-popup').data('options'); + if (options.maximized === true) return; + // trigger event + var edata = this.trigger({ phase: 'before', type: 'max', target: 'popup', options: options }); + if (edata.isCancelled === true) return; + // default behavior + w2popup.status = 'resizing'; + options.prevSize = $('#w2ui-popup').css('width') + ':' + $('#w2ui-popup').css('height'); + // do resize + w2popup.resize(10000, 10000, function () { + w2popup.status = 'open'; + options.maximized = true; + obj.trigger($.extend(edata, { phase: 'after'})); + // resize gird, form, layout inside popup + $('#w2ui-popup .w2ui-grid, #w2ui-popup .w2ui-form, #w2ui-popup .w2ui-layout').each(function () { + var name = $(this).attr('name'); + if (w2ui[name] && w2ui[name].resize) w2ui[name].resize(); + }) + }); + }, + + min: function () { + var obj = this; + var options = $('#w2ui-popup').data('options'); + if (options.maximized !== true) return; + var size = options.prevSize.split(':'); + // trigger event + var edata = this.trigger({ phase: 'before', type: 'min', target: 'popup', options: options }); + if (edata.isCancelled === true) return; + // default behavior + w2popup.status = 'resizing'; + // do resize + w2popup.resize(parseInt(size[0]), parseInt(size[1]), function () { + w2popup.status = 'open'; + options.maximized = false; + options.prevSize = null; + obj.trigger($.extend(edata, { phase: 'after'})); + // resize gird, form, layout inside popup + $('#w2ui-popup .w2ui-grid, #w2ui-popup .w2ui-form, #w2ui-popup .w2ui-layout').each(function () { + var name = $(this).attr('name'); + if (w2ui[name] && w2ui[name].resize) w2ui[name].resize(); + }) + }); + }, + + get: function () { + return $('#w2ui-popup').data('options'); + }, + + set: function (options) { + w2popup.open(options); + }, + + clear: function() { + $('#w2ui-popup .w2ui-popup-title').html(''); + $('#w2ui-popup .w2ui-popup-body').html(''); + $('#w2ui-popup .w2ui-popup-buttons').html(''); + }, + + reset: function () { + w2popup.open(w2popup.defaults); + }, + + load: function (options) { + w2popup.status = 'loading'; + if (options.url == null) { + console.log('ERROR: The url parameter is empty.'); + return; + } + var tmp = String(options.url).split('#'); + var url = tmp[0]; + var selector = tmp[1]; + if (options == null) options = {}; + // load url + var html = $('#w2ui-popup').data(url); + if (html != null) { + popup(html, selector); + } else { + $.get(url, function (data, status, obj) { // should always be $.get as it is template + popup(obj.responseText, selector); + $('#w2ui-popup').data(url, obj.responseText); // remember for possible future purposes + }); + } + function popup(html, selector) { + delete options.url; + $('body').append(''); + if (selector != null && $('#w2ui-tmp #'+selector).length > 0) { + $('#w2ui-tmp #' + selector).w2popup(options); + } else { + $('#w2ui-tmp > div').w2popup(options); + } + // link styles + if ($('#w2ui-tmp > style').length > 0) { + var style = $('
').append($('#w2ui-tmp > style').clone()).html(); + if ($('#w2ui-popup #div-style').length === 0) { + $('#w2ui-popup').append('
'); + } + $('#w2ui-popup #div-style').html(style); + } + $('#w2ui-tmp').remove(); + } + }, + + message: function (options) { + var obj = this; + $().w2tag(); // hide all tags + if (!options) options = { width: 200, height: 100 }; + var pWidth = parseInt($('#w2ui-popup').width()); + var pHeight = parseInt($('#w2ui-popup').height()); + options.originalWidth = options.width; + options.originalHeight = options.height; + if (parseInt(options.width) < 10) options.width = 10; + if (parseInt(options.height) < 10) options.height = 10; + if (options.hideOnClick == null) options.hideOnClick = false; + var poptions = $('#w2ui-popup').data('options') || {}; + var titleHeight = parseInt($('#w2ui-popup > .w2ui-popup-title').css('height')); + if (options.width == null || options.width > poptions.width - 10) { + options.width = poptions.width - 10; + } + if (options.height == null || options.height > poptions.height - titleHeight - 5) { + options.height = poptions.height - titleHeight - 5; // need margin from bottom only + } + // negative value means margin + if (options.originalHeight < 0) options.height = pHeight + options.originalHeight - titleHeight; + if (options.originalWidth < 0) options.width = pWidth + options.originalWidth * 2; // x 2 because there is left and right margin + + var head = $('#w2ui-popup .w2ui-popup-title'); + var msgCount = $('#w2ui-popup .w2ui-message').length; + + // convert action arrays into buttons + if (options.actions != null) { + options.buttons = ''; + Object.keys(options.actions).forEach(function (action) { + var handler = options.actions[action]; + if (typeof handler == 'function') { + options.buttons += '' + } + if (typeof handler == 'object') { + options.buttons += '' + } + if (typeof handler == 'string') { + options.buttons += handler + } + }); + } + + // remove message + if ($.trim(options.html) === '' && $.trim(options.body) === '' && $.trim(options.buttons) === '') { + var $msg = $('#w2ui-popup .w2ui-message').last(); + if (options.msgId != null) { + $msg = $('#w2ui-message'+ options.msgId); + } + var options = $msg.data('options') || {}; + // message close event + var edata = obj.trigger({ phase: 'before', type: 'msgClose', msgId: $msg.attr('data-msgId'), target: 'popup', options: options }); + if (edata.isCancelled === true) return; + // start hide transition + $msg.css(w2utils.cssPrefix({ + 'transition': '0.15s', + 'transform': 'translateY(-' + options.height + 'px)' + })); + var $focus = $('#w2ui-popup .w2ui-message'); + $focus = $($focus[$focus.length - 2]) + .css('z-index', 1500) + .data('msg-focus'); + if ($focus && $focus.length > 0) $focus.focus(); else obj.focus(); + if (msgCount == 1) w2popup.unlock(150); + setTimeout(function () { + $msg.remove(); + // default action + if (typeof options.onClose == 'function') { + options.onClose(edata); + } + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + }, 150); + } else { + if ($.trim(options.body) !== '' || $.trim(options.buttons) !== '') { + options.html = '
'+ options.body +'
'+ + '
'+ options.buttons +'
'; + } + // hide previous messages + $('#w2ui-popup .w2ui-message').css('z-index', 1390).data('msg-focus', $(':focus')); + head.css('z-index', 1501); + if (options.close == null) { + options.close = function () { + w2popup.message({ msgId: msgCount }) + } + } + // add message + $('#w2ui-popup .w2ui-box') + .before(''); + $('#w2ui-popup #w2ui-message'+ msgCount).data('options', options); + var display = $('#w2ui-popup #w2ui-message'+ msgCount).css('display'); + $('#w2ui-popup #w2ui-message'+ msgCount).css(w2utils.cssPrefix({ + 'transform': (display == 'none' ? 'translateY(-' + options.height + 'px)' : 'translateY(0px)') + })); + if (display == 'none') { + $('#w2ui-popup #w2ui-message'+ msgCount).show().html(options.html); + // timer needs to animation + setTimeout(function () { + $('#w2ui-popup #w2ui-message'+ msgCount).css( + $.extend( + w2utils.cssPrefix('transition', '.3s', false), + w2utils.cssPrefix({ + 'transform': (display == 'none' ? 'translateY(0px)' : 'translateY(-' + options.height + 'px)') + }) + ) + ); + }, 1); + // timer for lock + if (msgCount === 0) w2popup.lock(); + // message open event + var edata = obj.trigger({ phase: 'before', type: 'msgOpen', msgId: msgCount, target: 'popup', options: options }); + if (edata.isCancelled === true) return; + setTimeout(function() { + obj.focus(); + // has to be on top of lock + $('#w2ui-popup #w2ui-message'+ msgCount).css(w2utils.cssPrefix({ 'transition': '0s' })); + if (typeof options.onOpen == 'function') { + options.onOpen(edata); + } + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + }, 350); + } + } + }, + + focus: function () { + var tmp = null; + var pop = $('#w2ui-popup'); + var sel = 'input:visible, button:visible, select:visible, textarea:visible, [contentEditable], .w2ui-input'; + // clear previous blur + $(pop).find(sel).off('.keep-focus'); + // in message or popup + var cnt = $('#w2ui-popup .w2ui-message').length - 1; + var msg = $('#w2ui-popup #w2ui-message' + cnt); + if (msg.length > 0) { + var btn =$(msg[msg.length - 1]).find('button'); + if (btn.length > 0) btn[0].focus(); + tmp = msg; + } else if (pop.length > 0) { + var btn = pop.find('.w2ui-popup-buttons button'); + if (btn.length > 0) btn[0].focus(); + tmp = pop; + } + // keep focus/blur inside popup + $(tmp).find(sel) + .on('blur.keep-focus', function (event) { + setTimeout(function () { + var focus = $(':focus'); + if ((focus.length > 0 && !$(tmp).find(sel).is(focus)) || focus.hasClass('w2ui-popup-hidden')) { + var el = $(tmp).find(sel); + if (el.length > 0) el[0].focus(); + } + }, 1); + }); + }, + + lock: function (msg, showSpinner) { + var args = Array.prototype.slice.call(arguments, 0); + args.unshift($('#w2ui-popup')); + w2utils.lock.apply(window, args); + }, + + unlock: function (speed) { + w2utils.unlock($('#w2ui-popup'), speed); + }, + + // --- INTERNAL FUNCTIONS + + lockScreen: function (options) { + if ($('#w2ui-lock').length > 0) return false; + if (options == null) options = $('#w2ui-popup').data('options'); + if (options == null) options = {}; + options = $.extend({}, w2popup.defaults, options); + // show element + $('body').append('
'); + // lock screen + setTimeout(function () { + $('#w2ui-lock') + .css('opacity', options.opacity) + .css(w2utils.cssPrefix('transition', options.speed + 's opacity')); + }, 1); + // add events + if (options.modal == true) { + $('#w2ui-lock').on('mousedown', function () { + $('#w2ui-lock') + .css('opacity', '0.6') + .css(w2utils.cssPrefix('transition', '.1s')); + }); + $('#w2ui-lock').on('mouseup', function () { + setTimeout(function () { + $('#w2ui-lock') + .css('opacity', options.opacity) + .css(w2utils.cssPrefix('transition', '.1s')); + }, 100); + }); + } else { + $('#w2ui-lock').on('mousedown', function () { w2popup.close(); }); + } + return true; + }, + + unlockScreen: function (options) { + if ($('#w2ui-lock').length === 0) return false; + if (options == null) options = $('#w2ui-popup').data('options'); + if (options == null) options = {}; + options = $.extend({}, w2popup.defaults, options); + $('#w2ui-lock') + .css('opacity', '0') + .css(w2utils.cssPrefix('transition', options.speed + 's opacity')); + setTimeout(function () { + $('#w2ui-lock').remove(); + }, options.speed * 1000); + return true; + }, + + resizeMessages: function () { + var obj = this; + var options = $('#w2ui-popup').data('options'); + // see if there are messages and resize them + $('#w2ui-popup .w2ui-message').each(function () { + var moptions = $(this).data('options'); + var $popup = $('#w2ui-popup'); + if (parseInt(moptions.width) < 10) moptions.width = 10; + if (parseInt(moptions.height) < 10) moptions.height = 10; + var titleHeight = parseInt($popup.find('> .w2ui-popup-title').css('height')); + var pWidth = parseInt($popup.width()); + var pHeight = parseInt($popup.height()); + // recalc width + moptions.width = moptions.originalWidth; + if (moptions.width > pWidth - 10) { + moptions.width = pWidth - 10; + } + // recalc height + moptions.height = moptions.originalHeight; + if (moptions.height > pHeight - titleHeight - 5) { + moptions.height = pHeight - titleHeight - 5; + } + if (moptions.originalHeight < 0) moptions.height = pHeight + moptions.originalHeight - titleHeight; + if (moptions.originalWidth < 0) moptions.width = pWidth + moptions.originalWidth * 2; // x 2 because there is left and right margin + $(this).css({ + left : ((pWidth - moptions.width) / 2) + 'px', + width : moptions.width + 'px', + height : moptions.height + 'px' + }); + }); + }, + + resize: function (width, height, callBack) { + var obj = this; + var options = $('#w2ui-popup').data('options') || {}; + if (options.speed == null) options.speed = 0; + width = parseInt(width); + height = parseInt(height); + // calculate new position + var maxW, maxH; + if (window.innerHeight == undefined) { + maxW = parseInt(document.documentElement.offsetWidth); + maxH = parseInt(document.documentElement.offsetHeight); + if (w2utils.engine === 'IE7') { maxW += 21; maxH += 4; } + } else { + maxW = parseInt(window.innerWidth); + maxH = parseInt(window.innerHeight); + } + if (maxW - 10 < width) width = maxW - 10; + if (maxH - 10 < height) height = maxH - 10; + var top = (maxH - height) / 2 * 0.6; + var left = (maxW - width) / 2; + // resize there + $('#w2ui-popup') + .css(w2utils.cssPrefix({ + 'transition': options.speed + 's width, ' + options.speed + 's height, ' + options.speed + 's left, ' + options.speed + 's top' + })) + .css({ + 'top' : top, + 'left' : left, + 'width' : width, + 'height': height + }); + var tmp_int = setInterval(function () { obj.resizeMessages(); }, 10); // then messages resize nicely + setTimeout(function () { + clearInterval(tmp_int); + options.width = width; + options.height = height; + obj.resizeMessages(); + if (typeof callBack == 'function') callBack(); + }, (options.speed * 1000) + 50); // give extra 50 ms + }, + + /*********************** + * Internal + **/ + + // restores template + restoreTemplate: function () { + var options = $('#w2ui-popup').data('options'); + if (options == null) return; + var template = w2popup._template; + var title = options.title; + var body = options.body; + var buttons = options.buttons; + if (w2popup._prev) { + template = w2popup._prev.template; + title = w2popup._prev.title; + body = w2popup._prev.body; + buttons = w2popup._prev.buttons; + delete w2popup._prev; + } else { + delete w2popup._template; + } + if (template != null) { + var $tmp = $(template); + if ($tmp.length === 0) return; + if ($(body).attr('rel') == 'body') { + if (title) $tmp.append(title); + if (body) $tmp.append(body); + if (buttons) $tmp.append(buttons); + } else { + $tmp.append(body); + } + } + } + }; + + // merge in event handling + $.extend(w2popup, w2utils.event); + +})(jQuery); + +// ============================================ +// --- Common dialogs + +var w2alert = function (msg, title, callBack) { + var $ = jQuery; + if (title == null) title = w2utils.lang('Notification'); + if ($('#w2ui-popup').length > 0 && w2popup.status != 'closing') { + w2popup.message({ + width : 400, + height : 170, + body : '
' + msg + '
', + buttons : '', + onOpen: function () { + $('#w2ui-popup .w2ui-message .w2ui-popup-btn').focus(); + }, + onClose: function () { + if (typeof callBack == 'function') callBack(); + } + }); + } else { + w2popup.open({ + width : 450, + height : 220, + showMax : false, + showClose : false, + title : title, + body : '
' + msg + '
', + buttons : '', + onOpen: function (event) { + // do not use onComplete as it is slower + setTimeout(function () { $('#w2ui-popup .w2ui-popup-btn').focus(); }, 1); + }, + onKeydown: function (event) { + $('#w2ui-popup .w2ui-popup-btn').focus().addClass('clicked'); + }, + onClose: function () { + if (typeof callBack == 'function') callBack(); + } + }); + } + return { + ok: function (fun) { + callBack = fun; + return this; + }, + done: function (fun) { + callBack = fun; + return this; + } + }; +}; + +var w2confirm = function (msg, title, callBack) { + var $ = jQuery; + var options = {}; + var defaults = { + msg : '', + title : w2utils.lang('Confirmation'), + width : ($('#w2ui-popup').length > 0 ? 400 : 450), + height : ($('#w2ui-popup').length > 0 ? 170 : 220), + yes_text : 'Yes', + yes_class : '', + yes_style : '', + yes_callBack: null, + no_text : 'No', + no_class : '', + no_style : '', + no_callBack : null, + focus_to_no : false, + callBack : null + }; + if (arguments.length == 1 && typeof msg == 'object') { + $.extend(options, defaults, msg); + } else { + if (typeof title == 'function') { + $.extend(options, defaults, { + msg : msg, + callBack: title + }) + } else { + $.extend(options, defaults, { + msg : msg, + title : title, + callBack: callBack + }) + } + } + // if there is a yes/no button object + if (typeof options.btn_yes == 'object') { + options.yes_text = options.btn_yes.text || options.yes_text; + options.yes_class = options.btn_yes["class"] || options.yes_class; + options.yes_style = options.btn_yes.style || options.yes_style; + options.yes_callBack = options.btn_yes.callBack || options.yes_callBack; + } + if (typeof options.btn_no == 'object') { + options.no_text = options.btn_no.text || options.no_text; + options.no_class = options.btn_no["class"] || options.no_class; + options.no_style = options.btn_no.style || options.no_style; + options.no_callBack = options.btn_no.callBack || options.no_callBack; + } + if ($('#w2ui-popup').length > 0 && w2popup.status != 'closing' && w2popup.get()) { + if (options.width > w2popup.get().width) options.width = w2popup.get().width; + if (options.height > (w2popup.get().height - 50)) options.height = w2popup.get().height - 50; + w2popup.message({ + width : options.width, + height : options.height, + body : '
' + options.msg + '
', + buttons : (w2utils.settings.macButtonOrder + ? '' + + '' + : '' + + '' + ), + onOpen: function (event) { + $('#w2ui-popup .w2ui-message .w2ui-btn').on('click.w2confirm', function (event) { + w2popup._confirm_btn = event.target.id; + w2popup.message(); + }); + if (typeof options.onOpen == 'function') options.onOpen() + }, + onClose: function (event) { + // needed this because there might be other messages + $('#w2ui-popup .w2ui-message .w2ui-btn').off('click.w2confirm'); + // need to wait for message to slide up + setTimeout(function () { + if (typeof options.callBack == 'function') options.callBack(w2popup._confirm_btn); + if (w2popup._confirm_btn == 'Yes' && typeof options.yes_callBack == 'function') options.yes_callBack(); + if (w2popup._confirm_btn == 'No' && typeof options.no_callBack == 'function') options.no_callBack(); + }, 300); + if (typeof options.onClose == 'function') options.onClose() + } + // onKeydown will not work here + }); + + } else { + + if (!w2utils.isInt(options.height)) options.height = options.height + 50; + w2popup.open({ + width : options.width, + height : options.height, + title : options.title, + modal : true, + showClose : false, + body : '
' + options.msg + '
', + buttons : (w2utils.settings.macButtonOrder + ? '' + + '' + : '' + + '' + ), + onOpen: function (event) { + // do not use onComplete as it is slower + setTimeout(function () { + $('#w2ui-popup .w2ui-popup-btn').on('click', function (event) { + w2popup.close(); + if (typeof options.callBack == 'function') options.callBack(event.target.id); + if (event.target.id == 'Yes' && typeof options.yes_callBack == 'function') options.yes_callBack(); + if (event.target.id == 'No' && typeof options.no_callBack == 'function') options.no_callBack(); + }); + if(options.focus_to_no){ + $('#w2ui-popup .w2ui-popup-btn#No').focus(); + }else{ + $('#w2ui-popup .w2ui-popup-btn#Yes').focus(); + } + if (typeof options.onOpen == 'function') options.onOpen() + }, 1); + }, + onClose: function (event) { + if (typeof options.onClose == 'function') options.onClose() + }, + onKeydown: function (event) { + // if there are no messages + if ($('#w2ui-popup .w2ui-message').length === 0) { + switch (event.originalEvent.keyCode) { + case 13: // enter + $('#w2ui-popup .w2ui-popup-btn#Yes').focus().addClass('clicked'); // no need fo click as enter will do click + w2popup.close(); + break; + case 27: // esc + $('#w2ui-popup .w2ui-popup-btn#No').focus().click(); + w2popup.close(); + break; + } + } + } + }); + } + + return { + yes: function (fun) { + options.yes_callBack = fun; + return this; + }, + no: function (fun) { + options.no_callBack = fun; + return this; + } + }; +}; + +var w2prompt = function (label, title, callBack) { + var $ = jQuery; + var options = {}; + var defaults = { + title : w2utils.lang('Notification'), + width : ($('#w2ui-popup').length > 0 ? 400 : 450), + height : ($('#w2ui-popup').length > 0 ? 170 : 220), + label : '', + value : '', + attrs : '', + textarea : false, + ok_text : w2utils.lang('Ok'), + ok_class : '', + cancel_text : w2utils.lang('Cancel'), + cancel_class: '', + callBack : null, + onOpen : null, + onClose : null + } + w2popup.tmp = w2popup.tmp || {} + + if (arguments.length == 1 && typeof label == 'object') { + $.extend(options, defaults, label); + } else { + if (typeof title == 'function') { + $.extend(options, defaults, { + label : label, + callBack: title + }) + } else { + $.extend(options, defaults, { + label : label, + title : title, + callBack: callBack + }) + } + } + + if ($('#w2ui-popup').length > 0 && w2popup.status != 'closing' && w2popup.get()) { + if (options.width > w2popup.get().width) options.width = w2popup.get().width; + if (options.height > (w2popup.get().height - 50)) options.height = w2popup.get().height - 50; + w2popup.message({ + width : options.width, + height : options.height, + body : (options.textarea + ? '
'+ + '
' + options.label + '
'+ + ' '+ + '
' + : '
'+ + ' '+ + ' '+ + '
' + ), + buttons : (w2utils.settings.macButtonOrder + ? '' + + '' + : '' + + '' + ), + onOpen: function () { + $('#w2prompt').val(options.value).off('.w2prompt').on('keydown.w2prompt', function(event) { + if (event.keyCode == 13) { + $('#w2ui-popup .w2ui-message .w2ui-btn#Ok').click() + } + }); + $('#w2ui-popup .w2ui-message .w2ui-btn#Ok').off('.w2prompt').on('click.w2prompt', function (event) { + w2popup.tmp.btn = 'ok'; + w2popup.tmp.value = $('#w2prompt').val(); + w2popup.message(); + }); + $('#w2ui-popup .w2ui-message .w2ui-btn#Cancel').off('.w2prompt').on('click.w2prompt', function (event) { + w2popup.tmp.btn = 'cancel'; + w2popup.tmp.value = null; + w2popup.message(); + }); + // set focus + setTimeout(function () { $('#w2prompt').focus(); }, 100); + // some event + if (typeof options.onOpen == 'function') options.onOpen() + }, + onClose: function () { + // needed this because there might be other messages + $('#w2ui-popup .w2ui-message .w2ui-btn').off('click.w2prompt'); + // need to wait for message to slide up + setTimeout(function () { + btnClick(w2popup.tmp.btn, w2popup.tmp.value); + }, 300); + // some event + if (typeof options.onClose == 'function') options.onClose() + } + // onKeydown will not work here + }); + + } else { + + if (!w2utils.isInt(options.height)) options.height = options.height + 50; + w2popup.open({ + width : options.width, + height : options.height, + title : options.title, + modal : true, + showClose : false, + body : (options.textarea + ? '
'+ + '
' + options.label + '
'+ + ' '+ + '
' + : '
'+ + ' '+ + ' '+ + '
' + ), + buttons : (w2utils.settings.macButtonOrder + ? '' + + '' + : ''+ + '' + ), + onOpen: function (event) { + // do not use onComplete as it is slower + setTimeout(function () { + $('#w2prompt').val(options.value); + $('#w2prompt').w2field('text'); + $('#w2ui-popup .w2ui-popup-btn#Ok').on('click', function (event) { + w2popup.tmp.btn = 'ok'; + w2popup.tmp.value = $('#w2prompt').val(); + w2popup.close(); + }); + $('#w2ui-popup .w2ui-popup-btn#Cancel').on('click', function (event) { + w2popup.tmp.btn = 'cancel'; + w2popup.tmp.value = null; + w2popup.close(); + }); + $('#w2ui-popup .w2ui-popup-btn#Ok'); + // set focus + setTimeout(function () { $('#w2prompt').focus(); }, 100); + // some event + if (typeof options.onOpen == 'function') options.onOpen() + }, 1); + }, + onClose: function (event) { + // some event + btnClick(w2popup.tmp.btn, w2popup.tmp.value); + if (typeof options.onClose == 'function') options.onClose() + }, + onKeydown: function (event) { + // if there are no messages + if ($('#w2ui-popup .w2ui-message').length === 0) { + switch (event.originalEvent.keyCode) { + case 13: // enter + $('#w2ui-popup .w2ui-popup-btn#Ok').focus().addClass('clicked'); // no need fo click as enter will do click + break; + case 27: // esc + w2popup.tmp.btn = 'cancel'; + w2popup.tmp.value = null; + break; + } + } + } + }); + } + function btnClick(btn, value) { + if (btn == 'ok' && typeof options.ok_callBack == 'function') { + options.ok_callBack(value) + } + if (btn == 'cancel' && typeof options.cancel_callBack == 'function') { + options.cancel_callBack(value) + } + if (typeof options.callBack == 'function') { + options.callBack(btn, value); + } + } + return { + change: function (fun) { + $('#w2prompt').on('keyup', fun).keyup(); + return this; + }, + ok: function (fun) { + options.ok_callBack = fun; + return this; + }, + cancel: function (fun) { + options.cancel_callBack = fun; + return this; + } + }; +}; + +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2tabs - tabs widget +* - $().w2tabs - jQuery wrapper +* - Dependencies: jQuery, w2utils +* +* == NICE TO HAVE == +* - align = left, right, center ?? +* +* == 1.5 changes == +* - tab.caption - deprecated +* - getTabHTML() +* - refactored with display: flex +* - reorder +* - initReorder +* - dragMove +* - tmp +* +************************************************************************/ + +(function ($) { + var w2tabs = function (options) { + this.box = null; // DOM Element that holds the element + this.name = null; // unique name for w2ui + this.active = null; + this.reorder = false; + this.flow = 'down'; // can be down or up + this.tooltip = 'top|left'; // can be top, bottom, left, right + this.tabs = []; + this.routeData = {}; // data for dynamic routes + this.tmp = {}; // placeholder for internal variables + this.right = ''; + this.style = ''; + + $.extend(this, { handlers: [] }); + $.extend(true, this, w2obj.tabs, options); + }; + + // ==================================================== + // -- Registers as a jQuery plugin + + $.fn.w2tabs = function(method) { + if ($.isPlainObject(method)) { + // check name parameter + if (!w2utils.checkName(method, 'w2tabs')) return; + // extend tabs + var tabs = method.tabs || []; + var object = new w2tabs(method); + for (var i = 0; i < tabs.length; i++) { + object.tabs[i] = $.extend({}, w2tabs.prototype.tab, tabs[i]); + } + // register new object + w2ui[object.name] = object; + // render + if ($(this).length !== 0) { + object.render($(this)[0]); + } + return object; + } else { + var obj = w2ui[$(this).attr('name')]; + if (!obj) return null; + if (arguments.length > 0) { + if (obj[method]) obj[method].apply(obj, Array.prototype.slice.call(arguments, 1)); + return this; + } else { + return obj; + } + } + }; + + // ==================================================== + // -- Implementation of core functionality + + w2tabs.prototype = { + onClick : null, + onClose : null, + onRender : null, + onRefresh : null, + onResize : null, + onDestroy : null, + + tab : { + id : null, // command to be sent to all event handlers + text : null, + route : null, + hidden : false, + disabled : false, + closable : false, + tooltip : null, + style : '', + onClick : null, + onRefresh : null, + onClose : null + }, + + add: function (tab) { + return this.insert(null, tab); + }, + + insert: function (id, tab) { + if (!$.isArray(tab)) tab = [tab]; + // assume it is array + for (var i = 0; i < tab.length; i++) { + // checks + if (tab[i].id == null) { + console.log('ERROR: The parameter "id" is required but not supplied. (obj: '+ this.name +')'); + return; + } + if (!w2utils.checkUniqueId(tab[i].id, this.tabs, 'tabs', this.name)) return; + // add tab + var newTab = $.extend({}, w2tabs.prototype.tab, tab[i]); + if (id == null) { + this.tabs.push(newTab); + } else { + var middle = this.get(id, true); + var before = this.tabs[middle].id + this.insertTabHTML(before, newTab) + } + } + }, + + remove: function () { + var removed = 0; + for (var a = 0; a < arguments.length; a++) { + var tab = this.get(arguments[a]); + if (!tab) return false; + removed++; + // remove from array + this.tabs.splice(this.get(tab.id, true), 1); + // remove from screen + $(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(tab.id)).remove(); + } + this.resize(); + return removed; + }, + + select: function (id) { + if (this.active == id || this.get(id) == null) return false; + this.active = id; + this.refresh(); + return true; + }, + + set: function (id, tab) { + var index = this.get(id, true); + if (index == null) return false; + $.extend(this.tabs[index], tab); + this.refresh(id); + return true; + }, + + get: function (id, returnIndex) { + if (arguments.length === 0) { + var all = []; + for (var i1 = 0; i1 < this.tabs.length; i1++) { + if (this.tabs[i1].id != null) { + all.push(this.tabs[i1].id); + } + } + return all; + } else { + for (var i2 = 0; i2 < this.tabs.length; i2++) { + if (this.tabs[i2].id == id) { // need to be == since id can be numeric + return (returnIndex === true ? i2 : this.tabs[i2]); + } + } + } + return null; + }, + + show: function () { + var obj = this; + var shown = 0; + var tmp = []; + for (var a = 0; a < arguments.length; a++) { + var tab = this.get(arguments[a]); + if (!tab || tab.hidden === false) continue; + shown++; + tab.hidden = false; + tmp.push(tab.id); + } + setTimeout(function () { for (var t=0; t
' + ); + helper = $(obj.el).prev(); + helper + .css({ + 'color' : $(obj.el).css('color'), + 'font-family' : $(obj.el).css('font-family'), + 'font-size' : $(obj.el).css('font-size'), + 'padding-top' : $(obj.el).css('padding-top'), + 'padding-bottom' : $(obj.el).css('padding-bottom'), + 'padding-left' : $(obj.el).css('padding-left'), + 'padding-right' : 0, + 'margin-top' : (parseInt($(obj.el).css('margin-top'), 10) + 2) + 'px', + 'margin-bottom' : (parseInt($(obj.el).css('margin-bottom'), 10) + 1) + 'px', + 'margin-left' : $(obj.el).css('margin-left'), + 'margin-right' : 0 + }) + .on('click', function (event) { + if (obj.options.icon && typeof obj.onIconClick === 'function') { + // event before + var edata = obj.trigger({ phase: 'before', type: 'iconClick', target: obj.el, el: $(this).find('span.w2ui-icon')[0] }); + if (edata.isCancelled === true) return; + + // intentionally empty + + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + } else { + if (obj.type === 'list') { + $(obj.helpers.focus).find('input').focus(); + } else { + $(obj.el).focus(); + } + } + }); + $(obj.el).css('padding-left', (helper.width() + parseInt($(obj.el).css('padding-left'), 10)) + 'px'); + // remember helper + obj.helpers.prefix = helper; + } + }, 1); + }, + + addSuffix: function () { + var obj = this; + var helper, pr; + setTimeout(function () { + if (obj.type === 'clear') return; + var tmp = $(obj.el).data('tmp') || {}; + if (tmp['old-padding-right']) $(obj.el).css('padding-right', tmp['old-padding-right']); + tmp['old-padding-right'] = $(obj.el).css('padding-right'); + $(obj.el).data('tmp', tmp); + pr = parseInt($(obj.el).css('padding-right'), 10); + if (obj.options.arrows) { + // remove if already displayed + if (obj.helpers.arrows) $(obj.helpers.arrows).remove(); + // add fresh + $(obj.el).after( + '
 '+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'); + helper = $(obj.el).next(); + helper.css({ + 'color' : $(obj.el).css('color'), + 'font-family' : $(obj.el).css('font-family'), + 'font-size' : $(obj.el).css('font-size'), + 'height' : ($(obj.el).height() + parseInt($(obj.el).css('padding-top'), 10) + parseInt($(obj.el).css('padding-bottom'), 10) ) + 'px', + 'padding' : 0, + 'margin-top' : (parseInt($(obj.el).css('margin-top'), 10) + 1) + 'px', + 'margin-bottom' : 0, + 'border-left' : '1px solid silver' + }) + .css('margin-left', '-'+ (helper.width() + parseInt($(obj.el).css('margin-right'), 10) + 12) + 'px') + .on('mousedown', function (event) { + var body = $('body'); + body.on('mouseup', tmp); + body.data('_field_update_timer', setTimeout(update, 700)); + update(false); + // timer function + function tmp() { + clearTimeout(body.data('_field_update_timer')); + body.off('mouseup', tmp); + } + // update function + function update(notimer) { + $(obj.el).focus(); + obj.keyDown($.Event("keydown"), { + keyCode : ($(event.target).attr('type') === 'up' ? 38 : 40) + }); + if (notimer !== false) $('body').data('_field_update_timer', setTimeout(update, 60)); + } + }); + pr += helper.width() + 12; + $(obj.el).css('padding-right', pr + 'px'); + // remember helper + obj.helpers.arrows = helper; + } + if (obj.options.suffix !== '') { + // remove if already displayed + if (obj.helpers.suffix) $(obj.helpers.suffix).remove(); + // add fresh + $(obj.el).after( + '
'+ + obj.options.suffix + + '
'); + helper = $(obj.el).next(); + helper + .css({ + 'color' : $(obj.el).css('color'), + 'font-family' : $(obj.el).css('font-family'), + 'font-size' : $(obj.el).css('font-size'), + 'padding-top' : $(obj.el).css('padding-top'), + 'padding-bottom' : $(obj.el).css('padding-bottom'), + 'padding-left' : '3px', + 'padding-right' : $(obj.el).css('padding-right'), + 'margin-top' : (parseInt($(obj.el).css('margin-top'), 10) + 2) + 'px', + 'margin-bottom' : (parseInt($(obj.el).css('margin-bottom'), 10) + 1) + 'px' + }) + .on('click', function (event) { + if (obj.type === 'list') { + $(obj.helpers.focus).find('input').focus(); + } else { + $(obj.el).focus(); + } + }); + + helper.css('margin-left', '-'+ (w2utils.getSize(helper, 'width') + parseInt($(obj.el).css('margin-right'), 10) + 2) + 'px'); + pr += helper.width() + 3; + $(obj.el).css('padding-right', pr + 'px'); + // remember helper + obj.helpers.suffix = helper; + } + }, 1); + }, + + addFocus: function () { + var obj = this; + var options = this.options; + var width = 0; // 11 - show search icon, 0 do not show + var pholder; + // clean up & init + $(obj.helpers.focus).remove(); + // remember original tabindex + var tabIndex = parseInt($(obj.el).attr('tabIndex')); + if (!isNaN(tabIndex) && tabIndex !== -1) obj.el._tabIndex = tabIndex; + if (obj.el._tabIndex) tabIndex = obj.el._tabIndex; + if (tabIndex == null) tabIndex = -1; + if (isNaN(tabIndex)) tabIndex = 0; + // if there is id, add to search with "_search" + var searchId = ''; + if ($(obj.el).attr('id') != null) { + searchId = 'id="' + $(obj.el).attr('id') + '_search"' + } + // build helper + var html = + '
'+ + ' '+ + ' '+ + '
'; + $(obj.el).attr('tabindex', -1).before(html); + var helper = $(obj.el).prev(); + obj.helpers.focus = helper; + helper.css({ + width : $(obj.el).width(), + "margin-top" : $(obj.el).css('margin-top'), + "margin-left" : (parseInt($(obj.el).css('margin-left')) + parseInt($(obj.el).css('padding-left'))) + 'px', + "margin-bottom" : $(obj.el).css('margin-bottom'), + "margin-right" : $(obj.el).css('margin-right') + }) + .find('input') + .css({ + cursor : 'default', + width : '100%', + outline : 'none', + opacity : 1, + margin : 0, + border : '1px solid transparent', + padding : $(obj.el).css('padding-top'), + "padding-left" : 0, + "margin-left" : (width > 0 ? width + 6 : 0), + "background-color" : 'transparent' + }); + // INPUT events + helper.find('input') + .on('click', function (event) { + if ($('#w2ui-overlay').length === 0) obj.focus(event); + event.stopPropagation(); + }) + .on('focus', function (event) { + pholder = $(obj.el).attr('placeholder'); + $(obj.el).css({ 'outline': 'auto 5px #7DB4F3', 'outline-offset': '2px' }); + $(this).val(''); + $(obj.el).triggerHandler('focus'); + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + }) + .on('blur', function (event) { + $(obj.el).css('outline', 'none'); + $(this).val(''); + obj.refresh(); + $(obj.el).triggerHandler('blur'); + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + if (pholder != null) $(obj.el).attr('placeholder', pholder); + }) + .on('keydown', function (event) { + var el = this; + obj.keyDown(event); + setTimeout(function () { + if (el.value === '') $(obj.el).attr('placeholder', pholder); else $(obj.el).attr('placeholder', ''); + }, 10); + }) + .on('keyup', function (event) { obj.keyUp(event); }) + .on('keypress', function (event) { obj.keyPress(event); }); + // MAIN div + helper.on('click', function (event) { $(this).find('input').focus(); }); + obj.refresh(); + }, + + addMulti: function () { + var obj = this; + var options = this.options; + // clean up & init + $(obj.helpers.multi).remove(); + // build helper + var html = ''; + var margin = + 'margin-top : 0px; ' + + 'margin-bottom : 0px; ' + + 'margin-left : ' + $(obj.el).css('margin-left') + '; ' + + 'margin-right : ' + $(obj.el).css('margin-right') + '; '+ + 'width : ' + (w2utils.getSize(obj.el, 'width') + - parseInt($(obj.el).css('margin-left'), 10) + - parseInt($(obj.el).css('margin-right'), 10)) + + 'px;'; + // if there is id, add to search with "_search" + var searchId = ''; + if ($(obj.el).attr('id') != null) { + searchId = 'id="' + $(obj.el).attr('id') + '_search" ' + } + if (obj.type === 'enum') { + // remember original tabindex + var tabIndex = $(obj.el).attr('tabIndex'); + if (tabIndex && tabIndex !== -1) obj.el._tabIndex = tabIndex; + if (obj.el._tabIndex) tabIndex = obj.el._tabIndex; + if (tabIndex == null) tabIndex = 0; // default tabindex + + html = '
'+ + '
'+ + '
    '+ + '
  • '+ + ' '+ + '
  • '+ + '
'+ + '
'+ + '
'; + } + if (obj.type === 'file') { + html = '
'+ + '
'+ + ' '+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'; + } + // old bg and border + var tmp = $(obj.el).data('tmp') || {}; + tmp['old-background-color'] = $(obj.el).css('background-color'); + tmp['old-border-color'] = $(obj.el).css('border-color'); + $(obj.el).data('tmp', tmp); + + $(obj.el) + .before(html) + .css({ + 'background-color' : 'transparent', + 'border-color' : 'transparent' + }); + + var div = $(obj.el).prev(); + obj.helpers.multi = div; + if (obj.type === 'enum') { + $(obj.el).attr('tabindex', -1); + // INPUT events + div.find('input') + .on('click', function (event) { + if ($('#w2ui-overlay').length === 0) obj.focus(event); + $(obj.el).triggerHandler('click'); + }) + .on('focus', function (event) { + $(div).css({ 'outline': 'auto 5px #7DB4F3', 'outline-offset': '2px' }); + $(obj.el).triggerHandler('focus'); + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + }) + .on('blur', function (event) { + $(div).css('outline', 'none'); + $(obj.el).triggerHandler('blur'); + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; + }) + .on('keyup', function (event) { obj.keyUp(event); }) + .on('keydown', function (event) { obj.keyDown(event); }) + .on('keypress', function (event) { obj.keyPress(event); }); + // MAIN div + div.on('click', function (event) { $(this).find('input').focus(); }); + } + if (obj.type === 'file') { + $(obj.el).css('outline', 'none'); + div.find('input') + .off('.drag') + .on('click.drag', function (event) { + event.stopPropagation(); + if ($(obj.el).prop('readonly') || $(obj.el).prop('disabled')) return; + $(obj.el).focus(); + }) + .on('dragenter.drag', function (event) { + if ($(obj.el).prop('readonly') || $(obj.el).prop('disabled')) return; + $(div).addClass('w2ui-file-dragover'); + }) + .on('dragleave.drag', function (event) { + if ($(obj.el).prop('readonly') || $(obj.el).prop('disabled')) return; + $(div).removeClass('w2ui-file-dragover'); + }) + .on('drop.drag', function (event) { + if ($(obj.el).prop('readonly') || $(obj.el).prop('disabled')) return; + $(div).removeClass('w2ui-file-dragover'); + var files = event.originalEvent.dataTransfer.files; + for (var i = 0, l = files.length; i < l; i++) obj.addFile.call(obj, files[i]); + $(obj.el).focus(); + // cancel to stop browser behaviour + event.preventDefault(); + event.stopPropagation(); + }) + .on('dragover.drag', function (event) { + // cancel to stop browser behaviour + event.preventDefault(); + event.stopPropagation(); + }) + .on('change.drag', function () { + $(obj.el).focus(); + if (typeof this.files !== "undefined") { + for (var i = 0, l = this.files.length; i < l; i++) { + obj.addFile.call(obj, this.files[i]); + } + } + }); + } + obj.refresh(); + }, + + addFile: function (file) { + var obj = this; + var options = this.options; + var selected = $(obj.el).data('selected'); + var newItem = { + name : file.name, + type : file.type, + modified : file.lastModifiedDate, + size : file.size, + content : null, + file : file + }; + var size = 0; + var cnt = 0; + var err; + if (selected) { + for (var s = 0; s < selected.length; s++) { + // check for dups + if (selected[s].name == file.name && selected[s].size == file.size) return; + size += selected[s].size; + cnt++; + } + } + // trigger event + var edata = obj.trigger({ phase: 'before', type: 'add', target: obj.el, file: newItem, total: cnt, totalSize: size }); + if (edata.isCancelled === true) return; + // check params + if (options.maxFileSize !== 0 && newItem.size > options.maxFileSize) { + err = 'Maximum file size is '+ w2utils.formatSize(options.maxFileSize); + if (options.silent === false) $(obj.el).w2tag(err); + console.log('ERROR: '+ err); + return; + } + if (options.maxSize !== 0 && size + newItem.size > options.maxSize) { + err = w2utils.lang('Maximum total size is') + ' ' + w2utils.formatSize(options.maxSize); + if (options.silent === false) { + $(obj.el).w2tag(err); + } else { + console.log('ERROR: '+ err); + } + return; + } + if (options.max !== 0 && cnt >= options.max) { + err = w2utils.lang('Maximum number of files is') + ' '+ options.max; + if (options.silent === false) { + $(obj.el).w2tag(err); + } else { + console.log('ERROR: '+ err); + } + return; + } + selected.push(newItem); + // read file as base64 + if (typeof FileReader !== "undefined" && options.readContent === true) { + var reader = new FileReader(); + // need a closure + reader.onload = (function () { + return function (event) { + var fl = event.target.result; + var ind = fl.indexOf(','); + newItem.content = fl.substr(ind+1); + obj.refresh(); + $(obj.el).trigger('input').trigger('change'); + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + }; + })(); + reader.readAsDataURL(file); + } else { + obj.refresh(); + $(obj.el).trigger('input').trigger('change'); + obj.trigger($.extend(edata, { phase: 'after' })); + } + }, + + normMenu: function (menu, el) { + if ($.isArray(menu)) { + for (var m = 0; m < menu.length; m++) { + if (typeof menu[m] === 'string') { + menu[m] = { id: menu[m], text: menu[m] }; + } else if (menu[m] != null) { + if (menu[m].text != null && menu[m].id == null) menu[m].id = menu[m].text; + if (menu[m].text == null && menu[m].id != null) menu[m].text = menu[m].id; + if (menu[m].caption != null) menu[m].text = menu[m].caption; + } else { + menu[m] = { id: null, text: 'null' } + } + } + return menu; + } else if (typeof menu === 'function') { + return w2obj.field.prototype.normMenu.call(this, menu.call(this, el)); + } else if (typeof menu === 'object') { + var tmp = []; + for (var m in menu) tmp.push({ id: m, text: menu[m] }); + return tmp; + } + }, + + getMonthHTML: function (month, year, selected) { + var td = new Date(); + var months = w2utils.settings.fullmonths; + var daysCount = ['31', '28', '31', '30', '31', '30', '31', '31', '30', '31', '30', '31']; + var today = td.getFullYear() + '/' + (Number(td.getMonth()) + 1) + '/' + td.getDate(); + var days = w2utils.settings.fulldays.slice(); // creates copy of the array + var sdays = w2utils.settings.shortdays.slice(); // creates copy of the array + if (w2utils.settings.weekStarts !== 'M') { + days.unshift(days.pop()); + sdays.unshift(sdays.pop()); + } + var options = this.options; + if (options == null) options = {}; + // normalize date + year = w2utils.isInt(year) ? parseInt(year) : td.getFullYear(); + month = w2utils.isInt(month) ? parseInt(month) : td.getMonth() + 1; + if (month > 12) { month -= 12; year++; } + if (month < 1 || month === 0) { month += 12; year--; } + if (year/4 == Math.floor(year/4)) { daysCount[1] = '29'; } else { daysCount[1] = '28'; } + options.current = month + '/' + year; + + // start with the required date + td = new Date(year, month-1, 1); + var weekDay = td.getDay(); + var dayTitle = ''; + for (var i = 0; i < sdays.length; i++) dayTitle += '' + sdays[i] + ''; + + var html = + '
'+ + ' '+ + ' '+ + months[month-1] +', '+ year + + '
'+ + ''+ + ' ' + dayTitle + ''+ + ' '; + + var day = 1; + if (w2utils.settings.weekStarts !== 'M') weekDay++; + if(this.type === 'datetime') { + var dt_sel = w2utils.isDateTime(selected, options.format, true); + selected = w2utils.formatDate(dt_sel, w2utils.settings.dateFormat); + } + for (var ci = 1; ci < 43; ci++) { + if (weekDay === 0 && ci == 1) { + for (var ti = 0; ti < 6; ti++) html += ''; + ci += 6; + } else { + if (ci < weekDay || day > daysCount[month-1]) { + html += ''; + if ((ci) % 7 === 0) html += ''; + continue; + } + } + var dt = year + '/' + month + '/' + day; + var DT = new Date(dt); + var className = ''; + if (DT.getDay() === 6) className = ' w2ui-saturday'; + if (DT.getDay() === 0) className = ' w2ui-sunday'; + if (dt == today) className += ' w2ui-today'; + + var dspDay = day; + var col = ''; + var bgcol = ''; + var tmp_dt, tmp_dt_fmt; + if(this.type === 'datetime') { + // var fm = options.format.split('|')[0].trim(); + // tmp_dt = w2utils.formatDate(dt, fm); + tmp_dt = w2utils.formatDateTime(dt, options.format); + tmp_dt_fmt = w2utils.formatDate(dt, w2utils.settings.dateFormat); + } else { + tmp_dt = w2utils.formatDate(dt, options.format); + tmp_dt_fmt = tmp_dt; + } + if (options.colored && options.colored[tmp_dt_fmt] !== undefined) { // if there is predefined colors for dates + var tmp = options.colored[tmp_dt_fmt].split(':'); + bgcol = 'background-color: ' + tmp[0] + ';'; + col = 'color: ' + tmp[1] + ';'; + } + html += ''; + if (ci % 7 === 0 || (weekDay === 0 && ci == 1)) html += ''; + day++; + } + html += '
  
'+ + dspDay + + '
'; + return html; + }, + + getYearHTML: function () { + var months = w2utils.settings.shortmonths; + var start_year = w2utils.settings.dateStartYear; + var end_year = w2utils.settings.dateEndYear; + var mhtml = ''; + var yhtml = ''; + for (var m = 0; m < months.length; m++) { + mhtml += '
'+ months[m] + '
'; + } + for (var y = start_year; y <= end_year; y++) { + yhtml += '
'+ y + '
'; + } + return '
'+ mhtml +'
'+ yhtml +'
'; + }, + + getHourHTML: function () { + var tmp = []; + var options = this.options; + if (options == null) options = { format: w2utils.settings.timeFormat }; + var h24 = (options.format.indexOf('h24') > -1); + for (var a = 0; a < 24; a++) { + var time = (a >= 12 && !h24 ? a - 12 : a) + ':00' + (!h24 ? (a < 12 ? ' am' : ' pm') : ''); + if (a == 12 && !h24) time = '12:00 pm'; + if (!tmp[Math.floor(a/8)]) tmp[Math.floor(a/8)] = ''; + var tm1 = this.fromMin(this.toMin(time)); + var tm2 = this.fromMin(this.toMin(time) + 59); + if (this.type === 'datetime') { + var dt = w2utils.isDateTime(this.el.value, options.format, true); + var fm = options.format.split('|')[0].trim(); + tm1 = w2utils.formatDate(dt, fm) + ' ' + tm1; + tm2 = w2utils.formatDate(dt, fm) + ' ' + tm2; + } + tmp[Math.floor(a/8)] += '
'+ time +'
'; + } + var html = + '
'+ + '
'+ w2utils.lang('Select Hour') +'
'+ + '
'+ + ' ' + + ' ' + + ' ' + + '
'+ tmp[0] +''+ tmp[1] +''+ tmp[2] +'
'+ + '
'; + return html; + }, + + getMinHTML: function (hour) { + if (hour == null) hour = 0; + var options = this.options; + if (options == null) options = { format: w2utils.settings.timeFormat }; + var h24 = (options.format.indexOf('h24') > -1); + var tmp = []; + for (var a = 0; a < 60; a += 5) { + var time = (hour > 12 && !h24 ? hour - 12 : hour) + ':' + (a < 10 ? 0 : '') + a + ' ' + (!h24 ? (hour < 12 ? 'am' : 'pm') : ''); + var tm = time; + var ind = a < 20 ? 0 : (a < 40 ? 1 : 2); + if (!tmp[ind]) tmp[ind] = ''; + if (this.type === 'datetime') { + var dt = w2utils.isDateTime(this.el.value, options.format, true); + var fm = options.format.split('|')[0].trim(); + tm = w2utils.formatDate(dt, fm) + ' ' + tm; + } + tmp[ind] += '
'+ time +'
'; + } + var html = + '
'+ + '
'+ w2utils.lang('Select Minute') +'
'+ + '
'+ + ' ' + + ' ' + + ' ' + + '
'+ tmp[0] +''+ tmp[1] +''+ tmp[2] +'
'+ + '
'; + return html; + }, + + toMin: function (str) { + if (typeof str !== 'string') return null; + var tmp = str.split(':'); + if (tmp.length === 2) { + tmp[0] = parseInt(tmp[0]); + tmp[1] = parseInt(tmp[1]); + if (str.indexOf('pm') !== -1 && tmp[0] !== 12) tmp[0] += 12; + } else { + return null; + } + return tmp[0] * 60 + tmp[1]; + }, + + fromMin: function (time) { + var ret = ''; + if (time >= 24 * 60) time = time % (24 * 60); + if (time < 0) time = 24 * 60 + time; + var hour = Math.floor(time/60); + var min = ((time % 60) < 10 ? '0' : '') + (time % 60); + var options = this.options; + if (options == null) options = { format: w2utils.settings.timeFormat }; + if (options.format.indexOf('h24') !== -1) { + ret = hour + ':' + min; + } else { + ret = (hour <= 12 ? hour : hour - 12) + ':' + min + ' ' + (hour >= 12 ? 'pm' : 'am'); + } + return ret; + } + }; + + $.extend(w2field.prototype, w2utils.event); + w2obj.field = w2field; + +}) (jQuery); + +/************************************************************************ +* Library: Web 2.0 UI for jQuery (using prototypical inheritance) +* - Following objects defined +* - w2form - form widget +* - $().w2form - jQuery wrapper +* - Dependencies: jQuery, w2utils, w2fields, w2tabs, w2toolbar +* +* == NICE TO HAVE == +* - include delta on save +* - form should read '; + switch (field.type) { + case 'pass': + case 'password': + input = ''; + break; + case 'check': + case 'checks': + if (field.options.items == null && field.html.items != null) field.options.items = field.html.items; + var items = field.options.items; + input = ''; + // normalized options + if (!$.isArray(items)) items = []; + if (items.length > 0) { + items = w2obj.field.prototype.normMenu.call(this, items, field); + } + // generate + for (var i = 0; i < items.length; i++) { + input += '
'; + } + break; + + case 'checkbox': + input = ''; + break; + case 'radio': + input = ''; + // normalized options + if (field.options.items == null && field.html.items != null) field.options.items = field.html.items; + var items = field.options.items; + if (!$.isArray(items)) items = []; + if (items.length > 0) { + items = w2obj.field.prototype.normMenu.call(this, items, field); + } + // generate + for (var i = 0; i < items.length; i++) { + input += '
'; + } + break; + case 'select': + input = ''; + break; + case 'textarea': + input = ''; + break; + case 'toggle': + input = '
'; + break; + case 'map': + case 'array': + field.html.key = field.html.key || {}; + field.html.value = field.html.value || {}; + field.html.tabindex_str = tabindex_str + input = '' + (field.html.text || '') + '' + + ''+ + '
'; + break; + case 'html': + case 'div': + case 'custom': + input = '
'+ + (field && field.html && field.html.html ? field.html.html : '') + + '
'; + break; + case 'empty': + input = (field && field.html ? (field.html.html || '') + (field.html.text || '') : ''); + break; + + } + if (group !== '') { + if(page != field.html.page || column != field.html.column || (field.html.group && (group != field.html.group))){ + pages[page][column] += '\n \n '; + group = ''; + } + } + if (field.html.group && (group != field.html.group)) { + var collapsable = ''; + if (field.html.groupCollapsable) { + collapsable = '' + } + html += '\n
' + + '\n
' + + collapsable + field.html.group + '
\n' + + '
'; + group = field.html.group; + } + if (field.html.anchor == null) { + var span = (field.html.span != null ? 'w2ui-span'+ field.html.span : '') + if (field.html.span == -1) span = 'w2ui-span-none'; + var label = '' + w2utils.lang(field.type != 'checkbox' ? field.html.label : field.html.text) +'' + if (!field.html.label) label = '' + html += '\n
'+ + '\n '+ label + + ((field.type === 'empty') ? input : '\n
'+ input + (field.type != 'array' && field.type != 'map' ? w2utils.lang(field.type != 'checkbox' ? field.html.text : '') : '') + '
') + + '\n
'; + } else { + pages[field.html.page].anchors = pages[field.html.page].anchors || {}; + pages[field.html.page].anchors[field.html.anchor] = '
'+ + ((field.type === 'empty') ? input : '
'+ w2utils.lang(field.type != 'checkbox' ? field.html.label : field.html.text) + input + w2utils.lang(field.type != 'checkbox' ? field.html.text : '') + '
') + + '
'; + } + if (pages[field.html.page] == null) pages[field.html.page] = {}; + if (pages[field.html.page][field.html.column] == null) pages[field.html.page][field.html.column] = ''; + pages[field.html.page][field.html.column] += html; + page = field.html.page; + column = field.html.column; + } + if (group !== '') pages[page][column] += '\n
\n
'; + if (this.tabs.tabs) { + for (var i = 0; i < this.tabs.tabs.length; i++) if (pages[i] == null) pages[i] = []; + } + // buttons if any + var buttons = ''; + if (!$.isEmptyObject(this.actions)) { + var addClass = ''; + buttons += '\n
'; + tabindex = this.tabindexBase + this.fields.length + 1; + + for (var a in this.actions) { // it is an object + var act = this.actions[a]; + var info = { text: '', style: '', "class": '' }; + if ($.isPlainObject(act)) { + if (act.text == null && act.caption != null) { + console.log('NOTICE: form action.caption property is deprecated, please use action.text. Action ->', act); + act.text = act.caption; + } + if (act.text) info.text = act.text; + if (act.style) info.style = act.style; + if (act["class"]) info['class'] = act['class']; + } else { + info.text = a; + if (['save', 'update', 'create'].indexOf(a.toLowerCase()) !== -1) info['class'] = 'w2ui-btn-blue'; else info['class'] = ''; + } + buttons += '\n '; + tabindex++; + } + buttons += '\n
'; + } + html = ''; + for (var p = 0; p < pages.length; p++){ + html += '
'; + if (pages[p].before) { + html += pages[p].before; + } + html += '
'; + Object.keys(pages[p]).sort().forEach(function (c, ind) { + if (c == parseInt(c)) { + html += '
' + (pages[p][c] || '') + '\n
'; + } + }) + html += '\n
'; + if (pages[p].after) { + html += pages[p].after; + } + html += '\n
'; + // process page anchors + if (pages[p].anchors) { + Object.keys(pages[p].anchors).forEach(function (key, ind) { + html = html.replace(key, pages[p].anchors[key]); + }); + } + } + html += buttons; + return html; + }, + + toggleGroup: function (groupName, show) { + var el = $(this.box).find('.w2ui-group-title[data-group="' + w2utils.base64encode(groupName) + '"]') + if (el.next().css('display') == 'none' && show !== true) { + el.next().slideDown(300); + el.next().next().remove() + el.find('span').addClass('w2ui-icon-collapse').removeClass('w2ui-icon-expand'); + } else { + el.next().slideUp(300); + var css = 'width: ' + el.next().css('width') + ';' + + 'padding-left: ' + el.next().css('padding-left') + ';' + + 'padding-right: ' + el.next().css('padding-right') + ';' + + 'margin-left: ' + el.next().css('margin-left') + ';' + + 'margin-right: ' + el.next().css('margin-right') + ';' + setTimeout(function () { el.next().after('
') }, 100) + el.find('span').addClass('w2ui-icon-expand').removeClass('w2ui-icon-collapse'); + } + }, + + action: function (action, event) { + var act = this.actions[action]; + var click = act; + if ($.isPlainObject(act) && act.onClick) click = act.onClick; + // event before + var edata = this.trigger({ phase: 'before', target: action, type: 'action', action: act, originalEvent: event }); + if (edata.isCancelled === true) return; + // default actions + if (typeof click === 'function') click.call(this, event); + // event after + this.trigger($.extend(edata, { phase: 'after' })); + }, + + resize: function () { + var obj = this; + // event before + var edata = this.trigger({ phase: 'before', target: this.name, type: 'resize' }); + if (edata.isCancelled === true) return; + // default behaviour + var main = $(this.box).find('> div.w2ui-form-box'); + var header = $(this.box).find('> div .w2ui-form-header'); + var toolbar = $(this.box).find('> div .w2ui-form-toolbar'); + var tabs = $(this.box).find('> div .w2ui-form-tabs'); + var page = $(this.box).find('> div .w2ui-page'); + var cpage = $(this.box).find('> div .w2ui-page.page-'+ this.page); + var dpage = $(this.box).find('> div .w2ui-page.page-'+ this.page + ' > div'); + var buttons = $(this.box).find('> div .w2ui-buttons'); + // if no height, calculate it + resizeElements(); + if (this.autosize) { //we don't need auto-size every time + if (parseInt($(this.box).height()) === 0 || $(this.box).data('auto-size') === true) { + $(this.box).height( + (header.length > 0 ? w2utils.getSize(header, 'height') : 0) + + ((typeof this.tabs === 'object' && $.isArray(this.tabs.tabs) && this.tabs.tabs.length > 0) ? w2utils.getSize(tabs, 'height') : 0) + + ((typeof this.toolbar === 'object' && $.isArray(this.toolbar.items) && this.toolbar.items.length > 0) ? w2utils.getSize(toolbar, 'height') : 0) + + (page.length > 0 ? w2utils.getSize(dpage, 'height') + w2utils.getSize(cpage, '+height') + 12 : 0) + // why 12 ??? + (buttons.length > 0 ? w2utils.getSize(buttons, 'height') : 0) + ); + $(this.box).data('auto-size', true); + } + resizeElements(); + } + if (this.toolbar && this.toolbar.resize) this.toolbar.resize(); + if (this.tabs && this.tabs.resize) this.tabs.resize(); + // event after + obj.trigger($.extend(edata, { phase: 'after' })); + + function resizeElements() { + // resize elements + main.width($(obj.box).width()).height($(obj.box).height()); + toolbar.css('top', (obj.header !== '' ? w2utils.getSize(header, 'height') : 0)); + tabs.css('top', (obj.header !== '' ? w2utils.getSize(header, 'height') : 0) + + ((typeof obj.toolbar === 'object' && $.isArray(obj.toolbar.items) && obj.toolbar.items.length > 0) ? w2utils.getSize(toolbar, 'height') : 0)); + page.css('top', (obj.header !== '' ? w2utils.getSize(header, 'height') : 0) + + ((typeof obj.toolbar === 'object' && $.isArray(obj.toolbar.items) && obj.toolbar.items.length > 0) ? w2utils.getSize(toolbar, 'height') + 5 : 0) + + ((typeof obj.tabs === 'object' && $.isArray(obj.tabs.tabs) && obj.tabs.tabs.length > 0) ? w2utils.getSize(tabs, 'height') + 5 : 0)); + page.css('bottom', (buttons.length > 0 ? w2utils.getSize(buttons, 'height') : 0)); + } + }, + + refresh: function () { + var time = (new Date()).getTime(); + var obj = this; + if (!this.box) return; + if (!this.isGenerated || $(this.box).html() == null) return; + // event before + var edata = this.trigger({ phase: 'before', target: this.name, type: 'refresh', page: this.page, field: field, fields: arguments }); + if (edata.isCancelled === true) return; + var fields = Array.from(this.fields.keys()); + if (arguments.length > 0) { + fields = Array.from(arguments) + .map(function (fld, ind) { + if (typeof fld != 'string') console.log('ERROR: Arguments in refresh functions should be field names') + return this.get(fld, true); // get index of field + }.bind(this)) + .filter(function (fld, ind) { + if (fld != null) return true; else return false; + }); + } else { + // update what page field belongs + $(this.box).find('input, textarea, select').each(function (index, el) { + var name = ($(el).attr('name') != null ? $(el).attr('name') : $(el).attr('id')); + var field = obj.get(name); + if (field) { + // find page + var div = $(el).closest('.w2ui-page'); + if (div.length > 0) { + for (var i = 0; i < 100; i++) { + if (div.hasClass('page-'+i)) { field.page = i; break; } + } + } + } + }); + // default action + $(this.box).find('.w2ui-page').hide(); + $(this.box).find('.w2ui-page.page-' + this.page).show(); + $(this.box).find('.w2ui-form-header').html(this.header); + // refresh tabs if needed + if (typeof this.tabs === 'object' && $.isArray(this.tabs.tabs) && this.tabs.tabs.length > 0) { + $('#form_'+ this.name +'_tabs').show(); + this.tabs.active = this.tabs.tabs[this.page].id; + this.tabs.refresh(); + } else { + $('#form_'+ this.name +'_tabs').hide(); + } + // refresh tabs if needed + if (typeof this.toolbar === 'object' && $.isArray(this.toolbar.items) && this.toolbar.items.length > 0) { + $('#form_'+ this.name +'_toolbar').show(); + this.toolbar.refresh(); + } else { + $('#form_'+ this.name +'_toolbar').hide(); + } + } + // refresh values of fields + for (var f = 0; f < fields.length; f++) { + var field = this.fields[fields[f]]; + if (field.name == null && field.field != null) field.name = field.field; + if (field.field == null && field.name != null) field.field = field.name; + field.$el = $(this.box).find('[name="'+ String(field.name).replace(/\\/g, '\\\\') +'"]'); + field.el = field.$el[0]; + if (field.el) field.el.id = field.name; + var tmp = $(field).data('w2field'); + if (tmp) tmp.clear(); + $(field.$el) + .off('.w2form') + .on('change.w2form', function (event) { + var that = this; + var field = obj.get(this.name); + if (field == null) return; + if ($(this).data('skip_change') == true) { + $(this).data('skip_change', false) + return + } + + var value_new = this.value; + var value_previous = obj.getValue(this.name); + if (value_previous == null) value_previous = '' + + if (['list', 'enum', 'file'].indexOf(field.type) !== -1 && $(this).data('selected')) { + var nv = $(this).data('selected'); + var cv = obj.getValue(this.name); + if ($.isArray(nv)) { + value_new = []; + for (var i = 0; i < nv.length; i++) value_new[i] = $.extend(true, {}, nv[i]); // clone array + } + if ($.isPlainObject(nv)) { + value_new = $.extend(true, {}, nv); // clone object + } + if ($.isArray(cv)) { + value_previous = []; + for (var i = 0; i < cv.length; i++) value_previous[i] = $.extend(true, {}, cv[i]); // clone array + } + if ($.isPlainObject(cv)) { + value_previous = $.extend(true, {}, cv); // clone object + } + } + if (['toggle', 'checkbox'].indexOf(field.type) !== -1) { + value_new = ($(this).prop('checked') ? ($(this).prop('value') === 'on' ? true : $(this).prop('value')) : false); + } + if (['check', 'checks'].indexOf(field.type) !== -1) { + if (!Array.isArray(value_previous)) value_previous = []; + value_new = value_previous.slice(); + var tmp = field.options.items[$(this).attr('data-index')]; + if ($(this).prop('checked')) { + value_new.push(tmp.id) + } else { + value_new.splice(value_new.indexOf(tmp.id), 1) + } + } + // clean extra chars + if (['int', 'float', 'percent', 'money', 'currency'].indexOf(field.type) !== -1) { + value_new = $(this).data('w2field').clean(value_new); + } + if (value_new === value_previous) return; + // event before + var edata2 = obj.trigger({ phase: 'before', target: this.name, type: 'change', value_new: value_new, value_previous: value_previous, originalEvent: event }); + if (edata2.isCancelled === true) { + edata2.value_new = obj.getValue(this.name) + if ($(this).val() !== edata2.value_new) { + $(this).data('skip_change', true) + // if not immediate, then ignore it + setTimeout(function () { $(that).data('skip_change', false) }, 10) + } + $(this).val(edata2.value_new); // return previous value + } + // default action + var val = edata2.value_new; + if (['enum', 'file'].indexOf(field.type) !== -1) { + if (val.length > 0) { + var fld = $(field.el).data('w2field').helpers.multi; + $(fld).removeClass('w2ui-error'); + } + } + if (val === '' || val == null + || ($.isArray(val) && val.length === 0) || ($.isPlainObject(val) && $.isEmptyObject(val))) { + val = null; + } + obj.setValue(this.name, val); + // event after + obj.trigger($.extend(edata2, { phase: 'after' })); + }) + .on('input.w2form', function (event) { + var val = this.value; + if (event.target.type == 'checkbox') { + val = event.target.checked; + } + // remember original + if (obj.original == null) { + if (!$.isEmptyObject(obj.record)) { + obj.original = $.extend(true, {}, obj.record); + } else { + obj.original = {}; + } + } + // event before + var edata2 = obj.trigger({ phase: 'before', target: this.name, type: 'input', value_new: val, originalEvent: event }); + if (edata2.isCancelled === true) return; + + // event after + obj.trigger($.extend(edata2, { phase: 'after' })); + }); + // required + if (field.required) { + $(field.el).parent().parent().addClass('w2ui-required'); + } else { + $(field.el).parent().parent().removeClass('w2ui-required'); + } + // disabled + if (field.disabled != null) { + var $fld = $(field.el); + if (field.disabled) { + if ($fld.data('w2ui-tabIndex') == null) { + $fld.data('w2ui-tabIndex', $fld.prop('tabIndex')); + } + $(field.el) + .prop('readonly', true) + .prop('tabindex', -1) + .closest('.w2ui-field') + .addClass('w2ui-disabled'); + } else { + $(field.el) + .prop('readonly', false) + .prop('tabIndex', $fld.data('w2ui-tabIndex')) + .closest('.w2ui-field') + .removeClass('w2ui-disabled'); + } + } + // hidden + var tmp = field.el; + if (!tmp) tmp = $(this.box).find('#' + field.field) + if (field.hidden) { + $(tmp).closest('.w2ui-field').hide(); + } else { + $(tmp).closest('.w2ui-field').show(); + } + } + // attach actions on buttons + $(this.box).find('button, input[type=button]').each(function (index, el) { + $(el).off('click').on('click', function (event) { + var action = this.value; + if (this.id) action = this.id; + if (this.name) action = this.name; + obj.action(action, event); + }); + }); + // init controls with record + for (var f = 0; f < fields.length; f++) { + var field = this.fields[fields[f]]; + var value = (this.getValue(field.name) != null ? this.getValue(field.name) : ''); + if (!field.el) continue; + if (!$(field.el).hasClass('w2ui-input')) $(field.el).addClass('w2ui-input'); + field.type = String(field.type).toLowerCase(); + if (!field.options) field.options = {}; + switch (field.type) { + case 'text': + case 'textarea': + case 'email': + case 'pass': + case 'password': + field.el.value = value; + break; + case 'int': + case 'float': + case 'money': + case 'currency': + case 'percent': + // issue #761 + field.el.value = value; + $(field.el).w2field($.extend({}, field.options, { type: field.type })); + break; + case 'hex': + case 'alphanumeric': + case 'color': + case 'date': + case 'time': + field.el.value = value; + $(field.el).w2field($.extend({}, field.options, { type: field.type })); + break; + case 'toggle': + if (w2utils.isFloat(value)) value = parseFloat(value); + $(field.el).prop('checked', (value ? true : false)); + this.setValue(field.name, (value ? value : false)); + break; + case 'radio': + $(field.$el).prop('checked', false).each(function (index, el) { + if ($(el).val() == value) $(el).prop('checked', true); + }); + break; + case 'checkbox': + $(field.el).prop('checked', value ? true : false); + if (field.disabled === true || field.disabled === false) { + $(field.el).prop('disabled', field.disabled ? true : false) + } + break; + case 'check': + case 'checks': + if (Array.isArray(value)) { + value.forEach(function (val) { + $(field.el).closest('div').find('[data-value="' + val + '"]').prop('checked', true) + }) + } + if (field.disabled) { + $(field.el).closest('div').find('input[type=checkbox]').prop('disabled', true) + } else { + $(field.el).closest('div').find('input[type=checkbox]').removeProp('disabled') + } + break; + // enums + case 'list': + case 'combo': + if (field.type === 'list') { + var tmp_value = ($.isPlainObject(value) ? value.id : ($.isPlainObject(field.options.selected) ? field.options.selected.id : value)); + // normalized options + if (!field.options.items) field.options.items = []; + var items = field.options.items; + if (typeof items == 'function') items = items(); + // find value from items + var isFound = false; + if (Array.isArray(items)) { + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (item.id == tmp_value) { + value = $.extend(true, {}, item); + obj.setValue(field.name, value); + isFound = true; + break; + } + } + } + if (!isFound && value != null && value !== '') { + field.$el.data('find_selected', value); + } + } else if (field.type === 'combo' && !$.isPlainObject(value)) { + field.el.value = value; + } else if ($.isPlainObject(value) && value.text != null) { + field.el.value = value.text; + } else { + field.el.value = ''; + } + if (!$.isPlainObject(value)) value = {}; + $(field.el).w2field($.extend({}, field.options, { type: field.type, selected: value })); + break; + case 'enum': + case 'file': + var sel = []; + var isFound = false; + if (!$.isArray(value)) value = []; + if (typeof field.options.items != 'function') { + if (!$.isArray(field.options.items)) { + field.options.items = []; + } + // find value from items + value.forEach(function (val) { + field.options.items.forEach(function (it) { + if (it && (it.id == val || ($.isPlainObject(val) && it.id == val.id))) { + sel.push($.isPlainObject(it) ? $.extend(true, {}, it) : it); + isFound = true + } + }) + }) + } + if (!isFound && value != null && value.length !== 0) { + field.$el.data('find_selected', value); + sel = value + } + var opt = $.extend({}, field.options, { type: field.type, selected: sel }) + Object.keys(field.options).forEach(function(key) { + if (typeof field.options[key] == 'function') { + opt[key] = field.options[key] + } + }) + $(field.el).w2field(opt); + break; + + // standard HTML + case 'select': + // generate options + var items = field.options.items; + if (items != null && items.length > 0) { + items = w2obj.field.prototype.normMenu.call(this, items, field); + $(field.el).html(''); + for (var it = 0; it < items.length; it++) { + $(field.el).append('";a.addClass("w2ui-editable").html('"+s.outTag),setTimeout(function(){a.find("select").on("change",function(e){delete u.last.move}).on("blur",function(e){1!=$(this).data("keep-open")&&u.editChange.call(u,this,c,d,e)})},10);break;case"div":var y="font-family: "+(x=o.find("[col="+d+"] > div")).css("font-family")+"; font-size: "+x.css("font-size")+";";a.addClass("w2ui-editable").html('
"+s.outTag),null==e&&a.find("div.w2ui-input").text("object"!=typeof f?f:"");var b=a.find("div.w2ui-input").get(0);setTimeout(function(){var t=b;$(t).on("blur",function(e){1!=$(this).data("keep-open")&&u.editChange.call(u,t,c,d,e)})},10),null!=e&&$(b).text("object"!=typeof f?f:"");break;default:var x,y="font-family: "+(x=o.find("[col="+d+"] > div")).css("font-family")+"; font-size: "+x.css("font-size");a.addClass("w2ui-editable").html('"+s.outTag),"number"==s.type&&(f=w2utils.formatNumber(f)),"date"==s.type&&(f=w2utils.formatDate(w2utils.isDate(f,s.format,!0)||new Date,s.format)),null==e&&a.find("input").val("object"!=typeof f?f:"");b=a.find("input").get(0);$(b).w2field(s.type,$.extend(s,{selected:f})),setTimeout(function(){var e=b;"list"==s.type&&(e=$($(b).data("w2field").helpers.focus).find("input"),"object"!=typeof f&&""!=f&&e.val(f).css({opacity:1}).prev().css({opacity:1}),a.find("input").on("change",function(e){u.editChange.call(u,b,c,d,e)})),$(e).on("blur",function(e){1!=$(this).data("keep-open")&&u.editChange.call(u,b,c,d,e)})},10),null!=e&&$(b).val("object"!=typeof f?f:"")}setTimeout(function(){u.last.inEditMode&&(a.find("input, select, div.w2ui-input").data("old_value",g).on("mousedown",function(e){e.stopPropagation()}).on("click",function(e){"div"==s.type?_.call(a.find("div.w2ui-input")[0],null):_.call(a.find("input, select")[0],null)}).on("paste",function(e){var t=e.originalEvent;e.preventDefault();t=t.clipboardData.getData("text/plain");document.execCommand("insertHTML",!1,t)}).on("keydown",function(o){var a=this,e="DIV"==a.tagName.toUpperCase()?$(a).text():$(a).val();switch(o.keyCode){case 8:"list"!=s.type||$(b).data("w2field")||o.preventDefault();break;case 9:case 13:o.preventDefault();break;case 37:0===w2utils.getCursorPosition(a)&&o.preventDefault();break;case 39:w2utils.getCursorPosition(a)==e.length&&(w2utils.setCursorPosition(a,e.length),o.preventDefault())}setTimeout(function(){switch(o.keyCode){case 9:var e=r,t=o.shiftKey?u.prevCell(c,d,!0):u.nextCell(c,d,!0);if(null==t){var i=o.shiftKey?u.prevRow(c,d):u.nextRow(c,d);if(null!=i&&i!=c)for(var e=u.records[i].recid,s=0;si.width()&&i.width(s+20)}catch(e){}}},editChange:function(e,t,i,s){var n=this;setTimeout(function(){var e=$(n.box).find("#grid_"+n.name+"_focus");e.is(":focus")||e.focus()},10);var l=t<0;t=t<0?-t-1:t;var o=(l?this.summary:this.records)[t],a=this.columns[i],r=$("#grid_"+this.name+(!0===a.frozen?"_frec_":"_rec_")+w2utils.escapeId(o.recid)),d=e.tagName&&"DIV"==e.tagName.toUpperCase()?$(e).text():e.value,u=this.parseField(o,a.field),c=$(e).data("w2field");c&&("list"==c.type&&(d=$(e).data("selected")),!$.isEmptyObject(d)&&null!=d||(d=""),$.isPlainObject(d)||(d=c.clean(d))),"checkbox"==e.type&&(o.w2ui&&!1===o.w2ui.editable&&(e.checked=!e.checked),d=e.checked);var h={phase:"before",type:"change",target:this.name,input_id:e.id,recid:o.recid,index:t,column:i,originalEvent:s.originalEvent||s,value_new:d,value_previous:o.w2ui&&o.w2ui.changes&&o.w2ui.changes.hasOwnProperty(a.field)?o.w2ui.changes[a.field]:u,value_original:u};for(null!=$(s.target).data("old_value")&&(h.value_previous=$(s.target).data("old_value"));;){if("object"!=typeof(d=h.value_new)&&String(u)!=String(d)||"object"==typeof d&&d&&d.id!=u&&("object"!=typeof u||null==u||d.id!=u.id)){if(!0!==(h=this.trigger($.extend(h,{type:"change",phase:"before"}))).isCancelled){if(d!==h.value_new)continue;o.w2ui=o.w2ui||{},o.w2ui.changes=o.w2ui.changes||{},o.w2ui.changes[a.field]=h.value_new,this.trigger($.extend(h,{phase:"after"}))}}else if(!0!==(h=this.trigger($.extend(h,{type:"restore",phase:"before"}))).isCancelled){if(d!==h.value_new)continue;o.w2ui&&o.w2ui.changes&&delete o.w2ui.changes[a.field],o.w2ui&&$.isEmptyObject(o.w2ui.changes)&&delete o.w2ui.changes,this.trigger($.extend(h,{phase:"after"}))}break}r=$(r).find("[col="+i+"]");l||(o.w2ui&&o.w2ui.changes&&null!=o.w2ui.changes[a.field]?r.addClass("w2ui-changed"):r.removeClass("w2ui-changed"),r.replaceWith(this.getCellHTML(t,i,l))),$(this.box).find("div.w2ui-edit-box").remove(),this.show.toolbarSave&&(0'+w2utils.lang(i.msgDelete).replace("NN",s.length).replace("records",1==s.length?"record":"records")+"",buttons:w2utils.settings.macButtonOrder?'":'",onOpen:function(e){var t=$(this.box).find("input, textarea, select, button");t.off(".message").on("blur.message",function(e){t.index(e.target)+1===t.length&&(t.get(0).focus(),e.preventDefault())}).on("keydown.message",function(e){27==e.keyCode&&i.message()}),setTimeout(function(){$(this.box).find(".w2ui-btn.btn-default").focus(),clearTimeout(i.last.kbd_timer)}.bind(this),50)}})}},click:function(e,t){var i=(new Date).getTime(),s=null;if(!(1==this.last.cancelClick||t&&t.altKey))if("object"==typeof e&&null!==e&&(s=e.column,e=e.recid),null==t&&(t={}),i-parseInt(this.last.click_time)<350&&this.last.click_recid==e&&"click"==t.type)this.dblClick(e,t);else{this.last.bubbleEl&&($(this.last.bubbleEl).w2tag(),this.last.bubbleEl=null),this.last.click_time=i;var n=this.last.click_recid;this.last.click_recid=e,null==s&&t.target&&("TD"!=(v=t.target).tagName.toUpperCase()&&(v=$(v).parents("td")[0]),null!=$(v).attr("col")&&(s=parseInt($(v).attr("col"))));var l=this.trigger({phase:"before",target:this.name,type:"click",recid:e,column:s,originalEvent:t});if(!0!==l.isCancelled){var o=this,a=this.getSelection();$("#grid_"+this.name+"_check_all").prop("checked",!1);var i=this.get(e,!0),r=(this.records[i],[]);if(o.last.sel_ind=i,o.last.sel_col=s,o.last.sel_recid=e,o.last.sel_type="click",t.shiftKey&&0a[0].column?(d=a[0].column,s):(d=s,a[0].column);for(var p=d;p<=u;p++)r.push(p)}else c=this.get(n,!0),h=this.get(e,!0);var f=[];h=this.records.length?this.selectNone():this.selectAll());else if(t.altKey&&(s=this.getColumn(e))&&s.sortable&&this.sort(e,null,!(!t||!t.ctrlKey&&!t.metaKey)),"line-number"==i.field)this.getSelection().length>=this.records.length?this.selectNone():this.selectAll();else{t.shiftKey||t.metaKey||t.ctrlKey||this.selectNone();var i,e=this.getSelection(),s=this.getColumn(i.field,!0),n=[],l=[];if(0!=e.length&&t.shiftKey){var t=s,o=e[0].column;o.w2ui-message").length)27==i.keyCode&&this.message();else{var n=!1,l=$("#grid_"+s.name+"_records"),o=s.getSelection();0===o.length&&(n=!0);var a=o[0]||null,r=[],d=o[o.length-1];if("object"==typeof a&&null!=a){for(var a=o[0].recid,r=[],u=0;o[u]&&o[u].recid==a;)r.push(o[u].column),u++;d=o[o.length-1].recid}var c=s.get(a,!0),h=s.get(d,!0),p=(s.get(a),$("#grid_"+s.name+"_rec_"+(null!=c?w2utils.escapeId(s.records[c].recid):"none"))),f=!1,g=i.keyCode,m=i.shiftKey;switch(g){case 8:case 46:s.delete(),f=!0,i.stopPropagation();break;case 27:s.selectNone(),f=!0;break;case 65:if(!i.metaKey&&!i.ctrlKey)break;s.selectAll(),f=!0;break;case 13:if("row"==this.selectType&&!0===s.show.expandColumn){if(p.length<=0)break;s.toggle(a,i),f=!0}else{for(var w=0;wv&&s.last.sel_ind!=h?s.unselect(s.records[h].recid):s.select(s.records[v].recid);else if(s.last.sel_ind>v&&s.last.sel_ind!=h){v=h;for(y=[],w=0;w
'),$("#grid_"+this.name+"_frec_"+n).after(''+(this.show.lineNumbers?'':"")+'
'),!0===(s=this.trigger({phase:"before",type:"expand",target:this.name,recid:e,box_id:"grid_"+this.name+"_rec_"+e+"_expanded",fbox_id:"grid_"+this.name+"_frec_"+n+"_expanded"})).isCancelled)return $("#grid_"+this.name+"_rec_"+n+"_expanded_row").remove(),$("#grid_"+this.name+"_frec_"+n+"_expanded_row").remove(),!1;var o=$(this.box).find("#grid_"+this.name+"_rec_"+e+"_expanded"),t=$(this.box).find("#grid_"+this.name+"_frec_"+e+"_expanded"),l=o.find("> div:first-child").height();o.height()o&&(o=i[r].column),-1==a.indexOf(i[r].index)&&a.push(i[r].index);a.sort(function(e,t){return e-t});for(var d=0;d div.w2ui-grid-box").css("width",$(this.box).width()).css("height",$(this.box).height());var i=this.trigger({phase:"before",type:"resize",target:this.name});if(!0!==i.isCancelled)return e.resizeBoxes(),e.resizeRecords(),e.toolbar&&e.toolbar.resize&&e.toolbar.resize(),this.trigger($.extend(i,{phase:"after"})),(new Date).getTime()-t}},update:function(e){var t=(new Date).getTime();if(null==this.box)return 0;if(null==e){for(var i=this.last.range_start-1;i<=this.last.range_end-1;i++)if(!(i<0)){(o=this.records[i]||{}).w2ui||(o.w2ui={});for(var s=0;s'+i[0]+'
"+i[1]+'
'+s[0]+'
'+s[1]+"
",l=$("#grid_"+this.name+"_body",n.box).html(s),o=$("#grid_"+this.name+"_records",n.box),s=$("#grid_"+this.name+"_frecords",n.box),a=this;"row"==this.selectType&&(o.on("mouseover mouseout","tr",function(e){$("#grid_"+a.name+"_frec_"+w2utils.escapeId($(this).attr("recid"))).toggleClass("w2ui-record-hover","mouseover"==e.type)}),s.on("mouseover mouseout","tr",function(e){$("#grid_"+a.name+"_rec_"+w2utils.escapeId($(this).attr("recid"))).toggleClass("w2ui-record-hover","mouseover"==e.type)})),w2utils.isIOS?o.add(s).on("click","tr",function(e){a.dblClick($(this).attr("recid"),e)}):o.add(s).on("click","tr",function(e){a.click($(this).attr("recid"),e)}).on("contextmenu","tr",function(e){a.contextMenu($(this).attr("recid"),null,e)}),l.data("scrolldata",{lastTime:0,lastDelta:0,time:0}).find(".w2ui-grid-frecords").on("mousewheel DOMMouseScroll ",function(e){e.preventDefault();var t=e.originalEvent,i=l.data("scrolldata"),s=$(this).siblings(".w2ui-grid-records").addBack().filter(".w2ui-grid-records"),e=null!=typeof t.wheelDelta?-1*t.wheelDelta/120:(t.detail||t.deltaY)/3,t=s.scrollTop();i.time=+new Date,i.lastTime
'+this.msgEmpty+"
"):0<$("#grid_"+this.name+"_empty_msg").length&&$("#grid_"+this.name+"_empty_msg").remove(),0=this.searches.length?(this.last.field="",this.last.label=""):(this.last.field=this.searches[n].field,this.last.label=this.searches[n].label)}$(this.box).attr("name",this.name).addClass("w2ui-reset w2ui-grid w2ui-inactive").html('
"),"row"!=this.selectType&&$(this.box).addClass("w2ui-ss"),0<$(this.box).length&&($(this.box)[0].style.cssText+=this.style),this.initToolbar(),null!=this.toolbar&&this.toolbar.render($("#grid_"+this.name+"_toolbar")[0]),this.last.field&&"all"!=this.last.field&&(i=this.searchData,setTimeout(function(){x.initAllField(x.last.field,1==i.length?i[0].value:null)},1)),$("#grid_"+this.name+"_footer").html(this.getFooterHTML()),this.last.state||(this.last.state=this.stateSave(!0)),this.stateRestore(),s&&(this.clear(),this.refresh());for(var _,l=!1,o=0;o
'),(u=$(x.box).find(".w2ui-grid-records")).append('
'),u.append('
'),$("#grid_"+x.name+"_ghost").append(t).append(r.ghost)),d=$("#grid_"+x.name+"_ghost"),u=$(x.box).find(".w2ui-grid-records"),d.css({top:r.pos.top+u.scrollTop(),left:r.pos.left,"border-top":"1px solid #aaa","border-bottom":"1px solid #aaa"})):x.last.move.reorder=!1)}$(document).on("mousemove.w2ui-"+x.name,c).on("mouseup.w2ui-"+x.name,h),e.stopPropagation()}),this.updateToolbar(),this.trigger($.extend(e,{phase:"after"})),0===$(".w2ui-layout").length&&$(window).off("resize.w2ui-"+x.name).on("resize.w2ui-"+x.name,function(e){null==w2ui[x.name]?$(window).off("resize.w2ui-"+x.name):w2ui[x.name].resize()}),(new Date).getTime()-t}}function c(e){var t=x.last.move;if(t&&-1!=["select","select-column"].indexOf(t.type)&&(t.divX=e.screenX-t.x,t.divY=e.screenY-t.y,!(Math.abs(t.divX)<=1&&Math.abs(t.divY)<=1))){if(x.last.cancelClick=!0,1==x.reorderRows&&x.last.move.reorder){var i,s,n=$(x.box).find(".w2ui-grid-records");return(l="-none-"==(l=(d=$(e.target).parents("tr")).attr("recid"))?"bottom":l)!=t.from&&($("#grid_"+x.name+"_rec_"+t.recid),s=$("#grid_"+x.name+"_rec_"+l),$(x.box).find(".insert-before"),s.addClass("insert-before"),t.lastY=e.screenY,t.to=l,i=s.position(),s=$("#grid_"+x.name+"_ghost_line"),i?s.css({top:i.top+n.scrollTop(),left:t.pos.left,"border-top":"2px solid #769EFC"}):s.css({"border-top":"2px solid transparent"})),void $("#grid_"+x.name+"_ghost").css({top:t.pos.top+t.divY+n.scrollTop(),left:t.pos.left})}t.start&&t.recid&&(x.selectNone(),t.start=!1);var l,o=[];if(null==(l=("TR"==e.target.tagName.toUpperCase()?$(e.target):$(e.target).parents("tr")).attr("recid"))){if("row"!=x.selectType&&(!x.last.move||"select"!=x.last.move.type)){n=parseInt($(e.target).parents("td").attr("col"));if(isNaN(n))x.removeRange("column-selection"),$(x.box).find(".w2ui-grid-columns .w2ui-col-header, .w2ui-grid-fcolumns .w2ui-col-header").removeClass("w2ui-col-selected"),$(x.box).find(".w2ui-col-number").removeClass("w2ui-row-selected"),delete t.colRange;else{var a=n+"-"+n;t.columnn?n+"-"+t.column:a).split("-"),u=parseInt(d[0]);u<=parseInt(d[1]);u++)r.push(u);if(t.colRange!=a&&!0!==(_=x.trigger({phase:"before",type:"columnSelect",target:x.name,columns:r,isCancelled:!1})).isCancelled){null==t.colRange&&x.selectNone();d=a.split("-");$(x.box).find(".w2ui-grid-columns .w2ui-col-header, .w2ui-grid-fcolumns .w2ui-col-header").removeClass("w2ui-col-selected");for(var c=parseInt(d[0]);c<=parseInt(d[1]);c++)$(x.box).find("#grid_"+x.name+"_column_"+c+" .w2ui-col-header").addClass("w2ui-col-selected");$(x.box).find(".w2ui-col-number").not(".w2ui-head").addClass("w2ui-row-selected"),t.colRange=a,x.removeRange("column-selection"),x.addRange({name:"column-selection",range:[{recid:x.records[0].recid,column:d[0]},{recid:x.records[x.records.length-1].recid,column:d[1]}],style:"background-color: rgba(90, 145, 234, 0.1)"})}}}}else{a=x.get(t.recid,!0);if(!(null==a||x.records[a]&&x.records[a].recid!=t.recid))if(null!=(h=x.get(l,!0))){var h,p=parseInt(t.column),f=parseInt(("TD"==e.target.tagName.toUpperCase()?$(e.target):$(e.target).parents("td")).attr("col"));isNaN(p)&&isNaN(f)&&(p=0,f=x.columns.length-1),h",i=0;i ")}var l="object"!=typeof this.url?this.url:this.url.get;(l&&e.show.skipRecords||e.show.saveRestoreState)&&(t+=''),l&&e.show.skipRecords&&(t+='"),e.show.saveRestoreState&&(t+='"),t+="
'+w2utils.lang("Skip")+' "+w2utils.lang("Records")+"
"+w2utils.lang("Save Grid State")+'
"+w2utils.lang("Restore Default State")+"
",this.toolbar.get("w2ui-column-on-off").html=t}},initColumnDrag:function(e){if(this.columnGroups&&this.columnGroups.length)throw"Draggable columns are not currently supported with column groups.";var r=this,d={};function t(){d.pressed=!1,clearTimeout(d.timeout)}function i(o){d.timeout&&clearTimeout(d.timeout);var a=this;d.pressed=!0,d.timeout=setTimeout(function(){if(d.pressed&&0!==d.numberPreColumnsPresent){var e,t,i,s=["w2ui-col-number","w2ui-col-expand","w2ui-col-select"].concat(["w2ui-head-last"]);if($(o.originalEvent.target).parents().hasClass("w2ui-head")){for(var n=0,l=s.length;n=t[t.length-1]+i)return t.length;for(var s=0,n=t.length;s
'),d.markerLeft=$('
'));d.lastInt&&d.lastInt===e||(d.lastInt=e,d.marker.remove(),d.markerLeft.remove(),$(".w2ui-head").removeClass("w2ui-col-intersection"),e>=d.columns.length?($(d.columns[d.columns.length-1]).children("div:last").append(d.marker.addClass("right").removeClass("left")),$(d.columns[d.columns.length-1]).addClass("w2ui-col-intersection")):e<=d.numberPreColumnsPresent?($(d.columns[d.numberPreColumnsPresent]).prepend(d.marker.addClass("left").removeClass("right")).css({position:"relative"}),$(d.columns[d.numberPreColumnsPresent]).prev().addClass("w2ui-col-intersection")):($(d.columns[e]).children("div:last").prepend(d.marker.addClass("left").removeClass("right")),$(d.columns[e]).prev().children("div:last").append(d.markerLeft.addClass("right").removeClass("left")).css({position:"relative"}),$(d.columns[e-1]).addClass("w2ui-col-intersection")))}(d.targetInt),i=i,s=s,$(d.ghost).css({left:i-10,top:s-10}))}function c(e){d.pressed=!1;var t,i,s,n=$(".w2ui-grid-ghost"),l=r.trigger({type:"columnDragEnd",phase:"before",originalEvent:e,target:d.columnHead[0]});if(!0===l.isCancelled)return!1;t=r.columns[d.originalPos],i=r.columns,s=$(d.columns[Math.min(d.lastInt,d.columns.length-1)]),(e=d.lastInt
'+this.buttons.search.html+'
',this.toolbar.items.push({type:"html",id:"w2ui-search",html:e})),this.show.toolbarSearch&&this.multiSearch&&0 div',n.box).each(function(){var e=this.offsetWidth-this.scrollWidth;e div.w2ui-grid-box"),i=$("#grid_"+this.name+"_header"),s=$("#grid_"+this.name+"_toolbar"),n=$("#grid_"+this.name+"_summary"),o=$("#grid_"+this.name+"_fsummary"),a=$("#grid_"+this.name+"_footer"),r=$("#grid_"+this.name+"_body"),d=$("#grid_"+this.name+"_columns"),u=$("#grid_"+this.name+"_fcolumns"),c=$("#grid_"+this.name+"_records"),h=$("#grid_"+this.name+"_frecords"),p=$("#grid_"+this.name+"_scroll1"),f=8*String(this.total).length+10;f<34&&(f=34),null!=this.lineNumberWidth&&(f=this.lineNumberWidth);for(var g,m=!1,w=!1,v=0,y=0;ytable").height()+(m?w2utils.scrollBarSize():0)&&(w=!0),this.fixedBody?(g=t.height()-(this.show.header?w2utils.getSize(i,"height"):0)-(this.show.toolbar?w2utils.getSize(s,"height"):0)-("none"!=n.css("display")?w2utils.getSize(n,"height"):0)-(this.show.footer?w2utils.getSize(a,"height"):0),r.css("height",g)):(g=w2utils.getSize(d,"height")+w2utils.getSize($("#grid_"+l.name+"_records table"),"height")+(m?w2utils.scrollBarSize():0),l.height=g+w2utils.getSize(t,"+height")+(l.show.header?w2utils.getSize(i,"height"):0)+(l.show.toolbar?w2utils.getSize(s,"height"):0)+("none"!=n.css("display")?w2utils.getSize(n,"height"):0)+(l.show.footer?w2utils.getSize(a,"height"):0),t.css("height",l.height),r.css("height",g),e.css("height",w2utils.getSize(t,"height")+w2utils.getSize(e,"+height")));t=this.records.length,e="object"!=typeof this.url?this.url:this.url.get;if(0==this.searchData.length||e||(t=this.last.searchIds.length),this.fixedBody||(w=!1),m||w?(d.find("> table > tbody > tr:nth-child(1) td.w2ui-head-last").css("width",w2utils.scrollBarSize()).show(),c.css({top:(0 table > tbody > tr:nth-child(1) td.w2ui-head-last").hide(),c.css({top:(0=this.recordHeight&&(m-=this.recordHeight,b++),this.fixedBody){for(var x=t;x',l+='',i.show.lineNumbers&&(n+=''),i.show.selectColumn&&(n+=''),i.show.expandColumn&&(n+=''),l+='',i.show.orderColumn&&(l+='');for(var o=0;oi.last.colEnd)&&!a.frozen||(s='',a.frozen?n+=s:l+=s)}n+=' ',l+=' ',$("#grid_"+i.name+"_frecords > table").append(n),$("#grid_"+i.name+"_records > table").append(l)}if(0C&&!0!==D.hidden&&(S=D.hidden=!0),D.gridMinWidthparseInt(D.max)&&(D.sizeCalculated=D.max+"px"),O+=parseInt(D.sizeCalculated));var z=parseInt(C)-parseInt(O);if(0 table > tbody > tr:nth-child(1) td.w2ui-head-last").css("width",w2utils.scrollBarSize()).show();var I=1;this.show.lineNumbers&&(I+=f),this.show.selectColumn&&(I+=26),this.show.expandColumn&&(I+=26);for(y=0;y table > tbody > tr:nth-child(1) td").add(u.find("> table > tbody > tr:nth-child(1) td")).each(function(e,t){$(t).hasClass("w2ui-col-number")&&$(t).css("width",f);var i=$(t).attr("col");if(null!=i){if("start"==i){for(var s=0,n=0;n table > tbody > tr").length&&d.find("> table > tbody > tr:nth-child(1) td").add(u.find("> table > tbody > tr:nth-child(1) td")).html("").css({height:"0px",border:"0px",padding:"0px",margin:"0px"}),c.find("> table > tbody > tr:nth-child(1) td").add(h.find("> table > tbody > tr:nth-child(1) td")).each(function(e,t){$(t).hasClass("w2ui-col-number")&&$(t).css("width",f);var i=$(t).attr("col");if(null!=i){if("start"==i){for(var s=0,n=0;n table > tbody > tr:nth-child(1) td").add(o.find("> table > tbody > tr:nth-child(1) td")).each(function(e,t){$(t).hasClass("w2ui-col-number")&&$(t).css("width",f);var i=$(t).attr("col");if(null!=i){if("start"==i){for(var s=0,n=0;n',t=!1,i=0;iX",t=!0),null==s.inTag&&(s.inTag=""),null==s.outTag&&(s.outTag=""),null==s.style&&(s.style=""),null==s.type&&(s.type="text"),null==s.label&&null!=s.caption&&(console.log("NOTICE: grid search.caption property is deprecated, please use search.label. Search ->",s),s.label=s.caption);var l='";switch(e+=' '+n+' '+(s.label||"")+' '+l+' ',s.type){case"text":case"alphanumeric":case"hex":case"color":case"list":case"combo":case"enum":var o="width: 250px;";-1!=["hex","color"].indexOf(s.type)&&(o="width: 90px;"),e+='";break;case"int":case"float":case"money":case"currency":case"percent":case"date":case"time":case"datetime":o="width: 90px;";e+='";break;case"select":e+='"}e+=s.outTag+" "}}return e+='
"},initOperator:function(e,t){var i=this.searches[t],s=$("#grid_"+this.name+"_range_"+t),n=$("#grid_"+this.name+"_field_"+t),l=n.parent().find("span input");switch(n.show(),s.hide(),$(e).val()){case"between":s.show(),l.w2field(i.type,i.options);break;case"not null":case"null":n.hide(),n.val("1"),n.change()}},initSearches:function(){for(var t=this,e=0;e--',o=0;o'+(u=null==u&&null!=c.text?c.text:u)+""):r+='"}$("#grid_"+this.name+"_field_"+e).html(r)}null!=s?("int"==s.type&&-1!=["in","not in"].indexOf(s.operator)&&$("#grid_"+this.name+"_field_"+e).w2field("clear").val(s.value),$("#grid_"+this.name+"_operator_"+e).val(s.operator).trigger("change"),$.isArray(s.value)?-1!=["in","not in"].indexOf(s.operator)?$("#grid_"+this.name+"_field_"+e).val(s.value).trigger("change"):($("#grid_"+this.name+"_field_"+e).val(s.value[0]).trigger("change"),$("#grid_"+this.name+"_field2_"+e).val(s.value[1]).trigger("change")):null!=s.value&&$("#grid_"+this.name+"_field_"+e).val(s.value).trigger("change")):$("#grid_"+this.name+"_operator_"+e).val(l).trigger("change")}$("#w2ui-overlay-"+this.name+"-searchOverlay .w2ui-grid-searches *[rel=search]").on("keypress",function(e){13==e.keyCode&&(t.search(),$().w2overlay({name:t.name+"-searchOverlay"}))})},getColumnsHTML:function(){var e,t,i,f=this,s="",n="";return this.show.columnHeaders&&(n=0 ",f.columnGroups[s]),f.columnGroups[s].text=f.columnGroups[s].caption);""!=f.columnGroups[f.columnGroups.length-1].text&&f.columnGroups.push({text:""});f.show.lineNumbers&&(e+='
 
');f.show.selectColumn&&(e+='
 
');f.show.expandColumn&&(e+='
 
');var n=0;t+='',f.show.orderColumn&&(t+='
 
');for(var l=0;l",a),a.text=a.caption);for(var r=0,d=n;d');var p="function"==typeof a.text?a.text(a):a.text;i='"+h+'
'+(p||" ")+"
",a&&a.frozen?e+=i:t+=i}else{p="function"==typeof o.text?o.text(o):o.text;i='
'+(p||" ")+"
",a&&a.frozen?e+=i:t+=i}n+=o.span}return e+="",t+='',[e,t]}(),i=l(!1),s=e[0]+t[0]+i[0],e[1]+t[1]+i[1]):(s=(i=l(!0))[0],i[1])),[s,n];function l(e){var t="",i="";f.show.lineNumbers&&(t+='
#
"),f.show.selectColumn&&(t+='
"),f.show.expandColumn&&(t+='
 
');var s,n=0,l=0;i+='',f.show.orderColumn&&(i+='
 
');for(var o=0;o ",r),r.text=r.caption),null==r.size&&(r.size="100%"),o==l&&(l+=(s=f.columnGroups[n++]||{}).span),(of.last.colEnd)&&!r.frozen||r.hidden||!0===s.master&&!e||(a=f.getColumnCellHTML(o),r&&r.frozen?t+=a:i+=a)}return t+='
 
',i+='
 
',[t+="",i+=""]}},getColumnCellHTML:function(e){var t=this.columns[e];if(null==t)return"";for(var i=!this.reorderColumns||this.columnGroups&&this.columnGroups.length?"":" w2ui-reorder-cols-head ",s="",n=0;n"+(!1!==t.resizable?'
':"")+'
'+(r||" ")+"
"},columnTooltipShow:function(e){var t,i,s;"normal"!=this.columnTooltip&&(t=$(this.box).find("#grid_"+this.name+"_column_"+e),i=this.columns[e],s=this.columnTooltip,t.prop("_mouse_over",!0),setTimeout(function(){!0===t.prop("_mouse_over")&&!0!==t.prop("_mouse_tooltip")&&(t.prop("_mouse_tooltip",!0),t.w2tag(i.tooltip,{position:s,top:5}))},1))},columnTooltipHide:function(e){var t;"normal"!=this.columnTooltip&&(t=$(this.box).find("#grid_"+this.name+"_column_"+e),this.columns[e],t.removeProp("_mouse_over"),setTimeout(function(){!0!==t.prop("_mouse_over")&&!0===t.prop("_mouse_tooltip")&&(t.removeProp("_mouse_tooltip"),t.w2tag())},1))},getRecordsHTML:function(){var e=this.records.length,t="object"!=typeof this.url?this.url:this.url.get;(e=0!=this.searchData.length&&!t?this.last.searchIds.length:e)>this.vs_start?this.last.show_extra=this.vs_extra:this.last.show_extra=this.vs_start;var t=$("#grid_"+this.name+"_records"),i=Math.floor((t.height()||0)/this.recordHeight)+this.last.show_extra+1;(!this.fixedBody||e"+s[0],l=""+s[1];n+='',l+='';for(var o=0;o
',l+=' ',this.last.range_start=0,this.last.range_end=i,[n,l]},getSummaryHTML:function(){if(0!==this.summary.length){for(var e=this.getRecordHTML(-1,0),t=""+e[0],i="
"+e[1],s=0;s
",i+=""]}},scroll:function(e){(new Date).getTime();var n=this,t="object"!=typeof this.url?this.url:this.url.get,i=$("#grid_"+this.name+"_records"),s=$("#grid_"+this.name+"_frecords");e&&(p=e.target.scrollTop,h=e.target.scrollLeft,n.last.scrollTop=p,n.last.scrollLeft=h,$("#grid_"+n.name+"_columns")[0].scrollLeft=h,$("#grid_"+n.name+"_summary")[0].scrollLeft=h,s[0].scrollTop=p),this.last.bubbleEl&&($(this.last.bubbleEl).w2tag(),this.last.bubbleEl=null);var l=null,o=null;if(n.disableCVS||0n.last.scrollLeft&&null==l&&(l=u),d+a-30>n.last.scrollLeft+r&&null==o&&(o=u),d+=a);null==o&&(o=n.columns.length-1)}if(null!=l&&((l=l<0?0:l)==(o=o<0?0:o)&&(0n.last.colStart)for(u=n.last.colStart;u';null!=i&&(s=n.getCellHTML(parseInt(i),u,!1)),$(t).after(s)}),v.each(function(e,t){var i=$(t).parent().attr("index"),s='';null!=i&&(s=n.getCellHTML(parseInt(i),u,!0)),$(t).after(s)}));if(o>n.last.colEnd)for(u=n.last.colEnd+1;u<=o;u++)n.columns[u]&&(n.columns[u].frozen||n.columns[u].hidden)||(g.before(n.getColumnCellHTML(u)),w.each(function(e,t){var i=$(t).parent().attr("index"),s='';null!=i&&(s=n.getCellHTML(parseInt(i),u,!1)),$(t).before(s)}),y.each(function(e,t){var i=$(t).parent().attr("index")||-1,i=n.getCellHTML(parseInt(i),u,!0);$(t).before(i)}));n.last.colStart=l,n.last.colEnd=o,n.resizeRecords()}else{n.last.colStart=l,n.last.colEnd=o;var h=this.getColumnsHTML(),b=this.getRecordsHTML(),x=this.getSummaryHTML(),p=c.find("#grid_"+this.name+"_columns"),_=c.find("#grid_"+this.name+"_records"),k=c.find("#grid_"+this.name+"_frecords"),C=c.find("#grid_"+this.name+"_summary");p.find("tbody").html(h[1]),k.html(b[0]),_.prepend(b[1]),null!=x&&C.html(x[1]),setTimeout(function(){_.find("> table").not("table:first-child").remove(),C[0]&&(C[0].scrollLeft=n.last.scrollLeft)},1),n.resizeRecords()}}k=this.records.length;if(k>this.total&&-1!==this.total&&(k=this.total),0!==(k=0!=this.searchData.length&&!t?this.last.searchIds.length:k)&&0!==i.length&&0!==i.height()){k>this.vs_start?this.last.show_extra=this.vs_extra:this.last.show_extra=this.vs_start;b=Math.round(i[0].scrollTop/this.recordHeight+1),x=b+(Math.round(i.height()/this.recordHeight)-1);if(kthis.total&&-1!=this.total&&(S=this.total);var O=i.find("#grid_"+this.name+"_rec_top"),z=i.find("#grid_"+this.name+"_rec_bottom"),D=s.find("#grid_"+this.name+"_frec_top"),I=s.find("#grid_"+this.name+"_frec_bottom");-1!=String(O.next().prop("id")).indexOf("_expanded_row")&&(O.next().remove(),D.next().remove()),this.total>S&&-1!=String(z.prev().prop("id")).indexOf("_expanded_row")&&(z.prev().remove(),I.prev().remove());x=parseInt(O.next().attr("line")),t=parseInt(z.prev().attr("line"));if(x=x-this.last.show_extra+2&&1S))break;E.remove(),R.remove()}"bottom"==(j=i.find("#grid_"+this.name+"_rec_top").next().attr("line"))&&(j=S);for(var R,F,u=parseInt(j)-1;T<=u;u--)this.records[u-1]&&((R=this.records[u-1].w2ui)&&!Array.isArray(R.children)&&(R.expanded=!1),F=this.getRecordHTML(u-1,u),O.after(F[1]),D.after(F[0]));A(),setTimeout(function(){n.refreshRanges()},0)}var x=(T-1)*n.recordHeight,j=(k-S)*this.recordHeight;j<0&&(j=0),O.css("height",x+"px"),D.css("height",x+"px"),z.css("height",j+"px"),I.css("height",j+"px"),n.last.range_start=T,n.last.range_end=S,k
'),n.last.pull_more=!0,n.last.xhr_offset+=n.limit,n.request("get")}).find("td").html(n.autoLoad?'
':'
'+w2utils.lang("Load")+" "+n.limit+" "+w2utils.lang("More")+"...
"))}}function A(){n.markSearch&&(clearTimeout(n.last.marker_timer),n.last.marker_timer=setTimeout(function(){for(var e=[],t=0;t',l+='',this.show.lineNumbers&&(n+=''),this.show.selectColumn&&(n+=''),this.show.expandColumn&&(n+=''),l+='',this.show.orderColumn&&(l+='');for(var a=0;a';(p=this.columns[a]).frozen&&!p.hidden?n+=r:p.hidden||athis.last.colEnd||(l+=r)}return n+='',l+='',[n+="",l+=""]}var d="object"!=typeof this.url?this.url:this.url.get;if(!0!==i)if(0=this.last.searchIds.length)return"";e=this.last.searchIds[e],s=this.records[e]}else{if(e>=this.records.length)return"";s=this.records[e]}else{if(e>=this.summary.length)return"";s=this.summary[e]}if(!s)return"";null!=s.recid||null==this.recid||null!=(u=this.parseField(s,this.recid))&&(s.recid=u);w2utils.escapeId(s.recid);d=!1;-1!=o.indexes.indexOf(e)&&(d=!0);var u=s.w2ui?s.w2ui.style:"";null!=u&&"string"==typeof u||(u="");o=s.w2ui?s.w2ui.class:"";null!=o&&"string"==typeof o||(o=""),n+='",l+='",this.show.lineNumbers&&(n+='"+(!0!==i?this.getLineHTML(t,s):"")+""),this.show.selectColumn&&(s&&s.w2ui&&s.w2ui.hideCheckBox,n+=''+(!0===i||s.w2ui&&!0===s.w2ui.hideCheckBox?"":'
')+""),this.show.expandColumn&&(d="",d=s.w2ui&&!0===s.w2ui.expanded?"-":"+",s.w2ui&&"none"==s.w2ui.expanded&&(d=""),s.w2ui&&"spinner"==s.w2ui.expanded&&(d='
'),n+=''+(!0!==i?'
"+d+"
":"")+""),l+='',this.show.orderColumn&&(l+=''+(!0!==i?'
 
':"")+"");for(var c=0,h=0;;){var p,f,g,m=1;if(null==(p=this.columns[c]))break;if(p.hidden)c++,0this.last.colEnd)||p.frozen){if(s.w2ui&&"object"==typeof s.w2ui.colspan){var w=parseInt(s.w2ui.colspan[p.field])||null;if(1=this.columns.length);a++)this.columns[a].hidden&&v++;m=w-v,h=w-1}}m=this.getCellHTML(e,c,i,m);p.frozen?n+=m:l+=m,c++}else c++}return n+='',l+='',[n+="",l+=""]},getLineHTML:function(e){return"
"+e+"
"},getCellHTML:function(i,s,e,t){var n=this,l=this.columns[s];if(null==l)return"";var o,a,r,d=(!0!==e?this.records:this.summary)[i],u=-1!==i?this.getCellValue(i,s,e):"",c=-1!==i?this.getCellEditable(i,s):"",h="max-height: "+parseInt(this.recordHeight)+"px;"+(l.clipboardCopy?"margin-right: 20px":""),p=!e&&d&&d.w2ui&&d.w2ui.changes&&null!=d.w2ui.changes[l.field],f="",g="",m=this.last.selection,w=!1,v="";if(-1!=m.indexes.indexOf(i)&&(w=!0),null==t&&(t=d&&d.w2ui&&d.w2ui.colspan&&d.w2ui.colspan[l.field]?d.w2ui.colspan[l.field]:1),0===s&&d&&d.w2ui&&Array.isArray(d.w2ui.children)){for(var y,b=0,x=this.get(d.w2ui.parent_recid,!0);null!=x;){if(b++,null==(y=this.records[x].w2ui)||null==y.parent_recid)break;x=this.get(y.parent_recid,!0)}if(d.w2ui.parent_recid)for(var _=0;_
';v+='"}!0===l.info&&(l.info={}),null!=l.info&&(o="w2ui-icon-info","function"==typeof l.info.icon?o=l.info.icon(d):"object"==typeof l.info.icon?o=l.info.icon[this.parseField(d,l.field)]||"":"string"==typeof l.info.icon&&(o=l.info.icon),a=l.info.style||"","function"==typeof l.info.style?a=l.info.style(d):"object"==typeof l.info.style?a=l.info.style[this.parseField(d,l.field)]||"":"string"==typeof l.info.style&&(a=l.info.style),v+='"),null!=l.render&&-1!==i?("function"==typeof l.render&&(null!=(a=l.render.call(this,d,i,s,u))&&"object"==typeof a?(u=$.trim(a.html||""),g=a.class||"",f=a.style||""):u=$.trim(a),(u.length<4||"'+v+String(u)+"")),"object"==typeof l.render&&(u='
'+v+String(r)+"
"),"string"==typeof l.render&&(y=[],-1==(r=l.render.toLowerCase().indexOf(":"))?(y[0]=l.render.toLowerCase(),y[1]=""):(y[0]=l.render.toLowerCase().substr(0,r),y[1]=l.render.toLowerCase().substr(r+1)),r=w2utils.formatters[y[0]],u='
'+v+String(u)+"
")):(c&&-1!=["checkbox","check"].indexOf(c.type)&&(c=e?-(i+1):i,h+="text-align: center;",u='',v=""),u='
'+v+String(u)+"
"),null==u&&(u=""),"string"==typeof l.render&&(y=l.render.toLowerCase().split(":"),-1!=["number","int","float","money","currency","percent","size"].indexOf(y[0])&&(f+="text-align: right;")),d&&d.w2ui&&("object"==typeof d.w2ui.style&&("string"==typeof d.w2ui.style[s]&&(f+=d.w2ui.style[s]+";"),"string"==typeof d.w2ui.style[l.field]&&(f+=d.w2ui.style[l.field]+";")),"object"==typeof d.w2ui.class&&("string"==typeof d.w2ui.class[s]&&(g+=d.w2ui.class[s]+" "),"string"==typeof d.w2ui.class[l.field]&&(g+=d.w2ui.class[l.field]+" ")));h=!1;w&&-1!=$.inArray(s,m.columns[i])&&(h=!0);w="string"==typeof l.clipboardCopy?l.clipboardCopy:"Copy to clipboard",m="';return u='"+u+(""!=w2utils.stripTags(u)&&l.clipboardCopy&&w?m:"")+"",u=-1===i&&!0===e?'":u;function k(e){var t="";return n.show.recordTitles&&(null!=l.title?("function"==typeof l.title&&(t=l.title.call(n,d,i,s)),"string"==typeof l.title&&(t=l.title)):t=w2utils.stripTags(String(e).replace(/"/g,"''"))),null!=t?String(t):""}},clipboardCopy:function(e,t){var i=this.records[e],e=this.columns[t],t=e?this.parseField(i,e.field):"";"function"==typeof e.clipboardCopy&&(t=e.clipboardCopy(i)),$("#grid_"+this.name+"_focus").text(t).select(),document.execCommand("copy")},showBubble:function(e,t){var i="",s=this.columns[t].info,n=this.records[e],l=$(this.box).find("#grid_"+this.name+"_data_"+e+"_"+t+" .w2ui-info");if(this.last.bubbleEl&&$(this.last.bubbleEl).w2tag(),this.last.bubbleEl=l,null==s.fields){s.fields=[];for(var o=0;os.maxLength&&(c=c.substr(0,s.maxLength)+"..."),i+=""+a.text+""+((0===c?"0":c)||"")+"")):i+='
';i+=""}else if($.isPlainObject(r)){for(var d in i='',r){var u,c,h=r[d];""!=h&&"-"!=h&&"--"!=h&&"---"!=h?(u=String(h).split(":"),c=(a=null==(a=this.getColumn(u[0]))?{field:u[0],caption:u[0]}:a)?this.parseField(n,a.field):"",1")):i+=''}i+="
"+d+""+((c=null!=s.maxLength&&"string"==typeof c&&c.length>s.maxLength?c.substr(0,s.maxLength)+"...":c)||"")+"
"}$(l).w2tag($.extend({html:i,left:-4,position:"bottom|top",className:"w2ui-info-bubble",style:"",hideOnClick:!0},s.options||{}))},getCellEditable:function(e,t){var i=this.columns[t],s=this.records[e];if(!s||!i)return null;var n=s.w2ui?s.w2ui.editable:null;return!1===n?null:(null!=n&&!0!==n||"function"==typeof(n=i?i.editable:null)&&(i=this.getCellValue(e,t,!1),n=n.call(this,s,e,t,i)),n)},getCellValue:function(e,t,i){var t=this.columns[t],e=(!0!==i?this.records:this.summary)[e],s=this.parseField(e,t.field);return e&&e.w2ui&&e.w2ui.changes&&null!=e.w2ui.changes[t.field]&&(s=e.w2ui.changes[t.field]),$.isPlainObject(s)&&(t.options&&t.options.items?(val=t.options.items.find(function(e){return e.id==s.id}),s=val?val.text:s.id):null!=(s=null!=s.text?s.text:s).id&&(s=s.id)),s=null==s?"":s},getFooterHTML:function(){return'
'},status:function(e){var t,i;null!=e?$("#grid_"+this.name+"_footer").find(".w2ui-footer-left").html(e):(t="",0<(i=this.getSelection()).length&&(this.show.statusSelection&&1=this.columns.length)return null;var n=this.records[e].w2ui,l=(this.columns[t],this.columns[s]),n=n&&n.colspan&&!isNaN(n.colspan[l.field])?parseInt(n.colspan[l.field]):1;if(null==l)return null;if(l&&l.hidden||0===n)return this.nextCell(e,s,i);if(i){t=this.getCellEditable(e,t);if(null==t||-1!=["checkbox","check"].indexOf(t.type))return this.nextCell(e,s,i)}return s},prevCell:function(e,t,i){var s=t-1;if(s<0)return null;var n=this.records[e].w2ui,l=this.columns[s],n=n&&n.colspan&&!isNaN(n.colspan[l.field])?parseInt(n.colspan[l.field]):1;if(null==l)return null;if(l&&l.hidden||0===n)return this.prevCell(e,s,i);if(i){t=this.getCellEditable(e,t);if(null==t||-1!=["checkbox","check"].indexOf(t.type))return this.prevCell(e,s,i)}return s},nextRow:function(e,t){var i=this.last.searchIds,s=null;if(e+1this.records.length);)e++;var n=this.records[e].w2ui,l=this.columns[t],s=0===(n&&n.colspan&&null!=l&&!isNaN(n.colspan[l.field])?parseInt(n.colspan[l.field]):1)?this.nextRow(e,t):e}return s},prevRow:function(e,t){var i=this.last.searchIds,s=null;if(0i[0]){if(e--,0'+e+"",buttons:'",onOpen:function(e){setTimeout(function(){$(this.box).find(".w2ui-btn").focus()},25)},onClose:function(e){"function"==typeof t&&t()}}),w2utils.message.call(this,{box:this.box,path:"w2ui."+this.name,title:".w2ui-grid-header:visible",body:".w2ui-grid-box"},e)}},$.extend(w2grid.prototype,w2utils.event),w2obj.grid=w2grid}(jQuery),function(O){function a(e){this.box=null,this.name=null,this.panels=[],this.tmp={},this.padding=1,this.resizer=4,this.style="",O.extend(!0,this,w2obj.layout,e)}var z=["top","left","main","preview","right","bottom"];O.fn.w2layout=function(e){if(!O.isPlainObject(e)){var t=w2ui[O(this).attr("name")];return t?0"+t+""),l.status=!0,l;if(null==n)return console.log("ERROR: incorrect panel name. Panel name can be main, left, right, top, bottom, preview or css"),l.error=!0,l;if(null==t)return l;var o=this.trigger({phase:"before",type:"content",target:e,object:n,content:t,transition:i});if(!0===o.isCancelled)return l.cancelled=!0,l;if(t instanceof jQuery)return console.log("ERROR: You can not pass jQuery object to w2layout.content() method"),l;var a,r,d="#layout_"+this.name+"_panel_"+n.type,u=O(d+"> .w2ui-panel-content"),c=0;return 0 .w2ui-panel-content")).after('
'),r=O(d+"> .w2ui-panel-content.new-panel"),a.css("top",c),r.css("top",c),"object"==typeof t?(t.box=r[0],t.render()):r.html(t),w2utils.transition(a[0],r[0],i,function(){a.remove(),r.removeClass("new-panel"),r.css("overflow",n.overflow),O(d+"> .w2ui-panel-content").slice(1).remove(),s.resize(),-1!=window.navigator.userAgent.indexOf("MSIE")&&setTimeout(function(){s.resize()},100)}))),this.refresh(e),s.trigger(O.extend(o,{phase:"after"})),s.resize(),-1!=window.navigator.userAgent.indexOf("MSIE")&&setTimeout(function(){s.resize()},100),l},message:function(e,t){var i=this;"string"==typeof t&&(t={width:t.length<300?350:550,height:t.length<300?170:250,body:'
'+t+"
",buttons:'",onOpen:function(e){setTimeout(function(){O(this.box).find(".w2ui-btn").focus()},25)}});var s,n=this.get(e),l=O("#layout_"+this.name+"_panel_"+n.type).css("overflow");t&&(t.onClose&&(s=t.onClose),t.onClose=function(e){"function"==typeof s&&s(e),e.done(function(){O("#layout_"+i.name+"_panel_"+n.type).css("overflow",l)})}),O("#layout_"+this.name+"_panel_"+n.type).css("overflow","hidden"),w2utils.message.call(this,{box:O("#layout_"+this.name+"_panel_"+n.type),param:e,path:"w2ui."+this.name,title:".w2ui-panel-title:visible",body:".w2ui-panel-content"},t)},load:function(s,e,n,l){var o=this;return"css"==s?(O.get(e,function(e,t,i){o.html(s,i.responseText),l&&l()}),!0):null!=this.get(s)&&(O.get(e,function(e,t,i){o.html(s,i.responseText,n),l&&l(),o.resize(),-1!=window.navigator.userAgent.indexOf("MSIE")&&setTimeout(function(){o.resize()},100)}),!0)},sizeTo:function(e,t,i){var s=this;return null!=s.get(e)&&(O(s.box).find(" > div > .w2ui-panel").css(w2utils.cssPrefix("transition",!0!==i?".2s":"0s")),setTimeout(function(){s.set(e,{size:t})},1),setTimeout(function(){O(s.box).find(" > div > .w2ui-panel").css(w2utils.cssPrefix("transition","0s")),s.resize()},500),!0)},show:function(e,t){var i=this,s=this.trigger({phase:"before",type:"show",target:e,object:this.get(e),immediate:t});if(!0!==s.isCancelled){var n=i.get(e);return null==n?!1:(!(n.hidden=!1)===t?(O("#layout_"+i.name+"_panel_"+e).css({opacity:"1"}),i.trigger(O.extend(s,{phase:"after"})),i.resize()):(O("#layout_"+i.name+"_panel_"+e).css({opacity:"0"}),O(i.box).find(" > div > .w2ui-panel").css(w2utils.cssPrefix("transition",".2s")),setTimeout(function(){i.resize()},1),setTimeout(function(){O("#layout_"+i.name+"_panel_"+e).css({opacity:"1"})},250),setTimeout(function(){O(i.box).find(" > div > .w2ui-panel").css(w2utils.cssPrefix("transition","0s")),i.trigger(O.extend(s,{phase:"after"})),i.resize()},500)),!0)}},hide:function(e,t){var i=this,s=this.trigger({phase:"before",type:"hide",target:e,object:this.get(e),immediate:t});if(!0!==s.isCancelled){var n=i.get(e);return null==n?!1:((n.hidden=!0)===t?(O("#layout_"+i.name+"_panel_"+e).css({opacity:"0"}),i.trigger(O.extend(s,{phase:"after"})),i.resize()):(O(i.box).find(" > div > .w2ui-panel").css(w2utils.cssPrefix("transition",".2s")),O("#layout_"+i.name+"_panel_"+e).css({opacity:"0"}),setTimeout(function(){i.resize()},1),setTimeout(function(){O(i.box).find(" > div > .w2ui-panel").css(w2utils.cssPrefix("transition","0s")),i.trigger(O.extend(s,{phase:"after"})),i.resize()},500)),!0)}},toggle:function(e,t){var i=this.get(e);return null!=i&&(i.hidden?this.show(e,t):this.hide(e,t))},set:function(e,t){var i=this.get(e,!0);return null!=i&&(O.extend(this.panels[i],t),null==t.content&&null==t.resizable||this.refresh(e),this.resize(),!0)},get:function(e,t){for(var i=0;i .w2ui-panel-content");return 1!=e.length?null:e[0]},hideToolbar:function(e){var t=this.get(e);t&&(t.show.toolbar=!1,O("#layout_"+this.name+"_panel_"+e+"> .w2ui-panel-toolbar").hide(),this.resize())},showToolbar:function(e){var t=this.get(e);t&&(t.show.toolbar=!0,O("#layout_"+this.name+"_panel_"+e+"> .w2ui-panel-toolbar").show(),this.resize())},toggleToolbar:function(e){var t=this.get(e);t&&(t.show.toolbar?this.hideToolbar(e):this.showToolbar(e))},assignToolbar:function(e,t){"string"==typeof t&&null!=w2ui[t]&&(t=w2ui[t]);var i=this.get(e);i.toolbar=t;var s=O(this.box).find(e+"> .w2ui-panel-toolbar");null!=i.toolbar?(0===s.find("[name="+i.toolbar.name+"]").length?s.w2render(i.toolbar):null!=i.toolbar&&i.toolbar.refresh(),(t.owner=this).showToolbar(e),this.refresh(e)):(s.html(""),this.hideToolbar(e))},hideTabs:function(e){var t=this.get(e);t&&(t.show.tabs=!1,O("#layout_"+this.name+"_panel_"+e+"> .w2ui-panel-tabs").hide(),this.resize())},showTabs:function(e){var t=this.get(e);t&&(t.show.tabs=!0,O("#layout_"+this.name+"_panel_"+e+"> .w2ui-panel-tabs").show(),this.resize())},toggleTabs:function(e){var t=this.get(e);t&&(t.show.tabs?this.hideTabs(e):this.showTabs(e))},render:function(e){var u=this,t=(new Date).getTime(),i=u.trigger({phase:"before",type:"render",target:u.name,box:e});if(!0!==i.isCancelled){if(null!=e&&(0"),0
';O(u.box).find(" > div").append(n)}return O(u.box).find(" > div").append('
'),u.refresh(),u.trigger(O.extend(i,{phase:"after"})),setTimeout(function(){u.tmp.events={resize:function(e){null==w2ui[u.name]?O(window).off("resize.w2ui-"+u.name):w2ui[u.name].resize()},resizeStart:l,mouseMove:a,mouseUp:o},O(window).on("resize.w2ui-"+u.name,u.tmp.events.resize),u.resize()},0),(new Date).getTime()-t}function l(e,t){if(u.box){t=t||window.event,O(document).off("mousemove",u.tmp.events.mouseMove).on("mousemove",u.tmp.events.mouseMove),O(document).off("mouseup",u.tmp.events.mouseUp).on("mouseup",u.tmp.events.mouseUp),u.tmp.resize={type:e,x:t.screenX,y:t.screenY,diff_x:0,diff_y:0,value:0};for(var i=0;it.width&&(l=t.minSize-t.width),t.maxSize&&t.width+l>t.maxSize&&(l=t.maxSize-t.width),a.minSize+l>a.width&&(l=a.width-a.minSize);break;case"right":t.minSize+l>t.width&&(l=t.width-t.minSize),t.maxSize&&t.width-l>t.maxSize&&(l=t.width-t.maxSize),a.minSize-l>a.width&&(l=a.minSize-a.width);break;case"top":t.minSize-o>t.height&&(o=t.minSize-t.height),t.maxSize&&t.height+o>t.maxSize&&(o=t.maxSize-t.height),a.minSize+o>a.height&&(o=a.height-a.minSize);break;case"preview":case"bottom":t.minSize+o>t.height&&(o=t.height-t.minSize),t.maxSize&&t.height-o>t.maxSize&&(o=t.height-t.maxSize),a.minSize-o>a.height&&(o=a.minSize-a.height)}switch(i.diff_x=l,i.diff_y=o,i.type){case"top":case"preview":case"bottom":(i.diff_x=0) .w2ui-panel-content")[0],setTimeout(function(){0 .w2ui-panel-content").length&&(O(l+"> .w2ui-panel-content").removeClass().removeAttr("name").addClass("w2ui-panel-content").css("overflow",n.overflow)[0].style.cssText+=";"+n.style),n.content&&"function"==typeof n.content.render&&n.content.render()},1)):0 .w2ui-panel-content").length&&(O(l+"> .w2ui-panel-content").removeClass().removeAttr("name").addClass("w2ui-panel-content").html(n.content).css("overflow",n.overflow)[0].style.cssText+=";"+n.style);e=O(t.box).find(l+"> .w2ui-panel-tabs");n.show.tabs?0===e.find("[name="+n.tabs.name+"]").length&&null!=n.tabs?e.w2render(n.tabs):n.tabs.refresh():e.html("").removeClass("w2ui-tabs").hide(),e=O(t.box).find(l+"> .w2ui-panel-toolbar"),n.show.toolbar?0===e.find("[name="+n.toolbar.name+"]").length&&null!=n.toolbar?e.w2render(n.toolbar):n.toolbar.refresh():e.html("").removeClass("w2ui-toolbar").hide(),e=O(t.box).find(l+"> .w2ui-panel-title"),n.title?e.html(n.title).show():e.html("").hide()}else{if(0===O("#layout_"+t.name+"_panel_main").length)return void t.render();t.resize();for(var o=0;o div").css({width:s+"px",height:n+"px"});for(var l,o,a,r,d,u,c=this,h=this.get("main"),p=this.get("preview"),f=this.get("left"),g=this.get("right"),m=this.get("top"),w=this.get("bottom"),v=null!=p&&!0!==p.hidden,y=null!=f&&!0!==f.hidden,b=null!=g&&!0!==g.hidden,x=null!=m&&!0!==m.hidden,_=null!=w&&!0!==w.hidden,k=0;kthis.padding?this.resizer:this.padding,O("#layout_"+this.name+"_resizer_top").show().css({display:"block",left:l+"px",top:o+"px",width:a+"px",height:r+"px",cursor:"ns-resize"}).off("mousedown").on("mousedown",function(e){var t=c.trigger({phase:"before",type:"resizerClick",target:"top",originalEvent:e});if(!0!==t.isCancelled)return w2ui[c.name].tmp.events.resizeStart("top",e),c.trigger(O.extend(t,{phase:"after"})),!1}))):(O("#layout_"+this.name+"_panel_top").hide(),O("#layout_"+this.name+"_resizer_top").hide()),null!=f&&!0!==f.hidden?(o=(l=0)+(x?m.sizeCalculated+this.padding:0),a=f.sizeCalculated,r=n-(x?m.sizeCalculated+this.padding:0)-(_?w.sizeCalculated+this.padding:0),d=O("#layout_"+this.name+"_panel_left"),-1!=window.navigator.userAgent.indexOf("MSIE")&&0this.padding?this.resizer:this.padding,O("#layout_"+this.name+"_resizer_left").show().css({display:"block",left:l+"px",top:o+"px",width:a+"px",height:r+"px",cursor:"ew-resize"}).off("mousedown").on("mousedown",function(e){var t=c.trigger({phase:"before",type:"resizerClick",target:"left",originalEvent:e});if(!0!==t.isCancelled)return w2ui[c.name].tmp.events.resizeStart("left",e),c.trigger(O.extend(t,{phase:"after"})),!1}))):(O("#layout_"+this.name+"_panel_left").hide(),O("#layout_"+this.name+"_resizer_left").hide()),null!=g&&!0!==g.hidden?(l=s-g.sizeCalculated,o=0+(x?m.sizeCalculated+this.padding:0),a=g.sizeCalculated,r=n-(x?m.sizeCalculated+this.padding:0)-(_?w.sizeCalculated+this.padding:0),O("#layout_"+this.name+"_panel_right").css({display:"block",left:l+"px",top:o+"px",width:a+"px",height:r+"px"}).show(),g.width=a,g.height=r,g.resizable&&(l-=this.padding,a=this.resizer>this.padding?this.resizer:this.padding,O("#layout_"+this.name+"_resizer_right").show().css({display:"block",left:l+"px",top:o+"px",width:a+"px",height:r+"px",cursor:"ew-resize"}).off("mousedown").on("mousedown",function(e){var t=c.trigger({phase:"before",type:"resizerClick",target:"right",originalEvent:e});if(!0!==t.isCancelled)return w2ui[c.name].tmp.events.resizeStart("right",e),c.trigger(O.extend(t,{phase:"after"})),!1}))):(O("#layout_"+this.name+"_panel_right").hide(),O("#layout_"+this.name+"_resizer_right").hide()),null!=w&&!0!==w.hidden?(l=0,o=n-w.sizeCalculated,a=s,r=w.sizeCalculated,O("#layout_"+this.name+"_panel_bottom").css({display:"block",left:l+"px",top:o+"px",width:a+"px",height:r+"px"}).show(),w.width=a,w.height=r,w.resizable&&(o-=0===this.padding?0:this.padding,r=this.resizer>this.padding?this.resizer:this.padding,O("#layout_"+this.name+"_resizer_bottom").show().css({display:"block",left:l+"px",top:o+"px",width:a+"px",height:r+"px",cursor:"ns-resize"}).off("mousedown").on("mousedown",function(e){var t=c.trigger({phase:"before",type:"resizerClick",target:"bottom",originalEvent:e});if(!0!==t.isCancelled)return w2ui[c.name].tmp.events.resizeStart("bottom",e),c.trigger(O.extend(t,{phase:"after"})),!1}))):(O("#layout_"+this.name+"_panel_bottom").hide(),O("#layout_"+this.name+"_resizer_bottom").hide()),l=0+(y?f.sizeCalculated+this.padding:0),o=0+(x?m.sizeCalculated+this.padding:0),a=s-(y?f.sizeCalculated+this.padding:0)-(b?g.sizeCalculated+this.padding:0),r=n-(x?m.sizeCalculated+this.padding:0)-(_?w.sizeCalculated+this.padding:0)-(v?p.sizeCalculated+this.padding:0),d=O("#layout_"+this.name+"_panel_main"),-1!=window.navigator.userAgent.indexOf("MSIE")&&0this.padding?this.resizer:this.padding,O("#layout_"+this.name+"_resizer_preview").show().css({display:"block",left:l+"px",top:o+"px",width:a+"px",height:r+"px",cursor:"ns-resize"}).off("mousedown").on("mousedown",function(e){var t=c.trigger({phase:"before",type:"resizerClick",target:"preview",originalEvent:e});if(!0!==t.isCancelled)return w2ui[c.name].tmp.events.resizeStart("preview",e),c.trigger(O.extend(t,{phase:"after"})),!1}))):(O("#layout_"+this.name+"_panel_preview").hide(),O("#layout_"+this.name+"_resizer_preview").hide());for(var C=0;C .w2ui-panel-",$=0;T&&(T.title&&($+=w2utils.getSize(O(S+"title").css({top:$+"px",display:"block"}),"height")),T.show.tabs&&(null!=T.tabs&&w2ui[this.name+"_"+z[C]+"_tabs"]&&w2ui[this.name+"_"+z[C]+"_tabs"].resize(),$+=w2utils.getSize(O(S+"tabs").css({top:$+"px",display:"block"}),"height")),T.show.toolbar&&(null!=T.toolbar&&w2ui[this.name+"_"+z[C]+"_toolbar"]&&w2ui[this.name+"_"+z[C]+"_toolbar"].resize(),$+=w2utils.getSize(O(S+"toolbar").css({top:$+"px",display:"block"}),"height"))),O(S+"content").css({display:"block"}).css({top:$+"px"})}return clearTimeout(this._resize_timer),this._resize_timer=setTimeout(function(){for(var e in w2ui){var t;"function"==typeof w2ui[e].resize&&(null==w2ui[e].panels&&w2ui[e].resize(),0<(t=O(w2ui[e].box).parents(".w2ui-layout")).length&&t.attr("name")==c.name&&w2ui[e].resize())}},100),this.trigger(O.extend(i,{phase:"after"})),(new Date).getTime()-e}},destroy:function(){var e=this.trigger({phase:"before",type:"destroy",target:this.name});if(!0!==e.isCancelled)return null!=w2ui[this.name]&&(0"+e+""),"object"==typeof t&&(i.buttons+='"),"string"==typeof t&&(i.buttons+=t)})),0===c("#w2ui-popup").length){if(!0===(n=this.trigger({phase:"before",type:"open",target:"popup",options:i,present:!1})).isCancelled)return;w2popup.status="opening",w2popup.lockScreen(i);var a="";i.showClose&&(a+='
Close
'),i.showMax&&(a+='
Max
');o='
';c("body").append(o);l=c("#w2ui-popup");0'+a+'
';c("#w2ui-popup").html(o),i.title&&c("#w2ui-popup .w2ui-popup-title").append(i.title),i.buttons&&c("#w2ui-popup .w2ui-popup-buttons").append(i.buttons),i.body&&c("#w2ui-popup .w2ui-popup-body").append(i.body),setTimeout(function(){c("#w2ui-popup").css(w2utils.cssPrefix({transition:i.speed+"s opacity, "+i.speed+"s -webkit-transform"})).removeClass("w2ui-popup-opening"),t.focus()},1),setTimeout(function(){c("#w2ui-popup").css(w2utils.cssPrefix("transform",""))},1e3*i.speed),w2popup.status="open",t.trigger(c.extend(n,{phase:"after"}))}else if(!0===i.multiple)w2popup.message(e);else{if(null==w2popup._prev&&null!=w2popup._template&&t.restoreTemplate(),!0===(n=this.trigger({phase:"before",type:"open",target:"popup",options:i,present:!0})).isCancelled)return;w2popup.status="opening",null!=s&&(s.maximized||s.width==i.width&&s.height==i.height||w2popup.resize(i.width,i.height),i.prevSize=i.width+"px:"+i.height+"px",i.maximized=s.maximized);s=c("#w2ui-popup .w2ui-box").clone();s.removeClass("w2ui-box").addClass("w2ui-box-temp").find(".w2ui-popup-body").empty().append(i.body),"string"==typeof i.body&&0Close':"")+(i.showMax?'
Max
':"")).append(i.title),c("#w2ui-popup .w2ui-popup-body").removeClass("w2ui-popup-no-title"),c("#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp").css("top","")):(c("#w2ui-popup .w2ui-popup-title").hide().html(""),c("#w2ui-popup .w2ui-popup-body").addClass("w2ui-popup-no-title"),c("#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp").css("top","0px"));var r=c("#w2ui-popup .w2ui-box")[0],d=c("#w2ui-popup .w2ui-box-temp")[0];w2utils.transition(r,d,i.transition,function(){t.restoreTemplate(),c(r).remove(),c(d).removeClass("w2ui-box-temp").addClass("w2ui-box");var e=c(d).find(".w2ui-popup-body");1==e.length&&(e[0].style.cssText=i.style),c("#w2ui-popup").data("prev-size",null),t.focus()}),w2popup.status="open",t.trigger(c.extend(n,{phase:"after"}))}i._last_focus=c(":focus"),i.keyboard&&c(document).on("keydown",this.keydown);var u={resizing:!1,mvMove:function(e){if(1!=u.resizing)return;e=e||window.event;u.div_x=e.screenX-u.x,u.div_y=e.screenY-u.y;e=w2popup.trigger({phase:"before",type:"move",target:"popup",div_x:u.div_x,div_y:u.div_y});if(!0===e.isCancelled)return;c("#w2ui-popup").css(w2utils.cssPrefix({transition:"none",transform:"translate3d("+u.div_x+"px, "+u.div_y+"px, 0px)"})),w2popup.trigger(c.extend(e,{phase:"after"}))},mvStop:function(e){if(1!=u.resizing)return;e=e||window.event;w2popup.status="open",u.div_x=e.screenX-u.x,u.div_y=e.screenY-u.y,c("#w2ui-popup").css({left:u.pos_x+u.div_x+"px",top:u.pos_y+u.div_y+"px"}).css(w2utils.cssPrefix({transition:"none",transform:"translate3d(0px, 0px, 0px)"})),u.resizing=!1,c(document).off("mousemove",u.mvMove),c(document).off("mouseup",u.mvStop),u.isLocked||w2popup.unlock()}};return c("#w2ui-popup .w2ui-popup-title").on("mousedown",function(e){w2popup.get().maximized||function(e){e=e||window.event;w2popup.status="moving",u.resizing=!0,u.isLocked=1==c("#w2ui-popup > .w2ui-lock").length,u.x=e.screenX,u.y=e.screenY,u.pos_x=c("#w2ui-popup").position().left,u.pos_y=c("#w2ui-popup").position().top,u.isLocked||w2popup.lock({opacity:0});c(document).on("mousemove",u.mvMove),c(document).on("mouseup",u.mvStop),e.stopPropagation?e.stopPropagation():e.cancelBubble=!0;{if(!e.preventDefault)return;e.preventDefault()}}(e)}),this}setTimeout(function(){t.open.call(t,i)},100)},action:function(e,t){var i=this,s=c("#w2ui-popup").data("options");null!=t&&(i={parent:this,options:s=c("#w2ui-message"+t).data("options"),close:function(){w2popup.message({msgId:t})}});var n=s.actions[e],s=n;c.isPlainObject(n)&&n.onClick&&(s=n.onClick);n=this.trigger({phase:"before",target:e,msgId:t,type:"action",action:n,originalEvent:event});!0!==n.isCancelled&&("function"==typeof s&&s.call(i,event),this.trigger(c.extend(n,{phase:"after"})))},keydown:function(e){var t=c("#w2ui-popup").data("options");t&&!t.keyboard||!0!==(t=w2popup.trigger({phase:"before",type:"keydown",target:"popup",options:t,originalEvent:e})).isCancelled&&(27===e.keyCode&&(e.preventDefault(),0
':i.prefix="",o.addPrefix(),t=o.helpers.focus.find("input"),""===b(t).val()?(b(t).css("text-indent","-9999em").prev().css("opacity",0),b(o.el).val(a&&null!=a.text?w2utils.lang(a.text):"")):(b(t).css("text-indent",0).prev().css("opacity",1),b(o.el).val(""),setTimeout(function(){o.helpers.prefix&&o.helpers.prefix.hide();var e="position: absolute; opacity: 0; margin: 4px 0px 0px 2px; background-position: left !important;";i.icon?(b(t).css("margin-left","17px"),b(o.helpers.focus).find(".icon-search").attr("style",e+"width: 11px !important; opacity: 1; display: block")):(b(t).css("margin-left","0px"),b(o.helpers.focus).find(".icon-search").attr("style",e+"width: 0px !important; opacity: 0; display: none"))},1)),b(o.el).prop("readonly")||b(o.el).prop("disabled")?setTimeout(function(){b(o.helpers.prefix).css("opacity","0.6"),b(o.helpers.suffix).css("opacity","0.6")},1):setTimeout(function(){b(o.helpers.prefix).css("opacity","1"),b(o.helpers.suffix).css("opacity","1")},1))},1)),-1!==["enum","file"].indexOf(this.type)){var t="";if(a)for(var s=0;s  '):'
  
'+("enum"===o.type?n.text:n.name+' - '+w2utils.formatSize(n.size)+"");t+='
  • '+l+"
  • "}var r=o.helpers.multi,d=r.find("ul");r.attr("style",r.attr("style")+";"+i.style),b(o.el).css("z-index","-1"),b(o.el).prop("readonly")||b(o.el).prop("disabled")?setTimeout(function(){r[0].scrollTop=0,r.addClass("w2ui-readonly").find("li").css("opacity","0.9").parent().find("li.nomouse").hide().find("input").prop("readonly",!0).parents("ul").find(".w2ui-list-remove").hide()},1):setTimeout(function(){r.removeClass("w2ui-readonly").find("li").css("opacity","1").parent().find("li.nomouse").show().find("input").prop("readonly",!1).parents("ul").find(".w2ui-list-remove").show()},1),r.find(".w2ui-enum-placeholder").remove(),d.find("li").not("li.nomouse").remove(),""!==t?d.prepend(t):null!=b(o.el).attr("placeholder")&&""===r.find("input").val()&&(u="padding-top: "+b(this.el).css("padding-top")+";padding-left: "+b(this.el).css("padding-left")+"; box-sizing: "+b(this.el).css("box-sizing")+"; line-height: "+b(this.el).css("line-height")+"; font-size: "+b(this.el).css("font-size")+"; font-family: "+b(this.el).css("font-family")+"; ",r.prepend('
    '+b(o.el).attr("placeholder")+"
    ")),r.off("scroll.w2field").on("scroll.w2field",function(e){e=o.trigger({phase:"before",type:"scroll",target:o.el,originalEvent:e});!0!==e.isCancelled&&o.trigger(b.extend(e,{phase:"after"}))}).find("li").data("mouse","out").on("click",function(e){var t,i,s,n="LI"===e.target.tagName.toUpperCase()?e.target:b(e.target).parents("LI"),l=a[b(n).attr("index")];b(n).hasClass("nomouse")||(e.stopPropagation(),b(e.target).hasClass("w2ui-list-remove")?b(o.el).prop("readonly")||b(o.el).prop("disabled")||!0!==(t=o.trigger({phase:"before",type:"remove",target:o.el,originalEvent:e.originalEvent,item:l})).isCancelled&&(b().w2overlay(),a.splice(b(e.target).attr("index"),1),b(o.el).trigger("input").trigger("change"),b(e.target).parent().fadeOut("fast"),setTimeout(function(){o.refresh(),o.trigger(b.extend(t,{phase:"after"}))},300)):!0!==(t=o.trigger({phase:"before",type:"click",target:o.el,originalEvent:e.originalEvent,item:l})).isCancelled&&("file"===o.type&&(i="",/image/i.test(l.type)&&(i='
    '),s='style="padding: 3px"',i+='
    "+w2utils.lang("Name")+":"+l.name+"
    "+w2utils.lang("Size")+":"+w2utils.formatSize(l.size)+"
    "+w2utils.lang("Type")+": '+l.type+"
    "+w2utils.lang("Modified")+":"+w2utils.date(l.modified)+"
    ",b("#w2ui-overlay").remove(),b(n).w2overlay(i)),o.trigger(b.extend(t,{phase:"after"}))))}).on("mouseover",function(e){var t="LI"===e.target.tagName.toUpperCase()?e.target:b(e.target).parents("LI");if(!b(t).hasClass("nomouse")){if("out"===b(t).data("mouse")){var i=a[b(e.target).attr("index")],i=o.trigger({phase:"before",type:"mouseOver",target:o.el,originalEvent:e.originalEvent,item:i});if(!0===i.isCancelled)return;o.trigger(b.extend(i,{phase:"after"}))}b(t).data("mouse","over")}}).on("mouseout",function(t){var i="LI"===t.target.tagName.toUpperCase()?t.target:b(t.target).parents("LI");b(i).hasClass("nomouse")||(b(i).data("mouse","leaving"),setTimeout(function(){var e;"leaving"===b(i).data("mouse")&&(b(i).data("mouse","out"),e=a[b(t.target).attr("index")],!0!==(e=o.trigger({phase:"before",type:"mouseOut",target:o.el,originalEvent:t.originalEvent,item:e})).isCancelled&&o.trigger(b.extend(e,{phase:"after"})))},0))}),b(this.el).height("auto");d=b(r).find("> div.w2ui-multi-items").height()+2*w2utils.getSize(r,"+height");(d=d<26?26:d)>i.maxHeight&&(d=i.maxHeight),0',s.addSuffix()),l&&s.addPrefix(),s.tmp.current_width=n)},clean:function(e){if("number"==typeof e)return e;var t=this.options;return e=String(e).trim(),-1!==["int","float","money","currency","percent"].indexOf(this.type)&&("string"==typeof e&&(t.autoFormat&&-1!==["money","currency"].indexOf(this.type)&&(e=String(e).replace(t.moneyRE,"")),t.autoFormat&&"percent"===this.type&&(e=String(e).replace(t.percentRE,"")),e=(e=t.autoFormat&&-1!==["int","float"].indexOf(this.type)?String(e).replace(t.numberRE,""):e).replace(/\s+/g,"").replace(w2utils.settings.groupSymbol,"").replace(w2utils.settings.decimalSymbol,".")),parseFloat(e)==e&&(null!=t.min&&et.max&&(e=t.max,b(this.el).val(t.max))),e=""!==e&&w2utils.isFloat(e)?Number(e):""),e},format:function(e){var t=this.options;if(t.autoFormat&&""!==e)switch(this.type){case"money":case"currency":""!==(e=w2utils.formatNumber(e,t.currencyPrecision,t.groupSymbol))&&(e=t.currencyPrefix+e+t.currencySuffix);break;case"percent":""!==(e=w2utils.formatNumber(e,t.precision,t.groupSymbol))&&(e+="%");break;case"float":e=w2utils.formatNumber(e,t.precision,t.groupSymbol);break;case"int":e=w2utils.formatNumber(e,0,t.groupSymbol)}return e},change:function(e){var t,i=this,s=i.options;if(-1!==["int","float","money","currency","percent"].indexOf(this.type)){var n=b(this.el).val(),l=this.format(this.clean(b(this.el).val()));if(""!==n&&n!=l)return b(this.el).val(l).trigger("input").change(),e.stopPropagation(),e.preventDefault(),!1}"color"===this.type&&("rgb"!==(t=b(this.el).val()).substr(0,3).toLowerCase()&&(t="#"+t,8!==(e=b(this.el).val().length)&&6!==e&&3!==e&&(t="")),b(this.el).next().find("div").css("background-color",t),b(this.el).hasClass("has-focus")&&!0!==b(this.el).data("skipInit")&&this.updateOverlay()),-1!==["list","enum","file"].indexOf(this.type)&&(i.refresh(),setTimeout(function(){i.refresh()},5)),-1!==["date","time","datetime"].indexOf(this.type)&&(t=parseInt(i.el.value),w2utils.isInt(i.el.value)&&3e3=s.min||null==s.min?Number((o-a).toFixed(12)):s.min;b(i.el).val(r).trigger("input").change(),l=!0}l&&(e.preventDefault(),setTimeout(function(){i.el.setSelectionRange(i.el.value.length,i.el.value.length)},0))}if("date"===i.type){if(!s.keyboard||b(i.el).prop("readonly")||b(i.el).prop("disabled"))return;var d=864e5,a=1;switch((e.ctrlKey||e.metaKey)&&(a=10),(h=w2utils.isDate(b(i.el).val(),s.format,!0))||(h=new Date,d=0),n){case 38:if(e.shiftKey)break;var u=w2utils.formatDate(h.getTime()+d,s.format);10==a&&(u=w2utils.formatDate(new Date(h.getFullYear(),h.getMonth()+1,h.getDate()),s.format)),b(i.el).val(u).trigger("input").change(),l=!0;break;case 40:if(e.shiftKey)break;u=w2utils.formatDate(h.getTime()-d,s.format);10==a&&(u=w2utils.formatDate(new Date(h.getFullYear(),h.getMonth()-1,h.getDate()),s.format)),b(i.el).val(u).trigger("input").change(),l=!0}l&&(e.preventDefault(),setTimeout(function(){i.el.setSelectionRange(i.el.value.length,i.el.value.length),i.updateOverlay()},0))}if("time"===i.type){if(!s.keyboard||b(i.el).prop("readonly")||b(i.el).prop("disabled"))return;var a=e.ctrlKey||e.metaKey?60:1,o=b(i.el).val(),c=i.toMin(o)||i.toMin((new Date).getHours()+":"+((new Date).getMinutes()-1));switch(n){case 38:if(e.shiftKey)break;c+=a,l=!0;break;case 40:if(e.shiftKey)break;c-=a,l=!0}l&&(b(i.el).val(i.fromMin(c)).trigger("input").change(),e.preventDefault(),setTimeout(function(){i.el.setSelectionRange(i.el.value.length,i.el.value.length)},0))}if("datetime"===i.type){if(!s.keyboard||b(i.el).prop("readonly")||b(i.el).prop("disabled"))return;d=864e5,a=1;(e.ctrlKey||e.metaKey)&&(a=10);var h,p=b(i.el).val();switch((h=w2utils.isDateTime(p,this.options.format,!0))||(h=new Date,d=0),n){case 38:if(e.shiftKey)break;u=w2utils.formatDateTime(h.getTime()+d,s.format);10==a&&(u=w2utils.formatDateTime(new Date(h.getFullYear(),h.getMonth()+1,h.getDate()),s.format)),b(i.el).val(u).trigger("input").change(),l=!0;break;case 40:if(e.shiftKey)break;u=w2utils.formatDateTime(h.getTime()-d,s.format);10==a&&(u=w2utils.formatDateTime(new Date(h.getFullYear(),h.getMonth()-1,h.getDate()),s.format)),b(i.el).val(u).trigger("input").change(),l=!0}l&&(e.preventDefault(),setTimeout(function(){i.el.setSelectionRange(i.el.value.length,i.el.value.length),i.updateOverlay()},0))}if("color"===i.type){if(b(i.el).prop("readonly")||b(i.el).prop("disabled"))return;if((e.ctrlKey||e.metaKey)&&!e.shiftKey){var f=null;switch(n){case 38:f="up";break;case 40:f="down";break;case 39:f="right";break;case 37:f="left"}i.el.nav&&null!=f&&(p=i.el.nav(f),b(i.el).val(p).trigger("input").change(),e.preventDefault())}}if(-1!==["list","combo","enum"].indexOf(i.type)&&!b(i.el).prop("readonly")&&!b(i.el).prop("disabled")){var g=b(i.el).data("selected"),m=b(i.el),w=!1;switch(-1!==["list","enum"].indexOf(i.type)&&("list"===i.type&&(m=b(i.helpers.focus).find("input")),"enum"===i.type&&(m=b(i.helpers.multi).find("input")),-1==[37,38,39,40].indexOf(n)&&setTimeout(function(){i.refresh()},1),86==e.keyCode&&(e.ctrlKey||e.metaKey)&&setTimeout(function(){i.refresh(),i.search(),i.request()},50)),n){case 27:"list"===i.type&&(""!==m.val()&&m.val(""),e.stopPropagation());break;case 37:case 39:break;case 13:if(0===b("#w2ui-overlay").length)break;var v=s.items[s.index];if("enum"===i.type)if(null!=v){if(!0===(y=i.trigger({phase:"before",type:"add",target:i.el,originalEvent:e.originalEvent,item:v})).isCancelled)return;v=y.item,g.length>=s.max&&0=s.max&&0=s.items.length&&(s.index=s.items.length-1),i.updateOverlay(w),e.preventDefault(),void setTimeout(function(){var e;"enum"===i.type||"list"===i.type?(e=m.get(0)).setSelectionRange(e.value.length,e.value.length):i.el.setSelectionRange(i.el.value.length,i.el.value.length)},0);"enum"===i.type&&m.width(8*(m.val().length+2)+"px")}},keyUp:function(e){var t,i;-1!==["list","combo","enum"].indexOf(this.type)&&(b(this.el).prop("readonly")||b(this.el).prop("disabled")||-1==[16,17,18,20,37,39,91].indexOf(e.keyCode)&&(0===(t=b(this.helpers.focus).find("input")).length&&(t=b(this.el)),!0!==(i=this.trigger({phase:"before",type:"search",originalEvent:e,target:t,search:t.val()})).isCancelled&&(this.tmp.force_hide||this.request(),1==t.val().length&&this.refresh(),0!==b("#w2ui-overlay").length&&-1!=[38,40].indexOf(e.keyCode)||this.search(),this.trigger(b.extend(i,{phase:"after"})))))},clearCache:function(){this.options.items=[],this.tmp.xhr_loading=!1,this.tmp.xhr_search="",this.tmp.xhr_total=-1},request:function(e){var t,o=this,a=this.options,r=b(o.el).val()||"";if(a.url){if("enum"===o.type&&(r=0===(t=b(o.helpers.multi).find("input")).length?"":t.val()),"list"===o.type&&(t=b(o.helpers.focus).find("input"),r=0===t.length?"":t.val()),0!==a.minLength&&r.lengtho.tmp.xhr_search.length||r.length>=o.tmp.xhr_search.length&&r.substr(0,o.tmp.xhr_search.length)!==o.tmp.xhr_search||r.lengtha.cacheMax&&e.records.splice(a.cacheMax,1e5),null==a.recId&&null!=a.recid&&(a.recId=a.recid),(a.recId||a.recText)&&e.records.forEach(function(e){"string"==typeof a.recId&&(e.id=e[a.recId]),"function"==typeof a.recId&&(e.id=a.recId(e)),"string"==typeof a.recText&&(e.text=e[a.recText]),"function"==typeof a.recText&&(e.text=a.recText(e))}),o.tmp.xhr_loading=!1,o.tmp.xhr_search=r,o.tmp.xhr_total=e.records.length,o.tmp.lastError="",a.items=o.normMenu(e.records),""===r&&0===e.records.length?o.tmp.emptySet=!0:o.tmp.emptySet=!1,(s=b(o.el).data("find_selected"))&&(Array.isArray(s)?(n=[],s.forEach(function(t){var i=!1;a.items.forEach(function(e){(e.id==t||t&&t.id==e.id)&&(n.push(b.extend(!0,{},e)),i=!0)}),i||n.push(t)})):(n=s,a.items.forEach(function(e){(e.id==s||s&&s.id==e.id)&&(n=e)})),b(o.el).data("selected",n).removeData("find_selected").trigger("input").change()),o.search(),o.trigger(b.extend(i,{phase:"after"}))):console.log("ERROR: server did not return proper structure. It should return",{status:"success",records:[{id:1,text:"item"}]}))}).fail(function(e,t,i){var s,i={status:t,error:i,rawResponseText:e.responseText},i=o.trigger({phase:"before",type:"error",target:o.el,search:r,error:i,xhr:e});if(!0!==i.isCancelled){if("abort"!==t){try{s=b.parseJSON(e.responseText)}catch(e){}console.log("ERROR: Server communication failed.","\n EXPECTED:",{status:"success",records:[{id:1,text:"item"}]},"\n OR:",{status:"error",message:"error message"},"\n RECEIVED:","object"==typeof s?s:e.responseText)}o.tmp.xhr_loading=!1,o.tmp.xhr_search=r,o.tmp.xhr_total=0,o.tmp.emptySet=!0,o.tmp.lastError=i.error||"Server communication failed",a.items=[],o.clearCache(),o.search(),o.updateOverlay(!1),o.trigger(b.extend(i,{phase:"after"}))}}),o.trigger(b.extend(t,{phase:"after"})))},e)}}},search:function(){var e=this,t=this.options,i=b(e.el).val(),s=e.el,n=[],l=b(e.el).data("selected");if("enum"===e.type)for(var o in s=b(e.helpers.multi).find("input"),i=s.val(),l)l[o]&&n.push(l[o].id);else if("list"===e.type)for(var o in s=b(e.helpers.focus).find("input"),i=s.val(),l)l[o]&&n.push(l[o].id);var a=t.items;if(!0!==e.tmp.xhr_loading){for(var r=0,d=0;d',{css:{"background-color":"#f5f5f5"},onShow:function(e){w2utils.isIE&&b(".w2ui-calendar").on("mousedown",function(e){e=b(e.target);1===e.length&&"w2ui-jump-year"===e[0].id&&b("#w2ui-overlay").data("keepOpen",!0)})}}),(t=w2utils.isDate(b(n.el).val(),n.options.format,!0))&&(r=t.getMonth()+1,d=t.getFullYear()),function i(e,t){b("#w2ui-overlay > div > div").html(n.getMonthHTML(e,t,b(n.el).val())),b("#w2ui-overlay .w2ui-calendar-title").on("mousedown",function(){var e,t;b(this).next().hasClass("w2ui-calendar-jump")?b(this).next().remove():(b(this).after('
    '),b(this).next().hide().html(n.getYearHTML()).fadeIn(200),setTimeout(function(){b("#w2ui-overlay .w2ui-calendar-jump").find(".w2ui-jump-month, .w2ui-jump-year").on("click",function(){b(this).hasClass("w2ui-jump-month")&&(b(this).parent().find(".w2ui-jump-month").removeClass("selected"),b(this).addClass("selected"),t=b(this).attr("name")),b(this).hasClass("w2ui-jump-year")&&(b(this).parent().find(".w2ui-jump-year").removeClass("selected"),b(this).addClass("selected"),e=b(this).attr("name")),null!=e&&null!=t&&(b("#w2ui-overlay .w2ui-calendar-jump").fadeOut(100),setTimeout(function(){i(parseInt(t)+1,e)},100))}),b("#w2ui-overlay .w2ui-calendar-jump >:last-child").prop("scrollTop",2e3)},1))}),b("#w2ui-overlay .w2ui-date").on("mousedown",function(){var e=b(this).attr("date");b(n.el).val(e).trigger("input").change(),b(this).css({"background-color":"#B6D5FB","border-color":"#aaa"})}).on("mouseup",function(){setTimeout(function(){0',{css:{"background-color":"#fff"}});var i="h24"===this.options.format;b("#w2ui-overlay > div").html(n.getHourHTML()),b("#w2ui-overlay .w2ui-time").on("mousedown",function(e){b(this).css({"background-color":"#B6D5FB","border-color":"#aaa"});var t=b(this).attr("hour");b(n.el).val((12',{css:{"background-color":"#fff"}}),b("#w2ui-overlay > div").html(n.getMinHTML(t)),b("#w2ui-overlay .w2ui-time").on("mousedown",function(){b(this).css({"background-color":"#B6D5FB","border-color":"#aaa"});var e=b(this).attr("min");b(n.el).val((12',{css:{"background-color":"#f5f5f5"},onShow:function(e){w2utils.isIE&&b(".w2ui-calendar").on("mousedown",function(e){e=b(e.target);1===e.length&&"w2ui-jump-year"===e[0].id&&b("#w2ui-overlay").data("keepOpen",!0)})}}),(t=w2utils.isDateTime(b(n.el).val(),n.options.format,!0))&&(r=t.getMonth()+1,d=t.getFullYear());var s=null;!function i(e,t){b("#w2ui-overlay > div > div").html(n.getMonthHTML(e,t,b(n.el).val())+(l.btn_now?'
    '+w2utils.lang("Current Date & Time")+"
    ":"")),b("#w2ui-overlay .w2ui-calendar-title").on("mousedown",function(){var e,t;b(this).next().hasClass("w2ui-calendar-jump")?b(this).next().remove():(b(this).after('
    '),b(this).next().hide().html(n.getYearHTML()).fadeIn(200),setTimeout(function(){b("#w2ui-overlay .w2ui-calendar-jump").find(".w2ui-jump-month, .w2ui-jump-year").on("click",function(){b(this).hasClass("w2ui-jump-month")&&(b(this).parent().find(".w2ui-jump-month").removeClass("selected"),b(this).addClass("selected"),t=b(this).attr("name")),b(this).hasClass("w2ui-jump-year")&&(b(this).parent().find(".w2ui-jump-year").removeClass("selected"),b(this).addClass("selected"),e=b(this).attr("name")),null!=e&&null!=t&&(b("#w2ui-overlay .w2ui-calendar-jump").fadeOut(100),setTimeout(function(){i(parseInt(t)+1,e)},100))}),b("#w2ui-overlay .w2ui-calendar-jump >:last-child").prop("scrollTop",2e3)},1))}),b("#w2ui-overlay .w2ui-date").on("mousedown",function(){var e=b(this).attr("date");b(n.el).val(e).trigger("input").change(),b(this).css({"background-color":"#B6D5FB","border-color":"#aaa"}),s=new Date(b(this).attr("data-date"))}).on("mouseup",function(){var i,t;0',{css:{"background-color":"#fff"}});n.options.format;b("#w2ui-overlay > div").html(n.getHourHTML()),b("#w2ui-overlay .w2ui-time").on("mousedown",function(e){b(this).css({"background-color":"#B6D5FB","border-color":"#aaa"}),i=b(this).attr("hour"),s.setHours(i);var t=w2utils.formatDateTime(s,n.options.format);b(n.el).val(t).trigger("input").change()}),null==n.options.noMinutes||!1===n.options.noMinutes?b("#w2ui-overlay .w2ui-time").on("mouseup",function(){var e=b(this).attr("hour");0',{css:{"background-color":"#fff"}}),b("#w2ui-overlay > div").html(n.getMinHTML(e)),b("#w2ui-overlay .w2ui-time").on("mousedown",function(){b(this).css({"background-color":"#B6D5FB","border-color":"#aaa"}),t=b(this).attr("min"),s.setHours(i,t);var e=w2utils.formatDateTime(s,n.options.format);b(n.el).val(e).trigger("input").change()}).on("mouseup",function(){setTimeout(function(){0'+d+"",u=b.extend(!0,{},l,{search:!1,render:l.renderDrop,maxHeight:l.maxDropHeight,maxWidth:l.maxDropWidth,msgNoItems:d,onSelect:function(i){var e,t,s;"enum"===n.type?(e=b(n.el).data("selected"),!i.item||!0!==(t=n.trigger({phase:"before",type:"add",target:n.el,originalEvent:i.originalEvent,item:i.item})).isCancelled&&(e.length>=l.max&&0'+i.options.prefix+""),(e=b(i.el).prev()).css({color:b(i.el).css("color"),"font-family":b(i.el).css("font-family"),"font-size":b(i.el).css("font-size"),"padding-top":b(i.el).css("padding-top"),"padding-bottom":b(i.el).css("padding-bottom"),"padding-left":b(i.el).css("padding-left"),"padding-right":0,"margin-top":parseInt(b(i.el).css("margin-top"),10)+2+"px","margin-bottom":parseInt(b(i.el).css("margin-bottom"),10)+1+"px","margin-left":b(i.el).css("margin-left"),"margin-right":0}).on("click",function(e){var t;i.options.icon&&"function"==typeof i.onIconClick?!0!==(t=i.trigger({phase:"before",type:"iconClick",target:i.el,el:b(this).find("span.w2ui-icon")[0]})).isCancelled&&i.trigger(b.extend(t,{phase:"after"})):("list"===i.type?b(i.helpers.focus).find("input"):b(i.el)).focus()}),b(i.el).css("padding-left",e.width()+parseInt(b(i.el).css("padding-left"),10)+"px"),i.helpers.prefix=e))},1)},addSuffix:function(){var t,i,n=this;setTimeout(function(){var e;"clear"!==n.type&&((e=b(n.el).data("tmp")||{})["old-padding-right"]&&b(n.el).css("padding-right",e["old-padding-right"]),e["old-padding-right"]=b(n.el).css("padding-right"),b(n.el).data("tmp",e),i=parseInt(b(n.el).css("padding-right"),10),n.options.arrows&&(n.helpers.arrows&&b(n.helpers.arrows).remove(),b(n.el).after('
     
    '),(t=b(n.el).next()).css({color:b(n.el).css("color"),"font-family":b(n.el).css("font-family"),"font-size":b(n.el).css("font-size"),height:b(n.el).height()+parseInt(b(n.el).css("padding-top"),10)+parseInt(b(n.el).css("padding-bottom"),10)+"px",padding:0,"margin-top":parseInt(b(n.el).css("margin-top"),10)+1+"px","margin-bottom":0,"border-left":"1px solid silver"}).css("margin-left","-"+(t.width()+parseInt(b(n.el).css("margin-right"),10)+12)+"px").on("mousedown",function(t){var i=b("body");function s(e){b(n.el).focus(),n.keyDown(b.Event("keydown"),{keyCode:"up"===b(t.target).attr("type")?38:40}),!1!==e&&b("body").data("_field_update_timer",setTimeout(s,60))}i.on("mouseup",function e(){clearTimeout(i.data("_field_update_timer"));i.off("mouseup",e)}),i.data("_field_update_timer",setTimeout(s,700)),s(!1)}),i+=t.width()+12,b(n.el).css("padding-right",i+"px"),n.helpers.arrows=t),""!==n.options.suffix&&(n.helpers.suffix&&b(n.helpers.suffix).remove(),b(n.el).after('
    '+n.options.suffix+"
    "),(t=b(n.el).next()).css({color:b(n.el).css("color"),"font-family":b(n.el).css("font-family"),"font-size":b(n.el).css("font-size"),"padding-top":b(n.el).css("padding-top"),"padding-bottom":b(n.el).css("padding-bottom"),"padding-left":"3px","padding-right":b(n.el).css("padding-right"),"margin-top":parseInt(b(n.el).css("margin-top"),10)+2+"px","margin-bottom":parseInt(b(n.el).css("margin-bottom"),10)+1+"px"}).on("click",function(e){("list"===n.type?b(n.helpers.focus).find("input"):b(n.el)).focus()}),t.css("margin-left","-"+(w2utils.getSize(t,"width")+parseInt(b(n.el).css("margin-right"),10)+2)+"px"),i+=t.width()+3,b(n.el).css("padding-right",i+"px"),n.helpers.suffix=t))},1)},addFocus:function(){var i,s=this;this.options;b(s.helpers.focus).remove();var e=parseInt(b(s.el).attr("tabIndex"));isNaN(e)||-1===e||(s.el._tabIndex=e),null==(e=s.el._tabIndex?s.el._tabIndex:e)&&(e=-1),isNaN(e)&&(e=0);var t="",e='
    ';b(s.el).attr("tabindex",-1).before(e);e=b(s.el).prev();(s.helpers.focus=e).css({width:b(s.el).width(),"margin-top":b(s.el).css("margin-top"),"margin-left":parseInt(b(s.el).css("margin-left"))+parseInt(b(s.el).css("padding-left"))+"px","margin-bottom":b(s.el).css("margin-bottom"),"margin-right":b(s.el).css("margin-right")}).find("input").css({cursor:"default",width:"100%",outline:"none",opacity:1,margin:0,border:"1px solid transparent",padding:b(s.el).css("padding-top"),"padding-left":0,"margin-left":0,"background-color":"transparent"}),e.find("input").on("click",function(e){0===b("#w2ui-overlay").length&&s.focus(e),e.stopPropagation()}).on("focus",function(e){i=b(s.el).attr("placeholder"),b(s.el).css({outline:"auto 5px #7DB4F3","outline-offset":"2px"}),b(this).val(""),b(s.el).triggerHandler("focus"),e.stopPropagation?e.stopPropagation():e.cancelBubble=!0}).on("blur",function(e){b(s.el).css("outline","none"),b(this).val(""),s.refresh(),b(s.el).triggerHandler("blur"),e.stopPropagation?e.stopPropagation():e.cancelBubble=!0,null!=i&&b(s.el).attr("placeholder",i)}).on("keydown",function(e){var t=this;s.keyDown(e),setTimeout(function(){""===t.value?b(s.el).attr("placeholder",i):b(s.el).attr("placeholder","")},10)}).on("keyup",function(e){s.keyUp(e)}).on("keypress",function(e){s.keyPress(e)}),e.on("click",function(e){b(this).find("input").focus()}),s.refresh()},addMulti:function(){var n=this;this.options;b(n.helpers.multi).remove();var e,t="",i="margin-top : 0px; margin-bottom : 0px; margin-left : "+b(n.el).css("margin-left")+"; margin-right : "+b(n.el).css("margin-right")+"; width : "+(w2utils.getSize(n.el,"width")-parseInt(b(n.el).css("margin-left"),10)-parseInt(b(n.el).css("margin-right"),10))+"px;",s="";null!=b(n.el).attr("id")&&(s='id="'+b(n.el).attr("id")+'_search" '),"enum"===n.type&&((e=b(n.el).attr("tabIndex"))&&-1!==e&&(n.el._tabIndex=e),null==(e=n.el._tabIndex?n.el._tabIndex:e)&&(e=0),t='
    '),"file"===n.type&&(t='
    ');s=b(n.el).data("tmp")||{};s["old-background-color"]=b(n.el).css("background-color"),s["old-border-color"]=b(n.el).css("border-color"),b(n.el).data("tmp",s),b(n.el).before(t).css({"background-color":"transparent","border-color":"transparent"});var l=b(n.el).prev();n.helpers.multi=l,"enum"===n.type&&(b(n.el).attr("tabindex",-1),l.find("input").on("click",function(e){0===b("#w2ui-overlay").length&&n.focus(e),b(n.el).triggerHandler("click")}).on("focus",function(e){b(l).css({outline:"auto 5px #7DB4F3","outline-offset":"2px"}),b(n.el).triggerHandler("focus"),e.stopPropagation?e.stopPropagation():e.cancelBubble=!0}).on("blur",function(e){b(l).css("outline","none"),b(n.el).triggerHandler("blur"),e.stopPropagation?e.stopPropagation():e.cancelBubble=!0}).on("keyup",function(e){n.keyUp(e)}).on("keydown",function(e){n.keyDown(e)}).on("keypress",function(e){n.keyPress(e)}),l.on("click",function(e){b(this).find("input").focus()})),"file"===n.type&&(b(n.el).css("outline","none"),l.find("input").off(".drag").on("click.drag",function(e){e.stopPropagation(),b(n.el).prop("readonly")||b(n.el).prop("disabled")||b(n.el).focus()}).on("dragenter.drag",function(e){b(n.el).prop("readonly")||b(n.el).prop("disabled")||b(l).addClass("w2ui-file-dragover")}).on("dragleave.drag",function(e){b(n.el).prop("readonly")||b(n.el).prop("disabled")||b(l).removeClass("w2ui-file-dragover")}).on("drop.drag",function(e){if(!b(n.el).prop("readonly")&&!b(n.el).prop("disabled")){b(l).removeClass("w2ui-file-dragover");for(var t=e.originalEvent.dataTransfer.files,i=0,s=t.length;is.maxFileSize?(t="Maximum file size is "+w2utils.formatSize(s.maxFileSize),!1===s.silent&&b(i.el).w2tag(t),void console.log("ERROR: "+t)):0!==s.maxSize&&o+l.size>s.maxSize?(t=w2utils.lang("Maximum total size is")+" "+w2utils.formatSize(s.maxSize),void(!1===s.silent?b(i.el).w2tag(t):console.log("ERROR: "+t))):0!==s.max&&a>=s.max?(t=w2utils.lang("Maximum number of files is")+" "+s.max,void(!1===s.silent?b(i.el).w2tag(t):console.log("ERROR: "+t))):(n.push(l),void("undefined"!=typeof FileReader&&!0===s.readContent?((s=new FileReader).onload=function(e){var t=e.target.result,e=t.indexOf(",");l.content=t.substr(e+1),i.refresh(),b(i.el).trigger("input").trigger("change"),i.trigger(b.extend(d,{phase:"after"}))},s.readAsDataURL(e)):(i.refresh(),b(i.el).trigger("input").trigger("change"),i.trigger(b.extend(d,{phase:"after"})))))},normMenu:function(e,t){if(b.isArray(e)){for(var i=0;i'+r[h]+"";var p='
    '+n[e-1]+", "+t+'
    '+c+"",f=1;"M"!==w2utils.settings.weekStarts&&u++,"datetime"===this.type&&(n=w2utils.isDateTime(i,d.format,!0),i=w2utils.formatDate(n,w2utils.settings.dateFormat));for(var g=1;g<43;g++){if(0===u&&1==g){for(var m=0;m<6;m++)p+='';g+=6}else if(gl[e-1]){p+='',g%7==0&&(p+="");continue}var w=t+"/"+e+"/"+f,v=new Date(w),y="";6===v.getDay()&&(y=" w2ui-saturday"),0===v.getDay()&&(y=" w2ui-sunday"),w==o&&(y+=" w2ui-today");var b,x=f,_="",k="",C="datetime"===this.type?(b=w2utils.formatDateTime(w,d.format),w2utils.formatDate(w,w2utils.settings.dateFormat)):b=w2utils.formatDate(w,d.format);d.colored&&void 0!==d.colored[C]&&(k="background-color: "+(w=d.colored[C].split(":"))[0]+";",_="color: "+w[1]+";"),p+='",(g%7==0||0===u&&1==g)&&(p+=""),f++}return p+="
      
    '+x+"
    "},getYearHTML:function(){for(var e=w2utils.settings.shortmonths,t=w2utils.settings.dateStartYear,i=w2utils.settings.dateEndYear,s="",n="",l=0;l'+e[l]+"";for(var o=t;o<=i;o++)n+='
    '+o+"
    ";return'
    '+s+'
    '+n+"
    "},getHourHTML:function(){for(var e=[],t=this.options,i=-1<(t=null==t?{format:w2utils.settings.timeFormat}:t).format.indexOf("h24"),s=0;s<24;s++){var n=12!=s||i?(12<=s&&!i?s-12:s)+":00"+(i?"":s<12?" am":" pm"):"12:00 pm";e[Math.floor(s/8)]||(e[Math.floor(s/8)]="");var l,o,a=this.fromMin(this.toMin(n)),r=this.fromMin(this.toMin(n)+59);"datetime"===this.type&&(l=w2utils.isDateTime(this.el.value,t.format,!0),o=t.format.split("|")[0].trim(),a=w2utils.formatDate(l,o)+" "+a,r=w2utils.formatDate(l,o)+" "+r),e[Math.floor(s/8)]+='
    '+n+"
    "}return'
    '+w2utils.lang("Select Hour")+'
    '+e[0]+" "+e[1]+" "+e[2]+"
    "},getMinHTML:function(e){null==e&&(e=0);for(var t=this.options,i=-1<(t=null==t?{format:w2utils.settings.timeFormat}:t).format.indexOf("h24"),s=[],n=0;n<60;n+=5){var l,o,a=(12'+a+""}return'
    '+w2utils.lang("Select Minute")+'
    '+s[0]+" "+s[1]+" "+s[2]+"
    "},toMin:function(e){if("string"!=typeof e)return null;var t=e.split(":");return 2!==t.length?null:(t[0]=parseInt(t[0]),t[1]=parseInt(t[1]),-1!==e.indexOf("pm")&&12!==t[0]&&(t[0]+=12),60*t[0]+t[1])},fromMin:function(e){1440<=e&&(e%=1440),e<0&&(e=1440+e);var t=Math.floor(e/60),i=(e%60<10?"0":"")+e%60,e=this.options;return-1!==(e=null==e?{format:w2utils.settings.timeFormat}:e).format.indexOf("h24")?t+":"+i:(t<=12?t:t-12)+":"+i+" "+(12<=t?"pm":"am")}},b.extend(n.prototype,w2utils.event),w2obj.field=n}(jQuery),function(y){function h(e){this.name=null,this.header="",this.box=null,this.url="",this.routeData={},this.formURL="",this.formHTML="",this.page=0,this.recid=0,this.fields=[],this.actions={},this.record={},this.original=null,this.postData={},this.httpHeaders={},this.method=null,this.toolbar={},this.tabs={},this.style="",this.focus=0,this.autosize=!0,this.nestedFields=!0,this.multipart=!1,this.tabindexBase=0,this.isGenerated=!1,this.last={xhr:null,errors:[]},y.extend(!0,this,w2obj.form,e)}y.fn.w2form=function(e){if(!y.isPlainObject(e))return(t=w2ui[y(this).attr("name")])?0",c),c.field=c.name),a.fields[u]=c}for(u in i)y.isPlainObject(i[u])?a.record[u]=y.extend(!0,{},i[u]):a.record[u]=i[u];for(u in s)y.isPlainObject(s[u])?a.original[u]=y.extend(!0,{},s[u]):a.original[u]=s[u];return 0'+a.formHTML+""),y(a.box).html(a.formHTML),a.isGenerated=!0,a.render(a.box)),a}},h.prototype={onRequest:null,onLoad:null,onValidate:null,onSubmit:null,onProgress:null,onSave:null,onChange:null,onInput:null,onRender:null,onRefresh:null,onResize:null,onDestroy:null,onAction:null,onToolbar:null,onError:null,msgNotJSON:"Returned data is not in valid JSON format.",msgAJAXerror:"AJAX error. See console for more details.",msgRefresh:"Loading...",msgSaving:"Saving...",get:function(e,t){if(0===arguments.length){for(var i=[],s=0;s'+e+"",buttons:'",onOpen:function(e){setTimeout(function(){y(e.box).find(".w2ui-btn").focus()},25)}}),w2utils.message.call(this,{box:this.box,path:"w2ui."+this.name,title:".w2ui-form-header:visible",body:".w2ui-form-box"},e)},validate:function(e){null==e&&(e=!0),y().w2tag();for(var t=[],i=0;i'),setTimeout(function(){var e={cmd:"save"};e.recid=u.recid,e.name=u.name,y.extend(e,u.postData),y.extend(e,r),u.multipart||u.fields.forEach(function(e){"file"===e.type&&Array.isArray(u.getValue(e.field))&&u.getValue(e.field).forEach(function(e){delete e.file})}),e.record=y.extend(!0,{},u.record);var t=u.trigger({phase:"before",type:"submit",target:u.name,url:u.url,postData:e,httpHeaders:u.httpHeaders});if(!0!==t.isCancelled){var i=t.url;if("object"==typeof t.url&&t.url.save&&(i=t.url.save),u.last.xhr)try{u.last.xhr.abort()}catch(e){}if(!y.isEmptyObject(u.routeData)){var s=w2utils.parseRoute(i);if(0",r),r.html.label=r.html.caption),null==r.html.label&&(r.html.label=r.field),r.html=y.extend(!0,{label:"",span:6,attr:"",text:"",style:"",page:0,column:0},r.html),null==e&&(e=r.html.page),null==t&&(t=r.html.column);var d,u,c='";switch(r.type){case"pass":case"password":c='";break;case"check":case"checks":null==r.options.items&&null!=r.html.items&&(r.options.items=r.html.items);var h=r.options.items,c="";0<(h=!y.isArray(h)?[]:h).length&&(h=w2obj.field.prototype.normMenu.call(this,h,r));for(var p=0;p  '+h[p].text+"
    ";break;case"checkbox":c='";break;case"radio":c="",null==r.options.items&&null!=r.html.items&&(r.options.items=r.html.items);h=r.options.items;0<(h=!y.isArray(h)?[]:h).length&&(h=w2obj.field.prototype.normMenu.call(this,h,r));for(p=0;p  '+h[p].text+"
    ";break;case"select":c='";break;case"textarea":c='";break;case"toggle":c='
    ';break;case"map":case"array":r.html.key=r.html.key||{},r.html.value=r.html.value||{},r.html.tabindex_str=a,c=''+(r.html.text||"")+'
    ';break;case"html":case"div":case"custom":c='
    "+(r&&r.html&&r.html.html?r.html.html:"")+"
    ";break;case"empty":c=r&&r.html?(r.html.html||"")+(r.html.text||""):""}""!==s&&(e!=r.html.page||t!=r.html.column||r.html.group&&s!=r.html.group)&&(i[e][t]+="\n \n ",s=""),r.html.group&&s!=r.html.group&&(u="",r.html.groupCollapsable&&(u=''),o+='\n
    \n
    "+u+r.html.group+'
    \n
    ',s=r.html.group),null==r.html.anchor?(d=null!=r.html.span?"w2ui-span"+r.html.span:"",u=""+w2utils.lang("checkbox"!=r.type?r.html.label:r.html.text)+"",r.html.label||(u=""),o+='\n
    \n '+u+("empty"===r.type?c:"\n
    "+c+("array"!=r.type&&"map"!=r.type?w2utils.lang("checkbox"!=r.type?r.html.text:""):"")+"
    ")+"\n
    "):(i[r.html.page].anchors=i[r.html.page].anchors||{},i[r.html.page].anchors[r.html.anchor]='
    '+("empty"===r.type?c:"
    "+w2utils.lang("checkbox"!=r.type?r.html.label:r.html.text)+c+w2utils.lang("checkbox"!=r.type?r.html.text:"")+"
    ")+"
    "),null==i[r.html.page]&&(i[r.html.page]={}),null==i[r.html.page][r.html.column]&&(i[r.html.page][r.html.column]=""),i[r.html.page][r.html.column]+=o,e=r.html.page,t=r.html.column}if(""!==s&&(i[e][t]+="\n
    \n
    "),this.tabs.tabs)for(p=0;p",m),m.text=m.caption),m.text&&(w.text=m.text),m.style&&(w.style=m.style),m.class&&(w.class=m.class)):(w.text=g,-1!==["save","update","create"].indexOf(g.toLowerCase())?w.class="w2ui-btn-blue":w.class=""),f+='\n ",l++}f+="\n"}o="";for(var v=0;v',i[v].before&&(o+=i[v].before),o+='
    ',Object.keys(i[v]).sort().forEach(function(e,t){e==parseInt(e)&&(o+='
    '+(i[v][e]||"")+"\n
    ")}),o+="\n
    ",i[v].after&&(o+=i[v].after),o+="\n",i[v].anchors&&Object.keys(i[v].anchors).forEach(function(e,t){o=o.replace(e,i[v].anchors[e])});return o+=f},toggleGroup:function(e,t){var i,s=y(this.box).find('.w2ui-group-title[data-group="'+w2utils.base64encode(e)+'"]');"none"==s.next().css("display")&&!0!==t?(s.next().slideDown(300),s.next().next().remove(),s.find("span").addClass("w2ui-icon-collapse").removeClass("w2ui-icon-expand")):(s.next().slideUp(300),i="width: "+s.next().css("width")+";padding-left: "+s.next().css("padding-left")+";padding-right: "+s.next().css("padding-right")+";margin-left: "+s.next().css("margin-left")+";margin-right: "+s.next().css("margin-right")+";",setTimeout(function(){s.next().after('
    ')},100),s.find("span").addClass("w2ui-icon-expand").removeClass("w2ui-icon-collapse"))},action:function(e,t){var i=this.actions[e],s=i;y.isPlainObject(i)&&i.onClick&&(s=i.onClick);i=this.trigger({phase:"before",target:e,type:"action",action:i,originalEvent:t});!0!==i.isCancelled&&("function"==typeof s&&s.call(this,t),this.trigger(y.extend(i,{phase:"after"})))},resize:function(){var e,t,i,s,n,l,o,a,r=this,d=this.trigger({phase:"before",target:this.name,type:"resize"});function u(){e.width(y(r.box).width()).height(y(r.box).height()),i.css("top",""!==r.header?w2utils.getSize(t,"height"):0),s.css("top",(""!==r.header?w2utils.getSize(t,"height"):0)+("object"==typeof r.toolbar&&y.isArray(r.toolbar.items)&&0 div.w2ui-form-box"),t=y(this.box).find("> div .w2ui-form-header"),i=y(this.box).find("> div .w2ui-form-toolbar"),s=y(this.box).find("> div .w2ui-form-tabs"),n=y(this.box).find("> div .w2ui-page"),l=y(this.box).find("> div .w2ui-page.page-"+this.page),o=y(this.box).find("> div .w2ui-page.page-"+this.page+" > div"),a=y(this.box).find("> div .w2ui-buttons"),u(),this.autosize&&(0!==parseInt(y(this.box).height())&&!0!==y(this.box).data("auto-size")||(y(this.box).height((0'+a[g].text+"'+(e.html.key.text||"")+''+(e.html.value.text||"")+"";t.append(e)},p.el.mapRefresh=function(u,c){var s,n,l=1;"map"==p.type&&(null==(u=!y.isPlainObject(u)?{}:u)._order&&(u._order=Object.keys(u)),t=u._order),(t="array"==p.type?(u=!Array.isArray(u)?[]:u).map(function(e){return e.key}):t).forEach(function(t){s=c.find("#"+w2utils.escapeId(p.name)+"_key_"+l),n=c.find("#"+w2utils.escapeId(p.name)+"_value_"+l),0!=s.length&&0!=n.length||(p.el.mapAdd(p,c,l),s=c.find("#"+w2utils.escapeId(p.name)+"_key_"+l),n=c.find("#"+w2utils.escapeId(p.name)+"_value_"+l));var e,i=u[t];"array"!=p.type||0<(e=u.filter(function(e){return e.key==t})).length&&(i=e[0].value),s.val(t),n.val(i),!0!==p.disabled&&!1!==p.disabled||(s.prop("readOnly",!!p.disabled),n.prop("readOnly",!!p.disabled)),s.parents(".w2ui-map-field").attr("data-key",t),l++});var e=c.find("#"+w2utils.escapeId(p.name)+"_key_"+l).parent(),t=c.find("#"+w2utils.escapeId(p.name)+"_key_"+(l+1)).parent();0!==e.length||s&&(!0===s.prop("readOnly")||!0===s.prop("disabled"))||p.el.mapAdd(p,c,l),1==e.length&&1==t.length&&(e.removeAttr("data-key"),e.find(".key").val(t.find(".key").val()),e.find(".value").val(t.find(".value").val()),t.remove()),!0!==p.disabled&&!1!==p.disabled||(e.find(".key").prop("readOnly",!!p.disabled),e.find(".value").prop("readOnly",!!p.disabled)),y(p.el).next().find("input.w2ui-map").off(".mapChange").on("keyup.mapChange",function(e){var t=y(e.target).parents(".w2ui-map-field");13==e.keyCode&&t.next().find("input.key").focus()}).on("change.mapChange",function(){var e=y(event.target).parents(".w2ui-map-field"),i=e.attr("data-key"),t=e.find(".key").val(),s=e.find(".value").val(),n={},l={},o=null,a=null;n[t]=s,"array"==p.type&&(u.forEach(function(e,t){e.key==i&&(a=t)}),o=u[a]),null!=i&&"map"==p.type&&(l[i]=u[i]),null!=i&&"array"==p.type&&(l[i]=o.value);n=h.trigger({phase:"before",target:p.field,type:"change",originalEvent:event,value_new:n,value_previous:l});if(!0!==n.isCancelled){if("map"==p.type){delete u[i];l=u._order.indexOf(i);if(""!=t){if(null!=u[t]){for(var r,d=0;r=t+ ++d,null!=u[r];);t=r,e.find(".key").val(r)}u[t]=s,e.attr("data-key",t),-1!=l?u._order[l]=t:u._order.push(t)}else u._order.splice(l,1),e.find(".value").val("")}else"array"==p.type&&(""!=t?null==o?u.push({key:t,value:s}):(o.key=t,o.value=s):u.splice(a,1));h.setValue(p.field,u),p.el.mapRefresh(u,c),h.trigger(y.extend(n,{phase:"after"}))}})},p.el.mapRefresh(o,y(p.el).parent().find(".w2ui-map-container"))}(this,l);break;case"div":case"custom":y(l.el).html(o);break;case"html":case"empty":break;default:y(l.el).val(o),y(l.el).w2field(y.extend({},l.options,{type:l.type}))}}for(var m=y(this.box).find(".w2ui-page"),c=0;c *").length&&y(m[c]).wrapInner("
    ");return this.trigger(y.extend(t,{phase:"after"})),this.resize(),(new Date).getTime()-e}}},render:function(e){var t=(new Date).getTime(),i=this;if("object"==typeof e&&(0'+(""!==this.header?'
    '+this.header+"
    ":"")+' '+this.formHTML+"";return y(this.box).attr("name",this.name).addClass("w2ui-reset w2ui-form").html(e),0 input, select, textarea, div > label:nth-child(1) > :radio").not(".file-input");y(t[e]).is(":hidden")&&t.length>=e;)e++;t[e]&&t[e].focus()},destroy:function(){var e=this.trigger({phase:"before",target:this.name,type:"destroy"});!0!==e.isCancelled&&("object"==typeof this.toolbar&&this.toolbar.destroy&&this.toolbar.destroy(),"object"==typeof this.tabs&&this.tabs.destroy&&this.tabs.destroy(),0 div { + float: left; + width: 22px; + height: 22px; + border-radius: inherit; + background: #f5f5f5; + transition-duration: 0.3s; + transition-property: transform, background-color, box-shadow; + box-shadow: 0px 0px 1px #323232, 0 0 0 1px rgba(200, 200, 200, 0.6); + pointer-events: none; + margin-top: -1px; + margin-left: -1px; +} +input[type="checkbox"].w2ui-toggle.w2ui-small + div > div { + width: 16px; + height: 16px; +} +input[type="checkbox"].w2ui-toggle:checked + div > div { + transform: translate3d(24px, 0, 0); + background-color: #ffffff; +} +input[type="checkbox"].w2ui-toggle.w2ui-small:checked + div > div { + transform: translate3d(14px, 0, 0); +} +input[type="checkbox"].w2ui-toggle:focus { + outline: none; +} +input[type="checkbox"].w2ui-toggle:checked + div { + border: 1px solid #206FAD; + box-shadow: inset 0 0 0 12px #35A6EB; +} +input[type="checkbox"].w2ui-toggle:checked:focus + div { + box-shadow: 0px 0px 3px 2px #91baed, inset 0 0 0 12px #35A6EB; +} +input[type="checkbox"].w2ui-toggle:checked + div > div { + box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.3), 0px 0px 0 1px #206FAD; +} +input[type="checkbox"].w2ui-toggle.green:checked + div { + border: 1px solid #00a23f; + box-shadow: inset 0 0 0 12px #54B350; +} +input[type="checkbox"].w2ui-toggle.green:checked:focus + div { + box-shadow: 0px 0px 3px 2px #91baed, inset 0 0 0 12px #54B350; +} +input[type="checkbox"].w2ui-toggle.green:checked + div > div { + box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px #00a23f; +} +/************************************************* +* ---- Common Classes ---- +*/ +.w2ui-marker { + background-color: rgba(214, 161, 252, 0.5); +} +.w2ui-spinner { + display: inline-block; + background-size: 100%; + background-repeat: no-repeat; + background-image: url(data:image/gif;base64,R0lGODlhgACAAKIAAP///93d3bu7u5mZmQAA/wAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFBQAEACwCAAIAfAB8AAAD/0i63P4wygYqmDjrzbtflvWNZGliYXiubKuloivPLlzReD7al+7/Eh5wSFQIi8hHYBkwHUmD6CD5YTJLz49USuVYraRsZ7vtar7XnQ1Kjpoz6LRHvGlz35O4nEPP2O94EnpNc2sef1OBGIOFMId/inB6jSmPdpGScR19EoiYmZobnBCIiZ95k6KGGp6ni4wvqxilrqBfqo6skLW2YBmjDa28r6Eosp27w8Rov8ekycqoqUHODrTRvXsQwArC2NLF29UM19/LtxO5yJd4Au4CK7DUNxPebG4e7+8n8iv2WmQ66BtoYpo/dvfacBjIkITBE9DGlMvAsOIIZjIUAixliv9ixYZVtLUos5GjwI8gzc3iCGghypQqrbFsme8lwZgLZtIcYfNmTJ34WPTUZw5oRxdD9w0z6iOpO15MgTh1BTTJUKos39jE+o/KS64IFVmsFfYT0aU7capdy7at27dw48qdS7eu3bt480I02vUbX2F/JxYNDImw4GiGE/P9qbhxVpWOI/eFKtlNZbWXuzlmG1mv58+gQ4seTbq06dOoU6vGQZJy0FNlMcV+czhQ7SQmYd8eMhPs5BxVdfcGEtV3buDBXQ+fURxx8oM6MT9P+Fh6dOrH2zavc13u9JXVJb520Vp8dvC76wXMuN5Sepm/1WtkEZHDefnzR9Qvsd9+/wi8+en3X0ntYVcSdAE+UN4zs7ln24CaLagghIxBaGF8kFGoIYV+Ybghh841GIyI5ICIFoklJsigihmimJOLEbLYIYwxSgigiZ+8l2KB+Ml4oo/w8dijjcrouCORKwIpnJIjMnkkksalNeR4fuBIm5UEYImhIlsGCeWNNJphpJdSTlkml1jWeOY6TnaRpppUctcmFW9mGSaZceYopH9zkjnjUe59iR5pdapWaGqHopboaYua1qije67GJ6CuJAAAIfkEBQUABAAsCgACAFcAMAAAA/9Iutz+ML5Ag7w46z0r5WAoSp43nihXVmnrdusrv+s332dt4Tyo9yOBUJD6oQBIQGs4RBlHySSKyczVTtHoidocPUNZaZAr9F5FYbGI3PWdQWn1mi36buLKFJvojsHjLnshdhl4L4IqbxqGh4gahBJ4eY1kiX6LgDN7fBmQEJI4jhieD4yhdJ2KkZk8oiSqEaatqBekDLKztBG2CqBACq4wJRi4PZu1sA2+v8C6EJexrBAD1AOBzsLE0g/V1UvYR9sN3eR6lTLi4+TlY1wz6Qzr8u1t6FkY8vNzZTxaGfn6mAkEGFDgL4LrDDJDyE4hEIbdHB6ESE1iD4oVLfLAqPETIsOODwmCDJlv5MSGJklaS6khAQAh+QQFBQAEACwfAAIAVwAwAAAD/0i63P5LSAGrvTjrNuf+YKh1nWieIumhbFupkivPBEzR+GnnfLj3ooFwwPqdAshAazhEGUXJJIrJ1MGOUamJ2jQ9QVltkCv0XqFh5IncBX01afGYnDqD40u2z76JK/N0bnxweC5sRB9vF34zh4gjg4uMjXobihWTlJUZlw9+fzSHlpGYhTminKSepqebF50NmTyor6qxrLO0L7YLn0ALuhCwCrJAjrUqkrjGrsIkGMW/BMEPJcphLgDaABjUKNEh29vdgTLLIOLpF80s5xrp8ORVONgi8PcZ8zlRJvf40tL8/QPYQ+BAgjgMxkPIQ6E6hgkdjoNIQ+JEijMsasNY0RQix4gKP+YIKXKkwJIFF6JMudFEAgAh+QQFBQAEACw8AAIAQgBCAAAD/kg0PPowykmrna3dzXvNmSeOFqiRaGoyaTuujitv8Gx/661HtSv8gt2jlwIChYtc0XjcEUnMpu4pikpv1I71astytkGh9wJGJk3QrXlcKa+VWjeSPZHP4Rtw+I2OW81DeBZ2fCB+UYCBfWRqiQp0CnqOj4J1jZOQkpOUIYx/m4oxg5cuAaYBO4Qop6c6pKusrDevIrG2rkwptrupXB67vKAbwMHCFcTFxhLIt8oUzLHOE9Cy0hHUrdbX2KjaENzey9Dh08jkz8Tnx83q66bt8PHy8/T19vf4+fr6AP3+/wADAjQmsKDBf6AOKjS4aaHDgZMeSgTQcKLDhBYPEswoA1BBAgAh+QQFBQAEACxOAAoAMABXAAAD7Ei6vPOjyUkrhdDqfXHm4OZ9YSmNpKmiqVqykbuysgvX5o2HcLxzup8oKLQQix0UcqhcVo5ORi+aHFEn02sDeuWqBGCBkbYLh5/NmnldxajX7LbPBK+PH7K6narfO/t+SIBwfINmUYaHf4lghYyOhlqJWgqDlAuAlwyBmpVnnaChoqOkpaanqKmqKgGtrq+wsbA1srW2ry63urasu764Jr/CAb3Du7nGt7TJsqvOz9DR0tPU1TIA2ACl2dyi3N/aneDf4uPklObj6OngWuzt7u/d8fLY9PXr9eFX+vv8+PnYlUsXiqC3c6PmUUgAACH5BAUFAAQALE4AHwAwAFcAAAPpSLrc/m7IAau9bU7MO9GgJ0ZgOI5leoqpumKt+1axPJO1dtO5vuM9yi8TlAyBvSMxqES2mo8cFFKb8kzWqzDL7Xq/4LB4TC6bz1yBes1uu9uzt3zOXtHv8xN+Dx/x/wJ6gHt2g3Rxhm9oi4yNjo+QkZKTCgGWAWaXmmOanZhgnp2goaJdpKGmp55cqqusrZuvsJays6mzn1m4uRAAvgAvuBW/v8GwvcTFxqfIycA3zA/OytCl0tPPO7HD2GLYvt7dYd/ZX99j5+Pi6tPh6+bvXuTuzujxXens9fr7YPn+7egRI9PPHrgpCQAAIfkEBQUABAAsPAA8AEIAQgAAA/lIutz+UI1Jq7026h2x/xUncmD5jehjrlnqSmz8vrE8u7V5z/m5/8CgcEgsGo/IpHLJbDqf0Kh0ShBYBdTXdZsdbb/Yrgb8FUfIYLMDTVYz2G13FV6Wz+lX+x0fdvPzdn9WeoJGAYcBN39EiIiKeEONjTt0kZKHQGyWl4mZdREAoQAcnJhBXBqioqSlT6qqG6WmTK+rsa1NtaGsuEu6o7yXubojsrTEIsa+yMm9SL8osp3PzM2cStDRykfZ2tfUtS/bRd3ewtzV5pLo4eLjQuUp70Hx8t9E9eqO5Oku5/ztdkxi90qPg3x2EMpR6IahGocPCxp8AGtigwQAIfkEBQUABAAsHwBOAFcAMAAAA/9Iutz+MMo36pg4682J/V0ojs1nXmSqSqe5vrDXunEdzq2ta3i+/5DeCUh0CGnF5BGULC4tTeUTFQVONYAs4CfoCkZPjFar83rBx8l4XDObSUL1Ott2d1U4yZwcs5/xSBB7dBMBhgEYfncrTBGDW4WHhomKUY+QEZKSE4qLRY8YmoeUfkmXoaKInJ2fgxmpqqulQKCvqRqsP7WooriVO7u8mhu5NacasMTFMMHCm8qzzM2RvdDRK9PUwxzLKdnaz9y/Kt8SyR3dIuXmtyHpHMcd5+jvWK4i8/TXHff47SLjQvQLkU+fG29rUhQ06IkEG4X/Rryp4mwUxSgLL/7IqFETB8eONT6ChCFy5ItqJomES6kgAQAh+QQFBQAEACwKAE4AVwAwAAAD/0i63A4QuEmrvTi3yLX/4MeNUmieITmibEuppCu3sDrfYG3jPKbHveDktxIaF8TOcZmMLI9NyBPanFKJp4A2IBx4B5lkdqvtfb8+HYpMxp3Pl1qLvXW/vWkli16/3dFxTi58ZRcChwIYf3hWBIRchoiHiotWj5AVkpIXi4xLjxiaiJR/T5ehoomcnZ+EGamqq6VGoK+pGqxCtaiiuJVBu7yaHrk4pxqwxMUzwcKbyrPMzZG90NGDrh/JH8t72dq3IN1jfCHb3L/e5ebh4ukmxyDn6O8g08jt7tf26ybz+m/W9GNXzUQ9fm1Q/APoSWAhhfkMAmpEbRhFKwsvCsmosRIHx444PoKcIXKkjIImjTzjkQAAIfkEBQUABAAsAgA8AEIAQgAAA/VIBNz+8KlJq72Yxs1d/uDVjVxogmQqnaylvkArT7A63/V47/m2/8CgcEgsGo/IpHLJbDqf0Kh0Sj0FroGqDMvVmrjgrDcTBo8v5fCZki6vCW33Oq4+0832O/at3+f7fICBdzsChgJGeoWHhkV0P4yMRG1BkYeOeECWl5hXQ5uNIAOjA1KgiKKko1CnqBmqqk+nIbCkTq20taVNs7m1vKAnurtLvb6wTMbHsUq4wrrFwSzDzcrLtknW16tI2tvERt6pv0fi48jh5h/U6Zs77EXSN/BE8jP09ZFA+PmhP/xvJgAMSGBgQINvEK5ReIZhQ3QEMTBLAAAh+QQFBQAEACwCAB8AMABXAAAD50i6DA4syklre87qTbHn4OaNYSmNqKmiqVqyrcvBsazRpH3jmC7yD98OCBF2iEXjBKmsAJsWHDQKmw571l8my+16v+CweEwum8+hgHrNbrvbtrd8znbR73MVfg838f8BeoB7doN0cYZvaIuMjY6PkJGSk2gClgJml5pjmp2YYJ6dX6GeXaShWaeoVqqlU62ir7CXqbOWrLafsrNctjIDwAMWvC7BwRWtNsbGFKc+y8fNsTrQ0dK3QtXAYtrCYd3eYN3c49/a5NVj5eLn5u3s6e7x8NDo9fbL+Mzy9/T5+tvUzdN3Zp+GBAAh+QQJBQAEACwCAAIAfAB8AAAD/0i63P4wykmrvTjrzbv/YCiOZGmeaKqubOu+cCzPdArcQK2TOL7/nl4PSMwIfcUk5YhUOh3M5nNKiOaoWCuWqt1Ou16l9RpOgsvEMdocXbOZ7nQ7DjzTaeq7zq6P5fszfIASAYUBIYKDDoaGIImKC4ySH3OQEJKYHZWWi5iZG0ecEZ6eHEOio6SfqCaqpaytrpOwJLKztCO2jLi1uoW8Ir6/wCHCxMG2x7muysukzb230M6H09bX2Nna29zd3t/g4cAC5OXm5+jn3Ons7eba7vHt2fL16tj2+QL0+vXw/e7WAUwnrqDBgwgTKlzIsKHDh2gGSBwAccHEixAvaqTYcFCjRoYeNyoM6REhyZIHT4o0qPIjy5YTTcKUmHImx5cwE85cmJPnSYckK66sSAAj0aNIkypdyrSp06dQo0qdSrWq1atYs2rdyrWr169gwxZJAAA7); +} +/* common icons */ +.w2ui-icon { + background-repeat: no-repeat; + height: 16px; + width: 16px; + overflow: hidden; + margin: 2px 2px; + display: inline-block; +} +.w2ui-icon.icon-folder { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGrSURBVDjLxZO7ihRBFIa/6u0ZW7GHBUV0UQQTZzd3QdhMQxOfwMRXEANBMNQX0MzAzFAwEzHwARbNFDdwEd31Mj3X7a6uOr9BtzNjYjKBJ6nicP7v3KqcJFaxhBVtZUAK8OHlld2st7Xl3DJPVONP+zEUV4HqL5UDYHr5xvuQAjgl/Qs7TzvOOVAjxjlC+ePSwe6DfbVegLVuT4r14eTr6zvA8xSAoBLzx6pvj4l+DZIezuVkG9fY2H7YRQIMZIBwycmzH1/s3F8AapfIPNF3kQk7+kw9PWBy+IZOdg5Ug3mkAATy/t0usovzGeCUWTjCz0B+Sj0ekfdvkZ3abBv+U4GaCtJ1iEm6ANQJ6fEzrG/engcKw/wXQvEKxSEKQxRGKE7Izt+DSiwBJMUSm71rguMYhQKrBygOIRStf4TiFFRBvbRGKiQLWP29yRSHKBTtfdBmHs0BUpgvtgF4yRFR+NUKi0XZcYjCeCG2smkzLAHkbRBmP0/Uk26O5YnUActBp1GsAI+S5nRJJJal5K1aAMrq0d6Tm9uI6zjyf75dAe6tx/SsWeD//o2/Ab6IH3/h25pOAAAAAElFTkSuQmCC) no-repeat center; +} +.w2ui-icon.icon-page { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAINSURBVBgZBcG/r55zGAfg6/4+z3va01NHlYgzEfE7MdCIGISFgS4Gk8ViYyM2Mdlsko4GSf8Do0FLRCIkghhYJA3aVBtEz3nP89wf11VJvPDepdd390+8Nso5nESBQoq0pfvXm9fzWf19453LF85vASqJlz748vInb517dIw6EyYBIIG49u+xi9/c9MdvR//99MPPZ7+4cP4IZhhTPbwzT2d+vGoaVRRp1rRliVvHq+cfvM3TD82+7mun0o/ceO7NT+/4/KOXjwZU1ekk0840bAZzMQ2mooqh0A72d5x/6sB9D5zYnff3PoYBoWBgFKPKqDKqjCpjKr//dcu9p489dra88cydps30KswACfNEKanSaxhlntjJ8Mv12Paie+vZ+0+oeSwwQ0Iw1xAR1CiFNJkGO4wu3ZMY1AAzBI0qSgmCNJsJUEOtJSMaCTBDLyQ0CknAGOgyTyFFiLI2awMzdEcSQgSAAKVUmAeNkxvWJWCGtVlDmgYQ0GFtgg4pNtOwbBcwQy/Rife/2yrRRVI0qYCEBly8Z+P4qMEMy7JaVw72N568e+iwhrXoECQkfH91kY7jwwXMsBx1L93ZruqrK6uuiAIdSnTIKKPLPFcvay8ww/Hh+ufeznTXu49v95IMoQG3784gYXdTqvRmqn/Wpa/ADFX58MW3L71SVU9ETgEIQQQIOOzub+fhIvwPRDgeVjWDahIAAAAASUVORK5CYII=) no-repeat center; +} +/************************************************* +* ---- Locking portion of the screen (in grid, form, layout) +*/ +.w2ui-lock { + display: none; + position: absolute; + z-index: 1400; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + opacity: 0.15; + filter: alpha(opacity=15); + background-color: #333; +} +.w2ui-lock-msg { + display: none; + position: absolute; + z-index: 1400; + top: 50%; + left: 50%; + transform: translateX(-50%) translateY(-50%); + min-width: 100px; + max-width: 95%; + padding: 30px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + font-size: 13px; + font-family: OpenSans; + opacity: 0.8; + background-color: #555; + color: white; + text-align: center; + border-radius: 5px; + border: 2px solid #444; +} +.w2ui-lock-msg .w2ui-spinner { + display: inline-block; + width: 24px; + height: 24px; + margin: -3px 8px -7px -10px; +} +/************************************************* +* ---- Scroll contet, used in toolbar and tabs ---- +*/ +.w2ui-scroll-wrapper { + overflow: hidden; +} +.w2ui-scroll-left, +.w2ui-scroll-right { + top: 0; + width: 18px; + height: 100%; + cursor: default; + z-index: 10; + display: none; + position: absolute; +} +.w2ui-scroll-left:hover, +.w2ui-scroll-right:hover { + background-color: #ddd; +} +.w2ui-scroll-left { + left: 0; + box-shadow: 0px 0px 7px #5F5F5F; + background: #F7F7F7 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAzklEQVR4Ae2THRDEMBCFzy1ucatb3eJ2uhi3uNUtbnGrW9zi1rOdNzdvdl7nDpvYt/9/r7+/51myZZf/zXkD2iMHHRSb0x3oskwMieK05PwEXqP4ExSL0wp0ROao2OOuMPOMdUL6XU1/oGLcFWb+NqyTd2W/P/2qTr9h+nFXhOkHXRHiNyjrgp/U/V+WaQcaNY13zZI0A1JvcVqAnrGDTdtDtZUHjHIJhxxVLN0iqXgCP1l/7h8U9kc6abyJ4/eNWPpGdBv+XdUK0K8cnvcBly2rDr7C1HQAAAAASUVORK5CYII=') center center no-repeat; + background-size: 15px 12px; +} +.w2ui-scroll-right { + right: 0; + box-shadow: 0px 0px 7px #5F5F5F; + background: #F7F7F7 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVR4Ae2UG7TGMBCEr1vd4la3uMUtuli3utWtbnGLW9zi9l/bDMzJG7u12cfJfLunf1+UEC9Bv0vVQwJ8hjRCaZafflb1C9RQf4OD0gSDE+i+PiJAabFhQc1y1AYYsJGLY3lgxM17uWPO56yPiFDqVPWgRtpIHSd1zPnwkBsdI58OlNwx4fP2X0TgfMTOoHSdKOXkpyNvEyQh7ul+4swxJSTQuwNDxz68l/ukVNbu0Neen5Z+KvzWxBAqHds349uPFJ/jVOrPjxUq++OLf+20q5+noXo0AAAAAElFTkSuQmCC') center center no-repeat; + background-size: 15px 13px; +} +/************************************* +* ---- Notification message ---- +*/ +#w2ui-notify { + position: absolute; + display: flex; + flex-direction: column; + align-items: center; + left: 0px; + right: 0px; + bottom: 15px; + z-index: 10000; + overflow: hidden; +} +#w2ui-notify > div { + position: relative; + background-color: #292828ba; + color: #ffffff; + padding: 8px 44px 8px 16px; + border-radius: 4px; + box-shadow: 3px 3px 10px #9c9c9c; + max-height: 76px; + min-width: 100px; + max-width: 800px; + font-size: 16px; + text-shadow: 1px 0 0 black; +} +#w2ui-notify > div a { + color: #6cd0e8; + text-decoration: none; + cursor: pointer; +} +#w2ui-notify > div a:hover { + color: #a2f0ff; +} +#w2ui-notify > div span.w2ui-notify-close { + padding: 6px 6px; + border-radius: 3px; + font-size: 13px; + color: #c3c3c3; + position: absolute; + right: 5px; + top: 5px; +} +#w2ui-notify > div span.w2ui-notify-close:hover { + background-color: #807e7e; + color: #ffffff; +} +#w2ui-notify > div.w2ui-notify-error { + text-shadow: none; + background-color: rgba(255, 0, 0, 0.8); +} +#w2ui-notify > div.w2ui-notify-error .w2ui-notify-close { + color: #ffffff; +} +#w2ui-notify > div.w2ui-notify-error .w2ui-notify-close:hover { + background-color: #fcadad; + color: rgba(255, 0, 0, 0.8); +} +input[type=button].w2ui-btn, +button.w2ui-btn { + position: relative; + display: inline-block; + border-radius: 14px; + margin: 0px 3px; + padding: 6px 12px; + color: #666; + font-size: 12px; + border: 1px solid transparent; + background-image: linear-gradient(#E8E8EE 0%, #E8E8EE 100%); + outline: none; + box-shadow: 0px 1px 0px white; + cursor: default; + min-width: 75px; + line-height: 110%; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +input[type=button].w2ui-btn:hover, +button.w2ui-btn:hover { + text-decoration: none; + background-image: linear-gradient(#ddd 0%, #DDDDDD 100%); + color: #333; +} +input[type=button].w2ui-btn:active, +button.w2ui-btn:active, +input[type=button].w2ui-btn.clicked, +button.w2ui-btn.clicked { + background-image: linear-gradient(#ccc 0%, #ccc 100%); + text-shadow: 1px 1px 1px #eee; +} +input[type=button].w2ui-btn:focus:before, +button.w2ui-btn:focus:before { + content: ""; + border: 1px dashed #aaa; + border-radius: 15px; + position: absolute; + top: 2px; + bottom: 2px; + left: 2px; + right: 2px; + pointer-events: none; +} +input[type=button].w2ui-btn-blue, +button.w2ui-btn-blue { + color: white; + background-image: linear-gradient(#269DF0 0%, #269DF0 100%); + border: 1px solid #269DF0; + text-shadow: 0px 0px 1px #111; +} +input[type=button].w2ui-btn-blue:hover, +button.w2ui-btn-blue:hover { + color: white; + background-image: linear-gradient(#2391DD 0%, #2391DD 100%); + border: 1px solid #2391DD; + text-shadow: 0px 0px 1px #111; +} +input[type=button].w2ui-btn-blue:active, +button.w2ui-btn-blue:active, +input[type=button].w2ui-btn-blue.clicked, +button.w2ui-btn-blue.clicked { + color: white; + background-image: linear-gradient(#1E83C9 0%, #1E83C9 100%); + border: 1px solid #1268A6; + text-shadow: 0px 0px 1px #111; +} +input[type=button].w2ui-btn-blue:focus:before, +button.w2ui-btn-blue:focus:before { + border: 1px dashed #e8e8e8; +} +input[type=button].w2ui-btn-green, +button.w2ui-btn-green { + color: white; + background-image: linear-gradient(#52A452 0%, #52A452 100%); + border: 1px solid #52A452; + text-shadow: 0px 0px 1px #111; +} +input[type=button].w2ui-btn-green:hover, +button.w2ui-btn-green:hover { + color: white; + background-image: linear-gradient(#3F8F3D 0%, #3F8F3D 100%); + border: 1px solid #3F8F3D; + text-shadow: 0px 0px 1px #111; +} +input[type=button].w2ui-btn-green:active, +button.w2ui-btn-green:active, +input[type=button].w2ui-btn-green.clicked, +button.w2ui-btn-green.clicked { + color: white; + background-image: linear-gradient(#377D36 0%, #377D36 100%); + border: 1px solid #555; + text-shadow: 0px 0px 1px #111; +} +input[type=button].w2ui-btn-green:focus:before, +button.w2ui-btn-green:focus:before { + border: 1px dashed #e8e8e8; +} +input[type=button].w2ui-btn-orange, +button.w2ui-btn-orange { + color: white; + background-image: linear-gradient(#FB8822 0%, #FB8822 100%); + border: 1px solid #FB8822; + text-shadow: 0px 0px 1px #111; +} +input[type=button].w2ui-btn-orange:hover, +button.w2ui-btn-orange:hover { + color: white; + background-image: linear-gradient(#F1731F 0%, #F1731F 100%); + border: 1px solid #F1731F; + text-shadow: 0px 0px 1px #111; +} +input[type=button].w2ui-btn-orange:active, +button.w2ui-btn-orange:active, +input[type=button].w2ui-btn-orange.clicked, +button.w2ui-btn-orange.clicked { + color: white; + border: 1px solid #666; + background-image: linear-gradient(#B98747 0%, #B98747 100%); + text-shadow: 0px 0px 1px #111; +} +input[type=button].w2ui-btn-orange:focus:before, +button.w2ui-btn-orange:focus:before { + border: 1px dashed #f9f9f9; +} +input[type=button].w2ui-btn-red, +button.w2ui-btn-red { + color: white; + background-image: linear-gradient(#f9585a 0%, #f9585a 100%); + border: 1px solid #f9585a; + text-shadow: 0px 0px 1px #111; +} +input[type=button].w2ui-btn-red:hover, +button.w2ui-btn-red:hover { + color: white; + background-image: linear-gradient(#de4446 0%, #de4446 100%); + border: 1px solid #de4446; + text-shadow: 0px 0px 1px #111; +} +input[type=button].w2ui-btn-red:active, +button.w2ui-btn-red:active, +input[type=button].w2ui-btn-red.clicked, +button.w2ui-btn-red.clicked { + color: white; + border: 1px solid #861C1E; + background-image: linear-gradient(#9C2123 0%, #9C2123 100%); + text-shadow: 0px 0px 1px #111; +} +input[type=button].w2ui-btn-red:focus:before, +button.w2ui-btn-red:focus:before { + border: 1px dashed #ddd; +} +input[type=button].w2ui-btn-small, +button.w2ui-btn-small { + padding: 5px; + border-radius: 4px; + margin: 0px; + min-width: 0px; +} +input[type=button].w2ui-btn-small:focus:before, +button.w2ui-btn-small:focus:before { + border-radius: 2px; + top: 2px; + bottom: 2px; + left: 2px; + right: 2px; +} +input[type=button].w2ui-btn:disabled, +button.w2ui-btn:disabled { + border: 1px solid #e6e6e6; + background: #f7f7f7; + color: #bdbcbc; + text-shadow: none; +} +/* general overlay */ +.w2ui-overlay { + --tip-size: 8px; + position: fixed; + z-index: 1700; + opacity: 0; + transition: opacity 0.1s; + border-radius: 4px; +} +.w2ui-overlay * { + box-sizing: border-box; +} +.w2ui-overlay .w2ui-overlay-body { + display: inline-block; + border: 1px solid #474747; + border-radius: 4px; + padding: 4px 8px; + margin: 0px; + font-size: 12px; + font-family: OpenSans; + color: white; + text-shadow: 0px 1px 1px #4a4a4a; + background-color: #777777; + line-height: 1.4; + letter-spacing: 0.1px; + overflow: auto; +} +.w2ui-overlay .w2ui-overlay-body.w2ui-light { + color: #3c3c3c; + text-shadow: none; + background-color: #fffde9; + border: 1px solid #d2d2d2; + box-shadow: 0px 1px 1px 1px #ffffff; +} +.w2ui-overlay .w2ui-overlay-body.w2ui-white { + color: #3c3c3c; + text-shadow: none; + background-color: #fafafa; + border: 1px solid #cccace; + box-shadow: 0px 0px 1px 1px #ffffff; +} +.w2ui-overlay .w2ui-overlay-body.w2ui-arrow-right:before { + content: ""; + position: absolute; + left: calc(var(--tip-size, 8px) * -0.5 - 1px); + top: calc(50% - 1px); + transform: rotate(-45deg) translateY(-50%); + transform-origin: top center; + margin: 0; + border: inherit; + border-color: inherit; + background-color: inherit; + width: var(--tip-size, 8px); + height: var(--tip-size, 8px); + border-bottom-right-radius: 200px; + border-bottom-width: 0; + border-right-width: 0; +} +.w2ui-overlay .w2ui-overlay-body.w2ui-arrow-left:after { + content: ""; + position: absolute; + right: calc(var(--tip-size, 8px) * -0.5 - 1px); + top: calc(50% - 1px); + transform: rotate(135deg) translateY(-50%); + transform-origin: top center; + margin: 0; + border: inherit; + border-color: inherit; + background-color: inherit; + width: var(--tip-size, 8px); + height: var(--tip-size, 8px); + border-bottom-right-radius: 200px; + border-bottom-width: 0; + border-right-width: 0; +} +.w2ui-overlay .w2ui-overlay-body.w2ui-arrow-top:before { + content: ""; + position: absolute; + bottom: calc(var(--tip-size, 8px) * -0.5 + 3px); + left: 50%; + transform-origin: center left; + transform: rotate(-135deg) translateX(-50%); + margin: 0; + border: inherit; + border-color: inherit; + background-color: inherit; + width: var(--tip-size, 8px); + height: var(--tip-size, 8px); + border-bottom-right-radius: 200px; + border-bottom-width: 0; + border-right-width: 0; +} +.w2ui-overlay .w2ui-overlay-body.w2ui-arrow-bottom:after { + content: ""; + position: absolute; + top: calc(var(--tip-size, 8px) * -0.5); + left: 50%; + transform: rotate(45deg) translateX(-50%); + transform-origin: center left; + margin: 0; + border: inherit; + border-color: inherit; + background-color: inherit; + width: var(--tip-size, 8px); + height: var(--tip-size, 8px); + border-bottom-right-radius: 200px; + border-bottom-width: 0; + border-right-width: 0; +} +/* color overlay */ +.w2ui-colors { + padding: 8px; + padding-bottom: 0px; + background-color: white; + border-radius: 3px; + overflow: hidden; + width: 270px; + height: 240px; +} +.w2ui-colors * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-colors .w2ui-color-tabs { + display: flex; + background-color: #f7f7f7; + height: 34px; + margin: 14px -8px 0px -8px; + border-top: 1px solid #d6d6d6; +} +.w2ui-colors .w2ui-color-tabs .w2ui-color-tab { + display: inline-block; + width: 65px; + height: 32px; + border: 0; + border-top: 2px solid transparent; + border-radius: 1px; + margin: -1.5px 4px; + text-align: center; + font-size: 15px; + padding-top: 4px; + color: #7b7b7b; +} +.w2ui-colors .w2ui-color-tabs .w2ui-color-tab:hover { + background-color: #e1e1e1; +} +.w2ui-colors .w2ui-color-tabs .w2ui-color-tab.w2ui-selected { + border-top-color: #0175ff; +} +.w2ui-colors .w2ui-color-tabs .w2ui-color-tab .w2ui-icon { + padding-top: 1px; + width: 30px; +} +.w2ui-colors .w2ui-tab-content.tab-1 .w2ui-color-row { + display: flex; +} +.w2ui-colors .w2ui-tab-content.tab-1 .w2ui-color-row .w2ui-color { + cursor: default; + text-align: center; + display: inline-block; + width: 18px; + height: 18px; + padding: 6px; + margin: 1.5px; + border: 1px solid transparent; +} +.w2ui-colors .w2ui-tab-content.tab-1 .w2ui-color-row .w2ui-color:hover { + outline: 1px solid #666; + border: 1px solid #fff; +} +.w2ui-colors .w2ui-tab-content.tab-1 .w2ui-color-row .w2ui-color.w2ui-no-color { + border: 1px solid #efefef; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABlBMVEX/////TgCFoIUYAAAAGUlEQVR42uXHIQEAAACDsNO/NJ4Kn9uC8wsJkAARUrXAjwAAAABJRU5ErkJggg==) 15px 15px; +} +.w2ui-colors .w2ui-tab-content.tab-1 .w2ui-color-row .w2ui-color.w2ui-selected:before { + content: '\2022'; + position: relative; + left: -1px; + top: -8px; + color: white; + font-size: 14px; + text-shadow: 0px 0px 2px #222; +} +.w2ui-colors .w2ui-tab-content.tab-2 { + height: 184px; + padding: 1px 2px; +} +.w2ui-colors .w2ui-tab-content.tab-2 .palette { + position: relative; + width: 150px; + height: 125px; + outline: 1px solid #d2d2d2; +} +.w2ui-colors .w2ui-tab-content.tab-2 .palette .palette-bg { + height: 100%; + background-image: linear-gradient(0deg, #000000, rgba(204, 154, 129, 0)); + pointer-events: none; +} +.w2ui-colors .w2ui-tab-content.tab-2 .rainbow { + position: relative; + width: 150px; + height: 12px; + margin: 10px 0px 0px 0px; + background: linear-gradient(90deg, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); +} +.w2ui-colors .w2ui-tab-content.tab-2 .alpha { + position: relative; + width: 150px; + height: 12px; + margin: 20px 0px 0px 0px; + background-color: #fff; + background-image: linear-gradient(45deg, #bbb 25%, transparent 25%, transparent 75%, #bbb 75%, #bbb), linear-gradient(45deg, #bbb 25%, transparent 25%, transparent 75%, #bbb 75%, #bbb); + background-size: 12px 12px; + background-position: 0 0, 6px 6px; +} +.w2ui-colors .w2ui-tab-content.tab-2 .alpha .alpha-bg { + height: 100%; + background-image: linear-gradient(90deg, rgba(80, 80, 80, 0) 0%, #505050 100%); + pointer-events: none; +} +.w2ui-colors .w2ui-tab-content.tab-2 .value1 { + pointer-events: none; + position: absolute; + top: 0px; + display: inline-block; + width: 8px; + height: 8px; + border-radius: 10px; + border: 1px solid #999; + outline: 1px solid #bbb; + background-color: transparent; + box-shadow: 0px 0px 1px white; + transform: translateX(-3px) translateY(-3px); +} +.w2ui-colors .w2ui-tab-content.tab-2 .value2 { + pointer-events: none; + position: absolute; + top: -2px; + display: inline-block; + width: 8px; + height: 16px; + border-radius: 2px; + border: 1px solid #696969; + background-color: #ffffff; + box-shadow: 0px 0px 1px white; + transform: translateX(-1px); +} +.w2ui-colors .w2ui-tab-content.tab-2 .color-info { + float: right; + margin-right: -5px; +} +.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-preview-bg { + box-shadow: 0 0 1px #c3c3c3; + height: 40px; + background-color: #fff; + background-image: linear-gradient(45deg, #bbb 25%, transparent 25%, transparent 75%, #bbb 75%, #bbb), linear-gradient(45deg, #bbb 25%, transparent 25%, transparent 75%, #bbb 75%, #bbb); + background-size: 16px 16px; + background-position: 0 0, 8px 8px; + margin-bottom: 10px; +} +.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-preview, +.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-original { + height: 40px; + width: 50px; + float: left; +} +.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-part { + padding-top: 7px; +} +.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-part span { + display: inline-block; + width: 8px; + margin: 2px 1px 2px 5px; + color: #666; +} +.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-part input { + font-size: 12px; + border-radius: 2px; + border: 1px solid #ccc; + width: 30px; + text-align: right; + padding: 4px; + color: #333; +} +.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-part.opacity { + margin: 11px 0px 0px 8px; +} +.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-part.opacity span { + width: 42px; +} +.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-part.opacity input { + width: 38px; + text-align: center; +} +/* menu overlay */ +.w2ui-menu-top, +.w2ui-menu-search { + position: sticky; + top: 0; + background-color: white; + border-bottom: 1px dotted silver; +} +.w2ui-menu-search { + padding: 6px 4px; +} +.w2ui-menu-search .w2ui-icon { + position: absolute; + top: 11px; + left: 6px; + color: #90819c; + font-size: 14px; +} +.w2ui-menu-search #menu-search { + width: 100%; + padding: 5px 5px 5px 25px; +} +.w2ui-menu { + display: block; + color: black; + padding: 5px 0px; + border-radius: 5px; + overflow-x: hidden; + cursor: default; +} +.w2ui-menu .w2ui-menu-item { + display: flex; + align-content: stretch; + padding: 8px 5px; + user-select: none; +} +.w2ui-menu .w2ui-menu-item.w2ui-even { + color: inherit; + background-color: #FFF; +} +.w2ui-menu .w2ui-menu-item.w2ui-odd { + color: inherit; + background-color: #fbfbfb; +} +.w2ui-menu .w2ui-menu-item:hover { + background-color: #f0f3ff; +} +.w2ui-menu .w2ui-menu-item.w2ui-selected { + background-color: #e1e7ff; +} +.w2ui-menu .w2ui-menu-item.w2ui-disabled { + opacity: 0.4; + color: inherit; + background-color: transparent; +} +.w2ui-menu .w2ui-menu-item .menu-icon { + flex: none; + width: 26px; + height: 16px; + padding: 0; + margin: 0; +} +.w2ui-menu .w2ui-menu-item .menu-icon span { + width: 18px; + font-size: 14px; + color: #8D99A7; + display: inline-block; + padding-top: 1px; +} +.w2ui-menu .w2ui-menu-item .menu-text { + flex-grow: 1; + white-space: nowrap; +} +.w2ui-menu .w2ui-menu-item .menu-extra { + flex: none; + min-width: 10px; +} +.w2ui-menu .w2ui-menu-item .menu-extra span { + border: 1px solid #F6FCF4; + border-radius: 20px; + width: auto; + height: 18px; + padding: 2px 7px; + margin: 0 0 0 10px; + background-color: #F2F8F0; + color: #666; + box-shadow: 0px 0px 2px #474545; + text-shadow: 1px 1px 0px #FFF; +} +.w2ui-menu .w2ui-menu-item .menu-extra span.hotkey { + border: none; + border-radius: 0px; + background-color: transparent; + color: #888; + box-shadow: none; + text-shadow: none; +} +.w2ui-menu .w2ui-menu-item .menu-extra span.remove { + background-color: transparent; + border-color: transparent; + box-shadow: none; + padding: 0 5px; + border-radius: 3px; + position: relative; + margin-top: -3px; + display: block; + height: 20px; + width: 20px; + text-align: center; + user-select: none; +} +.w2ui-menu .w2ui-menu-item .menu-extra span.remove:hover { + background-color: #f9e7e7; + color: red; +} +.w2ui-menu .w2ui-menu-item .menu-extra span.remove:active { + background-color: #ffd1d1; +} +.w2ui-menu .w2ui-menu-divider { + padding: 5px; +} +.w2ui-menu .w2ui-menu-divider .line { + border-top: 1px dotted silver; +} +.w2ui-menu .w2ui-menu-divider.has-text { + height: 26px; + background-color: #fafafa; + border-top: 1px solid #f2f2f2; + border-bottom: 1px solid #f2f2f2; + text-align: center; +} +.w2ui-menu .w2ui-menu-divider.has-text .line { + display: block; + margin-top: 7px; +} +.w2ui-menu .w2ui-menu-divider.has-text .text { + display: inline-block; + position: relative; + top: -10px; + background-color: #fafafa; + padding: 0px 7px; + color: darkgrey; +} +.w2ui-menu .w2ui-no-items { + padding: 5px 15px; + text-align: center; + color: gray; +} +.w2ui-menu .w2ui-no-items .w2ui-spinner { + position: relative; + left: -2px; + margin-bottom: -5px; + width: 18px; + height: 18px; +} +.w2ui-menu .w2ui-sub-menu-box { + background-color: #fafafd; + border-top: 1px solid #d6e2e6; + border-bottom: 1px solid #d6e2e6; + padding: 0 3px; +} +.w2ui-menu .expanded .menu-extra span, +.w2ui-menu .collapsed .menu-extra span { + position: relative; + border-color: transparent; + background-color: transparent; + box-shadow: none; + padding: 0px 6px; + border-radius: 0px; + margin-left: 5px; +} +.w2ui-menu .expanded .menu-extra span:after, +.w2ui-menu .collapsed .menu-extra span:after { + content: ""; + position: absolute; + border-left: 5px solid #808080; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + transform: rotateZ(-90deg); + pointer-events: none; + margin-left: -2px; + margin-top: 3px; +} +.w2ui-menu .expanded .menu-extra span:hover, +.w2ui-menu .collapsed .menu-extra span:hover { + border-color: transparent; + background-color: transparent; +} +.w2ui-menu .collapsed .menu-extra span:after { + transform: rotateZ(90deg); +} +/* date overlay */ +.w2ui-calendar { + margin: 0px; + line-height: 1.1; + user-select: none; +} +.w2ui-calendar.w2ui-overlay-body { + border: 1px solid #cccace; + color: #3c3c3c; + text-shadow: none; + background-color: #fff; + box-shadow: 0px 1px 6px 1px #ebeaec; +} +.w2ui-calendar .w2ui-cal-title, +.w2ui-calendar .w2ui-time-title { + margin: 0px; + padding: 7px 2px; + background-color: #fafafa; + border-top: 1px solid #fefefe; + border-bottom: 1px solid #ddd; + color: #555; + text-align: center; + text-shadow: 1px 1px 1px #eee; + font-size: 16px; + cursor: pointer; +} +.w2ui-calendar .w2ui-cal-title .arrow-down, +.w2ui-calendar .w2ui-time-title .arrow-down { + position: relative; + top: -3px; + left: 5px; + opacity: 0.6; +} +.w2ui-calendar .w2ui-cal-previous, +.w2ui-calendar .w2ui-cal-next { + width: 30px; + height: 30px; + color: #666; + border: 1px solid transparent; + border-radius: 3px; + padding: 7px 5px; + margin: -4px 1px 0px 1px; + cursor: default; +} +.w2ui-calendar .w2ui-cal-previous:hover, +.w2ui-calendar .w2ui-cal-next:hover { + color: black; + border: 1px solid #f5f5f5; + background-color: #f9f7f7; +} +.w2ui-calendar .w2ui-cal-previous:active, +.w2ui-calendar .w2ui-cal-next:active { + color: black; + background-color: #f2f1f4; + border: 1px solid #e6dbfb; +} +.w2ui-calendar .w2ui-cal-previous > div, +.w2ui-calendar .w2ui-cal-next > div { + position: absolute; + border-left: 4px solid #888; + border-top: 4px solid #888; + border-right: 4px solid transparent; + border-bottom: 4px solid transparent; + width: 0px; + height: 0px; + padding: 0px; + margin: 3px 0px 0px 0px; +} +.w2ui-calendar .w2ui-cal-previous { + float: left; +} +.w2ui-calendar .w2ui-cal-previous > div { + -webkit-transform: rotate(-45deg); + -moz-transform: rotate(-45deg); + -ms-transform: rotate(-45deg); + -o-transform: rotate(-45deg); + transform: rotate(-45deg); + margin-left: 6px; +} +.w2ui-calendar .w2ui-cal-next { + float: right; +} +.w2ui-calendar .w2ui-cal-next > div { + -webkit-transform: rotate(135deg); + -moz-transform: rotate(135deg); + -ms-transform: rotate(135deg); + -o-transform: rotate(135deg); + transform: rotate(135deg); + margin-left: 2px; + margin-right: 2px; +} +.w2ui-calendar .w2ui-cal-jump { + display: flex; + background-color: #fdfdfd; +} +.w2ui-calendar .w2ui-cal-jump .w2ui-jump-month, +.w2ui-calendar .w2ui-cal-jump .w2ui-jump-year { + cursor: default; + text-align: center; + border: 1px solid transparent; + border-radius: 3px; + font-size: 14px; +} +.w2ui-calendar .w2ui-cal-jump #w2ui-jump-month { + width: 186px; + padding: 10px 5px 4px 3px; + border-right: 1px solid #efefef; + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: repeat(4, 52px); + grid-gap: 4px; +} +.w2ui-calendar .w2ui-cal-jump #w2ui-jump-month .w2ui-jump-month { + padding: 15px 0 0 0; +} +.w2ui-calendar .w2ui-cal-jump #w2ui-jump-year { + width: 90px; + height: 240px; + overflow-x: hidden; + overflow-y: auto; + margin: 0 2px; + display: flex; + flex-wrap: wrap; +} +.w2ui-calendar .w2ui-cal-jump #w2ui-jump-year .w2ui-jump-year { + width: 95%; + height: 30px; + padding: 5px 0; + margin: 1px 0; +} +.w2ui-calendar .w2ui-cal-jump .w2ui-jump-month:hover, +.w2ui-calendar .w2ui-cal-jump .w2ui-jump-year:hover { + color: black; + border: 1px solid #f5f5f5; + background-color: #f9f7f7; +} +.w2ui-calendar .w2ui-cal-jump .w2ui-jump-month.w2ui-selected, +.w2ui-calendar .w2ui-cal-jump .w2ui-jump-year.w2ui-selected { + color: black; + background-color: #f2f1f4; + border: 1px solid #e6dbfb; +} +.w2ui-calendar .w2ui-cal-now { + cursor: default; + padding: 3px; + text-align: center; + background-color: #f4f4f4; + margin: 5px; + border: 1px solid #e5e5e5; + border-radius: 4px; +} +.w2ui-calendar .w2ui-cal-now:hover { + color: #28759e; + border: 1px solid #c3d6df; +} +.w2ui-calendar .w2ui-cal-days { + width: 280px; + height: 240px; + padding: 2px; + display: grid; + grid-template-columns: repeat(7, 1fr); +} +.w2ui-calendar .w2ui-cal-days .w2ui-day { + border: 1px solid #fff; + border-radius: 3px; + color: black; + background-color: #f7f7f7; + padding: 8px 0 0 0; + cursor: default; + text-align: center; +} +.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-saturday, +.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-sunday { + border: 1px solid #fff; + color: #c8493b; + background-color: #f7f7f7; +} +.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-today { + background-color: #e2f7cd; +} +.w2ui-calendar .w2ui-cal-days .w2ui-day:hover { + background-color: #f2f1f4; + border: 1px solid #e6dbfb; +} +.w2ui-calendar .w2ui-cal-days .w2ui-day:active { + background-color: #eeebf3; + border: 1px solid #cec2e5; +} +.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-selected { + border: 1px solid #8cb067; +} +.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-weekday { + text-align: center; + background-color: #fff; + color: #a99cc2; +} +.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-weekday:hover { + border: 1px solid #fff; + background-color: #fff; +} +.w2ui-calendar .w2ui-cal-days .w2ui-day.outside { + color: #b5b5b5; + background-color: #fff; +} +.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-blocked { + color: #555; + background-color: #fff; + border: 1px solid #fff; +} +.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-blocked:after { + content: " "; + position: absolute; + color: #b3b3b378; + font-size: 27px; + padding: 0px; + font-family: verdana; + transform: translate(-15px, 15px) rotate(-36deg); + border-top: 1px solid #c9c2c2; + width: 24px; + transform-origin: top left; +} +/* time overlay */ +.w2ui-cal-time { + display: grid; + grid-template-columns: repeat(3, 1fr); + background-color: white; + cursor: default; +} +.w2ui-cal-time .w2ui-cal-column { + width: 90px; + display: flex; + flex-wrap: wrap; + padding: 4px; +} +.w2ui-cal-time .w2ui-cal-column:nth-child(even) { + background-color: #fafafa; +} +.w2ui-cal-time .w2ui-cal-column span { + width: 100%; + padding: 8px; + margin: 1px; + text-align: center; + border: 1px solid transparent; + border-radius: 2px; + white-space: nowrap; +} +.w2ui-cal-time .w2ui-cal-column span:hover { + background-color: #f2f1f4; + border: 1px solid #e6dbfb; +} +.w2ui-cal-time .w2ui-cal-column span:active { + background-color: #eeebf3; + border: 1px solid #cec2e5; +} +.w2ui-cal-time .w2ui-cal-column span.w2ui-blocked { + pointer-events: none; + text-decoration: line-through; + color: silver; +} +/************************************************* +* ---- Forms ---- +*/ +.w2ui-form { + position: relative; + color: black; + background-color: #fcfcfb; + border: 1px solid #e1e1e1; + border-radius: 3px; + padding: 0px; + overflow: hidden; +} +.w2ui-form > .w2ui-form-box { + position: absolute; + overflow: hidden; + top: 0; + bottom: 0; + left: 0; + right: 0; +} +.w2ui-form .w2ui-form-header { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 36px; + padding: 10px; + overflow: hidden; + font-size: 16px; + color: #444; + background-color: #ffffff; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + border-bottom: 1px solid #f1f1f1; +} +.w2ui-form .w2ui-form-toolbar { + position: absolute; + left: 0; + right: 0; + margin: 0; + padding: 2px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border-bottom: 1px solid #f1f1f1; +} +.w2ui-form .w2ui-form-tabs { + position: absolute; + left: 0; + right: 0; + margin: 0; + padding: 0; + height: 32px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + padding-top: 4px; + background-color: #fff; +} +.w2ui-form .w2ui-form-tabs .w2ui-tab.active { + background-color: #fcfcfb; +} +.w2ui-form .w2ui-page { + position: absolute; + left: 0; + right: 0; + overflow: auto; + padding: 10px 5px 0 5px; + border-left: 1px solid inherit; + border-right: 1px solid inherit; + background-color: inherit; + border-radius: 3px; +} +.w2ui-form .w2ui-column-container { + display: flex; + padding: 0; +} +.w2ui-form .w2ui-column-container .w2ui-column { + width: 100%; +} +.w2ui-form .w2ui-column-container .w2ui-column.col-0, +.w2ui-form .w2ui-column-container .w2ui-column.col-1, +.w2ui-form .w2ui-column-container .w2ui-column.col-2, +.w2ui-form .w2ui-column-container .w2ui-column.col-3, +.w2ui-form .w2ui-column-container .w2ui-column.col-4, +.w2ui-form .w2ui-column-container .w2ui-column.col-5, +.w2ui-form .w2ui-column-container .w2ui-column.col-6, +.w2ui-form .w2ui-column-container .w2ui-column.col-7, +.w2ui-form .w2ui-column-container .w2ui-column.col-8, +.w2ui-form .w2ui-column-container .w2ui-column.col-9, +.w2ui-form .w2ui-column-container .w2ui-column.col-10 { + padding: 0; + padding-left: 10px; +} +.w2ui-form .w2ui-column-container .w2ui-column.col-0 { + padding-left: 0px; +} +.w2ui-form .w2ui-buttons { + position: absolute; + left: 0; + right: 0; + bottom: 0; + text-align: center; + border-top: 1px solid #f1f1f1; + border-bottom: 0px solid #f1f1f1; + background-color: #fff; + padding: 15px 0px; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; +} +.w2ui-form .w2ui-buttons input[type="button"], +.w2ui-form .w2ui-buttons button { + min-width: 80px; + margin-right: 5px; +} +.w2ui-form input[type=checkbox]:not(.w2ui-toggle), +.w2ui-form input[type=radio] { + margin-top: 4px; + margin-bottom: 4px; + width: 14px; + height: 14px; +} +.w2ui-group-title { + padding: 5px 2px 0px 5px; + color: #656164cc; + text-shadow: 1px 1px 2px #fdfdfd; + font-size: 120%; +} +.w2ui-group-fields { + background-color: #fff; + margin: 5px 0px 14px 0px; + padding: 10px 5px; + border-top: 1px dotted #e1e1e1; + border-bottom: 1px dotted #e1e1e1; +} +.w2ui-field > label { + display: block; + float: left; + margin-top: 10px; + margin-bottom: 0px; + width: 120px; + padding: 0px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: right; + min-height: 20px; + color: #666; +} +.w2ui-field > div { + /* do not include width */ + margin-bottom: 3px; + margin-left: 128px; + padding: 4px; + min-height: 28px; + float: none; +} +.w2ui-field.w2ui-required > div { + position: relative; +} +.w2ui-field.w2ui-required:not(.w2ui-field-inline) > div::before { + content: '*'; + position: absolute; + margin-top: 7px; + margin-left: -8px; + color: red; +} +.w2ui-field.w2ui-required.w2ui-field-inline > div::before { + content: '' !important; +} +.w2ui-field.w2ui-disabled { + opacity: 0.45; + background-color: transparent !important; +} +.w2ui-field.w2ui-disabled input:not([type=button]):not([type=submit]):not([type=checkbox]):not([type=radio]), +.w2ui-field.w2ui-disabled select, +.w2ui-field.w2ui-disabled textarea { + border: 1px solid #bdc0c3 !important; + background-color: #f5f5f5 !important; +} +.w2ui-field.w2ui-span-none > label { + margin: 0; + padding: 5px 12px 0 4px; + display: block; + width: 98%; + text-align: left; +} +.w2ui-field.w2ui-span-none > div { + margin-left: 0; +} +.w2ui-field.w2ui-span0 > label { + display: none; +} +.w2ui-field.w2ui-span0 > div { + margin-left: 0; +} +.w2ui-field.w2ui-span1 > label { + width: 20px; +} +.w2ui-field.w2ui-span1 > div { + margin-left: 28px; +} +.w2ui-field.w2ui-span2 > label { + width: 40px; +} +.w2ui-field.w2ui-span2 > div { + margin-left: 48px; +} +.w2ui-field.w2ui-span3 > label { + width: 60px; +} +.w2ui-field.w2ui-span3 > div { + margin-left: 68px; +} +.w2ui-field.w2ui-span4 > label { + width: 80px; +} +.w2ui-field.w2ui-span4 > div { + margin-left: 88px; +} +.w2ui-field.w2ui-span5 > label { + width: 100px; +} +.w2ui-field.w2ui-span5 > div { + margin-left: 108px; +} +.w2ui-field.w2ui-span6 > label { + width: 120px; +} +.w2ui-field.w2ui-span6 > div { + margin-left: 128px; +} +.w2ui-field.w2ui-span7 > label { + width: 140px; +} +.w2ui-field.w2ui-span7 > div { + margin-left: 148px; +} +.w2ui-field.w2ui-span8 > label { + width: 160px; +} +.w2ui-field.w2ui-span8 > div { + margin-left: 168px; +} +.w2ui-field.w2ui-span9 > label { + width: 180px; +} +.w2ui-field.w2ui-span9 > div { + margin-left: 188px; +} +.w2ui-field.w2ui-span10 > label { + width: 200px; +} +.w2ui-field.w2ui-span10 > div { + margin-left: 208px; +} +.w2ui-field.w2ui-field-inline { + display: inline; +} +.w2ui-field.w2ui-field-inline > div { + display: inline; + margin: 0; + padding: 0; +} +.w2ui-field .w2ui-box-label { + user-select: none; + vertical-align: middle; +} +.w2ui-field .w2ui-box-label span, +.w2ui-field .w2ui-box-label input { + display: inline-block; + vertical-align: middle; +} +.w2ui-field .w2ui-box-label span { + padding-left: 3px; +} +.w2ui-field .w2ui-box-label input { + margin: 4px 0px 3px 0; +} +input:not([type=button]):not([type=submit]):not([type=checkbox]):not([type=radio]).w2ui-error, +textarea.w2ui-error { + border: 1px solid #FFA8A8; + background-color: #FFF4EB; +} +.w2field { + padding: 3px; + border-radius: 3px; + border: 1px solid silver; +} +.w2ui-field-helper { + position: absolute; + display: inline-block; + line-height: 100%; + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +.w2ui-field-helper .w2ui-field-up { + position: absolute; + top: 0px; + padding: 2px 3px; + cursor: pointer; + pointer-events: all; +} +.w2ui-field-helper .w2ui-field-down { + position: absolute; + bottom: 0px; + padding: 2px 3px; + cursor: pointer; + pointer-events: all; +} +.w2ui-field-helper .arrow-up:hover { + border-bottom-color: #81C6FF; +} +.w2ui-field-helper .arrow-down:hover { + border-top-color: #81C6FF; +} +.w2ui-field-helper .w2ui-icon-search { + position: absolute; + margin: 8px 0px 0px -2px; + display: none; + color: #777777; + width: 21px !important; + font-size: 13px; +} +.w2ui-field-helper .w2ui-icon-search.show-search { + display: block; +} +.w2ui-field-helper.w2ui-list { + color: inherit; + position: absolute; + padding: 0px; + margin: 0px; + min-height: 28px; + overflow: auto; + border: 1px solid #e0e0e0; + border-radius: 3px; + font-size: 6px; + line-height: 100%; + box-sizing: border-box; + pointer-events: all; + background-color: #f7fafa; +} +.w2ui-field-helper.w2ui-list.has-focus, +.w2ui-field-helper.w2ui-list:focus-within { + outline: auto #72B2FF; + background-color: white; +} +.w2ui-field-helper.w2ui-list input[type=text] { + -webkit-box-shadow: none; + -moz-box-shadow: none; + -ms-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items { + position: absolute; + display: inline-block; + margin: 0px; + padding: 0px; + pointer-events: none; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item { + pointer-events: all; + float: left; + margin: 3px 0px 0px 5px; + border-radius: 15px; + width: auto; + padding: 3px 24px 1px 12px; + border: 1px solid #b4d0de; + background-color: #EFF3F5; + white-space: nowrap; + cursor: default; + font-family: OpenSans; + font-size: 11px; + line-height: 100%; + height: 20px; + overflow: hidden; + text-overflow: ellipsis; + box-sizing: border-box; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item:hover { + background-color: #d0dbe1; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item:last-child { + border-radius: 0px; + border: 1px solid transparent; + background-color: transparent; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item:last-child input { + padding: 1px; + padding-top: 0px; + margin: 0px; + border: 0px; + outline: none; + height: auto; + line-height: 100%; + font-size: inherit; + font-family: inherit; + background-color: transparent; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item .w2ui-icon { + float: left; + color: #828aa7; + margin: 1px 2px 0 -6px; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item .w2ui-list-remove { + float: right; + width: 16px; + height: 16px; + margin: -2px -20px 0px 0px; + border-radius: 2px; + font-size: 12px; + border: 1px solid transparent; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item .w2ui-list-remove:hover { + background-color: #f6e5e5; + border: 1px solid #fac2c2; + color: red; + opacity: 1; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item .w2ui-list-remove:before { + position: relative; + display: inline-block; + left: 4px; + opacity: 0.7; + content: 'x'; + line-height: 1; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item > span.file-size { + pointer-events: none; + color: #777; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-search { + float: left; + margin: 0; + padding: 0; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-search input[type=text] { + pointer-events: all; + width: 0; + height: 20px; + padding: 3px 0 3px 0; + margin: 3px 0 0 5px; + border: 0; + background-color: transparent; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-search input[type=text]:focus { + outline: none; + border: 0; +} +.w2ui-field-helper.w2ui-list .w2ui-multi-file { + position: absolute; + left: 0px; + right: 0px; + top: 0px; + bottom: 0px; +} +.w2ui-field-helper.w2ui-list.w2ui-readonly .w2ui-multi-items > .li-item:hover { + background-color: #EFF3F5; +} +.w2ui-field-helper.w2ui-list.w2ui-file-dragover { + background-color: #E4FFDA; + border: 1px solid #93E07D; +} +.w2ui-field-helper.w2ui-list .w2ui-enum-placeholder { + display: inline; + position: absolute; + pointer-events: none; + color: #999; + box-sizing: border-box; +} +.w2ui-overlay .w2ui-file-preview { + padding: 1px; + background-color: white; +} +.w2ui-overlay .w2ui-file-info { + display: grid; + grid-template-columns: 1fr 2fr; + color: white; + padding: 6px 0; +} +.w2ui-overlay .w2ui-file-info .file-caption { + text-align: right; + color: silver; + padding-right: 10px; +} +.w2ui-overlay .w2ui-file-info .file-value { + color: white; +} +.w2ui-overlay .w2ui-file-info .file-type { + max-width: 200px; + display: block-inline; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.arrow-up { + background: none; + width: 0; + height: 0; + border-left: 4px solid transparent; + /* left arrow slant */ + border-right: 4px solid transparent; + /* right arrow slant */ + border-bottom: 5px solid #777; + /* bottom, add background color here */ + font-size: 0; + line-height: 0; +} +.arrow-down { + background: none; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid #777; + font-size: 0; + line-height: 0; +} +.arrow-left { + background: none; + width: 0; + height: 0; + border-bottom: 4px solid transparent; + /* left arrow slant */ + border-top: 4px solid transparent; + /* right arrow slant */ + border-right: 5px solid #777; + /* bottom, add background color here */ + font-size: 0; + line-height: 0; +} +.arrow-right { + background: none; + width: 0; + height: 0; + border-bottom: 4px solid transparent; + /* left arrow slant */ + border-top: 4px solid transparent; + /* right arrow slant */ + border-left: 5px solid #777; + /* bottom, add background color here */ + font-size: 0; + line-height: 0; +} +.w2ui-select { + cursor: default; + color: black !important; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAALCAQAAACnzwd+AAAAcklEQVR4AcXMsQFBQQDG4P9tAgC0gJYRQJZgKQMwCqCku6vVAAAA+NJHP4KHOk0aV2pRw61n4BBmyOxKQ8I4ehZeuhd3HTx6DQEGZ7sBfr2OOOOj3Yi43kMKs9sZknofOexqZ8npMygwWZTX51CipP+YA1OiZJbYYg9lAAAAAElFTkSuQmCC'); + background-size: 17px 6px; + background-position: right center; + background-repeat: no-repeat; +} +.w2ui-select.has-focus { + outline: auto #72B2FF; + background-color: white !important; +} +.w2ui-select[readonly], +.w2ui-select[disabled] { + background-image: none; + background-color: #f1f1f1 !important; + color: #777 !important; +} +/************************************************* +* ---- Layout ---- +*/ +.w2ui-layout { + position: relative; + overflow: hidden; +} +.w2ui-layout > div { + position: absolute; + overflow: hidden; + left: 0; + top: 0; + right: 0; + bottom: 0; +} +.w2ui-layout > div .w2ui-panel { + display: none; + position: absolute; + z-index: 120; +} +.w2ui-layout > div .w2ui-panel .w2ui-panel-title { + position: absolute; + left: 0px; + top: 0px; + right: 0px; + padding: 5px; + background-color: #fff; + color: #656164cc; + border: 1px solid #efefef; + border-bottom: 1px solid #f5f5f5; +} +.w2ui-layout > div .w2ui-panel .w2ui-panel-tabs { + position: absolute; + left: 0px; + top: 0px; + right: 0px; + z-index: 2; + display: none; + overflow: hidden; + background-color: #fff; + padding: 0px; +} +.w2ui-layout > div .w2ui-panel .w2ui-panel-tabs > .w2ui-tab.active { + background-color: #fcfcfc; +} +.w2ui-layout > div .w2ui-panel .w2ui-panel-toolbar { + position: absolute; + left: 0px; + top: 0px; + right: 0px; + z-index: 2; + display: none; + overflow: hidden; + background-color: #FAFAFA; + border-bottom: 1px solid #efefef; + padding: 2px; +} +.w2ui-layout > div .w2ui-panel .w2ui-panel-content { + position: absolute; + left: 0px; + top: 0px; + right: 0px; + bottom: 0px; + z-index: 1; + color: inherit; + background-color: #fcfcfc; +} +.w2ui-layout > div .w2ui-resizer { + display: none; + position: absolute; + z-index: 121; + background-color: transparent; +} +.w2ui-layout > div .w2ui-resizer:hover, +.w2ui-layout > div .w2ui-resizer.active { + background-color: #C8CAD1; +} +/************************************************* +* ---- Grid ---- +*/ +.w2ui-grid { + position: relative; + border: 1px solid #e1e1e1; + border-radius: 2px; + overflow: hidden !important; +} +.w2ui-grid > .w2ui-grid-box { + position: absolute; + overflow: hidden; + left: 0; + top: 0; + right: 0; + bottom: 0; +} +.w2ui-grid .w2ui-grid-header { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 36px; + padding: 10px; + overflow: hidden; + font-size: 16px; + color: #444; + background-color: #fff; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + border-bottom: 1px solid #e1e1e1 !important; +} +.w2ui-grid .w2ui-grid-toolbar { + position: absolute; + border-bottom: 1px solid #efefef; + background-color: #fafafa; + height: 52px; + padding: 9px 3px 0px 3px ; + margin: 0px; + box-shadow: 0px 1px 2px #f5f5f5; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-tb-button .w2ui-tb-icon { + margin: 3px 0px 0px 0px!important; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input { + position: relative; + width: 300px; + left: 0px; + top: -4px; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-down { + position: absolute; + top: 7px; + left: 4px; + color: #8c99a7; + font-size: 13px; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-grid-search-name { + position: absolute; + margin: 5px 0px 0px 3px; + padding: 4px 27px 4px 10px; + background-color: #fbfbfb; + border: 1px solid #b9b9b9; + border-radius: 15px; + pointer-events: none; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-grid-search-name .name-icon { + position: absolute; + margin-left: -6px; + color: #8c99a7; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-grid-search-name .name-text { + padding-left: 14px; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-grid-search-name .name-cross { + position: absolute; + margin-top: -4px; + margin-left: 7px; + padding: 4px 5px; + pointer-events: all; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-grid-search-name .name-cross:hover { + color: red; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-all { + outline: none !important; + border-radius: 4px !important; + line-height: normal !important; + height: 30px !important; + width: 300px !important; + border: 1px solid #e1e1e1 !important; + color: #000 !important; + background-color: #f1f1f1 !important; + padding: 1px 28px 0px 28px !important; + margin: 0px !important; + margin-top: 1px !important; + font-size: 13px !important; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-all:focus { + border: 1px solid #007cff !important; + background-color: white !important; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-drop { + position: absolute; + right: 2px; + top: 3px; + height: 26px; + width: 26px; + font-size: 16px; + color: #a4adb1; + cursor: pointer; + padding: 7px 2px 7px 2px; + border-radius: 4px; + background-color: transparent; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-drop span.w2ui-icon-drop { + position: relative; + top: -2px; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-drop:hover, +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-drop.checked { + color: #fff; + background-color: #56a1e2; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + border-top: 1px solid #ececec; + border-bottom: 1px solid #ececec; + background-color: #fcfdff; + margin: 7px -20px 0px -20px; + padding: 6px 50px 6px 20px; + height: 36px; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches > div { + white-space: nowrap; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches > span { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + border: 1px solid #88c3f7; + border-radius: 15px; + padding: 4px 12px; + margin: 0px 4px; + color: #4c9ad6; + font-size: 12px; + font-weight: bold; + background-color: #F5F9FE; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches > span > span { + font-size: 9px; + position: relative; + top: -1px; + left: 2px; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches .grid-search-line { + border-left: 1px solid #ececec; + width: 11px; + height: 22px; + margin-left: 7px; + margin-top: 1px; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches .w2ui-grid-search-logic { + border: 1px solid #c8c9ca !important; + color: #676767 !important; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches button.grid-search-btn { + margin: 0px 3px; + padding: 0px; + height: 24px; + font-size: 11px; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches button.grid-search-btn.btn-remove { + min-width: 26px; + position: absolute; + left: calc(100% - 35px); +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches .grid-search-count { + background-color: #4cb1fd; + border-radius: 10px; + color: white; + padding: 0px 6px 1px 6px; + font-size: 11px !important; + position: relative !important; + top: 0px !important; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches .grid-search-list li { + padding: 5px; +} +.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches .grid-search-list input { + position: relative; + top: 2px; + left: -3px; +} +.w2ui-grid .w2ui-grid-save-search { + padding-top: 30px; + text-align: center; +} +.w2ui-grid .w2ui-grid-save-search span { + width: 280px; + display: inline-block; + text-align: left; + padding-bottom: 4px; +} +.w2ui-grid .w2ui-grid-save-search .search-name { + width: 280px !important; +} +.w2ui-grid .w2ui-grid-body { + position: absolute; + overflow: hidden; + padding: 0px; + background-color: white; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +.w2ui-grid .w2ui-grid-body input, +.w2ui-grid .w2ui-grid-body select, +.w2ui-grid .w2ui-grid-body textarea { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + -o-user-select: text; + user-select: text; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-columns, +.w2ui-grid .w2ui-grid-body .w2ui-grid-fcolumns { + overflow: hidden; + position: absolute; + left: 0px; + top: 0px; + right: 0px; + box-shadow: 0px 1px 4px #efefef; + height: auto; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-columns table, +.w2ui-grid .w2ui-grid-body .w2ui-grid-fcolumns table { + height: auto; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-columns .w2ui-resizer, +.w2ui-grid .w2ui-grid-body .w2ui-grid-fcolumns .w2ui-resizer { + position: absolute; + z-index: 1000; + display: block; + background-image: none; + background-color: rgba(0, 0, 0, 0); + /* needed for IE */ + padding: 0px; + margin: 0px; + width: 6px; + height: 12px; + cursor: ew-resize; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords { + position: absolute; + left: 0px; + right: 0px; + top: 0px; + bottom: 0px; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd { + color: inherit; + background-color: #fff; + border-bottom: 1px solid #f5f5f5; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd.w2ui-record-hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd.w2ui-record-hover { + color: inherit; + background-color: #f3f3f3; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd.w2ui-empty-record:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd.w2ui-empty-record:hover { + background-color: #fff; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even { + color: inherit; + background-color: #fbfbfb; + border-bottom: 1px dotted #f5f5f5; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even.w2ui-record-hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even.w2ui-record-hover { + color: inherit; + background-color: #f3f3f3; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even.w2ui-empty-record:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even.w2ui-empty-record:hover { + background-color: #fbfbfb; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-selected, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-selected, +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr td.w2ui-selected, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr td.w2ui-selected { + color: black !important; + background-color: #d9eaff !important; + border-bottom: 1px solid transparent; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-inactive, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-inactive, +.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr td.w2ui-inactive, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr td.w2ui-inactive { + background-color: #e8edf5 !important; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-expanded1, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-expanded1 { + height: 0px; + border-bottom: 1px solid #b2bac0; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-expanded1 > div, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-expanded1 > div { + height: 0px; + border: 0px; + transition: height 0.3s, opacity 0.3s; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-expanded2, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-expanded2 { + height: 0px; + border-radius: 0px; + border-bottom: 1px solid #b2bac0; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-expanded2 > div, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-expanded2 > div { + height: 0px; + border: 0px; + transition: height 0.3s, opacity 0.3s; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-load-more, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-load-more { + cursor: pointer; + background-color: rgba(233, 237, 243, 0.5); + border-right: 1px solid #f1f1f1; + height: 43px; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-load-more > div, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-load-more > div { + text-align: center; + color: #777; + background-color: rgba(233, 237, 243, 0.5); + padding: 10px 0px 15px 0px; + height: 43px; + border-top: 1px dashed #D6D5D7; + border-bottom: 1px dashed #D6D5D7; + font-size: 12px; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-load-more > div:hover, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-load-more > div:hover { + color: #438ba2; + background-color: #f3f3f3; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-reoder-empty, +.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-reoder-empty { + background-color: #eee; + border-bottom: 1px dashed #aaa; + border-top: 1px dashed #aaa; +} +.w2ui-grid .w2ui-grid-body table { + border-spacing: 0px; + border-collapse: collapse; + table-layout: fixed; + width: 1px; +} +.w2ui-grid .w2ui-grid-body table .w2ui-head { + margin: 0px; + padding: 0px; + border-right: 1px solid #dcdcdc; + border-bottom: 1px solid #dcdcdc; + color: #656164; + background-image: linear-gradient(#ffffff, #f9f9f9); +} +.w2ui-grid .w2ui-grid-body table .w2ui-head > div { + padding: 7px 6px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + position: relative; +} +.w2ui-grid .w2ui-grid-body table td { + border-right: 1px solid #f1f1f1; + border-bottom: 0px solid #D6D5D7; + cursor: default; + overflow: hidden; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-soft-span, +.w2ui-grid .w2ui-grid-body table td.w2ui-soft-hidden { + border-right-color: transparent; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data { + margin: 0px; + padding: 0px; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data .w2ui-info { + position: relative; + top: 2px; + left: -1px; + font-size: 13px; + color: #8D99A7; + cursor: pointer; + width: 18px; + display: inline-block; + margin-right: 3px; + text-align: center; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data .w2ui-clipboard-copy { + float: right; + margin-top: -15px; + width: 20px; + height: 16px; + padding: 0px; + text-align: center; + cursor: pointer; + font-size: 13px; + color: #8d98a7; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data .w2ui-clipboard-copy:hover { + color: #545961; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data > div { + padding: 5px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data > div.flexible-record { + height: auto; + overflow: visible; + white-space: normal; +} +.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data .w2ui-show-children { + width: 16px; + height: 10px; + display: inline-block; + position: relative; + top: -1px; + cursor: pointer; +} +.w2ui-grid .w2ui-grid-body table td:last-child { + border-right: 0px; +} +.w2ui-grid .w2ui-grid-body table td:last-child div { + text-overflow: clip; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-number { + width: 34px; + color: #777; + background-color: rgba(233, 237, 243, 0.5); +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-number div { + padding: 0px 7px 0px 3px; + text-align: right; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-number.w2ui-head { + cursor: pointer; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-select { + width: 26px; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-select div { + padding: 0px 0px; + text-align: center; + overflow: hidden; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-select div input[type=checkbox] { + margin-top: 3px; + margin-bottom: 0px; + position: relative; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-expand { + width: 26px; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-expand div { + padding: 0px 0px; + text-align: center; + font-weight: bold; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-order { + width: 26px; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-order.w2ui-grid-data div { + cursor: move; + height: 18px; + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxyZWN0IHN0eWxlPSJmaWxsOiAjYWFhOyIgeD0iMCIgeT0iNCIgaGVpZ2h0PSIzIiB3aWR0aD0iMTYiPjwvcmVjdD4KICA8cmVjdCBzdHlsZT0iZmlsbDogI2FhYTsiIHg9IjAiIHk9IjkiIGhlaWdodD0iMyIgd2lkdGg9IjE2Ij48L3JlY3Q+Cjwvc3ZnPg=="); + background-position: 5px 2px; + background-size: 14px 12px; + background-repeat: no-repeat; +} +.w2ui-grid .w2ui-grid-body table .w2ui-col-selected { + background-color: #d1d1d1 !important; +} +.w2ui-grid .w2ui-grid-body table .w2ui-row-selected { + background-color: #e2e2e2 !important; +} +.w2ui-grid .w2ui-grid-body .w2ui-intersection-marker { + position: absolute; + top: 0; + left: 0; + margin-left: -5px; + height: 26px; + width: 10px; +} +.w2ui-grid .w2ui-grid-body .w2ui-intersection-marker.left { + left: 0; + margin-left: -5px; +} +.w2ui-grid .w2ui-grid-body .w2ui-intersection-marker.right { + right: 0; + margin-right: 5px; +} +.w2ui-grid .w2ui-grid-body .w2ui-intersection-marker .top-marker { + position: absolute; + top: 0; + height: 0; + width: 0; + border-top: 5px solid #72B2FF; + border-left: 5px solid transparent; + border-right: 5px solid transparent; +} +.w2ui-grid .w2ui-grid-body .w2ui-intersection-marker .bottom-marker { + position: absolute; + bottom: 0; + height: 0; + width: 0; + border-bottom: 5px solid #72B2FF; + border-left: 5px solid transparent; + border-right: 5px solid transparent; +} +.w2ui-grid .w2ui-grid-body div.w2ui-col-header { + height: auto !important; + width: 100%; + overflow: hidden; + padding-right: 10px !important; +} +.w2ui-grid .w2ui-grid-body div.w2ui-col-header > div.w2ui-sort-up { + border: 4px solid transparent; + border-bottom: 5px solid #8D99A7; + margin-top: -2px; + margin-right: -7px; + float: right; +} +.w2ui-grid .w2ui-grid-body div.w2ui-col-header > div.w2ui-sort-down { + border: 4px solid transparent; + border-top: 5px solid #8D99A7; + margin-top: 2px; + margin-right: -7px; + float: right; +} +.w2ui-grid .w2ui-grid-body .w2ui-col-group { + text-align: center; +} +.w2ui-grid .w2ui-grid-body .w2ui-grid-scroll1 { + position: absolute; + left: 0px; + bottom: 0px; + border-top: 1px solid #ddd; + border-right: 1px solid #ddd; + background-color: #FAFAFA; +} +.w2ui-grid .w2ui-grid-empty-msg { + position: absolute; + top: 27px; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(255, 255, 255, 0.65); +} +.w2ui-grid .w2ui-grid-empty-msg > div { + position: absolute; + left: 0; + right: 0; + top: 45%; + transform: translateY(-45%); + text-align: center; + font-size: 13px; + color: #666; +} +.w2ui-grid .w2ui-changed { + background: url(data:image/gif;base64,R0lGODlhCgAKAJEAALAABf///wAAAAAAACH5BAEAAAIALAAAAAAKAAoAAAIPlI8Hy8mbxIsSUnup3rQAADs=) no-repeat top right; +} +.w2ui-grid .w2ui-edit-box { + position: absolute; + z-index: 1001; + border: 1.5px solid #6299DA; + pointer-events: auto; + padding: 2px !important; + margin: 0px !important; + background-color: white; +} +.w2ui-grid .w2ui-edit-box .w2ui-editable div.w2ui-input { + outline: none; + padding: 0.5px 1.5px !important; +} +.w2ui-grid .w2ui-edit-box .w2ui-editable input { + top: -2px !important; + padding: 1.5px !important; +} +.w2ui-grid .w2ui-editable { + overflow: hidden; + height: 100% !important; + margin: 0 !important; + padding: 3.5px 2px 2px 2px !important; +} +.w2ui-grid .w2ui-editable input { + position: relative; + top: -1px; + border: 0 !important; + border-radius: 0 !important; + border-color: transparent !important; + padding: 3px !important; + display: inline-block; + width: 100% !important; + height: 100% !important; + pointer-events: auto !important; +} +.w2ui-grid .w2ui-editable div.w2ui-input { + position: relative; + top: -0.5px; + border: 0 transparent; + border-radius: 0 !important; + margin: 0px !important; + padding: 5px 3px !important; + display: inline-block; + width: 100% !important; + height: 100% !important; + pointer-events: auto !important; + background-color: white; + white-space: pre; + overflow: hidden; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + -o-user-select: text; + user-select: text; +} +.w2ui-grid .w2ui-editable input.w2ui-select { + outline: none !important; + background: #fff; +} +.w2ui-grid .w2ui-grid-summary { + position: absolute; + border-top: 1px solid gainsboro; + box-shadow: 0px -1px 4px #f0eeee; +} +.w2ui-grid .w2ui-grid-summary table { + color: inherit; +} +.w2ui-grid .w2ui-grid-summary table .w2ui-odd { + background-color: #fff; +} +.w2ui-grid .w2ui-grid-summary table .w2ui-even { + background-color: #fbfbfb; +} +.w2ui-grid .w2ui-grid-footer { + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0px; + padding: 0px; + text-align: center; + font-size: 11px; + height: 24px; + overflow: hidden; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + -o-user-select: text; + user-select: text; + box-shadow: 0px -1px 4px #f5f5f5; + color: #444; + background-color: #F8F8F8; + border-top: 1px solid #e4e4e4; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-left { + float: left; + padding-top: 5px; + padding-left: 5px; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-right { + float: right; + padding-top: 5px; + padding-right: 5px; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-center { + padding: 2px; + text-align: center; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-center .w2ui-footer-nav { + width: 110px; + margin: 0 auto; + padding: 0px; + text-align: center; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-center .w2ui-footer-nav input[type=text] { + padding: 1px 2px 2px 2px; + border-radius: 3px; + width: 40px; + text-align: center; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-center .w2ui-footer-nav a.w2ui-footer-btn { + display: inline-block; + border-radius: 3px; + cursor: pointer; + font-size: 11px; + line-height: 16px; + padding: 1px 5px; + width: 30px; + height: 18px; + margin-top: -1px; + color: black; + background-color: transparent; +} +.w2ui-grid .w2ui-grid-footer .w2ui-footer-center .w2ui-footer-nav a.w2ui-footer-btn:hover { + color: #000; + background-color: #AEC8FF; +} +.w2ui-grid .w2ui-grid-focus-input { + position: absolute; + top: 0; + right: 0; + z-index: -1; + opacity: 0; + overflow: hidden; + padding: 0px; + margin: 0px; + width: 1px; + height: 1px; + resize: none; + border: 0px; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr td.w2ui-selected { + background-color: #EEF4FE !important; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr td.w2ui-inactive { + background-color: #F4F6F9 !important; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table td { + border-right-width: 1px; + border-bottom: 1px solid #efefef; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd, +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even, +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd:hover, +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even:hover { + background-color: inherit; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr:first-child td { + border-top: 0px; + border-bottom: 0px; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr td.w2ui-selected { + background-color: #EEF4FE !important; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr td.w2ui-inactive { + background-color: #F4F6F9 !important; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table td { + border-right-width: 1px; + border-bottom: 1px solid #efefef; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd, +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even, +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd:hover, +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even:hover { + background-color: inherit; +} +.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr:first-child td { + border-bottom: 0px; +} +.w2ui-ss .w2ui-grid-body .w2ui-selection { + position: absolute; + z-index: 1000; + border: 1.5px solid #6299DA; + /* #457FC2; */ + pointer-events: none; +} +.w2ui-ss .w2ui-grid-body .w2ui-selection .w2ui-selection-resizer { + cursor: crosshair; + position: absolute; + bottom: 0px; + right: 0px; + width: 6px; + height: 6px; + margin-right: -3px; + margin-bottom: -3px; + background-color: #457FC2; + border: 0.5px solid #fff; + outline: 1px solid white; + pointer-events: auto; +} +.w2ui-ss .w2ui-grid-body .w2ui-selection.w2ui-inactive { + border: 1.5px solid #C0C2C5; +} +.w2ui-ss .w2ui-grid-body .w2ui-selection.w2ui-inactive .w2ui-selection-resizer { + background-color: #B0B0B0; +} +.w2ui-ss .w2ui-grid-body .w2ui-soft-range { + position: absolute; + pointer-events: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.w2ui-ss .w2ui-grid-body .w2ui-changed { + background: inherit; + /* do not show changed for spreadsheet */ +} +.w2ui-ss .w2ui-grid-body .w2ui-editable input { + outline: none !important; +} +.w2ui-info-bubble table { + font-family: OpenSans; + font-size: 12px; + color: white; + text-shadow: 1px 1px solid #999; +} +.w2ui-info-bubble table tr td:first-child { + white-space: nowrap; + padding: 2px; + padding-right: 10px; + color: #ddd; + vertical-align: top; +} +.w2ui-info-bubble table tr td:last-child { + white-space: pre; + padding: 2px; +} +.w2ui-overlay .w2ui-grid-search-suggest { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + padding: 10px; + background-color: white; + border-bottom: 1px solid #e6e6e6; + color: #444444; +} +.w2ui-overlay .w2ui-grid-search-single { + font-size: 12px; + padding-top: 10px; +} +.w2ui-overlay .w2ui-grid-search-single .field { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + border: 1px solid #a9b6c2; + border-radius: 4px; + padding: 4px 12px; + margin: 0 2px; + color: #4295d4; + background-color: #F5F9FE; +} +.w2ui-overlay .w2ui-grid-search-single .operator { + display: inline-block; + color: black; + background-color: #e6e6e6; + border-radius: 4px; + margin: 0 4px; + padding: 6px 10px; +} +.w2ui-overlay .w2ui-grid-search-single .value { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + border: 1px solid #a9b6c2; + border-radius: 4px; + margin: 0 2px; + padding: 4px 12px; +} +.w2ui-overlay .w2ui-grid-search-single .buttons { + text-align: left; + padding: 15px 10px 10px 0px; +} +.w2ui-overlay .w2ui-grid-search-advanced { + text-align: left; + padding: 0px; + background-color: #fff; + text-shadow: none; + border: 1px solid #cdcdd8; + box-shadow: 0px 3px 14px 1px #e8e8e8; +} +.w2ui-overlay .w2ui-grid-search-advanced .search-title { + padding: 20px 0px 9px 20px; + font-size: 17px; + font-weight: bold; + color: #555; +} +.w2ui-overlay .w2ui-grid-search-advanced .search-title .search-logic { + float: right; + padding-right: 10px; +} +.w2ui-overlay .w2ui-grid-search-advanced table { + color: #5f5f5f; + font-size: 13px; + padding: 12px 4px 0 4px; +} +.w2ui-overlay .w2ui-grid-search-advanced table td { + padding: 4px; + min-height: 40px; +} +.w2ui-overlay .w2ui-grid-search-advanced table td.caption { + text-align: right; + padding-right: 5px; + padding-left: 20px; +} +.w2ui-overlay .w2ui-grid-search-advanced table td.operator { + text-align: left; + padding: 5px; +} +.w2ui-overlay .w2ui-grid-search-advanced table td.operator select { + width: 100%; + color: black; +} +.w2ui-overlay .w2ui-grid-search-advanced table td.value { + padding-right: 5px; + padding-left: 5px; +} +.w2ui-overlay .w2ui-grid-search-advanced table td.value input[type=text] { + border-radius: 3px; + padding: 5px; + margin-right: 3px; + height: 28px; +} +.w2ui-overlay .w2ui-grid-search-advanced table td.value select { + padding: 0px 20px 5px 5px; + margin-right: 3px; + height: 28px; +} +.w2ui-overlay .w2ui-grid-search-advanced table td.actions:nth-child(1) { + padding: 25px 10px 10px 10px; + text-align: left; +} +.w2ui-overlay .w2ui-grid-search-advanced table td.actions:nth-child(2) { + padding: 25px 10px 10px 10px; + text-align: right; + background-color: #fff; +} +.w2ui-grid-skip { + width: 50px; + margin: -6px 3px; + padding: 3px !important; +} +.w2ui-popup { + position: fixed; + z-index: 1600; + overflow: hidden; + font-family: OpenSans; + border-radius: 6px; + padding: 0px; + margin: 0px; + border: 1px solid #777; + background-color: #fafafa; + box-shadow: 0px 0px 25px #555; +} +.w2ui-popup, +.w2ui-popup * { + box-sizing: border-box; +} +.w2ui-popup.w2ui-anim-open { + opacity: 0; + transform: scale(0.8); +} +.w2ui-popup.w2ui-anim-close { + opacity: 0; + transform: scale(0.9); +} +.w2ui-popup .w2ui-popup-title-btns { + float: right; + margin: 10px 10px 0 0; + font-size: 17px; +} +.w2ui-popup .w2ui-popup-title-btns .w2ui-popup-button { + float: right; + width: 25px; + height: 23px; + cursor: pointer; + color: #888; + margin: 0 0 0 2px; + z-index: 301; + position: relative; +} +.w2ui-popup .w2ui-popup-title-btns .w2ui-popup-button span.w2ui-icon { + width: 24px; + height: 23px; +} +.w2ui-popup .w2ui-popup-title-btns .w2ui-popup-button.w2ui-popup-close:hover { + color: #222; +} +.w2ui-popup .w2ui-popup-title-btns .w2ui-popup-button.w2ui-popup-max:hover { + color: #222; +} +.w2ui-popup .w2ui-popup-title { + padding: 10px; + border-radius: 6px 6px 0px 0px; + background-color: #fff; + border-bottom: 1px solid #eee; + position: absolute; + overflow: hidden; + height: 42px; + left: 0px; + right: 0px; + top: 0px; + text-overflow: ellipsis; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + cursor: move; + font-size: 17px; + color: #555; + z-index: 300; +} +.w2ui-popup .w2ui-box, +.w2ui-popup .w2ui-box-temp { + position: absolute; + left: 0px; + right: 0px; + top: 42px; + bottom: 58px; + z-index: 100; +} +.w2ui-popup .w2ui-popup-body { + font-size: 12px; + line-height: 130%; + padding: 0px 7px 7px 7px; + color: #000; + background-color: #fafafa; + position: absolute; + overflow: auto; + width: 100%; + height: 100%; +} +.w2ui-popup .w2ui-popup-buttons { + font-size: 11px; + padding: 14px; + border-radius: 0px 0px 6px 6px; + border-top: 1px solid #eee; + background-color: #fff; + text-align: center; + position: absolute; + overflow: hidden; + height: 56px; + left: 0px; + right: 0px; + bottom: 0px; + z-index: 200; +} +.w2ui-popup .w2ui-popup-no-title { + border-top-left-radius: 6px; + border-top-right-radius: 6px; + top: 0px; +} +.w2ui-popup .w2ui-popup-no-buttons { + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + bottom: 0px; +} +.w2ui-popup .w2ui-msg-text { + font-size: 14px; + line-height: 1.5; +} +.w2ui-popup .w2ui-prompt { + font-size: 12px; + padding: 0 10px; +} +.w2ui-popup .w2ui-prompt.textarea { + margin-top: 20px; +} +.w2ui-popup .w2ui-prompt > div { + margin-bottom: 5px; +} +.w2ui-popup .w2ui-prompt > label { + margin-right: 5px; +} +.w2ui-popup .w2ui-prompt input { + width: 230px; +} +.w2ui-popup .w2ui-prompt textarea { + width: 100%; + height: 50px; + resize: none; +} +.w2ui-message { + font-size: 12px; + position: absolute; + z-index: 250; + background-color: #fcfcfc; + border: 1px solid #999; + box-shadow: 0px 0px 15px #aaa; + box-sizing: border-box; + border-top: 0px; + border-radius: 0px 0px 6px 6px; + overflow: auto; +} +.w2ui-message .w2ui-msg-text { + font-size: 14px; + line-height: 1.5; +} +.w2ui-message .w2ui-message-body { + position: absolute; + top: 0px; + bottom: 45px; + left: 0px; + right: 0px; + overflow: auto; +} +.w2ui-message .w2ui-message-body .w2ui-centered { + line-height: 1.5; +} +.w2ui-message .w2ui-message-buttons { + position: absolute; + height: 45px; + bottom: 0px; + left: 0px; + right: 0px; + border-top: 1px solid #efefef; + background-color: white; + text-align: center; + padding: 8px; +} +/************************************************* +* ---- Sidebar ---- +*/ +.w2ui-sidebar { + position: relative; + cursor: default; + overflow: hidden; + background-color: #fbfbfb; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-sidebar * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} +.w2ui-sidebar > div { + position: absolute; + overflow: hidden; +} +.w2ui-sidebar .w2ui-sidebar-top { + position: absolute; + z-index: 2; + top: 0px; + left: 0px; + right: 0px; +} +.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-left, +.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-right { + position: absolute; + right: 2px; + top: 2px; + height: 24px; + padding: 5px; + border-radius: 2px; + background-size: 16px 12px; + background-position: center center; + background-repeat: no-repeat; + background-color: #fbfbfb; +} +.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-left:hover, +.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-right:hover { + background-color: #f1f1f1; +} +.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-left { + left: auto; + width: 25px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAzklEQVR4Ae2THRDEMBCFzy1ucatb3eJ2uhi3uNUtbnGrW9zi1rOdNzdvdl7nDpvYt/9/r7+/51myZZf/zXkD2iMHHRSb0x3oskwMieK05PwEXqP4ExSL0wp0ROao2OOuMPOMdUL6XU1/oGLcFWb+NqyTd2W/P/2qTr9h+nFXhOkHXRHiNyjrgp/U/V+WaQcaNY13zZI0A1JvcVqAnrGDTdtDtZUHjHIJhxxVLN0iqXgCP1l/7h8U9kc6abyJ4/eNWPpGdBv+XdUK0K8cnvcBly2rDr7C1HQAAAAASUVORK5CYII='); +} +.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-right { + left: 2px; + width: auto; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVR4Ae2UG7TGMBCEr1vd4la3uMUtuli3utWtbnGLW9zi9l/bDMzJG7u12cfJfLunf1+UEC9Bv0vVQwJ8hjRCaZafflb1C9RQf4OD0gSDE+i+PiJAabFhQc1y1AYYsJGLY3lgxM17uWPO56yPiFDqVPWgRtpIHSd1zPnwkBsdI58OlNwx4fP2X0TgfMTOoHSdKOXkpyNvEyQh7ul+4swxJSTQuwNDxz68l/ukVNbu0Neen5Z+KvzWxBAqHds349uPFJ/jVOrPjxUq++OLf+20q5+noXo0AAAAAElFTkSuQmCC'); +} +.w2ui-sidebar .w2ui-sidebar-bottom { + position: absolute; + z-index: 2; + bottom: 0px; + left: 0px; + right: 0px; +} +.w2ui-sidebar .w2ui-sidebar-body { + position: absolute; + z-index: 1; + overflow: auto; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + padding: 2px 0px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node { + position: relative; + border-radius: 4px; + margin: 0px 3px; + padding: 1px 0px; + border: 1px solid transparent; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-text { + color: black; + text-shadow: 0px 0px 0px #fff; + pointer-events: none; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-text:hover { + color: inherit; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-image > span { + color: #737485; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-handle { + display: inline-block; + padding: 0px; + margin: 0px; + height: 100%; + position: absolute; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node:hover { + background-color: #f1f1f1; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-image { + width: 22px; + text-align: center; + pointer-events: none; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-image > span { + color: #888; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled:hover { + background: transparent; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled .w2ui-node-text, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled:hover .w2ui-node-text, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled .w2ui-node-image, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled:hover .w2ui-node-image, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled .w2ui-node-image > span, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled:hover .w2ui-node-image > span { + opacity: 0.4; + filter: alpha(opacity=40); + color: black; + text-shadow: 0px 0px 0px #fff; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node input, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node button { + pointer-events: auto; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:hover { + background-color: #f3f5ff; + position: relative; + border: 1px solid #dee1ff; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected .w2ui-node-text, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:hover .w2ui-node-text, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected .w2ui-node-image, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:hover .w2ui-node-image, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected .w2ui-node-image > span, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:hover .w2ui-node-image > span { + color: inherit; + text-shadow: 0px 0px 0px #fff; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:before { + content: ""; + border: 1px dashed transparent; + border-radius: 4px; + position: absolute; + top: -1px; + bottom: -1px; + left: -1px; + right: -1px; + pointer-events: none; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-text { + white-space: nowrap; + padding: 5px 0px 5px 3px; + margin: 1px 0px 1px 22px; + position: relative; + z-index: 1; + font-size: 12px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-group { + white-space: nowrap; + overflow: hidden; + padding: 10px 0px 10px 10px; + margin: 0px; + cursor: default; + color: #6a5e88; + background-color: transparent; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-group :nth-child(1) { + /* show / hide link */ + margin-right: 10px; + float: right; + color: transparent; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-group :nth-child(2) { + /* title text */ + font-weight: normal; + text-transform: uppercase; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-sub { + overflow: hidden; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data { + padding: 2px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-node-image { + padding: 3px 0px 0px 0px; + float: left; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-node-image > span { + font-size: 16px; + color: #737485; + text-shadow: 0px 0px 0px #fff; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-node-image.w2ui-icon { + margin-top: 3px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-node-count { + float: right; + border: 1px solid #F6FCF4; + border-radius: 20px; + width: auto; + padding: 2px 7px; + margin: 3px 4px -2px 0; + background-color: #F2F8F0; + color: #666; + box-shadow: 0px 0px 2px #474545; + text-shadow: 1px 1px 0px #FFF; + position: relative; + z-index: 2; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-expanded { + float: right; + width: auto; + height: 18px; + position: relative; + z-index: 2; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed span, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-expanded span { + border-color: transparent; + background-color: transparent; + box-shadow: none; + padding: 2px 5px; + border-radius: 0px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed span:after, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-expanded span:after { + content: ""; + position: absolute; + border-left: 5px solid #808080; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + transform: rotateZ(-90deg); + pointer-events: none; + margin-left: -4px; + margin-top: 7px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed span:hover, +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-expanded span:hover { + border-color: transparent; + background-color: transparent; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed span:after { + transform: rotateZ(90deg); +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-flat { + display: block; + padding: 2px 0px; + text-align: center; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-flat .w2ui-node-image { + float: none; + text-align: center; + width: auto; + padding: 1px 0px; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-flat .w2ui-node-image > span { + font-size: 16px; + color: #737485; + text-shadow: 0px 0px 0px #fff; +} +.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-flat .w2ui-node-image.w2ui-icon { + width: 21px; +} +/************************************************* +* ---- Tabs ---- +*/ +.w2ui-tabs { + cursor: default; + overflow: hidden; + position: relative; + background-color: #fff; + min-height: 28px; + padding: 0px; + margin: 0px; +} +.w2ui-tabs .w2ui-tabs-line { + position: absolute; + left: 0px; + right: 0px; + bottom: 0px; + z-index: 1; + border: 0; + height: 1px; + background-color: #e2e2e2; +} +.w2ui-tabs .w2ui-scroll-left, +.w2ui-tabs .w2ui-scroll-right { + z-index: 30; + display: flex; +} +.w2ui-tabs .w2ui-scroll-wrapper { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-content: flex-start; + padding: 0 2px; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab { + height: 28px; + position: relative; + z-index: 20; + padding: 7px 20px 4px 20px; + text-align: center; + color: black; + background-color: transparent; + border: 2px solid transparent; + white-space: nowrap; + margin: 0px 1px; + border-radius: 0; + cursor: default; + user-select: none; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab.active { + color: #0175ff; + background-color: transparent; + border: 2px solid transparent; + border-bottom: 2px solid #0175ff; + margin-bottom: 0px; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab:hover { + background-color: #dfe1e630; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab.moving { + color: inherit; + background-color: #eee; + border: 2px solid transparent; + border-radius: 0px; + margin-bottom: 0px; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab.closable { + padding: 6px 28px 6px 20px; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab .w2ui-tab-close { + position: absolute; + right: 3px; + top: 5px; + color: #555; + float: right; + margin-top: -3px; + padding: 2px 4px; + width: 20px; + height: 20px; + opacity: 0.6; + border: 0px; + border-top: 3px solid transparent; + border-radius: 3px; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab .w2ui-tab-close:hover { + background-color: #f9e7e7; + color: red; + opacity: 1; + font-weight: bold; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab .w2ui-tab-close:active { + background-color: #ffd1d1; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab .w2ui-tab-close:before { + position: relative; + top: -2px; + left: 0px; + color: inherit; + text-shadow: inherit; + content: 'x'; +} +.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tabs-right { + padding: 8px 2px; + width: 100%; + text-align: right; + white-space: nowrap; +} +.w2ui-tabs.w2ui-tabs-up .w2ui-tabs-line { + top: 0px; + bottom: auto; +} +.w2ui-tabs.w2ui-tabs-up .w2ui-scroll-wrapper .w2ui-tab { + border: 2px solid transparent; + border-top: 2px solid transparent; + border-radius: 0 0 4px 4px; +} +.w2ui-tabs.w2ui-tabs-up .w2ui-scroll-wrapper .w2ui-tab.active { + border: 2px solid transparent; + border-top: 2px solid #0175ff; + margin-top: 0px; +} +/************************************************* +* ---- Toolbar ---- +*/ +.w2ui-toolbar { + background-color: #f5f5f5; + user-select: none; + padding: 2px; +} +.w2ui-toolbar .w2ui-tb-line { + overflow: hidden; + position: relative; + min-height: 28px; + padding: 2px; + margin: 0; +} +.w2ui-toolbar .disabled { + opacity: 0.3; + filter: alpha(opacity=30); +} +.w2ui-toolbar .w2ui-scroll-left, +.w2ui-toolbar .w2ui-scroll-right { + z-index: 30; + display: flex; +} +.w2ui-toolbar .w2ui-tb-line:nth-child(2), +.w2ui-toolbar .w2ui-tb-line:nth-child(3), +.w2ui-toolbar .w2ui-tb-line:nth-child(4) { + border-top: 1px solid #e7e7e7; + padding-top: 4px; + margin: 0; +} +.w2ui-toolbar .w2ui-scroll-wrapper { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-content: flex-start; + padding: 0; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button { + position: relative; + z-index: 20; + height: 30px; + min-width: 30px; + padding: 2px; + border: 1px solid transparent; + border-radius: 4px; + background-color: transparent; + white-space: nowrap; + margin: 0 1px; + cursor: default; + user-select: none; + flex-shrink: 0; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-icon { + float: left; + width: 22px; + margin: 4px 0 0 1px; + text-align: center; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-icon > span { + font-size: 15px; + color: #8D99A7; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-text { + margin-left: 20px; + color: black; + padding: 5px ; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-text .w2ui-tb-color-box { + display: inline-block; + height: 13px; + width: 13px; + margin: 0 -1px -2px 0; + border-radius: 1px; + border: 1px solid #fff; + box-shadow: 0 0 1px #555; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-text .w2ui-tb-count { + padding: 0 0 0 4px; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-text .w2ui-tb-count > span { + border: 1px solid #F6FCF4; + border-radius: 11px; + width: auto; + height: 18px; + padding: 0 6px 1px 6px; + background-color: #F2F8F0; + color: #666; + box-shadow: 0px 0px 2px #474545; + text-shadow: 1px 1px 0px #FFF; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-text .w2ui-tb-down { + display: inline-block; + width: 10px; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-text .w2ui-tb-down > span { + display: inline-block; + position: relative; + top: 3px; + left: 3px; + border: 4px solid transparent; + border-top: 5px solid #8D99A7; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.over { + border: 1px solid transparent; + background-color: #eaeaed; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.over .w2ui-tb-text { + color: black; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.checked { + border: 1px solid #d2d2d2; + background-color: white; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.checked .w2ui-tb-text { + color: black; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.down { + border: 1px solid #ccc; + background-color: #eaeaed; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.down .w2ui-tb-text { + color: #666; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.no-icon .w2ui-tb-text { + margin-left: 0; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-right { + width: 100%; + text-align: right; + white-space: nowrap; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-break { + background-image: linear-gradient(to bottom, rgba(153, 153, 153, 0.1) 0%, #999 40%, #999 60%, rgba(153, 153, 153, 0.1) 100%); + width: 1px; + height: 24px; + padding: 0; + margin: 3px 6px; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-html { + white-space: nowrap; +} +.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-spacer { + width: 100%; +} diff --git a/lib/w2ui/w2ui.es6.js b/lib/w2ui/w2ui.es6.js new file mode 100644 index 0000000..d48cc94 --- /dev/null +++ b/lib/w2ui/w2ui.es6.js @@ -0,0 +1,22158 @@ +/* w2ui 2.0.x (nightly) (1/16/2023, 8:50:38 AM) (c) http://w2ui.com, vitmalina@gmail.com */ +/** + * Part of w2ui 2.0 library + * - Dependencies: w2utils + * - on/off/trigger methods id not showing in help + * - refactored with event object + */ + +class w2event { + constructor(owner, edata) { + Object.assign(this, { + type: edata.type ?? null, + detail: edata, + owner, + target: edata.target ?? null, + phase: edata.phase ?? 'before', + object: edata.object ?? null, + execute: null, + isStopped: false, + isCancelled: false, + onComplete: null, + listeners: [] + }) + delete edata.type + delete edata.target + delete edata.object + this.complete = new Promise((resolve, reject) => { + this._resolve = resolve + this._reject = reject + }) + // needed empty catch function so that promise will not show error in the console + this.complete.catch(() => {}) + } + finish(detail) { + if (detail) { + w2utils.extend(this.detail, detail) + } + this.phase = 'after' + this.owner.trigger.call(this.owner, this) + } + done(func) { + this.listeners.push(func) + } + preventDefault() { + this._reject() + this.isCancelled = true + } + stopPropagation() { + this.isStopped = true + } +} +class w2base { + /** + * Initializes base object for w2ui, registers it with w2ui object + * + * @param {string} name - name of the object + * @returns + */ + constructor(name) { + this.activeEvents = [] // events that are currently processing + this.listeners = [] // event listeners + // register globally + if (typeof name !== 'undefined') { + if (!w2utils.checkName(name)) return + w2ui[name] = this + } + this.debug = false // if true, will trigger all events + } + /** + * Adds event listener, supports event phase and event scoping + * + * @param {*} edata - an object or string, if string "eventName:phase.scope" + * @param {*} handler + * @returns itself + */ + on(events, handler) { + if (typeof events == 'string') { + events = events.split(/[,\s]+/) // separate by comma or space + } else { + events = [events] + } + events.forEach(edata => { + let name = typeof edata == 'string' ? edata : (edata.type + ':' + edata.execute + '.' + edata.scope) + if (typeof edata == 'string') { + let [eventName, scope] = edata.split('.') + let [type, execute] = eventName.replace(':complete', ':after').replace(':done', ':after').split(':') + edata = { type, execute: execute ?? 'before', scope } + } + edata = w2utils.extend({ type: null, execute: 'before', onComplete: null }, edata) + // errors + if (!edata.type) { console.log('ERROR: You must specify event type when calling .on() method of '+ this.name); return } + if (!handler) { console.log('ERROR: You must specify event handler function when calling .on() method of '+ this.name); return } + if (!Array.isArray(this.listeners)) this.listeners = [] + this.listeners.push({ name, edata, handler }) + if (this.debug) { + console.log('w2base: add event', { name, edata, handler }) + } + }) + return this + } + /** + * Removes event listener, supports event phase and event scoping + * + * @param {*} edata - an object or string, if string "eventName:phase.scope" + * @param {*} handler + * @returns itself + */ + off(events, handler) { + if (typeof events == 'string') { + events = events.split(/[,\s]+/) // separate by comma or space + } else { + events = [events] + } + events.forEach(edata => { + let name = typeof edata == 'string' ? edata : (edata.type + ':' + edata.execute + '.' + edata.scope) + if (typeof edata == 'string') { + let [eventName, scope] = edata.split('.') + let [type, execute] = eventName.replace(':complete', ':after').replace(':done', ':after').split(':') + edata = { type: type || '*', execute: execute || '', scope: scope || '' } + } + edata = w2utils.extend({ type: null, execute: null, onComplete: null }, edata) + // errors + if (!edata.type && !edata.scope) { console.log('ERROR: You must specify event type when calling .off() method of '+ this.name); return } + if (!handler) { handler = null } + let count = 0 + // remove listener + this.listeners = this.listeners.filter(curr => { + if ( (edata.type === '*' || edata.type === curr.edata.type) + && (edata.execute === '' || edata.execute === curr.edata.execute) + && (edata.scope === '' || edata.scope === curr.edata.scope) + && (edata.handler == null || edata.handler === curr.edata.handler) + ) { + count++ // how many listeners removed + return false + } else { + return true + } + }) + if (this.debug) { + console.log(`w2base: remove event (${count})`, { name, edata, handler }) + } + }) + return this // needed for chaining + } + /** + * Triggers even listeners for a specific event, loops through this.listeners + * + * @param {Object} edata - Object + * @returns modified edata + */ + trigger(eventName, edata) { + if (arguments.length == 1) { + edata = eventName + } else { + edata.type = eventName + edata.target = edata.target ?? this + } + if (w2utils.isPlainObject(edata) && edata.phase == 'after') { + // find event + edata = this.activeEvents.find(event => { + if (event.type == edata.type && event.target == edata.target) { + return true + } + return false + }) + if (!edata) { + console.log(`ERROR: Cannot find even handler for "${edata.type}" on "${edata.target}".`) + return + } + console.log('NOTICE: This syntax "edata.trigger({ phase: \'after\' })" is outdated. Use edata.finish() instead.') + } else if (!(edata instanceof w2event)) { + edata = new w2event(this, edata) + this.activeEvents.push(edata) + } + let args, fun, tmp + if (!Array.isArray(this.listeners)) this.listeners = [] + if (this.debug) { + console.log(`w2base: trigger "${edata.type}:${edata.phase}"`, edata) + } + // process events in REVERSE order + for (let h = this.listeners.length-1; h >= 0; h--) { + let item = this.listeners[h] + if (item != null && (item.edata.type === edata.type || item.edata.type === '*') && + (item.edata.target === edata.target || item.edata.target == null) && + (item.edata.execute === edata.phase || item.edata.execute === '*' || item.edata.phase === '*')) + { + // add extra params if there + Object.keys(item.edata).forEach(key => { + if (edata[key] == null && item.edata[key] != null) { + edata[key] = item.edata[key] + } + }) + // check handler arguments + args = [] + tmp = new RegExp(/\((.*?)\)/).exec(String(item.handler).split('=>')[0]) + if (tmp) args = tmp[1].split(/\s*,\s*/) + if (args.length === 2) { + item.handler.call(this, edata.target, edata) // old way for back compatibility + if (this.debug) console.log(' - call (old)', item.handler) + } else { + item.handler.call(this, edata) // new way + if (this.debug) console.log(' - call', item.handler) + } + if (edata.isStopped === true || edata.stop === true) return edata // back compatibility edata.stop === true + } + } + // main object events + let funName = 'on' + edata.type.substr(0,1).toUpperCase() + edata.type.substr(1) + if (edata.phase === 'before' && typeof this[funName] === 'function') { + fun = this[funName] + // check handler arguments + args = [] + tmp = new RegExp(/\((.*?)\)/).exec(String(fun).split('=>')[0]) + if (tmp) args = tmp[1].split(/\s*,\s*/) + if (args.length === 2) { + fun.call(this, edata.target, edata) // old way for back compatibility + if (this.debug) console.log(' - call: on[Event] (old)', fun) + } else { + fun.call(this, edata) // new way + if (this.debug) console.log(' - call: on[Event]', fun) + } + if (edata.isStopped === true || edata.stop === true) return edata // back compatibility edata.stop === true + } + // item object events + if (edata.object != null && edata.phase === 'before' && typeof edata.object[funName] === 'function') { + fun = edata.object[funName] + // check handler arguments + args = [] + tmp = new RegExp(/\((.*?)\)/).exec(String(fun).split('=>')[0]) + if (tmp) args = tmp[1].split(/\s*,\s*/) + if (args.length === 2) { + fun.call(this, edata.target, edata) // old way for back compatibility + if (this.debug) console.log(' - call: edata.object (old)', fun) + } else { + fun.call(this, edata) // new way + if (this.debug) console.log(' - call: edata.object', fun) + } + if (edata.isStopped === true || edata.stop === true) return edata + } + // execute onComplete + if (edata.phase === 'after') { + if (typeof edata.onComplete === 'function') edata.onComplete.call(this, edata) + for (let i = 0; i < edata.listeners.length; i++) { + if (typeof edata.listeners[i] === 'function') { + edata.listeners[i].call(this, edata) + if (this.debug) console.log(' - call: done', fun) + } + } + edata._resolve(edata) + if (this.debug) { + console.log(`w2base: trigger "${edata.type}:${edata.phase}"`, edata) + } + } + return edata + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: none + * + * These are the master locale settings that will be used by w2utils + * + * "locale" should be the IETF language tag in the form xx-YY, + * where xx is the ISO 639-1 language code ( see https://en.wikipedia.org/wiki/ISO_639-1 ) and + * YY is the ISO 3166-1 alpha-2 country code ( see https://en.wikipedia.org/wiki/ISO_3166-2 ) + */ +const w2locale = { + 'locale' : 'en-US', + 'dateFormat' : 'm/d/yyyy', + 'timeFormat' : 'hh:mi pm', + 'datetimeFormat' : 'm/d/yyyy|hh:mi pm', + 'currencyPrefix' : '$', + 'currencySuffix' : '', + 'currencyPrecision' : 2, + 'groupSymbol' : ',', // aka "thousands separator" + 'decimalSymbol' : '.', + 'shortmonths' : ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'fullmonths' : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'shortdays' : ['M', 'T', 'W', 'T', 'F', 'S', 'S'], + 'fulldays' : ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], + 'weekStarts' : 'S', // can be "M" for Monday or "S" for Sunday + // phrases used in w2ui, should be empty for original language + // keep these up-to-date and in sorted order + // value = "---" to easier see what to translate + 'phrases': { + '${count} letters or more...': '---', + 'Add new record': '---', + 'Add New': '---', + 'Advanced Search': '---', + 'after': '---', + 'AJAX error. See console for more details.': '---', + 'All Fields': '---', + 'All': '---', + 'Any': '---', + 'Are you sure you want to delete ${count} ${records}?': '---', + 'Attach files by dragging and dropping or Click to Select': '---', + 'before': '---', + 'begins with': '---', + 'begins': '---', + 'between': '---', + 'buffered': '---', + 'Cancel': '---', + 'Close': '---', + 'Column': '---', + 'Confirmation': '---', + 'contains': '---', + 'Copied': '---', + 'Copy to clipboard': '---', + 'Current Date & Time': '---', + 'Delete selected records': '---', + 'Delete': '---', + 'Do you want to delete search item "${item}"?': '---', + 'Edit selected record': '---', + 'Edit': '---', + 'Empty list': '---', + 'ends with': '---', + 'ends': '---', + 'Field should be at least ${count} characters.': '---', + 'Hide': '---', + 'in': '---', + 'is not': '---', + 'is': '---', + 'less than': '---', + 'Line #': '---', + 'Load ${count} more...': '---', + 'Loading...': '---', + 'Maximum number of files is ${count}': '---', + 'Maximum total size is ${count}': '---', + 'Modified': '---', + 'more than': '---', + 'Multiple Fields': '---', + 'Name': '---', + 'No items found': '---', + 'No matches': '---', + 'No': '---', + 'none': '---', + 'Not a float': '---', + 'Not a hex number': '---', + 'Not a valid date': '---', + 'Not a valid email': '---', + 'Not alpha-numeric': '---', + 'Not an integer': '---', + 'Not in money format': '---', + 'not in': '---', + 'Notification': '---', + 'of': '---', + 'Ok': '---', + 'Opacity': '---', + 'Record ID': '---', + 'record': '---', + 'records': '---', + 'Refreshing...': '---', + 'Reload data in the list': '---', + 'Remove': '---', + 'Remove This Field': '---', + 'Request aborted.': '---', + 'Required field': '---', + 'Reset': '---', + 'Restore Default State': '---', + 'Returned data is not in valid JSON format.': '---', + 'Save changed records': '---', + 'Save Grid State': '---', + 'Save': '---', + 'Saved Searches': '---', + 'Saving...': '---', + 'Search took ${count} seconds': '---', + 'Search': '---', + 'Select Hour': '---', + 'Select Minute': '---', + 'selected': '---', + 'Server Response ${count} seconds': '---', + 'Show/hide columns': '---', + 'Show': '---', + 'Size': '---', + 'Skip': '---', + 'Sorting took ${count} seconds': '---', + 'Type to search...': '---', + 'Type': '---', + 'Yes': '---', + 'Yesterday': '---', + 'Your remote data source record count has changed, reloading from the first record.': '---' + } +} +/* mQuery 0.7 (nightly) (10/10/2022, 11:30:36 AM), vitmalina@gmail.com */ +class Query { + static version = 0.7 + constructor(selector, context, previous) { + this.context = context ?? document + this.previous = previous ?? null + let nodes = [] + if (Array.isArray(selector)) { + nodes = selector + } else if (selector instanceof Node || selector instanceof Window) { // any html element or Window + nodes = [selector] + } else if (selector instanceof Query) { + nodes = selector.nodes + } else if (typeof selector == 'string') { + if (typeof this.context.querySelector != 'function') { + throw new Error('Invalid context') + } + nodes = Array.from(this.context.querySelectorAll(selector)) + } else if (selector == null) { + nodes = [] + } else { + // if selector is itterable, then try to create nodes from it, also supports jQuery + let arr = Array.from(selector ?? []) + if (typeof selector == 'object' && Array.isArray(arr)) { + nodes = arr + } else { + throw new Error(`Invalid selector "${selector}"`) + } + } + this.nodes = nodes + this.length = nodes.length + // map nodes to object propoerties + this.each((node, ind) => { + this[ind] = node + }) + } + static _fragment(html) { + let tmpl = document.createElement('template') + tmpl.innerHTML = html + tmpl.content.childNodes.forEach(node => { + let newNode = Query._scriptConvert(node) + if (newNode != node) { + tmpl.content.replaceChild(newNode, node) + } + }) + return tmpl.content + } + // innerHTML, append, etc. script tags will not be executed unless they are proper script tags + static _scriptConvert(node) { + let convert = (txtNode) => { + let doc = txtNode.ownerDocument + let scNode = doc.createElement('script') + scNode.text = txtNode.text + let attrs = txtNode.attributes + for (let i = 0; i < attrs.length; i++) { + scNode.setAttribute(attrs[i].name, attrs[i].value) + } + return scNode + } + if (node.tagName == 'SCRIPT') { + node = convert(node) + } + if (node.querySelectorAll) { + node.querySelectorAll('script').forEach(textNode => { + textNode.parentNode.replaceChild(convert(textNode), textNode) + }) + } + return node + } + static _fixProp(name) { + let fixes = { + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing', + class: 'className', + colspan: 'colSpan', + contenteditable: 'contentEditable', + for: 'htmlFor', + frameborder: 'frameBorder', + maxlength: 'maxLength', + readonly: 'readOnly', + rowspan: 'rowSpan', + tabindex: 'tabIndex', + usemap: 'useMap' + } + return fixes[name] ? fixes[name] : name + } + _insert(method, html) { + let nodes = [] + let len = this.length + if (len < 1) return + let self = this + // TODO: need good unit test coverage for this function + if (typeof html == 'string') { + this.each(node => { + let clone = Query._fragment(html) + nodes.push(...clone.childNodes) + node[method](clone) + }) + } else if (html instanceof Query) { + let single = (len == 1) // if inserting into a single container, then move it there + html.each(el => { + this.each(node => { + // if insert before a single node, just move new one, else clone and move it + let clone = (single ? el : el.cloneNode(true)) + nodes.push(clone) + node[method](clone) + Query._scriptConvert(clone) + }) + }) + if (!single) html.remove() + } else if (html instanceof Node) { // any HTML element + this.each(node => { + // if insert before a single node, just move new one, else clone and move it + let clone = (len === 1 ? html : Query._fragment(html.outerHTML)) + nodes.push(...(len === 1 ? [html] : clone.childNodes)) + node[method](clone) + }) + if (len > 1) html.remove() + } else { + throw new Error(`Incorrect argument for "${method}(html)". It expects one string argument.`) + } + if (method == 'replaceWith') { + self = new Query(nodes, this.context, this) // must return a new collection + } + return self + } + _save(node, name, value) { + node._mQuery = node._mQuery ?? {} + if (Array.isArray(value)) { + node._mQuery[name] = node._mQuery[name] ?? [] + node._mQuery[name].push(...value) + } else if (value != null) { + node._mQuery[name] = value + } else { + delete node._mQuery[name] + } + } + get(index) { + if (index < 0) index = this.length + index + let node = this[index] + if (node) { + return node + } + if (index != null) { + return null + } + return this.nodes + } + eq(index) { + if (index < 0) index = this.length + index + let nodes = [this[index]] + if (nodes[0] == null) nodes = [] + return new Query(nodes, this.context, this) // must return a new collection + } + then(fun) { + let ret = fun(this) + return ret != null ? ret : this + } + find(selector) { + let nodes = [] + this.each(node => { + let nn = Array.from(node.querySelectorAll(selector)) + if (nn.length > 0) { + nodes.push(...nn) + } + }) + return new Query(nodes, this.context, this) // must return a new collection + } + filter(selector) { + let nodes = [] + this.each(node => { + if (node === selector + || (typeof selector == 'string' && node.matches && node.matches(selector)) + || (typeof selector == 'function' && selector(node)) + ) { + nodes.push(node) + } + }) + return new Query(nodes, this.context, this) // must return a new collection + } + next() { + let nodes = [] + this.each(node => { + let nn = node.nextElementSibling + if (nn) { nodes.push(nn) } + }) + return new Query(nodes, this.context, this) // must return a new collection + } + prev() { + let nodes = [] + this.each(node => { + let nn = node.previousElementSibling + if (nn) { nodes.push(nn)} + }) + return new Query(nodes, this.context, this) // must return a new collection + } + shadow(selector) { + let nodes = [] + this.each(node => { + // select shadow root if available + if (node.shadowRoot) nodes.push(node.shadowRoot) + }) + let col = new Query(nodes, this.context, this) + return selector ? col.find(selector) : col + } + closest(selector) { + let nodes = [] + this.each(node => { + let nn = node.closest(selector) + if (nn) { + nodes.push(nn) + } + }) + return new Query(nodes, this.context, this) // must return a new collection + } + host(all) { + let nodes = [] + // find shadow root or body + let top = (node) => { + if (node.parentNode) { + return top(node.parentNode) + } else { + return node + } + } + let fun = (node) => { + let nn = top(node) + nodes.push(nn.host ? nn.host : nn) + if (nn.host && all) fun(nn.host) + } + this.each(node => { + fun(node) + }) + return new Query(nodes, this.context, this) // must return a new collection + } + parent(selector) { + return this.parents(selector, true) + } + parents(selector, firstOnly) { + let nodes = [] + let add = (node) => { + if (nodes.indexOf(node) == -1) { + nodes.push(node) + } + if (!firstOnly && node.parentNode) { + return add(node.parentNode) + } + } + this.each(node => { + if (node.parentNode) add(node.parentNode) + }) + let col = new Query(nodes, this.context, this) + return selector ? col.filter(selector) : col + } + add(more) { + let nodes = more instanceof Query ? more.nodes : (Array.isArray(more) ? more : [more]) + return new Query(this.nodes.concat(nodes), this.context, this) // must return a new collection + } + each(func) { + this.nodes.forEach((node, ind) => { func(node, ind, this) }) + return this + } + append(html) { + return this._insert('append', html) + } + prepend(html) { + return this._insert('prepend', html) + } + after(html) { + return this._insert('after', html) + } + before(html) { + return this._insert('before', html) + } + replace(html) { + return this._insert('replaceWith', html) + } + remove() { + // remove from dom, but keep in current query + this.each(node => { node.remove() }) + return this + } + css(key, value) { + let css = key + let len = arguments.length + if (len === 0 || (len === 1 && typeof key == 'string')) { + if (this[0]) { + let st = this[0].style + // do not do computedStyleMap as it is not what on immediate element + if (typeof key == 'string') { + let pri = st.getPropertyPriority(key) + return st.getPropertyValue(key) + (pri ? '!' + pri : '') + } else { + return Object.fromEntries( + this[0].style.cssText + .split(';') + .filter(a => !!a) // filter non-empty + .map(a => { + return a.split(':').map(a => a.trim()) // trim strings + }) + ) + } + } else { + return undefined + } + } else { + if (typeof key != 'object') { + css = {} + css[key] = value + } + this.each((el, ind) => { + Object.keys(css).forEach(key => { + let imp = String(css[key]).toLowerCase().includes('!important') ? 'important' : '' + el.style.setProperty(key, String(css[key]).replace(/\!important/i, ''), imp) + }) + }) + return this + } + } + addClass(classes) { + this.toggleClass(classes, true) + return this + } + removeClass(classes) { + this.toggleClass(classes, false) + return this + } + toggleClass(classes, force) { + // split by comma or space + if (typeof classes == 'string') classes = classes.split(/[,\s]+/) + this.each(node => { + let classes2 = classes + // if not defined, remove all classes + if (classes2 == null && force === false) classes2 = Array.from(node.classList) + classes2.forEach(className => { + if (className !== '') { + let act = 'toggle' + if (force != null) act = force ? 'add' : 'remove' + node.classList[act](className) + } + }) + }) + return this + } + hasClass(classes) { + // split by comma or space + if (typeof classes == 'string') classes = classes.split(/[,\s]+/) + if (classes == null && this.length > 0) { + return Array.from(this[0].classList) + } + let ret = false + this.each(node => { + ret = ret || classes.every(className => { + return Array.from(node.classList ?? []).includes(className) + }) + }) + return ret + } + on(events, options, callback) { + if (typeof options == 'function') { + callback = options + options = undefined + } + let delegate + if (options?.delegate) { + delegate = options.delegate + delete options.delegate // not to pass to addEventListener + } + events = events.split(/[,\s]+/) // separate by comma or space + events.forEach(eventName => { + let [ event, scope ] = String(eventName).toLowerCase().split('.') + if (delegate) { + let fun = callback + callback = (event) => { + // event.target or any ancestors match delegate selector + let parent = query(event.target).parents(delegate) + if (parent.length > 0) { event.delegate = parent[0] } else { event.delegate = event.target } + if (event.target.matches(delegate) || parent.length > 0) { + fun(event) + } + } + } + this.each(node => { + this._save(node, 'events', [{ event, scope, callback, options }]) + node.addEventListener(event, callback, options) + }) + }) + return this + } + off(events, options, callback) { + if (typeof options == 'function') { + callback = options + options = undefined + } + events = (events ?? '').split(/[,\s]+/) // separate by comma or space + events.forEach(eventName => { + let [ event, scope ] = String(eventName).toLowerCase().split('.') + this.each(node => { + if (Array.isArray(node._mQuery?.events)) { + for (let i = node._mQuery.events.length - 1; i >= 0; i--) { + let evt = node._mQuery.events[i] + if (scope == null || scope === '') { + // if no scope, has to be exact match + if ((evt.event == event || event === '') && (evt.callback == callback || callback == null)) { + node.removeEventListener(evt.event, evt.callback, evt.options) + node._mQuery.events.splice(i, 1) + } + } else { + if ((evt.event == event || event === '') && evt.scope == scope) { + node.removeEventListener(evt.event, evt.callback, evt.options) + node._mQuery.events.splice(i, 1) + } + } + } + } + }) + }) + return this + } + trigger(name, options) { + let event, + mevent = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove'], + kevent = ['keydown', 'keyup', 'keypress'] + if (name instanceof Event || name instanceof CustomEvent) { + // MouseEvent and KeyboardEvent are instances of Event, no need to explicitly add + event = name + } else if (mevent.includes(name)) { + event = new MouseEvent(name, options) + } else if (kevent.includes(name)) { + event = new KeyboardEvent(name, options) + } else { + event = new Event(name, options) + } + this.each(node => { node.dispatchEvent(event) }) + return this + } + attr(name, value) { + if (value === undefined && typeof name == 'string') { + return this[0] ? this[0].getAttribute(name) : undefined + } else { + let obj = {} + if (typeof name == 'object') obj = name; else obj[name] = value + this.each(node => { + Object.entries(obj).forEach(([nm, val]) => { node.setAttribute(nm, val) }) + }) + return this + } + } + removeAttr() { + this.each(node => { + Array.from(arguments).forEach(attr => { + node.removeAttribute(attr) + }) + }) + return this + } + prop(name, value) { + if (value === undefined && typeof name == 'string') { + return this[0] ? this[0][name] : undefined + } else { + let obj = {} + if (typeof name == 'object') obj = name; else obj[name] = value + this.each(node => { + Object.entries(obj).forEach(([nm, val]) => { + let prop = Query._fixProp(nm) + node[prop] = val + if (prop == 'innerHTML') { + Query._scriptConvert(node) + } + }) + }) + return this + } + } + removeProp() { + this.each(node => { + Array.from(arguments).forEach(prop => { delete node[Query._fixProp(prop)] }) + }) + return this + } + data(key, value) { + if (key instanceof Object) { + Object.entries(key).forEach(item => { this.data(item[0], item[1]) }) + return + } + if (key && key.indexOf('-') != -1) { + console.error(`Key "${key}" contains "-" (dash). Dashes are not allowed in property names. Use camelCase instead.`) + } + if (arguments.length < 2) { + if (this[0]) { + let data = Object.assign({}, this[0].dataset) + Object.keys(data).forEach(key => { + if (data[key].startsWith('[') || data[key].startsWith('{')) { + try { data[key] = JSON.parse(data[key]) } catch (e) {} + } + }) + return key ? data[key] : data + } else { + return undefined + } + } else { + this.each(node => { + if (value != null) { + node.dataset[key] = value instanceof Object ? JSON.stringify(value) : value + } else { + delete node.dataset[key] + } + }) + return this + } + } + removeData(key) { + if (typeof key == 'string') key = key.split(/[,\s]+/) + this.each(node => { + key.forEach(k => { delete node.dataset[k] }) + }) + return this + } + show() { + return this.toggle(true) + } + hide() { + return this.toggle(false) + } + toggle(force) { + return this.each(node => { + let prev = node.style.display + let dsp = getComputedStyle(node).display + let isHidden = (prev == 'none' || dsp == 'none') + if (isHidden && (force == null || force === true)) { // show + node.style.display = node._mQuery?.prevDisplay ?? (prev == dsp && dsp != 'none' ? '' : 'block') + this._save(node, 'prevDisplay', null) + } + if (!isHidden && (force == null || force === false)) { // hide + if (dsp != 'none') this._save(node, 'prevDisplay', dsp) + node.style.setProperty('display', 'none') + } + }) + } + empty() { + return this.html('') + } + html(html) { + return this.prop('innerHTML', html) + } + text(text) { + return this.prop('textContent', text) + } + val(value) { + return this.prop('value', value) // must be prop + } + change() { + return this.trigger('change') + } + click() { + return this.trigger('click') + } +} +// create a new object each time +let query = function (selector, context) { + // if a function, use as onload event + if (typeof selector == 'function') { + if (document.readyState == 'complete') { + selector() + } else { + window.addEventListener('load', selector) + } + } else { + return new Query(selector, context) + } +} +// str -> doc-fragment +query.html = (str) => { let frag = Query._fragment(str); return query(frag.children, frag) } +query.version = Query.version +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2locale + * + * == TODO == + * - add w2utils.lang wrap for all captions in all buttons. + * - check transition (also with layout) + * - deprecate w2utils.tooltip + * + * == 2.0 changes + * - CSP - fixed inline events (w2utils.tooltip still has it) + * - transition returns a promise + * - removed jQuery + * - refactores w2utils.message() + * - added w2utils.confirm() + * - added isPlainObject + * - added stripSpaces + * - implemented marker + * - cssPrefix - deprecated + * - w2utils.debounce + */ + +// variable that holds all w2ui objects +let w2ui = {} +class Utils { + constructor () { + this.version = '2.0.x' + this.tmp = {} + this.settings = this.extend({}, { + 'dataType' : 'HTTPJSON', // can be HTTP, HTTPJSON, RESTFULL, JSON (case sensitive) + 'dateStartYear' : 1950, // start year for date-picker + 'dateEndYear' : 2030, // end year for date picker + 'macButtonOrder' : false, // if true, Yes on the right side + 'warnNoPhrase' : false, // call console.warn if lang() encounters a missing phrase + }, w2locale, { phrases: null }), // if there are no phrases, then it is original language + this.i18nCompare = Intl.Collator().compare + this.hasLocalStorage = testLocalStorage() + // some internal variables + this.isMac = /Mac/i.test(navigator.platform) + this.isMobile = /(iphone|ipod|ipad|mobile|android)/i.test(navigator.userAgent) + this.isIOS = /(iphone|ipod|ipad)/i.test(navigator.platform) + this.isAndroid = /(android)/i.test(navigator.userAgent) + this.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) + // Formatters: Primarily used in grid + this.formatters = { + 'number'(value, params) { + if (parseInt(params) > 20) params = 20 + if (parseInt(params) < 0) params = 0 + if (value == null || value === '') return '' + return w2utils.formatNumber(parseFloat(value), params, true) + }, + 'float'(value, params) { + return w2utils.formatters.number(value, params) + }, + 'int'(value, params) { + return w2utils.formatters.number(value, 0) + }, + 'money'(value, params) { + if (value == null || value === '') return '' + let data = w2utils.formatNumber(Number(value), w2utils.settings.currencyPrecision) + return (w2utils.settings.currencyPrefix || '') + data + (w2utils.settings.currencySuffix || '') + }, + 'currency'(value, params) { + return w2utils.formatters.money(value, params) + }, + 'percent'(value, params) { + if (value == null || value === '') return '' + return w2utils.formatNumber(value, params || 1) + '%' + }, + 'size'(value, params) { + if (value == null || value === '') return '' + return w2utils.formatSize(parseInt(value)) + }, + 'date'(value, params) { + if (params === '') params = w2utils.settings.dateFormat + if (value == null || value === 0 || value === '') return '' + let dt = w2utils.isDateTime(value, params, true) + if (dt === false) dt = w2utils.isDate(value, params, true) + return '' + w2utils.formatDate(dt, params) + '' + }, + 'datetime'(value, params) { + if (params === '') params = w2utils.settings.datetimeFormat + if (value == null || value === 0 || value === '') return '' + let dt = w2utils.isDateTime(value, params, true) + if (dt === false) dt = w2utils.isDate(value, params, true) + return '' + w2utils.formatDateTime(dt, params) + '' + }, + 'time'(value, params) { + if (params === '') params = w2utils.settings.timeFormat + if (params === 'h12') params = 'hh:mi pm' + if (params === 'h24') params = 'h24:mi' + if (value == null || value === 0 || value === '') return '' + let dt = w2utils.isDateTime(value, params, true) + if (dt === false) dt = w2utils.isDate(value, params, true) + return '' + w2utils.formatTime(value, params) + '' + }, + 'timestamp'(value, params) { + if (params === '') params = w2utils.settings.datetimeFormat + if (value == null || value === 0 || value === '') return '' + let dt = w2utils.isDateTime(value, params, true) + if (dt === false) dt = w2utils.isDate(value, params, true) + return dt.toString ? dt.toString() : '' + }, + 'gmt'(value, params) { + if (params === '') params = w2utils.settings.datetimeFormat + if (value == null || value === 0 || value === '') return '' + let dt = w2utils.isDateTime(value, params, true) + if (dt === false) dt = w2utils.isDate(value, params, true) + return dt.toUTCString ? dt.toUTCString() : '' + }, + 'age'(value, params) { + if (value == null || value === 0 || value === '') return '' + let dt = w2utils.isDateTime(value, null, true) + if (dt === false) dt = w2utils.isDate(value, null, true) + return '' + w2utils.age(value) + (params ? (' ' + params) : '') + '' + }, + 'interval'(value, params) { + if (value == null || value === 0 || value === '') return '' + return w2utils.interval(value) + (params ? (' ' + params) : '') + }, + 'toggle'(value, params) { + return (value ? 'Yes' : '') + }, + 'password'(value, params) { + let ret = '' + for (let i = 0; i < value.length; i++) { + ret += '*' + } + return ret + } + } + return + function testLocalStorage() { + // test if localStorage is available, see issue #1282 + let str = 'w2ui_test' + try { + localStorage.setItem(str, str) + localStorage.removeItem(str) + return true + } catch (e) { + return false + } + } + } + isBin(val) { + let re = /^[0-1]+$/ + return re.test(val) + } + isInt(val) { + let re = /^[-+]?[0-9]+$/ + return re.test(val) + } + isFloat(val) { + if (typeof val === 'string') { + val = val.replace(this.settings.groupSymbol, '') + .replace(this.settings.decimalSymbol, '.') + } + return (typeof val === 'number' || (typeof val === 'string' && val !== '')) && !isNaN(Number(val)) + } + isMoney(val) { + if (typeof val === 'object' || val === '') return false + if (this.isFloat(val)) return true + let se = this.settings + let re = new RegExp('^'+ (se.currencyPrefix ? '\\' + se.currencyPrefix + '?' : '') + + '[-+]?'+ (se.currencyPrefix ? '\\' + se.currencyPrefix + '?' : '') + + '[0-9]*[\\'+ se.decimalSymbol +']?[0-9]+'+ (se.currencySuffix ? '\\' + se.currencySuffix + '?' : '') +'$', 'i') + if (typeof val === 'string') { + val = val.replace(new RegExp(se.groupSymbol, 'g'), '') + } + return re.test(val) + } + isHex(val) { + let re = /^(0x)?[0-9a-fA-F]+$/ + return re.test(val) + } + isAlphaNumeric(val) { + let re = /^[a-zA-Z0-9_-]+$/ + return re.test(val) + } + isEmail(val) { + let email = /^[a-zA-Z0-9._%\-+]+@[а-яА-Яa-zA-Z0-9.-]+\.[а-яА-Яa-zA-Z]+$/ + return email.test(val) + } + isIpAddress(val) { + let re = new RegExp('^' + + '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' + + '$') + return re.test(val) + } + isDate(val, format, retDate) { + if (!val) return false + let dt = 'Invalid Date' + let month, day, year + if (format == null) format = this.settings.dateFormat + if (typeof val.getFullYear === 'function') { // date object + year = val.getFullYear() + month = val.getMonth() + 1 + day = val.getDate() + } else if (parseInt(val) == val && parseInt(val) > 0) { + val = new Date(parseInt(val)) + year = val.getFullYear() + month = val.getMonth() + 1 + day = val.getDate() + } else { + val = String(val) + // convert month formats + if (new RegExp('mon', 'ig').test(format)) { + format = format.replace(/month/ig, 'm').replace(/mon/ig, 'm').replace(/dd/ig, 'd').replace(/[, ]/ig, '/').replace(/\/\//g, '/').toLowerCase() + val = val.replace(/[, ]/ig, '/').replace(/\/\//g, '/').toLowerCase() + for (let m = 0, len = this.settings.fullmonths.length; m < len; m++) { + let t = this.settings.fullmonths[m] + val = val.replace(new RegExp(t, 'ig'), (parseInt(m) + 1)).replace(new RegExp(t.substr(0, 3), 'ig'), (parseInt(m) + 1)) + } + } + // format date + let tmp = val.replace(/-/g, '/').replace(/\./g, '/').toLowerCase().split('/') + let tmp2 = format.replace(/-/g, '/').replace(/\./g, '/').toLowerCase() + if (tmp2 === 'mm/dd/yyyy') { month = tmp[0]; day = tmp[1]; year = tmp[2] } + if (tmp2 === 'm/d/yyyy') { month = tmp[0]; day = tmp[1]; year = tmp[2] } + if (tmp2 === 'dd/mm/yyyy') { month = tmp[1]; day = tmp[0]; year = tmp[2] } + if (tmp2 === 'd/m/yyyy') { month = tmp[1]; day = tmp[0]; year = tmp[2] } + if (tmp2 === 'yyyy/dd/mm') { month = tmp[2]; day = tmp[1]; year = tmp[0] } + if (tmp2 === 'yyyy/d/m') { month = tmp[2]; day = tmp[1]; year = tmp[0] } + if (tmp2 === 'yyyy/mm/dd') { month = tmp[1]; day = tmp[2]; year = tmp[0] } + if (tmp2 === 'yyyy/m/d') { month = tmp[1]; day = tmp[2]; year = tmp[0] } + if (tmp2 === 'mm/dd/yy') { month = tmp[0]; day = tmp[1]; year = tmp[2] } + if (tmp2 === 'm/d/yy') { month = tmp[0]; day = tmp[1]; year = parseInt(tmp[2]) + 1900 } + if (tmp2 === 'dd/mm/yy') { month = tmp[1]; day = tmp[0]; year = parseInt(tmp[2]) + 1900 } + if (tmp2 === 'd/m/yy') { month = tmp[1]; day = tmp[0]; year = parseInt(tmp[2]) + 1900 } + if (tmp2 === 'yy/dd/mm') { month = tmp[2]; day = tmp[1]; year = parseInt(tmp[0]) + 1900 } + if (tmp2 === 'yy/d/m') { month = tmp[2]; day = tmp[1]; year = parseInt(tmp[0]) + 1900 } + if (tmp2 === 'yy/mm/dd') { month = tmp[1]; day = tmp[2]; year = parseInt(tmp[0]) + 1900 } + if (tmp2 === 'yy/m/d') { month = tmp[1]; day = tmp[2]; year = parseInt(tmp[0]) + 1900 } + } + if (!this.isInt(year)) return false + if (!this.isInt(month)) return false + if (!this.isInt(day)) return false + year = +year + month = +month + day = +day + dt = new Date(year, month - 1, day) + dt.setFullYear(year) + // do checks + if (month == null) return false + if (String(dt) === 'Invalid Date') return false + if ((dt.getMonth() + 1 !== month) || (dt.getDate() !== day) || (dt.getFullYear() !== year)) return false + if (retDate === true) return dt; else return true + } + isTime(val, retTime) { + // Both formats 10:20pm and 22:20 + if (val == null) return false + let max, am, pm + // -- process american format + val = String(val) + val = val.toUpperCase() + am = val.indexOf('AM') >= 0 + pm = val.indexOf('PM') >= 0 + let ampm = (pm || am) + if (ampm) max = 12; else max = 24 + val = val.replace('AM', '').replace('PM', '').trim() + // --- + let tmp = val.split(':') + let h = parseInt(tmp[0] || 0), m = parseInt(tmp[1] || 0), s = parseInt(tmp[2] || 0) + // accept edge case: 3PM is a good timestamp, but 3 (without AM or PM) is NOT: + if ((!ampm || tmp.length !== 1) && tmp.length !== 2 && tmp.length !== 3) { return false } + if (tmp[0] === '' || h < 0 || h > max || !this.isInt(tmp[0]) || tmp[0].length > 2) { return false } + if (tmp.length > 1 && (tmp[1] === '' || m < 0 || m > 59 || !this.isInt(tmp[1]) || tmp[1].length !== 2)) { return false } + if (tmp.length > 2 && (tmp[2] === '' || s < 0 || s > 59 || !this.isInt(tmp[2]) || tmp[2].length !== 2)) { return false } + // check the edge cases: 12:01AM is ok, as is 12:01PM, but 24:01 is NOT ok while 24:00 is (midnight; equivalent to 00:00). + // meanwhile, there is 00:00 which is ok, but 0AM nor 0PM are okay, while 0:01AM and 0:00AM are. + if (!ampm && max === h && (m !== 0 || s !== 0)) { return false } + if (ampm && tmp.length === 1 && h === 0) { return false } + if (retTime === true) { + if (pm && h !== 12) h += 12 // 12:00pm - is noon + if (am && h === 12) h += 12 // 12:00am - is midnight + return { + hours: h, + minutes: m, + seconds: s + } + } + return true + } + isDateTime(val, format, retDate) { + if (typeof val.getFullYear === 'function') { // date object + if (retDate !== true) return true + return val + } + let intVal = parseInt(val) + if (intVal === val) { + if (intVal < 0) return false + else if (retDate !== true) return true + else return new Date(intVal) + } + let tmp = String(val).indexOf(' ') + if (tmp < 0) { + if (String(val).indexOf('T') < 0 || String(new Date(val)) == 'Invalid Date') return false + else if (retDate !== true) return true + else return new Date(val) + } else { + if (format == null) format = this.settings.datetimeFormat + let formats = format.split('|') + let values = [val.substr(0, tmp), val.substr(tmp).trim()] + formats[0] = formats[0].trim() + if (formats[1]) formats[1] = formats[1].trim() + // check + let tmp1 = this.isDate(values[0], formats[0], true) + let tmp2 = this.isTime(values[1], true) + if (tmp1 !== false && tmp2 !== false) { + if (retDate !== true) return true + tmp1.setHours(tmp2.hours) + tmp1.setMinutes(tmp2.minutes) + tmp1.setSeconds(tmp2.seconds) + return tmp1 + } else { + return false + } + } + } + age(dateStr) { + let d1 + if (dateStr === '' || dateStr == null) return '' + if (typeof dateStr.getFullYear === 'function') { // date object + d1 = dateStr + } else if (parseInt(dateStr) == dateStr && parseInt(dateStr) > 0) { + d1 = new Date(parseInt(dateStr)) + } else { + d1 = new Date(dateStr) + } + if (String(d1) === 'Invalid Date') return '' + let d2 = new Date() + let sec = (d2.getTime() - d1.getTime()) / 1000 + let amount = '' + let type = '' + if (sec < 0) { + amount = 0 + type = 'sec' + } else if (sec < 60) { + amount = Math.floor(sec) + type = 'sec' + if (sec < 0) { amount = 0; type = 'sec' } + } else if (sec < 60*60) { + amount = Math.floor(sec/60) + type = 'min' + } else if (sec < 24*60*60) { + amount = Math.floor(sec/60/60) + type = 'hour' + } else if (sec < 30*24*60*60) { + amount = Math.floor(sec/24/60/60) + type = 'day' + } else if (sec < 365*24*60*60) { + amount = Math.floor(sec/30/24/60/60*10)/10 + type = 'month' + } else if (sec < 365*4*24*60*60) { + amount = Math.floor(sec/365/24/60/60*10)/10 + type = 'year' + } else if (sec >= 365*4*24*60*60) { + // factor in leap year shift (only older then 4 years) + amount = Math.floor(sec/365.25/24/60/60*10)/10 + type = 'year' + } + return amount + ' ' + type + (amount > 1 ? 's' : '') + } + interval(value) { + let ret = '' + if (value < 100) { + ret = '< 0.01 sec' + } else if (value < 1000) { + ret = (Math.floor(value / 10) / 100) + ' sec' + } else if (value < 10000) { + ret = (Math.floor(value / 100) / 10) + ' sec' + } else if (value < 60000) { + ret = Math.floor(value / 1000) + ' secs' + } else if (value < 3600000) { + ret = Math.floor(value / 60000) + ' mins' + } else if (value < 86400000) { + ret = Math.floor(value / 3600000 * 10) / 10 + ' hours' + } else if (value < 2628000000) { + ret = Math.floor(value / 86400000 * 10) / 10 + ' days' + } else if (value < 3.1536e+10) { + ret = Math.floor(value / 2628000000 * 10) / 10 + ' months' + } else { + ret = Math.floor(value / 3.1536e+9) / 10 + ' years' + } + return ret + } + date(dateStr) { + if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return '' + let d1 = new Date(dateStr) + if (this.isInt(dateStr)) d1 = new Date(Number(dateStr)) // for unix timestamps + if (String(d1) === 'Invalid Date') return '' + let months = this.settings.shortmonths + let d2 = new Date() // today + let d3 = new Date() + d3.setTime(d3.getTime() - 86400000) // yesterday + let dd1 = months[d1.getMonth()] + ' ' + d1.getDate() + ', ' + d1.getFullYear() + let dd2 = months[d2.getMonth()] + ' ' + d2.getDate() + ', ' + d2.getFullYear() + let dd3 = months[d3.getMonth()] + ' ' + d3.getDate() + ', ' + d3.getFullYear() + let time = (d1.getHours() - (d1.getHours() > 12 ? 12 :0)) + ':' + (d1.getMinutes() < 10 ? '0' : '') + d1.getMinutes() + ' ' + (d1.getHours() >= 12 ? 'pm' : 'am') + let time2 = (d1.getHours() - (d1.getHours() > 12 ? 12 :0)) + ':' + (d1.getMinutes() < 10 ? '0' : '') + d1.getMinutes() + ':' + (d1.getSeconds() < 10 ? '0' : '') + d1.getSeconds() + ' ' + (d1.getHours() >= 12 ? 'pm' : 'am') + let dsp = dd1 + if (dd1 === dd2) dsp = time + if (dd1 === dd3) dsp = this.lang('Yesterday') + return ''+ dsp +'' + } + formatSize(sizeStr) { + if (!this.isFloat(sizeStr) || sizeStr === '') return '' + sizeStr = parseFloat(sizeStr) + if (sizeStr === 0) return 0 + let sizes = ['Bt', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB'] + let i = parseInt( Math.floor( Math.log(sizeStr) / Math.log(1024) ) ) + return (Math.floor(sizeStr / Math.pow(1024, i) * 10) / 10).toFixed(i === 0 ? 0 : 1) + ' ' + (sizes[i] || '??') + } + formatNumber(val, fraction, useGrouping) { + if (val == null || val === '' || typeof val === 'object') return '' + let options = { + minimumFractionDigits: parseInt(fraction), + maximumFractionDigits: parseInt(fraction), + useGrouping: !!useGrouping + } + if (fraction == null || fraction < 0) { + options.minimumFractionDigits = 0 + options.maximumFractionDigits = 20 + } + return parseFloat(val).toLocaleString(this.settings.locale, options) + } + formatDate(dateStr, format) { // IMPORTANT dateStr HAS TO BE valid JavaScript Date String + if (!format) format = this.settings.dateFormat + if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return '' + let dt = new Date(dateStr) + if (this.isInt(dateStr)) dt = new Date(Number(dateStr)) // for unix timestamps + if (String(dt) === 'Invalid Date') return '' + let year = dt.getFullYear() + let month = dt.getMonth() + let date = dt.getDate() + return format.toLowerCase() + .replace('month', this.settings.fullmonths[month]) + .replace('mon', this.settings.shortmonths[month]) + .replace(/yyyy/g, ('000' + year).slice(-4)) + .replace(/yyy/g, ('000' + year).slice(-4)) + .replace(/yy/g, ('0' + year).slice(-2)) + .replace(/(^|[^a-z$])y/g, '$1' + year) // only y's that are not preceded by a letter + .replace(/mm/g, ('0' + (month + 1)).slice(-2)) + .replace(/dd/g, ('0' + date).slice(-2)) + .replace(/th/g, (date == 1 ? 'st' : 'th')) + .replace(/th/g, (date == 2 ? 'nd' : 'th')) + .replace(/th/g, (date == 3 ? 'rd' : 'th')) + .replace(/(^|[^a-z$])m/g, '$1' + (month + 1)) // only y's that are not preceded by a letter + .replace(/(^|[^a-z$])d/g, '$1' + date) // only y's that are not preceded by a letter + } + formatTime(dateStr, format) { // IMPORTANT dateStr HAS TO BE valid JavaScript Date String + if (!format) format = this.settings.timeFormat + if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return '' + let dt = new Date(dateStr) + if (this.isInt(dateStr)) dt = new Date(Number(dateStr)) // for unix timestamps + if (this.isTime(dateStr)) { + let tmp = this.isTime(dateStr, true) + dt = new Date() + dt.setHours(tmp.hours) + dt.setMinutes(tmp.minutes) + } + if (String(dt) === 'Invalid Date') return '' + let type = 'am' + let hour = dt.getHours() + let h24 = dt.getHours() + let min = dt.getMinutes() + let sec = dt.getSeconds() + if (min < 10) min = '0' + min + if (sec < 10) sec = '0' + sec + if (format.indexOf('am') !== -1 || format.indexOf('pm') !== -1) { + if (hour >= 12) type = 'pm' + if (hour > 12) hour = hour - 12 + if (hour === 0) hour = 12 + } + return format.toLowerCase() + .replace('am', type) + .replace('pm', type) + .replace('hhh', (hour < 10 ? '0' + hour : hour)) + .replace('hh24', (h24 < 10 ? '0' + h24 : h24)) + .replace('h24', h24) + .replace('hh', hour) + .replace('mm', min) + .replace('mi', min) + .replace('ss', sec) + .replace(/(^|[^a-z$])h/g, '$1' + hour) // only y's that are not preceded by a letter + .replace(/(^|[^a-z$])m/g, '$1' + min) // only y's that are not preceded by a letter + .replace(/(^|[^a-z$])s/g, '$1' + sec) // only y's that are not preceded by a letter + } + formatDateTime(dateStr, format) { + let fmt + if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return '' + if (typeof format !== 'string') { + fmt = [this.settings.dateFormat, this.settings.timeFormat] + } else { + fmt = format.split('|') + fmt[0] = fmt[0].trim() + fmt[1] = (fmt.length > 1 ? fmt[1].trim() : this.settings.timeFormat) + } + // older formats support + if (fmt[1] === 'h12') fmt[1] = 'h:m pm' + if (fmt[1] === 'h24') fmt[1] = 'h24:m' + return this.formatDate(dateStr, fmt[0]) + ' ' + this.formatTime(dateStr, fmt[1]) + } + stripSpaces(html) { + if (html == null) return html + switch (typeof html) { + case 'number': + break + case 'string': + html = String(html).replace(/(?:\r\n|\r|\n)/g, ' ').replace(/\s\s+/g, ' ').trim() + break + case 'object': + // does not modify original object, but creates a copy + if (Array.isArray(html)) { + html = this.extend([], html) + html.forEach((key, ind) => { + html[ind] = this.stripSpaces(key) + }) + } else { + html = this.extend({}, html) + Object.keys(html).forEach(key => { + html[key] = this.stripSpaces(html[key]) + }) + } + break + } + return html + } + stripTags(html) { + if (html == null) return html + switch (typeof html) { + case 'number': + break + case 'string': + html = String(html).replace(/<(?:[^>=]|='[^']*'|="[^"]*"|=[^'"][^\s>]*)*>/ig, '') + break + case 'object': + // does not modify original object, but creates a copy + if (Array.isArray(html)) { + html = this.extend([], html) + html.forEach((key, ind) => { + html[ind] = this.stripTags(key) + }) + } else { + html = this.extend({}, html) + Object.keys(html).forEach(key => { + html[key] = this.stripTags(html[key]) + }) + } + break + } + return html + } + encodeTags(html) { + if (html == null) return html + switch (typeof html) { + case 'number': + break + case 'string': + html = String(html).replace(/&/g, '&').replace(/>/g, '>').replace(/ { + html[ind] = this.encodeTags(key) + }) + } else { + html = this.extend({}, html) + Object.keys(html).forEach(key => { + html[key] = this.encodeTags(html[key]) + }) + } + break + } + return html + } + decodeTags(html) { + if (html == null) return html + switch (typeof html) { + case 'number': + break + case 'string': + html = String(html).replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/&/g, '&') + break + case 'object': + // does not modify original object, but creates a copy + if (Array.isArray(html)) { + html = this.extend([], html) + html.forEach((key, ind) => { + html[ind] = this.decodeTags(key) + }) + } else { + html = this.extend({}, html) + Object.keys(html).forEach(key => { + html[key] = this.decodeTags(html[key]) + }) + } + break + } + return html + } + escapeId(id) { + // This logic is borrowed from jQuery + if (id === '' || id == null) return '' + let re = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g + return (id + '').replace(re, (ch, asCodePoint) => { + if (asCodePoint) { + if (ch === '\0') return '\uFFFD' + return ch.slice( 0, -1 ) + '\\' + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + ' ' + } + return '\\' + ch + }) + } + unescapeId(id) { + // This logic is borrowed from jQuery + if (id === '' || id == null) return '' + let re = /\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\([^\r\n\f])/g + return id.replace(re, (escape, nonHex) => { + let high = '0x' + escape.slice( 1 ) - 0x10000 + return nonHex ? nonHex : high < 0 + ? String.fromCharCode(high + 0x10000 ) + : String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00) + }) + } + base64encode(input) { + // Fast Native support in Chrome since 2010 + return btoa(input) // binary to ascii + } + base64decode(input) { + // Fast Native support in Chrome since 2010 + return atob(input) // ascii to binary + } + async sha256(str) { + const utf8 = new TextEncoder().encode(str) + return crypto.subtle.digest('SHA-256', utf8).then((hashBuffer) => { + const hashArray = Array.from(new Uint8Array(hashBuffer)) + return hashArray.map((bytes) => bytes.toString(16).padStart(2, '0')).join('') + }) + } + transition(div_old, div_new, type, callBack) { + return new Promise((resolve, reject) => { + let styles = getComputedStyle(div_old) + let width = parseInt(styles.width) + let height = parseInt(styles.height) + let time = 0.5 + if (!div_old || !div_new) { + console.log('ERROR: Cannot do transition when one of the divs is null') + return + } + div_old.parentNode.style.cssText += 'perspective: 900px; overflow: hidden;' + div_old.style.cssText += '; position: absolute; z-index: 1019; backface-visibility: hidden' + div_new.style.cssText += '; position: absolute; z-index: 1020; backface-visibility: hidden' + switch (type) { + case 'slide-left': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)' + div_new.style.cssText += 'overflow: hidden; transform: translate3d('+ width + 'px, 0, 0)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)' + div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d(-'+ width +'px, 0, 0)' + }, 1) + break + case 'slide-right': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)' + div_new.style.cssText += 'overflow: hidden; transform: translate3d(-'+ width +'px, 0, 0)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0px, 0, 0)' + div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d('+ width +'px, 0, 0)' + }, 1) + break + case 'slide-down': + // init divs + div_old.style.cssText += 'overflow: hidden; z-index: 1; transform: translate3d(0, 0, 0)' + div_new.style.cssText += 'overflow: hidden; z-index: 0; transform: translate3d(0, 0, 0)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)' + div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, '+ height +'px, 0)' + }, 1) + break + case 'slide-up': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)' + div_new.style.cssText += 'overflow: hidden; transform: translate3d(0, '+ height +'px, 0)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)' + div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)' + }, 1) + break + case 'flip-left': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: rotateY(0deg)' + div_new.style.cssText += 'overflow: hidden; transform: rotateY(-180deg)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: rotateY(0deg)' + div_old.style.cssText += 'transition: '+ time +'s; transform: rotateY(180deg)' + }, 1) + break + case 'flip-right': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: rotateY(0deg)' + div_new.style.cssText += 'overflow: hidden; transform: rotateY(180deg)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: rotateY(0deg)' + div_old.style.cssText += 'transition: '+ time +'s; transform: rotateY(-180deg)' + }, 1) + break + case 'flip-down': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: rotateX(0deg)' + div_new.style.cssText += 'overflow: hidden; transform: rotateX(180deg)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: rotateX(0deg)' + div_old.style.cssText += 'transition: '+ time +'s; transform: rotateX(-180deg)' + }, 1) + break + case 'flip-up': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: rotateX(0deg)' + div_new.style.cssText += 'overflow: hidden; transform: rotateX(-180deg)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: rotateX(0deg)' + div_old.style.cssText += 'transition: '+ time +'s; transform: rotateX(180deg)' + }, 1) + break + case 'pop-in': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)' + div_new.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0); transform: scale(.8); opacity: 0;' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: scale(1); opacity: 1;' + div_old.style.cssText += 'transition: '+ time +'s;' + }, 1) + break + case 'pop-out': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0); transform: scale(1); opacity: 1;' + div_new.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0); opacity: 0;' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; opacity: 1;' + div_old.style.cssText += 'transition: '+ time +'s; transform: scale(1.7); opacity: 0;' + }, 1) + break + default: + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)' + div_new.style.cssText += 'overflow: hidden; translate3d(0, 0, 0); opacity: 0;' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; opacity: 1;' + div_old.style.cssText += 'transition: '+ time +'s' + }, 1) + break + } + setTimeout(() => { + if (type === 'slide-down') { + query(div_old).css('z-index', '1019') + query(div_new).css('z-index', '1020') + } + if (div_new) { + query(div_new) + .css({ 'opacity': '1' }) + .css({ 'transition': '', 'transform' : '' }) + } + if (div_old) { + query(div_old) + .css({ 'opacity': '1' }) + .css({ 'transition': '', 'transform' : '' }) + } + if (typeof callBack === 'function') callBack() + resolve() + }, time * 1000) + }) + } + lock(box, options = {}) { + if (box == null) return + if (typeof options == 'string') { + options = { msg: options } + } + if (arguments[2]) { + options.spinner = arguments[2] + } + options = this.extend({ + spinner: false + }, options) + // for backward compatibility + if (box?.[0] instanceof Node) { + box = Array.isArray(box) ? box : box.get() + } + if (!options.msg && options.msg !== 0) options.msg = '' + this.unlock(box) + let el = query(box).get(0) + let pWidth = el.scrollWidth + let pHeight = el.scrollHeight + // if it is body and only has absolute elements, its height will be 0, need to lock entire window + if (el.tagName == 'BODY') { + if (pWidth < innerWidth) pWidth = innerWidth + if (pHeight < innerHeight) pHeight = innerHeight + } + query(box).prepend( + `
    ` + + '
    ' + ) + let $lock = query(box).find('.w2ui-lock') + let $mess = query(box).find('.w2ui-lock-msg') + if (!options.msg) { + $mess.css({ + 'background-color': 'transparent', + 'background-image': 'none', + 'border': '0px', + 'box-shadow': 'none' + }) + } + if (options.spinner === true) { + options.msg = `
    ` + + options.msg + } + if (options.msg) { + $mess.html(options.msg).css('display', 'block') + } else { + $mess.remove() + } + if (options.opacity != null) { + $lock.css('opacity', options.opacity) + } + $lock.css({ display: 'block' }) + if (options.bgColor) { + $lock.css({ 'background-color': options.bgColor }) + } + let styles = getComputedStyle($lock.get(0)) + let opacity = styles.opacity ?? 0.15 + $lock + .on('mousedown', function() { + if (typeof options.onClick == 'function') { + options.onClick() + } else { + $lock.css({ + 'transition': '.2s', + 'opacity': opacity * 1.5 + }) + } + }) + .on('mouseup', function() { + if (typeof options.onClick !== 'function') { + $lock.css({ + 'transition': '.2s', + 'opacity': opacity + }) + } + }) + .on('mousewheel', function(event) { + if (event) { + event.stopPropagation() + event.preventDefault() + } + }) + } + unlock(box, speed) { + if (box == null) return + clearTimeout(box._prevUnlock) + // for backward compatibility + if (box?.[0] instanceof Node) { + box = Array.isArray(box) ? box : box.get() + } + if (this.isInt(speed) && speed > 0) { + query(box).find('.w2ui-lock').css({ + transition: (speed/1000) + 's', + opacity: 0, + }) + let _box = query(box).get(0) + clearTimeout(_box._prevUnlock) + _box._prevUnlock = setTimeout(() => { + query(box).find('.w2ui-lock').remove() + }, speed) + query(box).find('.w2ui-lock-msg').remove() + } else { + query(box).find('.w2ui-lock').remove() + query(box).find('.w2ui-lock-msg').remove() + } + } + /** + * Opens a context message, similar in parameters as w2popup.open() + * + * Sample Calls + * w2utils.message({ box: '#div' }, 'message').ok(() => {}) + * w2utils.message({ box: '#div' }, { text: 'message', width: 300 }).ok(() => {}) + * w2utils.message({ box: '#div' }, { text: 'message', actions: ['Save'] }).Save(() => {}) + * + * Used in w2grid, w2form, w2layout (should be in w2popup too) + * should be called with .call(...) method + * + * @param where = { + * box, // where to open + * after, // title if any, adds title heights + * param // additional parameters, used in layouts for panel + * } + * @param options { + * width, // (int), width in px, if negative, then it is maxWidth - width + * height, // (int), height in px, if negative, then it is maxHeight - height + * text, // centered text + * body, // body of the message + * buttons, // buttons of the message + * html, // if body & buttons are not defined, then html is the entire message + * focus, // int or id with a selector, default is 0 + * hideOn, // ['esc', 'click'], default is ['esc'] + * actions, // array of actions (only if buttons is not defined) + * onOpen, // event when opened + * onClose, // event when closed + * onAction, // event on action + * } + */ + message(where, options) { + let closeTimer, openTimer, edata + let removeLast = () => { + let msgs = query(where?.box).find('.w2ui-message') + if (msgs.length == 0) return // no messages already + options = msgs.get(0)._msg_options || {} + if (typeof options?.close == 'function') { + options.close() + } + } + let closeComplete = (options) => { + let focus = options.box._msg_prevFocus + if (query(where.box).find('.w2ui-message').length <= 1) { + if (where.owner) { + where.owner.unlock(where.param, 150) + } else { + this.unlock(where.box, 150) + } + } else { + query(where.box).find(`#w2ui-message-${where.owner?.name}-${options.msgIndex-1}`).css('z-index', 1500) + } + if (focus) { + let msg = query(focus).closest('.w2ui-message') + if (msg.length > 0) { + let opt = msg.get(0)._msg_options + opt.setFocus(focus) + } else { + focus.focus() + } + } else { + if (typeof where.owner?.focus == 'function') where.owner.focus() + } + query(options.box).remove() + if (options.msgIndex === 0) { + head.css('z-index', options.tmp.zIndex) + query(where.box).css('overflow', options.tmp.overflow) + } + // event after + if (options.trigger) { + edata.finish() + } + } + if (typeof options == 'string' || typeof options == 'number') { + options = { + width : (String(options).length < 300 ? 350 : 550), + height: (String(options).length < 300 ? 170: 250), + text : String(options), + } + } + if (typeof options != 'object') { + removeLast() + return + } + if (options.text != null) options.body = `
    ${options.text}
    ` + if (options.width == null) options.width = 350 + if (options.height == null) options.height = 170 + if (options.hideOn == null) options.hideOn = ['esc'] + // mix in events + if (options.on == null) { + let opts = options + options = new w2base() + w2utils.extend(options, opts) // needs to be w2utils + } + options.on('open', (event) => { + w2utils.bindEvents(query(options.box).find('.w2ui-eaction'), options) // options is w2base object + query(event.detail.box).find('button, input, textarea, [name=hidden-first]') + .off('.message') + .on('keydown.message', function(evt) { + if (evt.keyCode == 27 && options.hideOn.includes('esc')) { + if (options.cancelAction) { + options.action(options.cancelAction) + } else { + options.close() + } + } + }) + // timeout is needed because messages opens over 0.3 seconds + setTimeout(() => options.setFocus(options.focus), 300) + }) + options.off('.prom') + let prom = { + self: options, + action(callBack) { + options.on('action.prom', callBack) + return prom + }, + close(callBack) { + options.on('close.prom', callBack) + return prom + }, + open(callBack) { + options.on('open.prom', callBack) + return prom + }, + then(callBack) { + options.on('open:after.prom', callBack) + return prom + } + } + if (options.actions == null && options.buttons == null && options.html == null) { + options.actions = { Ok(event) { event.detail.self.close() }} + } + options.off('.buttons') + if (options.actions != null) { + options.buttons = '' + Object.keys(options.actions).forEach((action) => { + let handler = options.actions[action] + let btnAction = action + if (typeof handler == 'function') { + options.buttons += `` + } + if (typeof handler == 'object') { + options.buttons += `` + btnAction = Array.isArray(options.actions) ? handler.text : action + } + if (typeof handler == 'string') { + options.buttons += `` + btnAction = handler + } + if (typeof btnAction == 'string') { + btnAction = btnAction[0].toLowerCase() + btnAction.substr(1).replace(/\s+/g, '') + } + prom[btnAction] = function (callBack) { + options.on('action.buttons', (event) => { + let target = event.detail.action[0].toLowerCase() + event.detail.action.substr(1).replace(/\s+/g, '') + if (target == btnAction) callBack(event) + }) + return prom + } + }) + } + // trim if any + Array('html', 'body', 'buttons').forEach(param => { + options[param] = String(options[param] ?? '').trim() + }) + if (options.body !== '' || options.buttons !== '') { + options.html = ` +
    ${options.body || ''}
    +
    ${options.buttons || ''}
    + ` + } + let styles = getComputedStyle(query(where.box).get(0)) + let pWidth = parseFloat(styles.width) + let pHeight = parseFloat(styles.height) + let titleHeight = 0 + if (query(where.after).length > 0) { + styles = getComputedStyle(query(where.after).get(0)) + titleHeight = parseInt(styles.display != 'none' ? parseInt(styles.height) : 0) + } + if (options.width > pWidth) options.width = pWidth - 10 + if (options.height > pHeight - titleHeight) options.height = pHeight - 10 - titleHeight + options.originalWidth = options.width + options.originalHeight = options.height + if (parseInt(options.width) < 0) options.width = pWidth + options.width + if (parseInt(options.width) < 10) options.width = 10 + if (parseInt(options.height) < 0) options.height = pHeight + options.height - titleHeight + if (parseInt(options.height) < 10) options.height = 10 + // negative value means margin + if (options.originalHeight < 0) options.height = pHeight + options.originalHeight - titleHeight + if (options.originalWidth < 0) options.width = pWidth + options.originalWidth * 2 // x 2 because there is left and right margin + let head = query(where.box).find(where.after) // needed for z-index manipulations + if (!options.tmp) { + options.tmp = { + zIndex: head.css('z-index'), + overflow: styles.overflow + } + } + // remove message + if (options.html === '' && options.body === '' && options.buttons === '') { + removeLast() + } else { + options.msgIndex = query(where.box).find('.w2ui-message').length + if (options.msgIndex === 0 && typeof this.lock == 'function') { + query(where.box).css('overflow', 'hidden') + if (where.owner) { // where.praram is used in the panel + where.owner.lock(where.param) + } else { + this.lock(where.box) + } + } + // send back previous messages + query(where.box).find('.w2ui-message').css('z-index', 1390) + head.css('z-index', 1501) + // add message + let content = ` +
    + + ${options.html} + +
    ` + if (query(where.after).length > 0) { + query(where.box).find(where.after).after(content) + } else { + query(where.box).prepend(content) + } + options.box = query(where.box).find(`#w2ui-message-${where.owner?.name}-${options.msgIndex}`)[0] + w2utils.bindEvents(options.box, this) + query(options.box) + .addClass('animating') + // remember options and prev focus + options.box._msg_options = options + options.box._msg_prevFocus = document.activeElement + // timeout is needs so that callBacks are setup + setTimeout(() => { + // before event + edata = options.trigger('open', { target: this.name, box: options.box, self: options }) + if (edata.isCancelled === true) { + query(where.box).find(`#w2ui-message-${where.owner?.name}-${options.msgIndex}`).remove() + if (options.msgIndex === 0) { + head.css('z-index', options.tmp.zIndex) + query(where.box).css('overflow', options.tmp.overflow) + } + return + } + // slide down + query(options.box).css({ + transition: '0.3s', + transform: 'translateY(0px)' + }) + }, 0) + // timeout is needed so that animation can finish + openTimer = setTimeout(() => { + // has to be on top of lock + query(where.box) + .find(`#w2ui-message-${where.owner?.name}-${options.msgIndex}`) + .removeClass('animating') + .css({ 'transition': '0s' }) + // event after + edata.finish() + }, 300) + } + // action handler + options.action = (action, event) => { + let click = options.actions[action] + if (click instanceof Object && click.onClick) click = click.onClick + // event before + let edata = options.trigger('action', { target: this.name, action, self: options, + originalEvent: event, value: options.input ? options.input.value : null }) + if (edata.isCancelled === true) return + // default actions + if (typeof click === 'function') click(edata) + // event after + edata.finish() + } + options.close = () => { + edata = options.trigger('close', { target: 'self', box: options.box, self: options }) + if (edata.isCancelled === true) return + clearTimeout(openTimer) + if (query(options.box).hasClass('animating')) { + clearTimeout(closeTimer) + closeComplete(options) + return + } + // default behavior + query(options.box) + .addClass('w2ui-closing animating') + .css({ + 'transition': '0.15s', + 'transform': 'translateY(-' + options.height + 'px)' + }) + if (options.msgIndex !== 0) { + // previous message + query(where.box).find(`#w2ui-message-${where.owner?.name}-${options.msgIndex-1}`).css('z-index', 1499) + } + closeTimer = setTimeout(() => { closeComplete(options) }, 150) + } + options.setFocus = (focus) => { + // in message or popup + let cnt = query(where.box).find('.w2ui-message').length - 1 + let box = query(where.box).find(`#w2ui-message-${where.owner?.name}-${cnt}`) + let sel = 'input, button, select, textarea, [contentEditable], .w2ui-input' + if (focus != null) { + let el = isNaN(focus) + ? box.find(sel).filter(focus).get(0) + : box.find(sel).get(focus) + el?.focus() + } else { + box.find('[name=hidden-first]').get(0)?.focus() + } + // clear focus if there are other messages + query(where.box) + .find('.w2ui-message') + .find(sel + ',[name=hidden-first],[name=hidden-last]') + .off('.keep-focus') + // keep focus/blur inside popup + query(box) + .find(sel + ',[name=hidden-first],[name=hidden-last]') + .on('blur.keep-focus', function (event) { + setTimeout(() => { + let focus = document.activeElement + let inside = query(box).find(sel).filter(focus).length > 0 + let name = query(focus).attr('name') + if (!inside && focus && focus !== document.body) { + query(box).find(sel).get(0)?.focus() + } + if (name == 'hidden-last') { + query(box).find(sel).get(0)?.focus() + } + if (name == 'hidden-first') { + query(box).find(sel).get(-1)?.focus() + } + }, 1) + }) + } + return prom + } + /** + * Shows small notification message at the bottom of the page, or containter that you specify + * in options.where (could be element or a selector) + * + * w2utils.notify('Document saved') + * w2utils.notify('Mesage sent ${udon}', { actions: { undo: function () {...} }}) + * + * @param {String/Object} options can be { + * text: string, // message, can be html + * where: el/selector, // element or selector where to show, default is document.body + * timeout: int, // timeout when to hide, if 0 - indefinite + * error: boolean, // add error clases + * class: string, // additional class strings + * actions: object // object with action functions, it should correspot to templated text: '... ${action} ...' + * } + * @returns promise + */ + notify(text, options) { + return new Promise(resolve => { + if (typeof text == 'object') { + options = text + text = options.text + } + options = options || {} + options.where = options.where ?? document.body + options.timeout = options.timeout ?? 15_000 // 15 secodns or will be hidden on route change + if (typeof this.tmp.notify_resolve == 'function') { + this.tmp.notify_resolve() + query(this.tmp.notify_where).find('#w2ui-notify').remove() + } + this.tmp.notify_resolve = resolve + this.tmp.notify_where = options.where + clearTimeout(this.tmp.notify_timer) + if (text) { + if (typeof options.actions == 'object') { + let actions = {} + Object.keys(options.actions).forEach(action => { + actions[action] = `${action}` + }) + text = this.execTemplate(text, actions) + } + let html = ` +
    +
    + ${text} + +
    +
    ` + query(options.where).append(html) + query(options.where).find('#w2ui-notify').find('.w2ui-notify-close') + .on('click', event => { + query(options.where).find('#w2ui-notify').remove() + resolve() + }) + if (options.actions) { + query(options.where).find('#w2ui-notify .w2ui-notify-link') + .on('click', event => { + let value = query(event.target).attr('value') + options.actions[value]() + query(options.where).find('#w2ui-notify').remove() + resolve() + }) + } + if (options.timeout > 0) { + this.tmp.notify_timer = setTimeout(() => { + query(options.where).find('#w2ui-notify').remove() + resolve() + }, options.timeout) + } + } + }) + } + confirm(where, options) { + if (typeof options == 'string') { + options = { text: options } + } + w2utils.normButtons(options, { yes: 'Yes', no: 'No' }) + let prom = w2utils.message(where, options) + if (prom) { + prom.action(event => { + event.detail.self.close() + }) + } + return prom + } + /** + * Normalizes yes, no buttons for confirmation dialog + * + * @param {*} options + * @returns options + */ + normButtons(options, btn) { + options.actions = options.actions ?? {} + let btns = Object.keys(btn) + btns.forEach(name => { + let action = options['btn_' + name] + if (action) { + btn[name] = { + text: w2utils.lang(action.text ?? ''), + class: action.class ?? '', + style: action.style ?? '', + attrs: action.attrs ?? '' + } + delete options['btn_' + name] + } + Array('text', 'class', 'style', 'attrs').forEach(suffix => { + if (options[name + '_' + suffix]) { + if (typeof btn[name] == 'string') { + btn[name] = { text: btn[name] } + } + btn[name][suffix] = options[name + '_' + suffix] + delete options[name + '_' + suffix] + } + }) + }) + if (btns.includes('yes') && btns.includes('no')) { + if (w2utils.settings.macButtonOrder) { + w2utils.extend(options.actions, { no: btn.no, yes: btn.yes }) + } else { + w2utils.extend(options.actions, { yes: btn.yes, no: btn.no }) + } + } + if (btns.includes('ok') && btns.includes('cancel')) { + if (w2utils.settings.macButtonOrder) { + w2utils.extend(options.actions, { cancel: btn.cancel, ok: btn.ok }) + } else { + w2utils.extend(options.actions, { ok: btn.ok, cancel: btn.cancel }) + } + } + return options + } + getSize(el, type) { + el = query(el) // for backward compatibility + let ret = 0 + if (el.length > 0) { + el = el[0] + let styles = getComputedStyle(el) + switch (type) { + case 'width' : + ret = parseFloat(styles.width) + if (styles.width === 'auto') ret = 0 + break + case 'height' : + ret = parseFloat(styles.height) + if (styles.height === 'auto') ret = 0 + break + default: + ret = parseFloat(styles[type] ?? 0) || 0 + break + } + } + return ret + } + getStrWidth(str, styles) { + query('body').append(` +
    + ${this.encodeTags(str)} +
    `) + let width = query('#_tmp_width')[0].clientWidth + query('#_tmp_width').remove() + return width + } + execTemplate(str, replace_obj) { + if (typeof str !== 'string' || !replace_obj || typeof replace_obj !== 'object') { + return str + } + return str.replace(/\${([^}]+)?}/g, function($1, $2) { return replace_obj[$2]||$2 }) + } + marker(el, items, options = { onlyFirst: false, wholeWord: false }) { + if (!Array.isArray(items)) { + if (items != null && items !== '') { + items = [items] + } else { + items = [] + } + } + let ww = options.wholeWord + query(el).each(el => { + clearMerkers(el) + items.forEach(str => { + if (typeof str !== 'string') str = String(str) + let replaceValue = (matched) => { // mark new + return '' + matched + '' + } + // escape regex special chars + str = str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&').replace(/&/g, '&') + .replace(//g, '<') + let regex = new RegExp((ww ? '\\b' : '') + str + (ww ? '\\b' : '')+ '(?!([^<]+)?>)', + 'i' + (!options.onlyFirst ? 'g' : '')) // only outside tags + el.innerHTML = el.innerHTML.replace(regex, replaceValue) + }) + }) + function clearMerkers(el) { + let markerRE = /\((.|\n|\r)*)\<\/span\>/ig + while (el.innerHTML.indexOf('='.includes(phrase)) { + return this.execTemplate(phrase, params) + } + let translation = this.settings.phrases[phrase] + if (translation == null) { + translation = phrase + if (this.settings.warnNoPhrase) { + if (!this.settings.missing) { + this.settings.missing = {} + } + this.settings.missing[phrase] = '---' // collect phrases for translation, warn once + this.settings.phrases[phrase] = '---' + console.log(`Missing translation for "%c${phrase}%c", see %c w2utils.settings.phrases %c with value "---"`, + 'color: orange', '', + 'color: #999', '') + } + } else if (translation === '---' && !this.settings.warnNoPhrase) { + translation = phrase + } + if (translation === '---') { + translation = `---` + } + return this.execTemplate(translation, params) + } + locale(locale, keepPhrases, noMerge) { + return new Promise((resolve, reject) => { + // if locale is an array we call this function recursively and merge the results + if (Array.isArray(locale)) { + this.settings.phrases = {} + let proms = [] + let files = {} + locale.forEach((file, ind) => { + if (file.length === 5) { + file = 'locale/'+ file.toLowerCase() +'.json' + locale[ind] = file + } + proms.push(this.locale(file, true, false)) + }) + Promise.allSettled(proms) + .then(res => { + // order of files is important to merge + res.forEach(r => { if (r.value) files[r.value.file] = r.value.data }) + locale.forEach(file => { + this.settings = this.extend({}, this.settings, files[file]) + }) + resolve() + }) + return + } + if (!locale) locale = 'en-us' + // if locale is an object, then merge it with w2utils.settings + if (locale instanceof Object) { + this.settings = this.extend({}, this.settings, w2locale, locale) + return + } + if (locale.length === 5) { + locale = 'locale/'+ locale.toLowerCase() +'.json' + } + // load from the file + fetch(locale, { method: 'GET' }) + .then(res => res.json()) + .then(data => { + if (noMerge !== true) { + if (keepPhrases) { + // keep phrases, useful for recursive calls + this.settings = this.extend({}, this.settings, data) + } else { + // clear phrases from language before merging + this.settings = this.extend({}, this.settings, w2locale, { phrases: {} }, data) + } + } + resolve({ file: locale, data }) + }) + .catch((err) => { + console.log('ERROR: Cannot load locale '+ locale) + reject(err) + }) + }) + } + scrollBarSize() { + if (this.tmp.scrollBarSize) return this.tmp.scrollBarSize + let html = ` +
    +
    1
    +
    + ` + query('body').append(html) + this.tmp.scrollBarSize = 100 - query('#_scrollbar_width > div')[0].clientWidth + query('#_scrollbar_width').remove() + return this.tmp.scrollBarSize + } + checkName(name) { + if (name == null) { + console.log('ERROR: Property "name" is required but not supplied.') + return false + } + if (w2ui[name] != null) { + console.log(`ERROR: Object named "${name}" is already registered as w2ui.${name}.`) + return false + } + if (!this.isAlphaNumeric(name)) { + console.log('ERROR: Property "name" has to be alpha-numeric (a-z, 0-9, dash and underscore).') + return false + } + return true + } + checkUniqueId(id, items, desc, obj) { + if (!Array.isArray(items)) items = [items] + let isUnique = true + items.forEach(item => { + if (item.id === id) { + console.log(`ERROR: The item id="${id}" is not unique within the ${desc} "${obj}".`, items) + isUnique = false + } + }) + return isUnique + } + /** + * Takes an object and encodes it into params string to be passed as a url + * { a: 1, b: 'str'} => "a=1&b=str" + * { a: 1, b: { c: 2 }} => "a=1&b[c]=2" + * { a: 1, b: {c: { k: 'dfdf' } } } => "a=1&b[c][k]=dfdf" + */ + encodeParams(obj, prefix = '') { + let str = '' + Object.keys(obj).forEach(key => { + if (str != '') str += '&' + if (typeof obj[key] == 'object') { + str += this.encodeParams(obj[key], prefix + key + (prefix ? ']' : '') + '[') + } else { + str += `${prefix}${key}${prefix ? ']' : ''}=${obj[key]}` + } + }) + return str + } + parseRoute(route) { + let keys = [] + let path = route + .replace(/\/\(/g, '(?:/') + .replace(/\+/g, '__plus__') + .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, (_, slash, format, key, capture, optional) => { + keys.push({ name: key, optional: !! optional }) + slash = slash || '' + return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' + (optional || '') + }) + .replace(/([\/.])/g, '\\$1') + .replace(/__plus__/g, '(.+)') + .replace(/\*/g, '(.*)') + return { + path : new RegExp('^' + path + '$', 'i'), + keys : keys + } + } + getCursorPosition(input) { + if (input == null) return null + let caretOffset = 0 + let doc = input.ownerDocument || input.document + let win = doc.defaultView || doc.parentWindow + let sel + if (['INPUT', 'TEXTAREA'].includes(input.tagName)) { + caretOffset = input.selectionStart + } else { + if (win.getSelection) { + sel = win.getSelection() + if (sel.rangeCount > 0) { + let range = sel.getRangeAt(0) + let preCaretRange = range.cloneRange() + preCaretRange.selectNodeContents(input) + preCaretRange.setEnd(range.endContainer, range.endOffset) + caretOffset = preCaretRange.toString().length + } + } else if ( (sel = doc.selection) && sel.type !== 'Control') { + let textRange = sel.createRange() + let preCaretTextRange = doc.body.createTextRange() + preCaretTextRange.moveToElementText(input) + preCaretTextRange.setEndPoint('EndToEnd', textRange) + caretOffset = preCaretTextRange.text.length + } + } + return caretOffset + } + setCursorPosition(input, pos, posEnd) { + if (input == null) return + let range = document.createRange() + let el, sel = window.getSelection() + if (['INPUT', 'TEXTAREA'].includes(input.tagName)) { + input.setSelectionRange(pos, posEnd ?? pos) + } else { + for (let i = 0; i < input.childNodes.length; i++) { + let tmp = query(input.childNodes[i]).text() + if (input.childNodes[i].tagName) { + tmp = query(input.childNodes[i]).html() + tmp = tmp.replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/ /g, ' ') + } + if (pos <= tmp.length) { + el = input.childNodes[i] + if (el.childNodes && el.childNodes.length > 0) el = el.childNodes[0] + if (el.childNodes && el.childNodes.length > 0) el = el.childNodes[0] + break + } else { + pos -= tmp.length + } + } + if (el == null) return + if (pos > el.length) pos = el.length + range.setStart(el, pos) + if (posEnd) { + range.setEnd(el, posEnd) + } else { + range.collapse(true) + } + sel.removeAllRanges() + sel.addRange(range) + } + } + parseColor(str) { + if (typeof str !== 'string') return null; else str = str.trim().toUpperCase() + if (str[0] === '#') str = str.substr(1) + let color = {} + if (str.length === 3) { + color = { + r: parseInt(str[0] + str[0], 16), + g: parseInt(str[1] + str[1], 16), + b: parseInt(str[2] + str[2], 16), + a: 1 + } + } else if (str.length === 6) { + color = { + r: parseInt(str.substr(0, 2), 16), + g: parseInt(str.substr(2, 2), 16), + b: parseInt(str.substr(4, 2), 16), + a: 1 + } + } else if (str.length === 8) { + color = { + r: parseInt(str.substr(0, 2), 16), + g: parseInt(str.substr(2, 2), 16), + b: parseInt(str.substr(4, 2), 16), + a: Math.round(parseInt(str.substr(6, 2), 16) / 255 * 100) / 100 // alpha channel 0-1 + } + } else if (str.length > 4 && str.substr(0, 4) === 'RGB(') { + let tmp = str.replace('RGB', '').replace(/\(/g, '').replace(/\)/g, '').split(',') + color = { + r: parseInt(tmp[0], 10), + g: parseInt(tmp[1], 10), + b: parseInt(tmp[2], 10), + a: 1 + } + } else if (str.length > 5 && str.substr(0, 5) === 'RGBA(') { + let tmp = str.replace('RGBA', '').replace(/\(/g, '').replace(/\)/g, '').split(',') + color = { + r: parseInt(tmp[0], 10), + g: parseInt(tmp[1], 10), + b: parseInt(tmp[2], 10), + a: parseFloat(tmp[3]) + } + } else { + // word color + return null + } + return color + } + // h=0..360, s=0..100, v=0..100 + hsv2rgb(h, s, v, a) { + let r, g, b, i, f, p, q, t + if (arguments.length === 1) { + s = h.s; v = h.v; a = h.a; h = h.h + } + h = h / 360 + s = s / 100 + v = v / 100 + i = Math.floor(h * 6) + f = h * 6 - i + p = v * (1 - s) + q = v * (1 - f * s) + t = v * (1 - (1 - f) * s) + switch (i % 6) { + case 0: r = v, g = t, b = p; break + case 1: r = q, g = v, b = p; break + case 2: r = p, g = v, b = t; break + case 3: r = p, g = q, b = v; break + case 4: r = t, g = p, b = v; break + case 5: r = v, g = p, b = q; break + } + return { + r: Math.round(r * 255), + g: Math.round(g * 255), + b: Math.round(b * 255), + a: (a != null ? a : 1) + } + } + // r=0..255, g=0..255, b=0..255 + rgb2hsv(r, g, b, a) { + if (arguments.length === 1) { + g = r.g; b = r.b; a = r.a; r = r.r + } + let max = Math.max(r, g, b), min = Math.min(r, g, b), + d = max - min, + h, + s = (max === 0 ? 0 : d / max), + v = max / 255 + switch (max) { + case min: h = 0; break + case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break + case g: h = (b - r) + d * 2; h /= 6 * d; break + case b: h = (r - g) + d * 4; h /= 6 * d; break + } + return { + h: Math.round(h * 360), + s: Math.round(s * 100), + v: Math.round(v * 100), + a: (a != null ? a : 1) + } + } + tooltip(html, options) { + let actions + let showOn = 'mouseenter' + let hideOn = 'mouseleave' + if (typeof html == 'object') { + options = html + } + options = options || {} + if (typeof html == 'string') { + options.html = html + } + if (options.showOn) { + showOn = options.showOn + delete options.showOn + } + if (options.hideOn) { + hideOn = options.hideOn + delete options.hideOn + } + if (!options.name) options.name = 'no-name' + // base64 is needed to avoid '"<> and other special chars conflicts + actions = ` on${showOn}="w2tooltip.show(this, ` + + `JSON.parse(w2utils.base64decode('${this.base64encode(JSON.stringify(options))}')))" ` + + `on${hideOn}="w2tooltip.hide('${options.name}')"` + return actions + } + // determins if it is plain Object, not DOM element, nor a function, event, etc. + isPlainObject(value) { + if (value == null) { // null or undefined + return false + } + if (Object.prototype.toString.call(value) !== '[object Object]') { + return false + } + if (value.constructor === undefined) { + return true + } + let proto = Object.getPrototypeOf(value) + return proto === null || proto === Object.prototype + } + /** + * Deep copy of an object or an array. Function, events and HTML elements will not be cloned, + * you can choose to include them or not, by default they are included. + * You can also exclude certain elements from final object if used with options: { exclude } + */ + clone(obj, options) { + let ret + options = Object.assign({ functions: true, elements: true, events: true, exclude: [] }, options ?? {}) + if (Array.isArray(obj)) { + ret = Array.from(obj) + ret.forEach((value, ind) => { + ret[ind] = this.clone(value, options) + }) + } else if (this.isPlainObject(obj)) { + ret = {} + Object.assign(ret, obj) + if (options.exclude) { + options.exclude.forEach(key => { delete ret[key] }) // delete excluded keys + } + Object.keys(ret).forEach(key => { + ret[key] = this.clone(ret[key], options) + if (ret[key] === undefined) delete ret[key] // do not include undefined elements + }) + } else { + if ((obj instanceof Function && !options.functions) + || (obj instanceof Node && !options.elements) + || (obj instanceof Event && !options.events) + ) { + // do not include these objects, otherwise include them uncloned + } else { + // primitive variable or function, event, dom element, etc, - all these are not cloned + ret = obj + } + } + return ret + } + /** + * Deep extend an object, if an array, it overwrrites it, cloning objects in the process + * target, source1, source2, ... + */ + extend(target, source) { + if (Array.isArray(target)) { + if (Array.isArray(source)) { + target.splice(0, target.length) // empty array but keep the reference + source.forEach(s => { target.push(this.clone(s)) }) + } else { + throw new Error('Arrays can be extended with arrays only') + } + } else if (target instanceof Node || target instanceof Event) { + throw new Error('HTML elmenents and events cannot be extended') + } else if (target && typeof target == 'object' && source != null) { + if (typeof source != 'object') { + throw new Error('Object can be extended with other objects only.') + } + Object.keys(source).forEach(key => { + if (target[key] != null && typeof target[key] == 'object' + && source[key] != null && typeof source[key] == 'object') { + let src = this.clone(source[key]) + // do not extend HTML elements and events, but overwrite them + if (target[key] instanceof Node || target[key] instanceof Event) { + target[key] = src + } else { + // if an array needs to be extended with an object, then convert it to empty object + if (Array.isArray(target[key]) && this.isPlainObject(src)) { + target[key] = {} + } + this.extend(target[key], src) + } + } else { + target[key] = this.clone(source[key]) + } + }) + } else if (source != null) { + throw new Error('Object is not extendable, only {} or [] can be extended.') + } + // other arguments + if (arguments.length > 2) { + for (let i = 2; i < arguments.length; i++) { + this.extend(target, arguments[i]) + } + } + return target + } + /* + * @author Lauri Rooden (https://github.com/litejs/natural-compare-lite) + * @license MIT License + */ + naturalCompare(a, b) { + let i, codeA + , codeB = 1 + , posA = 0 + , posB = 0 + , alphabet = String.alphabet + function getCode(str, pos, code) { + if (code) { + for (i = pos; code = getCode(str, i), code < 76 && code > 65;) ++i + return +str.slice(pos - 1, i) + } + code = alphabet && alphabet.indexOf(str.charAt(pos)) + return code > -1 ? code + 76 : ((code = str.charCodeAt(pos) || 0), code < 45 || code > 127) ? code + : code < 46 ? 65 // - + : code < 48 ? code - 1 + : code < 58 ? code + 18 // 0-9 + : code < 65 ? code - 11 + : code < 91 ? code + 11 // A-Z + : code < 97 ? code - 37 + : code < 123 ? code + 5 // a-z + : code - 63 + } + + if ((a+='') != (b+='')) for (;codeB;) { + codeA = getCode(a, posA++) + codeB = getCode(b, posB++) + if (codeA < 76 && codeB < 76 && codeA > 66 && codeB > 66) { + codeA = getCode(a, posA, posA) + codeB = getCode(b, posB, posA = i) + posB = i + } + if (codeA != codeB) return (codeA < codeB) ? -1 : 1 + } + return 0 + } + normMenu(menu, el) { + if (Array.isArray(menu)) { + menu.forEach((it, m) => { + if (typeof it === 'string' || typeof it === 'number') { + menu[m] = { id: it, text: String(it) } + } else if (it != null) { + if (it.caption != null && it.text == null) it.text = it.caption + if (it.text != null && it.id == null) it.id = it.text + if (it.text == null && it.id != null) it.text = it.id + } else { + menu[m] = { id: null, text: 'null' } + } + }) + return menu + } else if (typeof menu === 'function') { + let newMenu = menu.call(this, menu, el) + return w2utils.normMenu.call(this, newMenu) + } else if (typeof menu === 'object') { + return Object.keys(menu).map(key => { return { id: key, text: menu[key] } }) + } + } + bindEvents(selector, subject) { + // format is + //
    ='["","param1","param2",...]'> -- should be valid JSON (no undefined) + //
    ="|param1|param2"> + // -- can have "event", "this", "stop", "stopPrevent", "alert" - as predefined objects + if (selector.length == 0) return + // for backward compatibility + if (selector?.[0] instanceof Node) { + selector = Array.isArray(selector) ? selector : selector.get() + } + query(selector).each((el) => { + let actions = query(el).data() + Object.keys(actions).forEach(name => { + let events = ['click', 'dblclick', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout', 'mousedown', 'mousemove', 'mouseup', + 'contextmenu', 'focus', 'focusin', 'focusout', 'blur', 'input', 'change', 'keydown', 'keyup', 'keypress'] + if (events.indexOf(String(name).toLowerCase()) == -1) { + return + } + let params = actions[name] + if (typeof params == 'string') { + params = params.split('|').map(key => { + if (key === 'true') key = true + if (key === 'false') key = false + if (key === 'undefined') key = undefined + if (key === 'null') key = null + if (parseFloat(key) == key) key = parseFloat(key) + let quotes = ['\'', '"', '`'] + if (typeof key == 'string' && quotes.includes(key[0]) && quotes.includes(key[key.length-1])) { + key = key.substring(1, key.length-1) + } + return key + }) + } + let method = params[0] + params = params.slice(1) // should be new array + query(el) + .off(name + '.w2utils-bind') + .on(name + '.w2utils-bind', function(event) { + switch (method) { + case 'alert': + alert(params[0]) // for testing purposes + break + case 'stop': + event.stopPropagation() + break + case 'prevent': + event.preventDefault() + break + case 'stopPrevent': + event.stopPropagation() + event.preventDefault() + return false + break + default: + if (subject[method] == null) { + throw new Error(`Cannot dispatch event as the method "${method}" does not exist.`) + } + subject[method].apply(subject, params.map((key, ind) => { + switch (String(key).toLowerCase()) { + case 'event': + return event + case 'this': + return this + default: + return key + } + })) + } + }) + }) + }) + } + debounce(func, wait = 250) { + let timeout + return (...args) => { + clearTimeout(timeout) + timeout = setTimeout(() => { func(...args) }, wait) + } + } +} +var w2utils = new Utils() // eslint-disable-line -- needs to be functional/module scope variable +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base + * + * == 2.0 changes + * - CSP - fixed inline events + * - removed jQuery dependency + * - popup.open - returns promise like object + * - popup.confirm - refactored + * - popup.message - refactored + * - removed popup.options.mutliple + * - refactores w2alert, w2confirm, w2prompt + * - add w2popup.open().on('') + * - removed w2popup.restoreTemplate + * - deprecated onMsgOpen and onMsgClose + * - deprecated options.bgColor + * - rename focus -> setFocus + * - added center() // will auto center on window resize + * - close(immediate), also refactored if popup is closed when opening + */ + +class Dialog extends w2base { + constructor() { + super() + this.defaults = { + title: '', + text: '', // just a text (will be centered) + body: '', + buttons: '', + width: 450, + height: 250, + focus: null, // brings focus to the element, can be a number or selector + actions: null, // actions object + style: '', // style of the message div + speed: 0.3, + modal: false, + maximized: false, // this is a flag to show the state - to open the popup maximized use openMaximized instead + keyboard: true, // will close popup on esc if not modal + showClose: true, + showMax: false, + transition: null, + openMaximized: false, + moved: false + } + this.name = 'popup' + this.status = 'closed' // string that describes current status + this.onOpen = null + this.onClose = null + this.onMax = null + this.onMin = null + this.onToggle = null + this.onKeydown = null + this.onAction = null + this.onMove = null + this.tmp = {} + // event handler for resize + this.handleResize = (event) => { + // if it was moved by the user, do not auto resize + if (!this.options.moved) { + this.center(undefined, undefined, true) + } + } + } + /** + * Sample calls + * - w2popup.open('ddd').ok(() => { w2popup.close() }) + * - w2popup.open('ddd', { height: 120 }).ok(() => { w2popup.close() }) + * - w2popup.open({ body: 'text', title: 'caption', actions: ["Close"] }).close(() => { w2popup.close() }) + * - w2popup.open({ body: 'text', title: 'caption', actions: { Close() { w2popup.close() }} }) + */ + open(options) { + let self = this + if (this.status == 'closing' || query('#w2ui-popup').hasClass('animating')) { + // if called when previous is closing + this.close(true) + } + // get old options and merge them + let old_options = this.options + if (['string', 'number'].includes(typeof options)) { + options = w2utils.extend({ + title: 'Notification', + body: `
    ${options}
    `, + actions: { Ok() { self.close() }}, + cancelAction: 'ok' + }, arguments[1] ?? {}) + } + if (options.text != null) options.body = `
    ${options.text}
    ` + options = Object.assign({}, this.defaults, old_options, { title: '', body : '' }, options, { maximized: false }) + this.options = options + // if new - reset event handlers + if (query('#w2ui-popup').length === 0) { + this.off('*') + Object.keys(this).forEach(key => { + if (key.startsWith('on') && key != 'on') this[key] = null + }) + } + // reassign events + Object.keys(options).forEach(key => { + if (key.startsWith('on') && key != 'on' && options[key]) { + this[key] = options[key] + } + }) + options.width = parseInt(options.width) + options.height = parseInt(options.height) + let edata, msg, tmp + let { top, left } = this.center() + let prom = { + self: this, + action(callBack) { + self.on('action.prom', callBack) + return prom + }, + close(callBack) { + self.on('close.prom', callBack) + return prom + }, + then(callBack) { + self.on('open:after.prom', callBack) + return prom + } + } + // convert action arrays into buttons + if (options.actions != null && !options.buttons) { + options.buttons = '' + Object.keys(options.actions).forEach((action) => { + let handler = options.actions[action] + let btnAction = action + if (typeof handler == 'function') { + options.buttons += `` + } + if (typeof handler == 'object') { + options.buttons += `` + btnAction = Array.isArray(options.actions) ? handler.text : action + } + if (typeof handler == 'string') { + options.buttons += `` + btnAction = handler + } + if (typeof btnAction == 'string') { + btnAction = btnAction[0].toLowerCase() + btnAction.substr(1).replace(/\s+/g, '') + } + prom[btnAction] = function (callBack) { + self.on('action.buttons', (event) => { + let target = event.detail.action[0].toLowerCase() + event.detail.action.substr(1).replace(/\s+/g, '') + if (target == btnAction) callBack(event) + }) + return prom + } + }) + } + // check if message is already displayed + if (query('#w2ui-popup').length === 0) { + // trigger event + edata = this.trigger('open', { target: 'popup', present: false }) + if (edata.isCancelled === true) return + this.status = 'opening' + // output message + w2utils.lock(document.body, { + opacity: 0.3, + onClick: options.modal ? null : () => { this.close() } + }) + let btn = '' + if (options.showClose) { + btn += `
    + +
    ` + } + if (options.showMax) { + btn += `
    + +
    ` + } + // first insert just body + let styles = ` + left: ${left}px; + top: ${top}px; + width: ${parseInt(options.width)}px; + height: ${parseInt(options.height)}px; + transition: ${options.speed}s + ` + msg = `
    ` + query('body').append(msg) + query('#w2ui-popup')[0]._w2popup = { + self: this, + created: new Promise((resolve) => { this._promCreated = resolve }), + opened: new Promise((resolve) => { this._promOpened = resolve }), + closing: new Promise((resolve) => { this._promClosing = resolve }), + closed: new Promise((resolve) => { this._promClosed = resolve }), + } + // then content + styles = `${!options.title ? 'top: 0px !important;' : ''} ${!options.buttons ? 'bottom: 0px !important;' : ''}` + msg = ` + +
    ${btn}
    +
    +
    +
    +
    +
    +
    + + ` + query('#w2ui-popup').html(msg) + if (options.title) query('#w2ui-popup .w2ui-popup-title').append(w2utils.lang(options.title)) + if (options.buttons) query('#w2ui-popup .w2ui-popup-buttons').append(options.buttons) + if (options.body) query('#w2ui-popup .w2ui-popup-body').append(options.body) + // allow element to render + setTimeout(() => { + query('#w2ui-popup') + .css('transition', options.speed + 's') + .removeClass('w2ui-anim-open') + w2utils.bindEvents('#w2ui-popup .w2ui-eaction', this) + query('#w2ui-popup').find('.w2ui-popup-body').show() + this._promCreated() + }, 1) + // clean transform + clearTimeout(this._timer) + this._timer = setTimeout(() => { + this.status = 'open' + self.setFocus(options.focus) + // event after + edata.finish() + this._promOpened() + query('#w2ui-popup').removeClass('animating') + }, options.speed * 1000) + } else { + // trigger event + edata = this.trigger('open', { target: 'popup', present: true }) + if (edata.isCancelled === true) return + // check if size changed + this.status = 'opening' + if (old_options != null) { + if (!old_options.maximized && (old_options.width != options.width || old_options.height != options.height)) { + this.resize(options.width, options.height) + } + options.prevSize = options.width + 'px:' + options.height + 'px' + options.maximized = old_options.maximized + } + // show new items + let cloned = query('#w2ui-popup .w2ui-box').get(0).cloneNode(true) + query(cloned).removeClass('w2ui-box').addClass('w2ui-box-temp').find('.w2ui-popup-body').empty().append(options.body) + query('#w2ui-popup .w2ui-box').after(cloned) + if (options.buttons) { + query('#w2ui-popup .w2ui-popup-buttons').show().html('').append(options.buttons) + query('#w2ui-popup .w2ui-popup-body').removeClass('w2ui-popup-no-buttons') + query('#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp').css('bottom', '') + } else { + query('#w2ui-popup .w2ui-popup-buttons').hide().html('') + query('#w2ui-popup .w2ui-popup-body').addClass('w2ui-popup-no-buttons') + query('#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp').css('bottom', '0px') + } + if (options.title) { + query('#w2ui-popup .w2ui-popup-title') + .show() + .html((options.showClose + ? `
    + +
    ` + : '') + + (options.showMax + ? `
    + +
    ` + : '')) + .append(options.title) + query('#w2ui-popup .w2ui-popup-body').removeClass('w2ui-popup-no-title') + query('#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp').css('top', '') + } else { + query('#w2ui-popup .w2ui-popup-title').hide().html('') + query('#w2ui-popup .w2ui-popup-body').addClass('w2ui-popup-no-title') + query('#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp').css('top', '0px') + } + // transition + let div_old = query('#w2ui-popup .w2ui-box')[0] + let div_new = query('#w2ui-popup .w2ui-box-temp')[0] + query('#w2ui-popup').addClass('animating') + w2utils.transition(div_old, div_new, options.transition, () => { + // clean up + query(div_old).remove() + query(div_new).removeClass('w2ui-box-temp').addClass('w2ui-box') + let $body = query(div_new).find('.w2ui-popup-body') + if ($body.length == 1) { + $body[0].style.cssText = options.style + $body.show() + } + // focus on first button + self.setFocus(options.focus) + query('#w2ui-popup').removeClass('animating') + }) + // call event onOpen + this.status = 'open' + edata.finish() + w2utils.bindEvents('#w2ui-popup .w2ui-eaction', this) + query('#w2ui-popup').find('.w2ui-popup-body').show() + } + if (options.openMaximized) { + this.max() + } + // save new options + options._last_focus = document.activeElement + // keyboard events + if (options.keyboard) { + query(document.body).on('keydown', (event) => { + this.keydown(event) + }) + } + query(window).on('resize', this.handleResize) + // initialize move + tmp = { + resizing : false, + mvMove : mvMove, + mvStop : mvStop + } + query('#w2ui-popup .w2ui-popup-title').on('mousedown', function(event) { + if (!self.options.maximized) mvStart(event) + }) + return prom + // handlers + function mvStart(evt) { + if (!evt) evt = window.event + self.status = 'moving' + let rect = query('#w2ui-popup').get(0).getBoundingClientRect() + Object.assign(tmp, { + resizing: true, + isLocked: query('#w2ui-popup > .w2ui-lock').length == 1 ? true : false, + x : evt.screenX, + y : evt.screenY, + pos_x : rect.x, + pos_y : rect.y, + }) + if (!tmp.isLocked) self.lock({ opacity: 0 }) + query(document.body) + .on('mousemove.w2ui-popup', tmp.mvMove) + .on('mouseup.w2ui-popup', tmp.mvStop) + if (evt.stopPropagation) evt.stopPropagation(); else evt.cancelBubble = true + if (evt.preventDefault) evt.preventDefault(); else return false + } + function mvMove(evt) { + if (tmp.resizing != true) return + if (!evt) evt = window.event + tmp.div_x = evt.screenX - tmp.x + tmp.div_y = evt.screenY - tmp.y + // trigger event + let edata = self.trigger('move', { target: 'popup', div_x: tmp.div_x, div_y: tmp.div_y, originalEvent: evt }) + if (edata.isCancelled === true) return + // default behavior + query('#w2ui-popup').css({ + 'transition': 'none', + 'transform' : 'translate3d('+ tmp.div_x +'px, '+ tmp.div_y +'px, 0px)' + }) + self.options.moved = true + // event after + edata.finish() + } + function mvStop(evt) { + if (tmp.resizing != true) return + if (!evt) evt = window.event + self.status = 'open' + tmp.div_x = (evt.screenX - tmp.x) + tmp.div_y = (evt.screenY - tmp.y) + query('#w2ui-popup') + .css({ + 'left': (tmp.pos_x + tmp.div_x) + 'px', + 'top' : (tmp.pos_y + tmp.div_y) + 'px' + }) + .css({ + 'transition': 'none', + 'transform' : 'translate3d(0px, 0px, 0px)' + }) + tmp.resizing = false + query(document.body).off('.w2ui-popup') + if (!tmp.isLocked) self.unlock() + } + } + load(options) { + return new Promise((resolve, reject) => { + if (typeof options == 'string') { + options = { url: options } + } + if (options.url == null) { + console.log('ERROR: The url is not defined.') + reject('The url is not defined') + return + } + this.status = 'loading' + let [url, selector] = String(options.url).split('#') + if (url) { + fetch(url).then(res => res.text()).then(html => { + resolve(this.template(html, selector, options)) + }) + } + }) + } + template(data, id, options = {}) { + let html + try { + html = query(data) + } catch (e) { + html = query.html(data) + } + if (id) html = html.filter('#' + id) + Object.assign(options, { + width: parseInt(query(html).css('width')), + height: parseInt(query(html).css('height')), + title: query(html).find('[rel=title]').html(), + body: query(html).find('[rel=body]').html(), + buttons: query(html).find('[rel=buttons]').html(), + style: query(html).find('[rel=body]').get(0).style.cssText, + }) + return this.open(options) + } + action(action, event) { + let click = this.options.actions[action] + if (click instanceof Object && click.onClick) click = click.onClick + // event before + let edata = this.trigger('action', { action, target: 'popup', self: this, + originalEvent: event, value: this.input ? this.input.value : null }) + if (edata.isCancelled === true) return + // default actions + if (typeof click === 'function') click.call(this, event) + // event after + edata.finish() + } + keydown(event) { + if (this.options && !this.options.keyboard) return + // trigger event + let edata = this.trigger('keydown', { target: 'popup', originalEvent: event }) + if (edata.isCancelled === true) return + // default behavior + switch (event.keyCode) { + case 27: + event.preventDefault() + if (query('#w2ui-popup .w2ui-message').length == 0) { + if (this.options.cancelAction) { + this.action(this.options.cancelAction) + } else { + this.close() + } + } + break + } + // event after + edata.finish() + } + close(immediate) { + // trigger event + let edata = this.trigger('close', { target: 'popup' }) + if (edata.isCancelled === true) return + let cleanUp = () => { + // return template + query('#w2ui-popup').remove() + // restore active + if (this.options._last_focus && this.options._last_focus.length > 0) this.options._last_focus.focus() + this.status = 'closed' + this.options = {} + // event after + edata.finish() + this._promClosed() + } + if (query('#w2ui-popup').length === 0 || this.status == 'closed') { // already closed + return + } + if (this.status == 'opening') { // if it is opening + immediate = true + } + if (this.status == 'closing' && immediate === true) { + cleanUp() + clearTimeout(this.tmp.closingTimer) + w2utils.unlock(document.body, 0) + return + } + // default behavior + this.status = 'closing' + query('#w2ui-popup') + .css('transition', this.options.speed + 's') + .addClass('w2ui-anim-close animating') + w2utils.unlock(document.body, 300) + this._promClosing() + if (immediate) { + cleanUp() + } else { + this.tmp.closingTimer = setTimeout(cleanUp, this.options.speed * 1000) + } + // remove keyboard events + if (this.options.keyboard) { + query(document.body).off('keydown', this.keydown) + } + query(window).off('resize', this.handleResize) + } + toggle() { + let edata = this.trigger('toggle', { target: 'popup' }) + if (edata.isCancelled === true) return + // default action + if (this.options.maximized === true) this.min(); else this.max() + // event after + setTimeout(() => { + edata.finish() + }, (this.options.speed * 1000) + 50) + } + max() { + if (this.options.maximized === true) return + // trigger event + let edata = this.trigger('max', { target: 'popup' }) + if (edata.isCancelled === true) return + // default behavior + this.status = 'resizing' + let rect = query('#w2ui-popup').get(0).getBoundingClientRect() + this.options.prevSize = rect.width + ':' + rect.height + // do resize + this.resize(10000, 10000, () => { + this.status = 'open' + this.options.maximized = true + edata.finish() + }) + } + min() { + if (this.options.maximized !== true) return + let size = this.options.prevSize.split(':') + // trigger event + let edata = this.trigger('min', { target: 'popup' }) + if (edata.isCancelled === true) return + // default behavior + this.status = 'resizing' + // do resize + this.options.maximized = false + this.resize(parseInt(size[0]), parseInt(size[1]), () => { + this.status = 'open' + this.options.prevSize = null + edata.finish() + }) + } + clear() { + query('#w2ui-popup .w2ui-popup-title').html('') + query('#w2ui-popup .w2ui-popup-body').html('') + query('#w2ui-popup .w2ui-popup-buttons').html('') + } + reset() { + this.open(this.defaults) + } + message(options) { + return w2utils.message({ + owner: this, + box : query('#w2ui-popup').get(0), + after: '.w2ui-popup-title' + }, options) + } + confirm(options) { + return w2utils.confirm({ + owner: this, + box : query('#w2ui-popup'), + after: '.w2ui-popup-title' + }, options) + } + setFocus(focus) { + let box = query('#w2ui-popup') + let sel = 'input, button, select, textarea, [contentEditable], .w2ui-input' + if (focus != null) { + let el = isNaN(focus) + ? box.find(sel).filter(focus).get(0) + : box.find(sel).get(focus) + el?.focus() + } else { + let el = box.find('[name=hidden-first]').get(0) + if (el) el.focus() + } + // keep focus/blur inside popup + query(box).find(sel + ',[name=hidden-first],[name=hidden-last]') + .off('.keep-focus') + .on('blur.keep-focus', function (event) { + setTimeout(() => { + let focus = document.activeElement + let inside = query(box).find(sel).filter(focus).length > 0 + let name = query(focus).attr('name') + if (!inside && focus && focus !== document.body) { + query(box).find(sel).get(0)?.focus() + } + if (name == 'hidden-last') { + query(box).find(sel).get(0)?.focus() + } + if (name == 'hidden-first') { + query(box).find(sel).get(-1)?.focus() + } + }, 1) + }) + } + lock(msg, showSpinner) { + let args = Array.from(arguments) + args.unshift(query('#w2ui-popup')) + w2utils.lock(...args) + } + unlock(speed) { + w2utils.unlock(query('#w2ui-popup'), speed) + } + center(width, height, force) { + let maxW, maxH + if (window.innerHeight == undefined) { + maxW = parseInt(document.documentElement.offsetWidth) + maxH = parseInt(document.documentElement.offsetHeight) + } else { + maxW = parseInt(window.innerWidth) + maxH = parseInt(window.innerHeight) + } + width = parseInt(width ?? this.options.width) + height = parseInt(height ?? this.options.height) + if (this.options.maximized === true) { + width = maxW + height = maxH + } + if (maxW - 10 < width) width = maxW - 10 + if (maxH - 10 < height) height = maxH - 10 + let top = (maxH - height) / 2 + let left = (maxW - width) / 2 + if (force) { + query('#w2ui-popup').css({ + 'transition': 'none', + 'top' : top + 'px', + 'left' : left + 'px', + 'width' : width + 'px', + 'height': height + 'px' + }) + this.resizeMessages() // then messages resize nicely + } + return { top, left, width, height } + } + resize(newWidth, newHeight, callBack) { + let self = this + if (this.options.speed == null) this.options.speed = 0 + // calculate new position + let { top, left, width, height } = this.center(newWidth, newHeight) + let speed = this.options.speed + query('#w2ui-popup').css({ + 'transition': `${speed}s width, ${speed}s height, ${speed}s left, ${speed}s top`, + 'top' : top + 'px', + 'left' : left + 'px', + 'width' : width + 'px', + 'height': height + 'px' + }) + let tmp_int = setInterval(() => { self.resizeMessages() }, 10) // then messages resize nicely + setTimeout(() => { + clearInterval(tmp_int) + self.resizeMessages() + if (typeof callBack == 'function') callBack() + }, (this.options.speed * 1000) + 50) // give extra 50 ms + } + // internal function + resizeMessages() { + // see if there are messages and resize them + query('#w2ui-popup .w2ui-message').each(msg => { + let mopt = msg._msg_options + let popup = query('#w2ui-popup') + if (parseInt(mopt.width) < 10) mopt.width = 10 + if (parseInt(mopt.height) < 10) mopt.height = 10 + let rect = popup[0].getBoundingClientRect() + let titleHeight = parseInt(popup.find('.w2ui-popup-title')[0].clientHeight) + let pWidth = parseInt(rect.width) + let pHeight = parseInt(rect.height) + // re-calc width + mopt.width = mopt.originalWidth + if (mopt.width > pWidth - 10) { + mopt.width = pWidth - 10 + } + // re-calc height + mopt.height = mopt.originalHeight + if (mopt.height > pHeight - titleHeight - 5) { + mopt.height = pHeight - titleHeight - 5 + } + if (mopt.originalHeight < 0) mopt.height = pHeight + mopt.originalHeight - titleHeight + if (mopt.originalWidth < 0) mopt.width = pWidth + mopt.originalWidth * 2 // x 2 because there is left and right margin + query(msg).css({ + left : ((pWidth - mopt.width) / 2) + 'px', + width : mopt.width + 'px', + height : mopt.height + 'px' + }) + }) + } +} +function w2alert(msg, title, callBack) { + let prom + let options = { + title: w2utils.lang(title ?? 'Notification'), + body: `
    ${msg}
    `, + showClose: false, + actions: ['Ok'], + cancelAction: 'ok' + } + if (query('#w2ui-popup').length > 0 && w2popup.status != 'closing') { + prom = w2popup.message(options) + } else { + prom = w2popup.open(options) + } + prom.ok((event) => { + if (typeof event.detail.self?.close == 'function') { + event.detail.self.close() + } + if (typeof callBack == 'function') callBack() + }) + return prom +} +function w2confirm(msg, title, callBack) { + let prom + let options = msg + if (['string', 'number'].includes(typeof options)) { + options = { msg: options } + } + if (options.msg) { + options.body = `
    ${options.msg}
    `, + delete options.msg + } + w2utils.extend(options, { + title: w2utils.lang(title ?? 'Confirmation'), + showClose: false, + modal: true, + cancelAction: 'no' + }) + w2utils.normButtons(options, { yes: 'Yes', no: 'No' }) + if (query('#w2ui-popup').length > 0 && w2popup.status != 'closing') { + prom = w2popup.message(options) + } else { + prom = w2popup.open(options) + } + prom.self + .off('.confirm') + .on('action:after.confirm', (event) => { + if (typeof event.detail.self?.close == 'function') { + event.detail.self.close() + } + if (typeof callBack == 'function') callBack(event.detail.action) + }) + return prom +} +function w2prompt(label, title, callBack) { + let prom + let options = label + if (['string', 'number'].includes(typeof options)) { + options = { label: options } + } + if (options.label) { + options.focus = 0 + options.body = (options.textarea + ? `
    +
    ${options.label}
    + +
    ` + : `
    + + +
    ` + ) + } + w2utils.extend(options, { + title: w2utils.lang(title ?? 'Notification'), + showClose: false, + modal: true, + cancelAction: 'cancel' + }) + w2utils.normButtons(options, { ok: 'Ok', cancel: 'Cancel' }) + if (query('#w2ui-popup').length > 0 && w2popup.status != 'closing') { + prom = w2popup.message(options) + } else { + prom = w2popup.open(options) + } + if (prom.self.box) { + prom.self.input = query(prom.self.box).find('#w2prompt').get(0) + } else { + prom.self.input = query('#w2ui-popup .w2ui-popup-body #w2prompt').get(0) + } + if (options.value !== null) { + prom.self.input.select() + } + prom.change = function (callback) { + prom.self.on('change', callback) + return this + } + prom.self + .off('.prompt') + .on('open:after.prompt', (event) => { + let box = event.detail.box ? event.detail.box : query('#w2ui-popup .w2ui-popup-body').get(0) + w2utils.bindEvents(query(box).find('#w2prompt'), { + keydown(evt) { + if (evt.keyCode == 27) evt.stopPropagation() + }, + change(evt) { + let edata = prom.self.trigger('change', { target: 'prompt', originalEvent: evt }) + if (edata.isCancelled === true) return + if (evt.keyCode == 13 && evt.ctrlKey) { + prom.self.action('Ok', evt) + } + if (evt.keyCode == 27) { + prom.self.action('Cancel', evt) + } + edata.finish() + } + }) + query(box).find('.w2ui-eaction').trigger('keyup') + }) + .on('action:after.prompt', (event) => { + if (typeof event.detail.self?.close == 'function') { + event.detail.self.close() + } + if (typeof callBack == 'function') callBack(event.detail.action) + }) + return prom +} +let w2popup = new Dialog() +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base + * + * 2.0 Changes + * - multiple tooltips to the same anchor + * + * TODO + * - load menu items from URL + */ + +class Tooltip { + // no need to extend w2base, as each individual tooltip extends it + static active = {} // all defined tooltips + constructor() { + this.defaults = { + name : null, // name for the overlay, otherwise input id is used + html : '', // text or html + style : '', // additional style for the overlay + class : '', // add class for w2ui-tooltip-body + position : 'top|bottom', // can be left, right, top, bottom + align : '', // can be: both, both:XX left, right, both, top, bottom + anchor : null, // element it is attached to, if anchor is body, then it is context menu + anchorClass : '', // add class for anchor when tooltip is shown + anchorStyle : '', // add style for anchor when tooltip is shown + autoShow : false, // if autoShow true, then tooltip will show on mouseEnter and hide on mouseLeave + autoShowOn : null, // when options.autoShow = true, mouse event to show on + autoHideOn : null, // when options.autoShow = true, mouse event to hide on + arrowSize : 8, // size of the carret + margin : 0, // extra margin from the anchor + screenMargin : 2, // min margin from screen to tooltip + autoResize : true, // auto resize based on content size and available size + margin : 1, // distance from the anchor + offsetX : 0, // delta for left coordinate + offsetY : 0, // delta for top coordinate + maxWidth : null, // max width + maxHeight : null, // max height + watchScroll : null, // attach to onScroll event // TODO: + watchResize : null, // attach to onResize event // TODO: + hideOn : null, // events when to hide tooltip, ['click', 'change', 'key', 'focus', 'blur'], + onThen : null, // called when displayed + onShow : null, // callBack when shown + onHide : null, // callBack when hidden + onUpdate : null, // callback when tooltip gets updated + onMove : null // callback when tooltip is moved + } + } + static observeRemove = new MutationObserver((mutations) => { + let cnt = 0 + Object.keys(Tooltip.active).forEach(name => { + let overlay = Tooltip.active[name] + if (overlay.displayed) { + if (!overlay.anchor || !overlay.anchor.isConnected) { + overlay.hide() + } else { + cnt++ + } + } + }) + // remove observer, as there is no active tooltips + if (cnt === 0) { + Tooltip.observeRemove.disconnect() + } + }) + trigger(event, data) { + if (arguments.length == 2) { + let type = event + event = data + data.type = type + } + if (event.overlay) { + return event.overlay.trigger(event) + } else { + console.log('ERROR: cannot find overlay where to trigger events') + } + } + get(name) { + if (arguments.length == 0) { + return Object.keys(Tooltip.active) + } else if (name === true) { + return Tooltip.active + } else { + return Tooltip.active[name.replace(/[\s\.#]/g, '_')] + } + } + attach(anchor, text) { + let options, overlay + let self = this + if (arguments.length == 0) { + return + } else if (arguments.length == 1 && anchor.anchor) { + options = anchor + anchor = options.anchor + } else if (arguments.length === 2 && typeof text === 'string') { + options = { anchor, html: text } + text = options.html + } else if (arguments.length === 2 && text != null && typeof text === 'object') { + options = text + text = options.html + } + options = w2utils.extend({}, this.defaults, options || {}) + if (!text && options.text) text = options.text + if (!text && options.html) text = options.html + // anchor is func var + delete options.anchor + // define tooltip + let name = (options.name ? options.name : anchor.id) + if (anchor == document || anchor == document.body) { + anchor = document.body + name = 'context-menu' + } + if (!name) { + name = 'noname-' + Object.keys(Tooltip.active).length + console.log('NOTICE: name property is not defined for tooltip, could lead to too many instances') + } + // clean name as it is used as id and css selector + name = name.replace(/[\s\.#]/g, '_') + if (Tooltip.active[name]) { + overlay = Tooltip.active[name] + overlay.prevOptions = overlay.options + overlay.options = options // do not merge or extend, otherwiser menu items get merged too + // overlay.options = w2utils.extend({}, overlay.options, options) + overlay.anchor = anchor // as HTML elements are not copied + if (overlay.prevOptions.html != overlay.options.html || overlay.prevOptions.class != overlay.options.class + || overlay.prevOptions.style != overlay.options.style) { + overlay.needsUpdate = true + } + options = overlay.options // it was recreated + } else { + overlay = new w2base() + Object.assign(overlay, { + id: 'w2overlay-' + name, name, options, anchor, + displayed: false, + tmp: { + observeResize: new ResizeObserver(() => { + this.resize(overlay.name) + }) + }, + hide() { + self.hide(name) + } + }) + Tooltip.active[name] = overlay + } + // move events on to overlay layer + Object.keys(overlay.options).forEach(key => { + let val = overlay.options[key] + if (key.startsWith('on') && typeof val == 'function') { + overlay[key] = val + delete overlay.options[key] + } + }) + // add event for auto show/hide + if (options.autoShow === true) { + options.autoShowOn = options.autoShowOn ?? 'mouseenter' + options.autoHideOn = options.autoHideOn ?? 'mouseleave' + options.autoShow = false + } + if (options.autoShowOn) { + let scope = 'autoShow-' + overlay.name + query(anchor) + .off(`.${scope}`) + .on(`${options.autoShowOn}.${scope}`, event => { + self.show(overlay.name) + event.stopPropagation() + }) + delete options.autoShowOn + } + if (options.autoHideOn) { + let scope = 'autoHide-' + overlay.name + query(anchor) + .off(`.${scope}`) + .on(`${options.autoHideOn}.${scope}`, event => { + self.hide(overlay.name) + event.stopPropagation() + }) + delete options.autoHideOn + } + overlay.off('.attach') + let ret = { + overlay, + then: (callback) => { + overlay.on('show:after.attach', event => { callback(event) }) + return ret + }, + show: (callback) => { + overlay.on('show.attach', event => { callback(event) }) + return ret + }, + hide: (callback) => { + overlay.on('hide.attach', event => { callback(event) }) + return ret + }, + update: (callback) => { + overlay.on('update.attach', event => { callback(event) }) + return ret + }, + move: (callback) => { + overlay.on('move.attach', event => { callback(event) }) + return ret + } + } + return ret + } + update(name, html) { + let overlay = Tooltip.active[name] + if (overlay) { + overlay.needsUpdate = true + overlay.options.html = html + this.show(name) + } else { + console.log(`Tooltip "${name}" is not displayed. Cannot update it.`) + } + } + show(name) { + if (name instanceof HTMLElement || name instanceof Object) { + let options = name + if (name instanceof HTMLElement) { + options = arguments[1] || {} + options.anchor = name + } + let ret = this.attach(options) + query(ret.overlay.anchor) + .off('.autoShow-' + ret.overlay.name) + .off('.autoHide-' + ret.overlay.name) + // need a timer, so that events would be preperty set + setTimeout(() => { this.show(ret.overlay.name) }, 1) + return ret + } + let edata + let self = this + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + if (!overlay) return + let options = overlay.options + if (!overlay || (overlay.displayed && !overlay.needsUpdate)) { + this.resize(overlay?.name) + return + } + let position = options.position.split('|') + let isVertical = ['top', 'bottom'].includes(position[0]) + // enforce nowrap only when align=both and vertical + let overlayStyles = (options.align == 'both' && isVertical ? '' : 'white-space: nowrap;') + if (options.maxWidth && w2utils.getStrWidth(options.html, '') > options.maxWidth) { + overlayStyles = 'width: '+ options.maxWidth + 'px; white-space: inherit; overflow: auto;' + } + overlayStyles += ' max-height: '+ (options.maxHeight ? options.maxHeight : window.innerHeight - 40) + 'px;' + // if empty content - then hide it + if (options.html === '' || options.html == null) { + self.hide(name) + return + } else if (overlay.box) { + // if already present, update it + edata = this.trigger('update', { target: name, overlay }) + if (edata.isCancelled === true) { + // restore previous options + if (overlay.prevOptions) { + overlay.options = overlay.prevOptions + delete overlay.prevOptions + } + return + } + query(overlay.box) + .find('.w2ui-overlay-body') + .attr('style', (options.style || '') + '; ' + overlayStyles) + .removeClass() // removes all classes + .addClass('w2ui-overlay-body ' + options.class) + .html(options.html) + this.resize(overlay.name) + } else { + // event before + edata = this.trigger('show', { target: name, overlay }) + if (edata.isCancelled === true) return + // normal processing + query('body').append( + // pointer-events will be re-enabled leter + ``) + overlay.box = query('#'+w2utils.escapeId(overlay.id))[0] + overlay.displayed = true + let names = query(overlay.anchor).data('tooltipName') ?? [] + names.push(name) + query(overlay.anchor).data('tooltipName', names) // make available to element overlay attached to + w2utils.bindEvents(overlay.box, {}) + // remember anchor's original styles + overlay.tmp.originalCSS = '' + if (query(overlay.anchor).length > 0) { + overlay.tmp.originalCSS = query(overlay.anchor)[0].style.cssText + } + this.resize(overlay.name) + } + if (options.anchorStyle) { + overlay.anchor.style.cssText += ';' + options.anchorStyle + } + if (options.anchorClass) { + // do not add w2ui-focus to body + if (!(options.anchorClass == 'w2ui-focus' && overlay.anchor == document.body)) { + query(overlay.anchor).addClass(options.anchorClass) + } + } + // add on hide events + if (typeof options.hideOn == 'string') options.hideOn = [options.hideOn] + if (!Array.isArray(options.hideOn)) options.hideOn = [] + // initial scroll + Object.assign(overlay.tmp, { + scrollLeft: document.body.scrollLeft, + scrollTop: document.body.scrollTop + }) + addHideEvents() + addWatchEvents(document.body) + // first show empty tooltip, so it will popup up in the right position + query(overlay.box).show() + overlay.tmp.observeResize.observe(overlay.box) + // observer element removal from DOM + Tooltip.observeRemove.observe(document.body, { subtree: true, childList: true }) + // then insert html and it will adjust + query(overlay.box) + .css('opacity', 1) + .find('.w2ui-overlay-body') + .html(options.html) + /** + * pointer-events: none is needed to avoid cases when popup is shown right under the cursor + * or it will trigger onmouseout, onmouseleave and other events. + */ + setTimeout(() => { query(overlay.box).css({ 'pointer-events': 'auto' }).data('ready', 'yes') }, 100) + delete overlay.needsUpdate + // expose overlay to DOM element + overlay.box.overlay = overlay + // event after + if (edata) edata.finish() + return { overlay } + function addWatchEvents(el) { + let scope = 'tooltip-' + overlay.name + let queryEl = el + if (el.tagName == 'BODY') { + queryEl = el.ownerDocument + } + query(queryEl) + .off(`.${scope}`) + .on(`scroll.${scope}`, event => { + Object.assign(overlay.tmp, { + scrollLeft: el.scrollLeft, + scrollTop: el.scrollTop + }) + self.resize(overlay.name) + }) + } + function addHideEvents() { + let hide = (event) => { self.hide(overlay.name) } + let $anchor = query(overlay.anchor) + let scope = 'tooltip-' + overlay.name + // document click + query('body').off(`.${scope}`) + if (options.hideOn.includes('doc-click')) { + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + // otherwise hides on click to focus + $anchor + .off(`.${scope}-doc`) + .on(`click.${scope}-doc`, (event) => { event.stopPropagation() }) + } + query('body').on(`click.${scope}`, hide) + } + if (options.hideOn.includes('focus-change')) { + query('body') + .on(`focusin.${scope}`, (e) => { + if (document.activeElement != overlay.anchor) { + self.hide(overlay.name) + } + }) + } + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + $anchor.off(`.${scope}`) + options.hideOn.forEach(event => { + if (['doc-click', 'focus-change'].indexOf(event) == -1) { + $anchor.on(`${event}.${scope}`, { once: true }, hide) + } + }) + } + } + } + hide(name) { + let overlay + if (arguments.length == 0) { + // hide all tooltips + Object.keys(Tooltip.active).forEach(name => { this.hide(name) }) + return + } + if (name instanceof HTMLElement) { + let names = query(name).data('tooltipName') ?? [] + names.forEach(name => { this.hide(name) }) + return + } + if (typeof name == 'string') { + name = name.replace(/[\s\.#]/g, '_') + overlay = Tooltip.active[name] + } + if (!overlay || !overlay.box) return + delete Tooltip.active[name] + // event before + let edata = this.trigger('hide', { target: name, overlay }) + if (edata.isCancelled === true) return + let scope = 'tooltip-' + overlay.name + // normal processing + overlay.tmp.observeResize?.disconnect() + if (overlay.options.watchScroll) { + query(overlay.options.watchScroll) + .off('.w2scroll-' + overlay.name) + } + // if no active tooltip then disable observeRemove + let cnt = 0 + Object.keys(Tooltip.active).forEach(key => { + let overlay = Tooltip.active[key] + if (overlay.displayed) { + cnt++ + } + }) + if (cnt == 0) { + Tooltip.observeRemove.disconnect() + } + query('body').off(`.${scope}`) // hide to click event here + query(document).off(`.${scope}`) // scroll event here + // remove element + overlay.box.remove() + overlay.box = null + overlay.displayed = false + // remove name from anchor properties + let names = query(overlay.anchor).data('tooltipName') ?? [] + let ind = names.indexOf(overlay.name) + if (ind != -1) names.splice(names.indexOf(overlay.name), 1) + if (names.length == 0) { + query(overlay.anchor).removeData('tooltipName') + } else { + query(overlay.anchor).data('tooltipName', names) + } + // restore original CSS + overlay.anchor.style.cssText = overlay.tmp.originalCSS + query(overlay.anchor) + .off(`.${scope}`) + .removeClass(overlay.options.anchorClass) + // event after + edata.finish() + } + resize(name) { + if (arguments.length == 0) { + Object.keys(Tooltip.active).forEach(key => { + let overlay = Tooltip.active[key] + if (overlay.displayed) this.resize(overlay.name) + }) + return + } + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + let pos = this.getPosition(overlay.name) + let newPos = pos.left + 'x' + pos.top + let edata + if (overlay.tmp.lastPos != newPos) { + edata = this.trigger('move', { target: name, overlay, pos }) + } + query(overlay.box) + .css({ + left: pos.left + 'px', + top : pos.top + 'px' + }) + .then(query => { + if (pos.width != null) { + query.css('width', pos.width + 'px') + .find('.w2ui-overlay-body') + .css('width', '100%') + } + if (pos.height != null) { + query.css('height', pos.height + 'px') + .find('.w2ui-overlay-body') + .css('height', '100%') + } + }) + .find('.w2ui-overlay-body') + .removeClass('w2ui-arrow-right w2ui-arrow-left w2ui-arrow-top w2ui-arrow-bottom') + .addClass(pos.arrow.class) + .closest('.w2ui-overlay') + .find('style') + .text(pos.arrow.style) + if (overlay.tmp.lastPos != newPos && edata) { + overlay.tmp.lastPos = newPos + edata.finish() + } + } + getPosition(name) { + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + if (!overlay || !overlay.box) { + return + } + let options = overlay.options + if (overlay.tmp.resizedY || overlay.tmp.resizedX) { + query(overlay.box).css({ width: '', height: '', scroll: 'auto' }) + } + let scrollSize = w2utils.scrollBarSize() + let hasScrollBarX = !(document.body.scrollWidth == document.body.clientWidth) + let hasScrollBarY = !(document.body.scrollHeight == document.body.clientHeight) + let max = { + width: window.innerWidth - (hasScrollBarY ? scrollSize : 0), + height: window.innerHeight - (hasScrollBarX ? scrollSize : 0) + } + let position = options.position == 'auto' ? 'top|bottom|right|left'.split('|') : options.position.split('|') + let isVertical = ['top', 'bottom'].includes(position[0]) + let content = overlay.box.getBoundingClientRect() + let anchor = overlay.anchor.getBoundingClientRect() + if (overlay.anchor == document.body) { + // context menu + let { x, y, width, height } = options.originalEvent + anchor = { left: x - 2, top: y - 4, width, height, arrow: 'none' } + } + let arrowSize = options.arrowSize + if (anchor.arrow == 'none') arrowSize = 0 + // space available + let available = { // tipsize adjustment should be here, not in max.width/max.height + top: anchor.top, + bottom: max.height - (anchor.top + anchor.height) - + (hasScrollBarX ? scrollSize : 0), + left: anchor.left, + right: max.width - (anchor.left + anchor.width) + (hasScrollBarY ? scrollSize : 0), + } + // size of empty tooltip + if (content.width < 22) content.width = 22 + if (content.height < 14) content.height = 14 + let left, top, width, height // tooltip position + let found = '' + let arrow = { + offset: 0, + class: '', + style: `#${overlay.id} { --tip-size: ${arrowSize}px; }` + } + let adjust = { left: 0, top: 0 } + let bestFit = { posX: '', x: 0, posY: '', y: 0 } + // find best position + position.forEach(pos => { + if (['top', 'bottom'].includes(pos)) { + if (!found && (content.height + arrowSize/1.893) < available[pos]) { // 1.893 = 1 + sin(90) + found = pos + } + if (available[pos] > bestFit.y) { + Object.assign(bestFit, { posY: pos, y: available[pos] }) + } + } + if (['left', 'right'].includes(pos)) { + if (!found && (content.width + arrowSize/1.893) < available[pos]) { // 1.893 = 1 + sin(90) + found = pos + } + if (available[pos] > bestFit.x) { + Object.assign(bestFit, { posX: pos, x: available[pos] }) + } + } + }) + // if not found, use best (greatest available space) position + if (!found) { + if (isVertical) { + found = bestFit.posY + } else { + found = bestFit.posX + } + } + if (options.autoResize) { + if (['top', 'bottom'].includes(found)) { + if (content.height > available[found]) { + height = available[found] + overlay.tmp.resizedY = true + } else { + overlay.tmp.resizedY = false + } + } + if (['left', 'right'].includes(found)) { + if (content.width > available[found]) { + width = available[found] + overlay.tmp.resizedX = true + } else { + overlay.tmp.resizedX = false + } + } + } + usePosition(found) + if (isVertical) anchorAlignment() + screenAdjust() + let extraTop = (found == 'top' ? -options.margin : (found == 'bottom' ? options.margin : 0)) + let extraLeft = (found == 'left' ? -options.margin : (found == 'right' ? options.margin : 0)) + // adjust for scrollbar + top = Math.floor((top + parseFloat(options.offsetY) + parseFloat(extraTop)) * 100) / 100 + left = Math.floor((left + parseFloat(options.offsetX) + parseFloat(extraLeft)) * 100) / 100 + return { left, top, arrow, adjust, width, height, pos: found } + function usePosition(pos) { + arrow.class = anchor.arrow ? anchor.arrow : `w2ui-arrow-${pos}` + switch (pos) { + case 'top': { + left = anchor.left + (anchor.width - (width ?? content.width)) / 2 + top = anchor.top - (height ?? content.height) - arrowSize / 1.5 + 1 + break + } + case 'bottom': { + left = anchor.left + (anchor.width - (width ?? content.width)) / 2 + top = anchor.top + anchor.height + arrowSize / 1.25 + 1 + break + } + case 'left': { + left = anchor.left - (width ?? content.width) - arrowSize / 1.2 - 1 + top = anchor.top + (anchor.height - (height ?? content.height)) / 2 + break + } + case 'right': { + left = anchor.left + anchor.width + arrowSize / 1.2 + 1 + top = anchor.top + (anchor.height - (height ?? content.height)) / 2 + break + } + } + } + function anchorAlignment() { + // top/bottom alignments + if (options.align == 'left') { + adjust.left = anchor.left - left + left = anchor.left + } + if (options.align == 'right') { + adjust.left = (anchor.left + anchor.width - (width ?? content.width)) - left + left = anchor.left + anchor.width - (width ?? content.width) + } + if (['top', 'bottom'].includes(found) && options.align.startsWith('both')) { + let minWidth = options.align.split(':')[1] ?? 50 + if (anchor.width >= minWidth) { + left = anchor.left + width = anchor.width + } + } + // left/right alignments + if (options.align == 'top') { + adjust.top = anchor.top - top + top = anchor.top + } + if (options.align == 'bottom') { + adjust.top = (anchor.top + anchor.height - (height ?? content.height)) - top + top = anchor.top + anchor.height - (height ?? content.height) + } + if (['left', 'right'].includes(found) && options.align.startsWith('both')) { + let minHeight = options.align.split(':')[1] ?? 50 + if (anchor.height >= minHeight) { + top = anchor.top + height = anchor.height + } + } + } + function screenAdjust() { + let adjustArrow + // adjust tip if needed after alignment + if ((['left', 'right'].includes(options.align) && anchor.width < (width ?? content.width)) + || (['top', 'bottom'].includes(options.align) && anchor.height < (height ?? content.height)) + ) { + adjustArrow = true + } + // if off screen then adjust + let minLeft = (found == 'right' ? arrowSize : options.screenMargin) + let minTop = (found == 'bottom' ? arrowSize : options.screenMargin) + let maxLeft = max.width - (width ?? content.width) - (found == 'left' ? arrowSize : options.screenMargin) + let maxTop = max.height - (height ?? content.height) - (found == 'top' ? arrowSize : options.screenMargin) + 3 + // adjust X + if (['top', 'bottom'].includes(found) || options.autoResize) { + if (left < minLeft) { + adjustArrow = true + adjust.left -= left + left = minLeft + } + if (left > maxLeft) { + adjustArrow = true + adjust.left -= left - maxLeft + left += maxLeft - left + } + } + // adjust Y + if (['left', 'right'].includes(found) || options.autoResize) { + if (top < minTop) { + adjustArrow = true + adjust.top -= top + top = minTop + } + if (top > maxTop) { + adjustArrow = true + adjust.top -= top - maxTop + top += maxTop - top + } + } + // moves carret to adjust it with element width + if (adjustArrow) { + let aType = isVertical ? 'left' : 'top' + let sType = isVertical ? 'width' : 'height' + arrow.offset = -adjust[aType] + let maxOffset = content[sType] / 2 - arrowSize + if (Math.abs(arrow.offset) > maxOffset + arrowSize) { + arrow.class = '' // no arrow + } + if (Math.abs(arrow.offset) > maxOffset) { + arrow.offset = arrow.offset < 0 ? -maxOffset : maxOffset + } + arrow.style = w2utils.stripSpaces(`#${overlay.id} .w2ui-overlay-body:after, + #${overlay.id} .w2ui-overlay-body:before { + --tip-size: ${arrowSize}px; + margin-${aType}: ${arrow.offset}px; + }`) + } + } + } +} +class ColorTooltip extends Tooltip { + constructor() { + super() + this.palette = [ + ['000000', '333333', '555555', '777777', '888888', '999999', 'AAAAAA', 'CCCCCC', 'DDDDDD', 'EEEEEE', 'F7F7F7', 'FFFFFF'], + ['FF011B', 'FF9838', 'FFC300', 'FFFD59', '86FF14', '14FF7A', '2EFFFC', '2693FF', '006CE7', '9B24F4', 'FF21F5', 'FF0099'], + ['FFEAEA', 'FCEFE1', 'FCF4DC', 'FFFECF', 'EBFFD9', 'D9FFE9', 'E0FFFF', 'E8F4FF', 'ECF4FC', 'EAE6F4', 'FFF5FE', 'FCF0F7'], + ['F4CCCC', 'FCE5CD', 'FFF1C2', 'FFFDA1', 'D5FCB1', 'B5F7D0', 'BFFFFF', 'D6ECFF', 'CFE2F3', 'D9D1E9', 'FFE3FD', 'FFD9F0'], + ['EA9899', 'F9CB9C', 'FFE48C', 'F7F56F', 'B9F77E', '84F0B1', '83F7F7', 'B5DAFF', '9FC5E8', 'B4A7D6', 'FAB9F6', 'FFADDE'], + ['E06666', 'F6B26B', 'DEB737', 'E0DE51', '8FDB48', '52D189', '4EDEDB', '76ACE3', '6FA8DC', '8E7CC3', 'E07EDA', 'F26DBD'], + ['CC0814', 'E69138', 'AB8816', 'B5B20E', '6BAB30', '27A85F', '1BA8A6', '3C81C7', '3D85C6', '674EA7', 'A14F9D', 'BF4990'], + ['99050C', 'B45F17', '80650E', '737103', '395E14', '10783D', '13615E', '094785', '0A5394', '351C75', '780172', '782C5A'] + ] + this.defaults = w2utils.extend({}, this.defaults, { + advanced : false, + transparent : true, + position : 'top|bottom', + class : 'w2ui-white', + color : '', + liveUpdate : true, + arrowSize : 12, + autoResize : false, + anchorClass : 'w2ui-focus', + autoShowOn : 'focus', + hideOn : ['doc-click', 'focus-change'], + onSelect : null, + onLiveUpdate: null + }) + } + attach(anchor, text) { + let options + if (arguments.length == 1 && anchor.anchor) { + options = anchor + anchor = options.anchor + } else if (arguments.length === 2 && text != null && typeof text === 'object') { + options = text + options.anchor = anchor + } + let prevHideOn = options.hideOn + options = w2utils.extend({}, this.defaults, options || {}) + if (prevHideOn) { + options.hideOn = prevHideOn + } + options.style += '; padding: 0;' + // add remove transparent color + if (options.transparent && this.palette[0][1] == '333333') { + this.palette[0].splice(1, 1) + this.palette[0].push('') + } + if (!options.transparent && this.palette[0][1] != '333333') { + this.palette[0].splice(1, 0, '333333') + this.palette[0].pop() + } + if (options.color) options.color = String(options.color).toUpperCase() + if (typeof options.color === 'string' && options.color.substr(0,1) === '#') options.color = options.color.substr(1) + // needed for keyboard navigation + this.index = [-1, -1] + let ret = super.attach(options) + let overlay = ret.overlay + overlay.options.html = this.getColorHTML(overlay.name, options) + overlay.on('show.attach', event => { + let overlay = event.detail.overlay + let anchor = overlay.anchor + let options = overlay.options + if (['INPUT', 'TEXTAREA'].includes(anchor.tagName) && !options.color && anchor.value) { + overlay.tmp.initColor = anchor.value + } + delete overlay.newColor + }) + overlay.on('show:after.attach', event => { + if (ret.overlay?.box) { + let actions = query(ret.overlay.box).find('.w2ui-eaction') + w2utils.bindEvents(actions, this) + this.initControls(ret.overlay) + } + }) + overlay.on('update:after.attach', event => { + if (ret.overlay?.box) { + let actions = query(ret.overlay.box).find('.w2ui-eaction') + w2utils.bindEvents(actions, this) + this.initControls(ret.overlay) + } + }) + overlay.on('hide.attach', event => { + let overlay = event.detail.overlay + let anchor = overlay.anchor + let color = overlay.newColor ?? overlay.options.color ?? '' + if (['INPUT', 'TEXTAREA'].includes(anchor.tagName) && anchor.value != color) { + anchor.value = color + } + let edata = this.trigger('select', { color, target: overlay.name, overlay }) + if (edata.isCancelled === true) return + // event after + edata.finish() + }) + ret.liveUpdate = (callback) => { + overlay.on('liveUpdate.attach', (event) => { callback(event) }) + return ret + } + ret.select = (callback) => { + overlay.on('select.attach', (event) => { callback(event) }) + return ret + } + return ret + } + // regular panel handler, adds selection class + select(color, name) { + let target + this.index = [-1, -1] + if (typeof name != 'string') { + target = name.target + this.index = query(target).attr('index').split(':') + name = query(target).closest('.w2ui-overlay').attr('name') + } + let overlay = this.get(name) + // event before + let edata = this.trigger('liveUpdate', { color, target: name, overlay, param: arguments[1] }) + if (edata.isCancelled === true) return + // if anchor is input - live update + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName) && overlay.options.liveUpdate) { + query(overlay.anchor).val(color) + } + overlay.newColor = color + query(overlay.box).find('.w2ui-selected').removeClass('w2ui-selected') + if (target) { + query(target).addClass('w2ui-selected') + } + // event after + edata.finish() + } + // used for keyboard navigation, if any + nextColor(direction) { // TODO: check it + let pal = this.palette + switch (direction) { + case 'up': + this.index[0]-- + break + case 'down': + this.index[0]++ + break + case 'right': + this.index[1]++ + break + case 'left': + this.index[1]-- + break + } + if (this.index[0] < 0) this.index[0] = 0 + if (this.index[0] > pal.length - 2) this.index[0] = pal.length - 2 + if (this.index[1] < 0) this.index[1] = 0 + if (this.index[1] > pal[0].length - 1) this.index[1] = pal[0].length - 1 + return pal[this.index[0]][this.index[1]] + } + tabClick(index, name) { + if (typeof name != 'string') { + name = query(name.target).closest('.w2ui-overlay').attr('name') + } + let overlay = this.get(name) + let tab = query(overlay.box).find(`.w2ui-color-tab:nth-child(${index})`) + query(overlay.box).find('.w2ui-color-tab').removeClass('w2ui-selected') + query(tab).addClass('w2ui-selected') + query(overlay.box) + .find('.w2ui-tab-content') + .hide() + .closest('.w2ui-colors') + .find('.tab-'+ index) + .show() + } + // generate HTML with color pallent and controls + getColorHTML(name, options) { + let html = ` +
    +
    ` + for (let i = 0; i < this.palette.length; i++) { + html += '
    ' + for (let j = 0; j < this.palette[i].length; j++) { + let color = this.palette[i][j] + let border = '' + if (color === 'FFFFFF') border = '; border: 1px solid #efefef' + html += ` +
      +
    ` + } + html += '
    ' + if (i < 2) html += '
    ' + } + html += '
    ' + // advanced tab + html += ` + ` + // color tabs on the bottom + html += ` +
    +
    +
    +
    + ${(typeof options.html == 'string' ? options.html : '')} +
    +
    ` + return html + } + // bind advanced tab controls + initControls(overlay) { + let initial // used for mouse events + let self = this + let options = overlay.options + let rgb = w2utils.parseColor(options.color || overlay.tmp.initColor) + if (rgb == null) { + rgb = { r: 140, g: 150, b: 160, a: 1 } + } + let hsv = w2utils.rgb2hsv(rgb) + if (options.advanced === true) { + this.tabClick(2, overlay.name) + } + setColor(hsv, true, true) + // even for rgb, hsv inputs + query(overlay.box).find('input') + .off('.w2color') + .on('change.w2color', (event) => { + let el = query(event.target) + let val = parseFloat(el.val()) + let max = parseFloat(el.attr('max')) + if (isNaN(val)) { + val = 0 + el.val(0) + } + if (max > 1) val = parseInt(val) // trancate fractions + if (max > 0 && val > max) { + el.val(max) + val = max + } + if (val < 0) { + el.val(0) + val = 0 + } + let name = el.attr('name') + let color = {} + if (['r', 'g', 'b', 'a'].indexOf(name) !== -1) { + rgb[name] = val + hsv = w2utils.rgb2hsv(rgb) + } else if (['h', 's', 'v'].indexOf(name) !== -1) { + color[name] = val + } + setColor(color, true) + }) + // click on original color resets it + query(overlay.box).find('.color-original') + .off('.w2color') + .on('click.w2color', (event) => { + let tmp = w2utils.parseColor(query(event.target).css('background-color')) + if (tmp != null) { + rgb = tmp + hsv = w2utils.rgb2hsv(rgb) + setColor(hsv, true) + } + }) + // color sliders events + let mDown = `${!w2utils.isIOS ? 'mousedown' : 'touchstart'}.w2color` + let mUp = `${!w2utils.isIOS ? 'mouseup' : 'touchend'}.w2color` + let mMove = `${!w2utils.isIOS ? 'mousemove' : 'touchmove'}.w2color` + query(overlay.box).find('.palette, .rainbow, .alpha') + .off('.w2color') + .on(`${mDown}.w2color`, mouseDown) + return + function setColor(color, fullUpdate, initial) { + if (color.h != null) hsv.h = color.h + if (color.s != null) hsv.s = color.s + if (color.v != null) hsv.v = color.v + if (color.a != null) { rgb.a = color.a; hsv.a = color.a } + rgb = w2utils.hsv2rgb(hsv) + let newColor = 'rgba('+ rgb.r +','+ rgb.g +','+ rgb.b +','+ rgb.a +')' + let cl = [ + Number(rgb.r).toString(16).toUpperCase(), + Number(rgb.g).toString(16).toUpperCase(), + Number(rgb.b).toString(16).toUpperCase(), + (Math.round(Number(rgb.a)*255)).toString(16).toUpperCase() + ] + cl.forEach((item, ind) => { if (item.length === 1) cl[ind] = '0' + item }) + newColor = cl[0] + cl[1] + cl[2] + cl[3] + if (rgb.a === 1) { + newColor = cl[0] + cl[1] + cl[2] + } + query(overlay.box).find('.color-preview').css('background-color', '#' + newColor) + query(overlay.box).find('input').each(el => { + if (el.name) { + if (rgb[el.name] != null) el.value = rgb[el.name] + if (hsv[el.name] != null) el.value = hsv[el.name] + if (el.name === 'a') el.value = rgb.a + } + }) + // if it is in pallette + if (initial) { + let color = overlay.tmp?.initColor || newColor + query(overlay.box).find('.color-original') + .css('background-color', '#'+color) + query(overlay.box).find('.w2ui-colors .w2ui-selected') + .removeClass('w2ui-selected') + query(overlay.box).find(`.w2ui-colors [name="${color}"]`) + .addClass('w2ui-selected') + // if has transparent color, open advanced tab + if (newColor.length == 8) { + self.tabClick(2, overlay.name) + } + } else { + self.select(newColor, overlay.name) + } + if (fullUpdate) { + updateSliders() + refreshPalette() + } + } + function updateSliders() { + let el1 = query(overlay.box).find('.palette .value1') + let el2 = query(overlay.box).find('.rainbow .value2') + let el3 = query(overlay.box).find('.alpha .value2') + let offset1 = parseInt(el1[0].clientWidth) / 2 + let offset2 = parseInt(el2[0].clientWidth) / 2 + el1.css({ + 'left': (hsv.s * 150 / 100 - offset1) + 'px', + 'top': ((100 - hsv.v) * 125 / 100 - offset1) + 'px' + }) + el2.css('left', (hsv.h/(360/150) - offset2) + 'px') + el3.css('left', (rgb.a*150 - offset2) + 'px') + } + function refreshPalette() { + let cl = w2utils.hsv2rgb(hsv.h, 100, 100) + let rgb = `${cl.r},${cl.g},${cl.b}` + query(overlay.box).find('.palette') + .css('background-image', `linear-gradient(90deg, rgba(${rgb},0) 0%, rgba(${rgb},1) 100%)`) + } + function mouseDown(event) { + let el = query(this).find('.value1, .value2') + let offset = parseInt(el.prop('clientWidth')) / 2 + if (el.hasClass('move-x')) el.css({ left: (event.offsetX - offset) + 'px' }) + if (el.hasClass('move-y')) el.css({ top: (event.offsetY - offset) + 'px' }) + initial = { + el : el, + x : event.pageX, + y : event.pageY, + width : el.prop('parentNode').clientWidth, + height : el.prop('parentNode').clientHeight, + left : parseInt(el.css('left')), + top : parseInt(el.css('top')) + } + mouseMove(event) + query('body') + .off('.w2color') + .on(mMove, mouseMove) + .on(mUp, mouseUp) + } + function mouseUp(event) { + query('body').off('.w2color') + } + function mouseMove(event) { + let el = initial.el + let divX = event.pageX - initial.x + let divY = event.pageY - initial.y + let newX = initial.left + divX + let newY = initial.top + divY + let offset = parseInt(el.prop('clientWidth')) / 2 + if (newX < -offset) newX = -offset + if (newY < -offset) newY = -offset + if (newX > initial.width - offset) newX = initial.width - offset + if (newY > initial.height - offset) newY = initial.height - offset + if (el.hasClass('move-x')) el.css({ left : newX + 'px' }) + if (el.hasClass('move-y')) el.css({ top : newY + 'px' }) + // move + let name = query(el.get(0).parentNode).attr('name') + let x = parseInt(el.css('left')) + offset + let y = parseInt(el.css('top')) + offset + if (name === 'palette') { + setColor({ + s: Math.round(x / initial.width * 100), + v: Math.round(100 - (y / initial.height * 100)) + }) + } + if (name === 'rainbow') { + let h = Math.round(360 / 150 * x) + setColor({ h: h }) + refreshPalette() + } + if (name === 'alpha') { + setColor({ a: parseFloat(Number(x / 150).toFixed(2)) }) + } + } + } +} +class MenuTooltip extends Tooltip { + constructor() { + super() + // ITEM STRUCTURE + // item : { + // id : null, + // text : '', + // style : '', + // icon : '', + // count : '', + // tooltip : '', + // hotkey : '', + // remove : false, + // items : [] + // indent : 0, + // type : null, // check/radio + // group : false, // groupping for checks + // expanded : false, + // hidden : false, + // checked : null, + // disabled : false + // ... + // } + this.defaults = w2utils.extend({}, this.defaults, { + type : 'normal', // can be normal, radio, check + items : [], + index : null, // current selected + render : null, + spinner : false, + msgNoItems : w2utils.lang('No items found'), + topHTML : '', + menuStyle : '', + filter : false, + markSearch : false, + match : 'contains', // is, begins, ends, contains + search : false, // top search TODO: Check + altRows : false, + arrowSize : 10, + align : 'left', + position : 'bottom|top', + class : 'w2ui-white', + anchorClass : 'w2ui-focus', + autoShowOn : 'focus', + hideOn : ['doc-click', 'focus-change', 'select'], // also can 'item-remove' + onSelect : null, + onSubMenu : null, + onRemove : null + }) + } + attach(anchor, text) { + let options + if (arguments.length == 1 && anchor.anchor) { + options = anchor + anchor = options.anchor + } else if (arguments.length === 2 && text != null && typeof text === 'object') { + options = text + options.anchor = anchor + } + let prevHideOn = options.hideOn + options = w2utils.extend({}, this.defaults, options || {}) + if (prevHideOn) { + options.hideOn = prevHideOn + } + options.style += '; padding: 0;' + if (options.items == null) { + options.items = [] + } + options.html = this.getMenuHTML(options) + let ret = super.attach(options) + let overlay = ret.overlay + overlay.on('show:after.attach, update:after.attach', event => { + if (ret.overlay?.box) { + let search = '' + // reset selected and active chain + overlay.selected = null + overlay.options.items = w2utils.normMenu(overlay.options.items) + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + search = overlay.anchor.value + overlay.selected = overlay.anchor.dataset.selectedIndex + } + let actions = query(ret.overlay.box).find('.w2ui-eaction') + w2utils.bindEvents(actions, this) + let count = this.applyFilter(overlay.name, null, search) + overlay.tmp.searchCount = count + overlay.tmp.search = search + this.refreshSearch(overlay.name) + this.initControls(ret.overlay) + this.refreshIndex(overlay.name) + } + }) + overlay.on('hide:after.attach', event => { + w2tooltip.hide(overlay.name + '-tooltip') + }) + ret.select = (callback) => { + overlay.on('select.attach', (event) => { callback(event) }) + return ret + } + ret.remove = (callback) => { + overlay.on('remove.attach', (event) => { callback(event) }) + return ret + } + ret.subMenu = (callback) => { + overlay.on('subMenu.attach', (event) => { callback(event) }) + return ret + } + return ret + } + update(name, items) { + let overlay = Tooltip.active[name] + if (overlay) { + let options = overlay.options + if (options.items != items) { + options.items = items + } + let menuHTML = this.getMenuHTML(options) + if (options.html != menuHTML) { + options.html = menuHTML + overlay.needsUpdate = true + this.show(name) + } + } else { + console.log(`Tooltip "${name}" is not displayed. Cannot update it.`) + } + } + initControls(overlay) { + query(overlay.box).find('.w2ui-menu:not(.w2ui-sub-menu)') + .off('.w2menu') + .on('mouseDown.w2menu', { delegate: '.w2ui-menu-item' }, event => { + let dt = event.delegate.dataset + this.menuDown(overlay, event, dt.index, dt.parents) + }) + .on((w2utils.isIOS ? 'touchStart' : 'click') + '.w2menu', { delegate: '.w2ui-menu-item' }, event => { + let dt = event.delegate.dataset + this.menuClick(overlay, event, parseInt(dt.index), dt.parents) + }) + .find('.w2ui-menu-item') + .off('.w2menu') + .on('mouseEnter.w2menu', event => { + let dt = event.target.dataset + let tooltip = overlay.options.items[dt.index]?.tooltip + if (tooltip) { + w2tooltip.show({ + name: overlay.name + '-tooltip', + anchor: event.target, + html: tooltip, + position: 'right|left', + hideOn: ['doc-click'] + }) + } + }) + .on('mouseLeave.w2menu', event => { + w2tooltip.hide(overlay.name + '-tooltip') + }) + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + query(overlay.anchor) + .off('.w2menu') + .on('input.w2menu', event => { + // if user types, clear selection + // let dt = event.target.dataset + // delete dt.selected + // delete dt.selectedIndex + }) + .on('keyup.w2menu', event => { + event._searchType = 'filter' + this.keyUp(overlay, event) + }) + } + if (overlay.options.search) { + query(overlay.box).find('#menu-search') + .off('.w2menu') + .on('keyup.w2menu', event => { + event._searchType = 'search' + this.keyUp(overlay, event) + }) + } + } + getCurrent(name, id) { + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + let options = overlay.options + let selected = (id ? id : overlay.selected ?? '').split('-') + let last = selected.length-1 + let index = selected[last] + let parents = selected.slice(0, selected.length-1).join('-') + index = w2utils.isInt(index) ? parseInt(index) : 0 + // items + let items = options.items + selected.forEach((id, ind) => { + // do not go to the last one + if (ind < selected.length - 1) { + items = items[id].items + } + }) + return { last, index, items, item: items[index], parents } + } + getMenuHTML(options, items, subMenu, parentIndex) { + if (options.spinner) { + return ` +
    +
    +
    + ${w2utils.lang('Loading...')} +
    +
    ` + } + if (!parentIndex) parentIndex = [] + if (items == null) { + items = options.items + } + if (!Array.isArray(items)) items = [] + let count = 0 + let icon = null + let topHTML = '' + if (!subMenu && options.search) { + topHTML += ` + ` + items.forEach(item => item.hidden = false) + } + if (!subMenu && options.topHTML) { + topHTML += `
    ${options.topHTML}
    ` + } + let menu_html = ` + ${topHTML} +
    + ` + items.forEach((mitem, f) => { + icon = mitem.icon + let index = (parentIndex.length > 0 ? parentIndex.join('-') + '-' : '') + f + if (icon == null) icon = null // icon might be undefined + if (['radio', 'check'].indexOf(options.type) != -1 && !Array.isArray(mitem.items) && mitem.group !== false) { + if (mitem.checked === true) icon = 'w2ui-icon-check'; else icon = 'w2ui-icon-empty' + } + if (mitem.hidden !== true) { + let txt = mitem.text + let icon_dsp = '' + let subMenu_dsp = '' + if (typeof options.render === 'function') txt = options.render(mitem, options) + if (typeof txt == 'function') txt = txt(mitem, options) + if (icon) { + if (String(icon).slice(0, 1) !== '<') { + icon = `` + } + icon_dsp = `` + } + // render only if non-empty + if (mitem.type !== 'break' && txt != null && txt !== '' && String(txt).substr(0, 2) != '--') { + let classes = ['w2ui-menu-item'] + if (options.altRows == true) { + classes.push(count % 2 === 0 ? 'w2ui-even' : 'w2ui-odd') + } + let colspan = 1 + if (icon_dsp === '') colspan++ + if (mitem.count == null && mitem.hotkey == null && mitem.remove !== true && mitem.items == null) colspan++ + if (mitem.tooltip == null && mitem.hint != null) mitem.tooltip = mitem.hint // for backward compatibility + let count_dsp = '' + if (mitem.remove === true) { + count_dsp = 'x' + } else if (mitem.items != null) { + let _items = [] + if (typeof mitem.items == 'function') { + _items = mitem.items(mitem) + } else if (Array.isArray(mitem.items)) { + _items = mitem.items + } + count_dsp = '' + subMenu_dsp = ` +
    + ${this.getMenuHTML(options, _items, true, parentIndex.concat(f))} +
    ` + } else { + if (mitem.count != null) count_dsp += '' + mitem.count + '' + if (mitem.hotkey != null) count_dsp += '' + mitem.hotkey + '' + } + if (mitem.disabled === true) classes.push('w2ui-disabled') + if (mitem._noSearchInside === true) classes.push('w2ui-no-search-inside') + if (subMenu_dsp !== '') { + classes.push('has-sub-menu') + if (mitem.expanded) { + classes.push('expanded') + } else { + classes.push('collapsed') + } + } + menu_html += ` +
    +
    + ${icon_dsp} + + +
    + ${subMenu_dsp}` + count++ + } else { + // horizontal line + let divText = (txt ?? '').replace(/^-+/g, '') + menu_html += ` +
    +
    + ${divText ? `
    ${divText}
    ` : ''} +
    ` + } + } + items[f] = mitem + }) + if (count === 0 && options.msgNoItems) { + menu_html += ` +
    + ${w2utils.lang(options.msgNoItems)} +
    ` + } + menu_html += '
    ' + return menu_html + } + // Refreshed only selected item highligh, used in keyboard navigation + refreshIndex(name) { + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + if (!overlay) return + if (!overlay.displayed) { + this.show(overlay.name) + } + let view = query(overlay.box).find('.w2ui-overlay-body').get(0) + let search = query(overlay.box).find('.w2ui-menu-search, .w2ui-menu-top').get(0) + query(overlay.box).find('.w2ui-menu-item.w2ui-selected') + .removeClass('w2ui-selected') + let el = query(overlay.box).find(`.w2ui-menu-item[index="${overlay.selected}"]`) + .addClass('w2ui-selected') + .get(0) + if (el) { + if (el.offsetTop + el.clientHeight > view.clientHeight + view.scrollTop) { + el.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'start' }) + } + if (el.offsetTop < view.scrollTop + (search ? search.clientHeight : 0)) { + el.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' }) + } + } + } + // show/hide searched items + refreshSearch(name) { + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + if (!overlay) return + if (!overlay.displayed) { + this.show(overlay.name) + } + query(overlay.box).find('.w2ui-no-items').hide() + query(overlay.box).find('.w2ui-menu-item, .w2ui-menu-divider').each(el => { + let cur = this.getCurrent(name, el.getAttribute('index')) + if (cur.item.hidden) { + query(el).hide() + } else { + let search = overlay.tmp?.search + if (search && overlay.options.markSearch) { + w2utils.marker(el, search, { onlyFirst: overlay.options.match == 'begins' }) + } + query(el).show() + } + }) + // hide empty menus + query(overlay.box).find('.w2ui-sub-menu').each(sub => { + let hasItems = query(sub).find('.w2ui-menu-item').get().some(el => { + return el.style.display != 'none' ? true : false + }) + let parent = this.getCurrent(name, sub.dataset.parent) + // only if parent is expaneded + if (parent.item.expanded) { + if (!hasItems) { + query(sub).parent().hide() + } else { + query(sub).parent().show() + } + } + }) + // show empty message + if (overlay.tmp.searchCount == 0 || overlay.options?.items?.length == 0) { + if (query(overlay.box).find('.w2ui-no-items').length == 0) { + query(overlay.box).find('.w2ui-menu:not(.w2ui-sub-menu)').append(` +
    + ${w2utils.lang(overlay.options.msgNoItems)} +
    `) + } + query(overlay.box).find('.w2ui-no-items').show() + } + } + /** + * Loops through the items and markes item.hidden for those that need to be hidden. + * Return the number of visible items. + */ + applyFilter(name, items, search) { + let count = 0 + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + let options = overlay.options + if (options.filter === false) { + return + } + if (items == null) items = overlay.options.items + if (search == null) { + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + search = overlay.anchor.value + } else { + search = '' + } + } + let selectedIds = [] + if (options.selected) { + if (Array.isArray(options.selected)) { + selectedIds = options.selected.map(item => { + return item?.id ?? item + }) + } else if (options.selected?.id) { + selectedIds = [options.selected.id] + } + } + items.forEach(item => { + let prefix = '' + let suffix = '' + if (['is', 'begins', 'begins with'].indexOf(options.match) !== -1) prefix = '^' + if (['is', 'ends', 'ends with'].indexOf(options.match) !== -1) suffix = '$' + try { + let re = new RegExp(prefix + search + suffix, 'i') + if (re.test(item.text) || item.text === '...') { + item.hidden = false + } else { + item.hidden = true + } + } catch (e) {} + // do not show selected items + if (options.hideSelected && selectedIds.includes(item.id)) { + item.hidden = true + } + // search nested items + if (Array.isArray(item.items) && item.items.length > 0) { + delete item._noSearchInside + let subCount = this.applyFilter(name, item.items, search) + if (subCount > 0) { + count += subCount + if (item.hidden) item._noSearchInside = true + // only expand items if search is not empty + if (search) item.expanded = true + item.hidden = false + } + } + if (item.hidden !== true) count++ + }) + overlay.tmp.activeChain = this.getActiveChain(name, items) + overlay.selected = null + return count + } + /** + * Builds an array of item ids that sequencial in navigation with up/down keys. + * Skips hidden and disabled items and goes into nested structures. + */ + getActiveChain(name, items, parents = [], res = [], noSave) { + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + if (overlay.tmp.activeChain != null) { + return overlay.tmp.activeChain + } + if (items == null) items = overlay.options.items + items.forEach((item, ind) => { + if (!item.hidden && !item.disabled && !item?.text?.startsWith('--')) { + res.push(parents.concat([ind]).join('-')) + if (Array.isArray(item.items) && item.items.length > 0 && item.expanded) { + parents.push(ind) + this.getActiveChain(name, item.items, parents, res, true) + parents.pop() + } + } + }) + if (noSave == null) { + overlay.tmp.activeChain = res + } + return res + } + menuDown(overlay, event, index, parentIndex) { + let options = overlay.options + let items = options.items + let icon = query(event.delegate).find('.w2ui-icon') + let menu = query(event.target).closest('.w2ui-menu:not(.w2ui-sub-menu)') + if (typeof parentIndex == 'string' && parentIndex !== '') { + let ids = parentIndex.split('-') + ids.forEach(id => { + items = items[id].items + }) + } + let item = items[index] + if (item.disabled) { + return + } + let uncheck = (items, parent) => { + items.forEach((other, ind) => { + if (other.id == item.id) return + if (other.group === item.group && other.checked) { + menu + .find(`.w2ui-menu-item[index="${(parent ? parent + '-' : '') + ind}"] .w2ui-icon`) + .removeClass('w2ui-icon-check') + .addClass('w2ui-icon-empty') + items[ind].checked = false + } + if (Array.isArray(other.items)) { + uncheck(other.items, ind) + } + }) + } + if ((options.type === 'check' || options.type === 'radio') && item.group !== false + && !query(event.target).hasClass('remove') + && !query(event.target).closest('.w2ui-menu-item').hasClass('has-sub-menu')) { + item.checked = options.type == 'radio' ? true : !item.checked + if (item.checked) { + if (options.type === 'radio') { + query(event.target).closest('.w2ui-menu').find('.w2ui-icon') + .removeClass('w2ui-icon-check') + .addClass('w2ui-icon-empty') + } + if (options.type === 'check' && item.group != null) { + uncheck(options.items) + } + icon.removeClass('w2ui-icon-empty').addClass('w2ui-icon-check') + } else if (options.type === 'check') { + icon.removeClass('w2ui-icon-check').addClass('w2ui-icon-empty') + } + } + // highlight record + if (!query(event.target).hasClass('remove')) { + menu.find('.w2ui-menu-item').removeClass('w2ui-selected') + query(event.delegate).addClass('w2ui-selected') + } + } + menuClick(overlay, event, index, parentIndex) { + let options = overlay.options + let items = options.items + let $item = query(event.delegate).closest('.w2ui-menu-item') + let keepOpen = options.hideOn.includes('select') ? false : true + if (event.shiftKey || event.metaKey || event.ctrlKey) { + keepOpen = true + } + if (typeof parentIndex == 'string' && parentIndex !== '') { + let ids = parentIndex.split('-') + ids.forEach(id => { + items = items[id].items + }) + } else { + parentIndex = null + } + if (typeof items == 'function') { + items = items({ overlay, index, parentIndex, event }) + } + let item = items[index] + if (item.disabled && !query(event.target).hasClass('remove')) { + return + } + let edata + if (query(event.target).hasClass('remove')) { + edata = this.trigger('remove', { originalEvent: event, target: overlay.name, + overlay, item, index, parentIndex, el: $item[0] }) + if (edata.isCancelled === true) { + return + } + keepOpen = !options.hideOn.includes('item-remove') + $item.remove() + } else if ($item.hasClass('has-sub-menu')) { + edata = this.trigger('subMenu', { originalEvent: event, target: overlay.name, + overlay, item, index, parentIndex, el: $item[0] }) + if (edata.isCancelled === true) { + return + } + keepOpen = true + if ($item.hasClass('expanded')) { + item.expanded = false + $item.removeClass('expanded').addClass('collapsed') + query($item.get(0).nextElementSibling).hide() + overlay.selected = parseInt($item.attr('index')) + } else { + item.expanded = true + $item.addClass('expanded').removeClass('collapsed') + query($item.get(0).nextElementSibling).show() + overlay.selected = parseInt($item.attr('index')) + } + } else { + // find items that are selected + let selected = this.findChecked(options.items) + overlay.selected = parseInt($item.attr('index')) + edata = this.trigger('select', { originalEvent: event, target: overlay.name, + overlay, item, index, parentIndex, selected, keepOpen, el: $item[0] }) + if (edata.isCancelled === true) { + return + } + if (item.keepOpen != null) { + keepOpen = item.keepOpen + } + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + overlay.anchor.dataset.selected = item.id + overlay.anchor.dataset.selectedIndex = overlay.selected + } + } + if (!keepOpen) { + this.hide(overlay.name) + } + // if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + // overlay.anchor.focus() + // } + // event after + edata.finish() + } + findChecked(items) { + let found = [] + items.forEach(item => { + if (item.checked) found.push(item) + if (Array.isArray(item.items)) { + found = found.concat(this.findChecked(item.items)) + } + }) + return found + } + keyUp(overlay, event) { + let options = overlay.options + let search = event.target.value + let key = event.keyCode + let filter = true + let refreshIndex = false + switch (key) { + case 8: { // delete + // if search empty and delete is clicked, do not filter nor show overlay + if (search === '' && !overlay.displayed) filter = false + break + } + case 13: { // enter + if (!overlay.displayed || !overlay.selected) return + let { index, parents } = this.getCurrent(overlay.name) + event.delegate = query(overlay.box).find('.w2ui-selected').get(0) + // reset active chain for folders + this.menuClick(overlay, event, parseInt(index), parents) + filter = false + break + } + case 27: { // escape + filter = false + if (overlay.displayed) { + this.hide(overlay.name) + } else { + // clear selected + let el = overlay.anchor + if (['INPUT', 'TEXTAREA'].includes(el.tagName)) { + el.value = '' + delete el.dataset.selected + delete el.dataset.selectedIndex + } + } + break + } + case 37: { // left + if (!overlay.displayed) return + let { item, index, parents } = this.getCurrent(overlay.name) + // collapse parent if any + if (parents) { + item = options.items[parents] + index = parseInt(parents) + parents = '' + refreshIndex = true + } + if (Array.isArray(item?.items) && item.items.length > 0 && item.expanded) { + event.delegate = query(overlay.box).find(`.w2ui-menu-item[index="${index}"]`).get(0) + overlay.selected = index + this.menuClick(overlay, event, parseInt(index), parents) + } + filter = false + break + } + case 39: { // right + if (!overlay.displayed) return + let { item, index, parents } = this.getCurrent(overlay.name) + if (Array.isArray(item?.items) && item.items.length > 0 && !item.expanded) { + event.delegate = query(overlay.box).find('.w2ui-selected').get(0) + this.menuClick(overlay, event, parseInt(index), parents) + } + filter = false + break + } + case 38: { // up + if (!overlay.displayed) { + break + } + let chain = this.getActiveChain(overlay.name) + if (overlay.selected == null || overlay.selected?.length == 0) { + overlay.selected = chain[chain.length-1] + } else { + let ind = chain.indexOf(overlay.selected) + // selected not in chain of items + if (ind == -1) { + overlay.selected = chain[chain.length-1] + } + // not first item + if (ind > 0) { + overlay.selected = chain[ind - 1] + } + } + filter = false + refreshIndex = true + event.preventDefault() + break + } + case 40: { // down + if (!overlay.displayed) { + break + } + let chain = this.getActiveChain(overlay.name) + if (overlay.selected == null || overlay.selected?.length == 0) { + overlay.selected = chain[0] + } else { + let ind = chain.indexOf(overlay.selected) + // selected not in chain of items + if (ind == -1) { + overlay.selected = chain[0] + } + // not the last item + if (ind < chain.length - 1) { + overlay.selected = chain[ind + 1] + } + } + filter = false + refreshIndex = true + event.preventDefault() + break + } + } + // filter + if (filter && overlay.displayed && ((options.filter && event._searchType == 'filter') + || (options.search && event._searchType == 'search'))) { + let count = this.applyFilter(overlay.name, null, search) + overlay.tmp.searchCount = count + overlay.tmp.search = search + // if selected is not in searched items + if (count === 0 || !this.getActiveChain(overlay.name).includes(overlay.selected)) { + overlay.selected = null + } + this.refreshSearch(overlay.name) + } + if (refreshIndex) { + this.refreshIndex(overlay.name) + } + } +} +class DateTooltip extends Tooltip { + constructor() { + super() + let td = new Date() + this.daysCount = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + this.today = td.getFullYear() + '/' + (Number(td.getMonth()) + 1) + '/' + td.getDate() + this.defaults = w2utils.extend({}, this.defaults, { + position : 'top|bottom', + class : 'w2ui-calendar', + type : 'date', // can be date/time/datetime + format : '', + value : '', // initial date (in w2utils.settings format) + start : null, + end : null, + blockDates : [], // array of blocked dates + blockWeekdays : [], // blocked weekdays 0 - sunday, 1 - monday, etc + colored : {}, // ex: { '3/13/2022': 'bg-color|text-color' } + arrowSize : 12, + autoResize : false, + anchorClass : 'w2ui-focus', + autoShowOn : 'focus', + hideOn : ['doc-click', 'focus-change'], + onSelect : null + }) + } + attach(anchor, text) { + let options + if (arguments.length == 1 && anchor.anchor) { + options = anchor + anchor = options.anchor + } else if (arguments.length === 2 && text != null && typeof text === 'object') { + options = text + options.anchor = anchor + } + let prevHideOn = options.hideOn + options = w2utils.extend({}, this.defaults, options || {}) + if (prevHideOn) { + options.hideOn = prevHideOn + } + if (!options.format) { + let df = w2utils.settings.dateFormat + let tf = w2utils.settings.timeFormat + if (options.type == 'date') { + options.format = df + } else if (options.type == 'time') { + options.format = tf + } else { + options.format = df + '|' + tf + } + } + let cal = options.type == 'time' ? this.getHourHTML(options) : this.getMonthHTML(options) + options.style += '; padding: 0;' + options.html = cal.html + let ret = super.attach(options) + let overlay = ret.overlay + Object.assign(overlay.tmp, cal) + overlay.on('show.attach', event => { + let overlay = event.detail.overlay + let anchor = overlay.anchor + let options = overlay.options + if (['INPUT', 'TEXTAREA'].includes(anchor.tagName) && !options.value && anchor.value) { + overlay.tmp.initValue = anchor.value + } + delete overlay.newValue + delete overlay.newDate + }) + overlay.on('show:after.attach', event => { + if (ret.overlay?.box) { + this.initControls(ret.overlay) + } + }) + overlay.on('update:after.attach', event => { + if (ret.overlay?.box) { + this.initControls(ret.overlay) + } + }) + overlay.on('hide.attach', event => { + let overlay = event.detail.overlay + let anchor = overlay.anchor + if (overlay.newValue != null) { + if (overlay.newDate) { + overlay.newValue = overlay.newDate + ' ' + overlay.newValue + } + if (['INPUT', 'TEXTAREA'].includes(anchor.tagName) && anchor.value != overlay.newValue) { + anchor.value = overlay.newValue + } + let edata = this.trigger('select', { date: overlay.newValue, target: overlay.name, overlay }) + if (edata.isCancelled === true) return + // event after + edata.finish() + } + }) + ret.select = (callback) => { + overlay.on('select.attach', (event) => { callback(event) }) + return ret + } + return ret + } + initControls(overlay) { + let options = overlay.options + let moveMonth = (inc) => { + let { month, year } = overlay.tmp + month += inc + if (month > 12) { + month = 1 + year++ + } + if (month < 1 ) { + month = 12 + year-- + } + let cal = this.getMonthHTML(options, month, year) + Object.assign(overlay.tmp, cal) + query(overlay.box).find('.w2ui-overlay-body').html(cal.html) + this.initControls(overlay) + } + let checkJump = (event, dblclick) => { + query(event.target).parent().find('.w2ui-jump-month, .w2ui-jump-year') + .removeClass('w2ui-selected') + query(event.target).addClass('w2ui-selected') + let dt = new Date() + let { jumpMonth, jumpYear } = overlay.tmp + if (dblclick) { + if (jumpYear == null) jumpYear = dt.getFullYear() + if (jumpMonth == null) jumpMonth = dt.getMonth() + 1 + } + if (jumpMonth && jumpYear) { + let cal = this.getMonthHTML(options, jumpMonth, jumpYear) + Object.assign(overlay.tmp, cal) + query(overlay.box).find('.w2ui-overlay-body').html(cal.html) + overlay.tmp.jump = false + this.initControls(overlay) + } + } + // events for next/prev buttons and title + query(overlay.box).find('.w2ui-cal-title') + .off('.calendar') + // click on title + .on('click.calendar', event => { + Object.assign(overlay.tmp, { jumpYear: null, jumpMonth: null }) + if (overlay.tmp.jump) { + let { month, year } = overlay.tmp + let cal = this.getMonthHTML(options, month, year) + query(overlay.box).find('.w2ui-overlay-body').html(cal.html) + overlay.tmp.jump = false + } else { + query(overlay.box).find('.w2ui-overlay-body .w2ui-cal-days') + .replace(this.getYearHTML()) + let el = query(overlay.box).find(`[name="${overlay.tmp.year}"]`).get(0) + if (el) el.scrollIntoView(true) + overlay.tmp.jump = true + } + this.initControls(overlay) + event.stopPropagation() + }) + // prev button + .find('.w2ui-cal-previous') + .off('.calendar') + .on('click.calendar', event => { + moveMonth(-1) + event.stopPropagation() + }) + .parent() + // next button + .find('.w2ui-cal-next') + .off('.calendar') + .on('click.calendar', event => { + moveMonth(1) + event.stopPropagation() + }) + // now button + query(overlay.box).find('.w2ui-cal-now') + .off('.calendar') + .on('click.calendar', event => { + if (options.type == 'datetime') { + if (overlay.newDate) { + overlay.newValue = w2utils.formatTime(new Date(), options.format.split('|')[1]) + } else { + overlay.newValue = w2utils.formatDateTime(new Date(), options.format) + } + } else if (options.type == 'date') { + overlay.newValue = w2utils.formatDate(new Date(), options.format) + } else if (options.type == 'time') { + overlay.newValue = w2utils.formatTime(new Date(), options.format) + } + this.hide(overlay.name) + }) + // events for dates + query(overlay.box) + .off('.calendar') + .on('click.calendar', { delegate: '.w2ui-day.w2ui-date' }, event => { + if (options.type == 'datetime') { + overlay.newDate = query(event.target).attr('date') + query(overlay.box).find('.w2ui-overlay-body').html(this.getHourHTML(overlay.options).html) + this.initControls(overlay) + } else { + overlay.newValue = query(event.target).attr('date') + this.hide(overlay.name) + } + }) + // click on month + .on('click.calendar', { delegate: '.w2ui-jump-month' }, event => { + overlay.tmp.jumpMonth = parseInt(query(event.target).attr('name')) + checkJump(event) + }) + // double click on month + .on('dblclick.calendar', { delegate: '.w2ui-jump-month' }, event => { + overlay.tmp.jumpMonth = parseInt(query(event.target).attr('name')) + checkJump(event, true) + }) + // click on year + .on('click.calendar', { delegate: '.w2ui-jump-year' }, event => { + overlay.tmp.jumpYear = parseInt(query(event.target).attr('name')) + checkJump(event) + }) + // dbl click on year + .on('dblclick.calendar', { delegate: '.w2ui-jump-year' }, event => { + overlay.tmp.jumpYear = parseInt(query(event.target).attr('name')) + checkJump(event, true) + }) + // click on hour + .on('click.calendar', { delegate: '.w2ui-time.hour' }, event => { + let hour = query(event.target).attr('hour') + let min = this.str2min(options.value) % 60 + if (overlay.tmp.initValue && !options.value) { + min = this.str2min(overlay.tmp.initValue) % 60 + } + if (options.noMinutes) { + overlay.newValue = this.min2str(hour * 60, options.format) + this.hide(overlay.name) + } else { + overlay.newValue = hour + ':' + min + let html = this.getMinHTML(hour, options).html + query(overlay.box).find('.w2ui-overlay-body').html(html) + this.initControls(overlay) + } + }) + // click on minute + .on('click.calendar', { delegate: '.w2ui-time.min' }, event => { + let hour = Math.floor(this.str2min(overlay.newValue) / 60) + let time = (hour * 60) + parseInt(query(event.target).attr('min')) + overlay.newValue = this.min2str(time, options.format) + this.hide(overlay.name) + }) + } + getMonthHTML(options, month, year) { + let days = w2utils.settings.fulldays.slice() // creates copy of the array + let sdays = w2utils.settings.shortdays.slice() // creates copy of the array + if (w2utils.settings.weekStarts !== 'M') { + days.unshift(days.pop()) + sdays.unshift(sdays.pop()) + } + let td = new Date() + let dayLengthMil = 1000 * 60 * 60 * 24 + let selected = options.type === 'datetime' + ? w2utils.isDateTime(options.value, options.format, true) + : w2utils.isDate(options.value, options.format, true) + let selected_dsp = w2utils.formatDate(selected) + // normalize date + if (month == null || year == null) { + year = selected ? selected.getFullYear() : td.getFullYear() + month = selected ? selected.getMonth() + 1 : td.getMonth() + 1 + } + if (month > 12) { month -= 12; year++ } + if (month < 1 || month === 0) { month += 12; year-- } + if (year/4 == Math.floor(year/4)) { this.daysCount[1] = 29 } else { this.daysCount[1] = 28 } + options.current = month + '/' + year + // start with the required date + td = new Date(year, month-1, 1) + let weekDay = td.getDay() + let weekDays = '' + let st = w2utils.settings.weekStarts + for (let i = 0; i < sdays.length; i++) { + let isSat = (st == 'M' && i == 5) || (st != 'M' && i == 6) ? true : false + let isSun = (st == 'M' && i == 6) || (st != 'M' && i == 0) ? true : false + weekDays += `
    ${sdays[i]}
    ` + } + let html = ` +
    +
    +
    +
    +
    +
    +
    + ${w2utils.settings.fullmonths[month-1]}, ${year} + +
    +
    + ${weekDays} + ` + let DT = new Date(`${year}/${month}/1`) // first of month + /** + * Move to noon, instead of midnight. If not, then the date when time saving happens + * will be duplicated in the calendar + */ + DT = new Date(DT.getTime() + dayLengthMil * 0.5) + let weekday = DT.getDay() + if (w2utils.settings.weekStarts == 'M') weekDay-- + if (weekday > 0) { + DT = new Date(DT.getTime() - (weekDay * dayLengthMil)) + } + for (let ci = 0; ci < 42; ci++) { + let className = [] + let dt = `${DT.getFullYear()}/${DT.getMonth()+1}/${DT.getDate()}` + if (DT.getDay() === 6) className.push('w2ui-saturday') + if (DT.getDay() === 0) className.push('w2ui-sunday') + if (DT.getMonth() + 1 !== month) className.push('outside') + if (dt == this.today) className.push('w2ui-today') + let dspDay = DT.getDate() + let col = '' + let bgcol = '' + let tmp_dt, tmp_dt_fmt + if (options.type === 'datetime') { + tmp_dt = w2utils.formatDateTime(dt, options.format) + tmp_dt_fmt = w2utils.formatDate(dt, w2utils.settings.dateFormat) + } else { + tmp_dt = w2utils.formatDate(dt, options.format) + tmp_dt_fmt = tmp_dt + } + if (options.colored && options.colored[tmp_dt_fmt] !== undefined) { // if there is predefined colors for dates + let tmp = options.colored[tmp_dt_fmt].split('|') + bgcol = 'background-color: ' + tmp[0] + ';' + col = 'color: ' + tmp[1] + ';' + } + html += `
    + ${dspDay} +
    ` + DT = new Date(DT.getTime() + dayLengthMil) + } + html += '
    ' + if (options.btnNow) { + let label = w2utils.lang('Today' + (options.type == 'datetime' ? ' & Now' : '')) + html += `
    ${label}
    ` + } + return { html, month, year } + } + getYearHTML() { + let mhtml = '' + let yhtml = '' + for (let m = 0; m < w2utils.settings.fullmonths.length; m++) { + mhtml += `
    ${w2utils.settings.shortmonths[m]}
    ` + } + for (let y = w2utils.settings.dateStartYear; y <= w2utils.settings.dateEndYear; y++) { + yhtml += `
    ${y}
    ` + } + return `
    +
    ${mhtml}
    +
    ${yhtml}
    +
    ` + } + getHourHTML(options) { + options = options ?? {} + if (!options.format) options.format = w2utils.settings.timeFormat + let h24 = (options.format.indexOf('h24') > -1) + let value = options.value ? options.value : (options.anchor ? options.anchor.value : '') + let tmp = [] + for (let a = 0; a < 24; a++) { + let time = (a >= 12 && !h24 ? a - 12 : a) + ':00' + (!h24 ? (a < 12 ? ' am' : ' pm') : '') + if (a == 12 && !h24) time = '12:00 pm' + if (!tmp[Math.floor(a/8)]) tmp[Math.floor(a/8)] = '' + let tm1 = this.min2str(this.str2min(time)) + let tm2 = this.min2str(this.str2min(time) + 59) + if (options.type === 'datetime') { + let dt = w2utils.isDateTime(value, options.format, true) + let fm = options.format.split('|')[0].trim() + tm1 = w2utils.formatDate(dt, fm) + ' ' + tm1 + tm2 = w2utils.formatDate(dt, fm) + ' ' + tm2 + } + let valid = this.inRange(tm1, options) || this.inRange(tm2, options) + tmp[Math.floor(a/8)] += `${time}` + } + let html = `
    +
    ${w2utils.lang('Select Hour')}
    +
    +
    ${tmp[0]}
    +
    ${tmp[1]}
    +
    ${tmp[2]}
    +
    + ${options.btnNow ? `
    ${w2utils.lang('Now')}
    ` : '' } +
    ` + return { html } + } + getMinHTML(hour, options) { + if (hour == null) hour = 0 + options = options ?? {} + if (!options.format) options.format = w2utils.settings.timeFormat + let h24 = (options.format.indexOf('h24') > -1) + let value = options.value ? options.value : (options.anchor ? options.anchor.value : '') + let tmp = [] + for (let a = 0; a < 60; a += 5) { + let time = (hour > 12 && !h24 ? hour - 12 : hour) + ':' + (a < 10 ? 0 : '') + a + ' ' + (!h24 ? (hour < 12 ? 'am' : 'pm') : '') + let tm = time + let ind = a < 20 ? 0 : (a < 40 ? 1 : 2) + if (!tmp[ind]) tmp[ind] = '' + if (options.type === 'datetime') { + let dt = w2utils.isDateTime(value, options.format, true) + let fm = options.format.split('|')[0].trim() + tm = w2utils.formatDate(dt, fm) + ' ' + tm + } + tmp[ind] += `${time}` + } + let html = `
    +
    ${w2utils.lang('Select Minute')}
    +
    +
    ${tmp[0]}
    +
    ${tmp[1]}
    +
    ${tmp[2]}
    +
    + ${options.btnNow ? `
    ${w2utils.lang('Now')}
    ` : '' } +
    ` + return { html } + } + // checks if date is in range (loost at start, end, blockDates, blockWeekdays) + inRange(str, options, dateOnly) { + let inRange = false + if (options.type === 'date') { + let dt = w2utils.isDate(str, options.format, true) + if (dt) { + // enable range + if (options.start || options.end) { + let st = (typeof options.start === 'string' ? options.start : query(options.start).val()) + let en = (typeof options.end === 'string' ? options.end : query(options.end).val()) + let start = w2utils.isDate(st, options.format, true) + let end = w2utils.isDate(en, options.format, true) + let current = new Date(dt) + if (!start) start = current + if (!end) end = current + if (current >= start && current <= end) inRange = true + } else { + inRange = true + } + // block predefined dates + if (Array.isArray(options.blockDates) && options.blockDates.includes(str)) inRange = false + // block weekdays + if (Array.isArray(options.blockWeekdays) && options.blockWeekdays.includes(dt.getDay())) inRange = false + } + } else if (options.type === 'time') { + if (options.start || options.end) { + let tm = this.str2min(str) + let tm1 = this.str2min(options.start) + let tm2 = this.str2min(options.end) + if (!tm1) tm1 = tm + if (!tm2) tm2 = tm + if (tm >= tm1 && tm <= tm2) inRange = true + } else { + inRange = true + } + } else if (options.type === 'datetime') { + let dt = w2utils.isDateTime(str, options.format, true) + if (dt) { + let format = options.format.split('|').map(format => format.trim()) + if (dateOnly) { + let date = w2utils.formatDate(dt, format[0]) + let opts = w2utils.extend({}, options, { type: 'date', format: format[0] }) + if (this.inRange(date, opts)) inRange = true + } else { + let time = w2utils.formatTime(dt, format[1]) + let opts = { type: 'time', format: format[1], start: options.startTime, end: options.endTime } + if (this.inRange(time, opts)) inRange = true + } + } + } + return inRange + } + // converts time into number of minutes since midnight -- '11:50am' => 710 + str2min(str) { + if (typeof str !== 'string') return null + let tmp = str.split(':') + if (tmp.length === 2) { + tmp[0] = parseInt(tmp[0]) + tmp[1] = parseInt(tmp[1]) + if (str.indexOf('pm') !== -1 && tmp[0] !== 12) tmp[0] += 12 + if (str.includes('am') && tmp[0] == 12) tmp[0] = 0 // 12:00am - is midnight + } else { + return null + } + return tmp[0] * 60 + tmp[1] + } + // converts minutes since midnight into time str -- 710 => '11:50am' + min2str(time, format) { + let ret = '' + if (time >= 24 * 60) time = time % (24 * 60) + if (time < 0) time = 24 * 60 + time + let hour = Math.floor(time/60) + let min = ((time % 60) < 10 ? '0' : '') + (time % 60) + if (!format) { format = w2utils.settings.timeFormat} + if (format.indexOf('h24') !== -1) { + ret = hour + ':' + min + } else { + ret = (hour <= 12 ? hour : hour - 12) + ':' + min + ' ' + (hour >= 12 ? 'pm' : 'am') + } + return ret + } +} +let w2tooltip = new Tooltip() +let w2menu = new MenuTooltip() +let w2color = new ColorTooltip() +let w2date = new DateTooltip() + +/* +// pull records from remote source for w2menu +clearCache() { + let options = this.options + options.items = [] + this.tmp.xhr_loading = false + this.tmp.xhr_search = '' + this.tmp.xhr_total = -1 +} +request(interval) { + let obj = this + let options = this.options + let search = $(obj.el).val() || '' + // if no url - do nothing + if (!options.url) return + // -- + if (obj.type === 'enum') { + let tmp = $(obj.helpers.multi).find('input') + if (tmp.length === 0) search = ''; else search = tmp.val() + } + if (obj.type === 'list') { + let tmp = $(obj.helpers.focus).find('input') + if (tmp.length === 0) search = ''; else search = tmp.val() + } + if (options.minLength !== 0 && search.length < options.minLength) { + options.items = [] // need to empty the list + this.updateOverlay() + return + } + if (interval == null) interval = options.interval + if (obj.tmp.xhr_search == null) obj.tmp.xhr_search = '' + if (obj.tmp.xhr_total == null) obj.tmp.xhr_total = -1 + // check if need to search + if (options.url && $(obj.el).prop('readonly') !== true && $(obj.el).prop('disabled') !== true && ( + (options.items.length === 0 && obj.tmp.xhr_total !== 0) || + (obj.tmp.xhr_total == options.cacheMax && search.length > obj.tmp.xhr_search.length) || + (search.length >= obj.tmp.xhr_search.length && search.substr(0, obj.tmp.xhr_search.length) !== obj.tmp.xhr_search) || + (search.length < obj.tmp.xhr_search.length) + )) { + // empty list + if (obj.tmp.xhr) try { obj.tmp.xhr.abort() } catch (e) {} + obj.tmp.xhr_loading = true + obj.search() + // timeout + clearTimeout(obj.tmp.timeout) + obj.tmp.timeout = setTimeout(() => { + // trigger event + let url = options.url + let postData = { + search : search, + max : options.cacheMax + } + $.extend(postData, options.postData) + let edata = obj.trigger({ phase: 'before', type: 'request', search: search, target: obj.el, url: url, postData: postData }) + if (edata.isCancelled === true) return + url = edata.url + postData = edata.postData + let ajaxOptions = { + type : 'GET', + url : url, + data : postData, + dataType : 'JSON' // expected from server + } + if (options.method) ajaxOptions.type = options.method + if (w2utils.settings.dataType === 'JSON') { + ajaxOptions.type = 'POST' + ajaxOptions.data = JSON.stringify(ajaxOptions.data) + ajaxOptions.contentType = 'application/json' + } + if (w2utils.settings.dataType === 'HTTPJSON') { + ajaxOptions.data = { request: JSON.stringify(ajaxOptions.data) } + } + if (w2utils.settings.dataType === 'RESTFULLJSON') { + ajaxOptions.data = JSON.stringify(ajaxOptions.data) + ajaxOptions.contentType = 'application/json' + } + if (options.method != null) ajaxOptions.type = options.method + obj.tmp.xhr = $.ajax(ajaxOptions) + .done((data, status, xhr) => { + // trigger event + let edata2 = obj.trigger({ phase: 'before', type: 'load', target: obj.el, search: postData.search, data: data, xhr: xhr }) + if (edata2.isCancelled === true) return + // default behavior + data = edata2.data + if (typeof data === 'string') data = JSON.parse(data) + // if server just returns array + if (Array.isArray(data)) { + data = { records: data } + } + // needed for backward compatibility + if (data.records == null && data.items != null) { + data.records = data.items + delete data.items + } + // handles Golang marshal of empty arrays to null + if (data.status == 'success' && data.records == null) { + data.records = [] + } + if (!Array.isArray(data.records)) { + console.error('ERROR: server did not return proper data structure', '\n', + ' - it should return', { status: 'success', records: [{ id: 1, text: 'item' }] }, '\n', + ' - or just an array ', [{ id: 1, text: 'item' }], '\n', + ' - actual response', typeof data === 'object' ? data : xhr.responseText) + return + } + // remove all extra items if more then needed for cache + if (data.records.length > options.cacheMax) data.records.splice(options.cacheMax, 100000) + // map id and text + if (options.recId == null && options.recid != null) options.recId = options.recid // since lower-case recid is used in grid + if (options.recId || options.recText) { + data.records.forEach((item) => { + if (typeof options.recId === 'string') item.id = item[options.recId] + if (typeof options.recId === 'function') item.id = options.recId(item) + if (typeof options.recText === 'string') item.text = item[options.recText] + if (typeof options.recText === 'function') item.text = options.recText(item) + }) + } + // remember stats + obj.tmp.xhr_loading = false + obj.tmp.xhr_search = search + obj.tmp.xhr_total = data.records.length + obj.tmp.lastError = '' + options.items = w2utils.normMenu(data.records) + if (search === '' && data.records.length === 0) obj.tmp.emptySet = true; else obj.tmp.emptySet = false + // preset item + let find_selected = $(obj.el).data('find_selected') + if (find_selected) { + let sel + if (Array.isArray(find_selected)) { + sel = [] + find_selected.forEach((find) => { + let isFound = false + options.items.forEach((item) => { + if (item.id == find || (find && find.id == item.id)) { + sel.push($.extend(true, {}, item)) + isFound = true + } + }) + if (!isFound) sel.push(find) + }) + } else { + sel = find_selected + options.items.forEach((item) => { + if (item.id == find_selected || (find_selected && find_selected.id == item.id)) { + sel = item + } + }) + } + $(obj.el).data('selected', sel).removeData('find_selected').trigger('input').trigger('change') + } + obj.search() + // event after + obj.trigger($.extend(edata2, { phase: 'after' })) + }) + .fail((xhr, status, error) => { + // trigger event + let errorObj = { status: status, error: error, rawResponseText: xhr.responseText } + let edata2 = obj.trigger({ phase: 'before', type: 'error', target: obj.el, search: search, error: errorObj, xhr: xhr }) + if (edata2.isCancelled === true) return + // default behavior + if (status !== 'abort') { + let data + try { data = JSON.parse(xhr.responseText) } catch (e) {} + console.error('ERROR: server did not return proper data structure', '\n', + ' - it should return', { status: 'success', records: [{ id: 1, text: 'item' }] }, '\n', + ' - or just an array ', [{ id: 1, text: 'item' }], '\n', + ' - actual response', typeof data === 'object' ? data : xhr.responseText) + } + // reset stats + obj.tmp.xhr_loading = false + obj.tmp.xhr_search = search + obj.tmp.xhr_total = 0 + obj.tmp.emptySet = true + obj.tmp.lastError = (edata2.error || 'Server communication failed') + options.items = [] + obj.clearCache() + obj.search() + obj.updateOverlay(false) + // event after + obj.trigger($.extend(edata2, { phase: 'after' })) + }) + // event after + obj.trigger($.extend(edata, { phase: 'after' })) + }, interval) + } +} +search() { + let obj = this + let options = this.options + let search = $(obj.el).val() + let target = obj.el + let ids = [] + let selected = $(obj.el).data('selected') + if (obj.type === 'enum') { + target = $(obj.helpers.multi).find('input') + search = target.val() + for (let s in selected) { if (selected[s]) ids.push(selected[s].id) } + } + else if (obj.type === 'list') { + target = $(obj.helpers.focus).find('input') + search = target.val() + for (let s in selected) { if (selected[s]) ids.push(selected[s].id) } + } + let items = options.items + if (obj.tmp.xhr_loading !== true) { + let shown = 0 + for (let i = 0; i < items.length; i++) { + let item = items[i] + if (options.compare != null) { + if (typeof options.compare === 'function') { + item.hidden = (options.compare.call(this, item, search) === false ? true : false) + } + } else { + let prefix = '' + let suffix = '' + if (['is', 'begins'].indexOf(options.match) !== -1) prefix = '^' + if (['is', 'ends'].indexOf(options.match) !== -1) suffix = '$' + try { + let re = new RegExp(prefix + search + suffix, 'i') + if (re.test(item.text) || item.text === '...') item.hidden = false; else item.hidden = true + } catch (e) {} + } + if (options.filter === false) item.hidden = false + // do not show selected items + if (obj.type === 'enum' && $.inArray(item.id, ids) !== -1) item.hidden = true + if (item.hidden !== true) { shown++; delete item.hidden } + } + // preselect first item + options.index = [] + options.spinner = false + setTimeout(() => { + if (options.markSearch && $('#w2ui-overlay .no-matches').length == 0) { // do not highlight when no items + $('#w2ui-overlay').w2marker(search) + } + }, 1) + } else { + items.splice(0, options.cacheMax) + options.spinner = true + } + // only update overlay when it is displayed already + if ($('#w2ui-overlay').length > 0) { + obj.updateOverlay() + } +} +*/ +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2tooltip, w2color, w2menu + * + * == TODO == + * - tab navigation (index state) + * - vertical toolbar + * - w2menu on second click of tb button should hide + * - button display groups for each show/hide, possibly add state: { single: t/f, multiple: t/f, type: 'font' } + * - item.count - should just support html, so a custom block can be created, such as a colored line + * + * == 2.0 changes + * - CSP - fixed inline events + * - removed jQuery dependency + * - item.icon - can be class or or + * - new w2tooltips and w2menu + * - scroll returns promise + * - added onMouseEntter, onMouseLeave, onMouseDown, onMouseUp events + * - add(..., skipRefresh), insert(..., skipRefresh) + */ + +class w2toolbar extends w2base { + constructor(options) { + super(options.name) + this.box = null // DOM Element that holds the element + this.name = null // unique name for w2ui + this.routeData = {} // data for dynamic routes + this.items = [] + this.right = '' // HTML text on the right of toolbar + this.tooltip = 'top|left'// can be top, bottom, left, right + this.onClick = null + this.onMouseDown = null + this.onMouseUp = null + this.onMouseEnter = null // mouse enter the button event + this.onMouseLeave = null + this.onRender = null + this.onRefresh = null + this.onResize = null + this.onDestroy = null + this.item_template = { + id: null, // command to be sent to all event handlers + type: 'button', // button, check, radio, drop, menu, menu-radio, menu-check, break, html, spacer + text: null, + html: '', + tooltip: null, // w2toolbar.tooltip should be + count: null, + hidden: false, + disabled: false, + checked: false, // used for radio buttons + icon: null, + route: null, // if not null, it is route to go + arrow: null, // arrow down for drop/menu types + style: null, // extra css style for caption + group: null, // used for radio buttons + items: null, // for type menu* it is an array of items in the menu + selected: null, // used for menu-check, menu-radio + color: null, // color value - used in color pickers + overlay: { // additional options for overlay + anchorClass: '' + }, + onClick: null, + onRefresh: null + } + this.last = { + badge: {} + } + // mix in options, w/o items + let items = options.items + delete options.items + Object.assign(this, options) + // add item via method to makes sure item_template is applied + if (Array.isArray(items)) this.add(items, true) + // need to reassign back to keep it in config + options.items = items + // render if box specified + if (typeof this.box == 'string') this.box = query(this.box).get(0) + if (this.box) this.render(this.box) + } + add(items, skipRefresh) { + this.insert(null, items, skipRefresh) + } + insert(id, items, skipRefresh) { + if (!Array.isArray(items)) items = [items] + items.forEach((item, idx, arr) => { + if (typeof item === 'string') { + item = arr[idx] = { id: item, text: item } + } + // checks + let valid = ['button', 'check', 'radio', 'drop', 'menu', 'menu-radio', 'menu-check', 'color', 'text-color', 'html', + 'break', 'spacer', 'new-line'] + if (!valid.includes(String(item.type))) { + console.log('ERROR: The parameter "type" should be one of the following:', valid, `, but ${item.type} is supplied.`, item) + return + } + if (item.id == null && !['break', 'spacer', 'new-line'].includes(item.type)) { + console.log('ERROR: The parameter "id" is required but not supplied.', item) + return + } + if (item.type == null) { + console.log('ERROR: The parameter "type" is required but not supplied.', item) + return + } + if (!w2utils.checkUniqueId(item.id, this.items, 'toolbar', this.name)) return + // add item + let newItem = w2utils.extend({}, this.item_template, item) + if (newItem.type == 'menu-check') { + if (!Array.isArray(newItem.selected)) newItem.selected = [] + if (Array.isArray(newItem.items)) { + newItem.items.forEach(it => { + if (typeof it === 'string') { + it = arr[idx] = { id: it, text: it } + } + if (it.checked && !newItem.selected.includes(it.id)) newItem.selected.push(it.id) + if (!it.checked && newItem.selected.includes(it.id)) it.checked = true + if (it.checked == null) it.checked = false + }) + } + } else if (newItem.type == 'menu-radio') { + if (Array.isArray(newItem.items)) { + newItem.items.forEach((it, idx, arr) => { + if (typeof it === 'string') { + it = arr[idx] = { id: it, text: it } + } + if (it.checked && newItem.selected == null) newItem.selected = it.id; else it.checked = false + if (!it.checked && newItem.selected == it.id) it.checked = true + if (it.checked == null) it.checked = false + }) + } + } + if (id == null) { + this.items.push(newItem) + } else { + let middle = this.get(id, true) + this.items = this.items.slice(0, middle).concat([newItem], this.items.slice(middle)) + } + newItem.line = newItem.line ?? 1 + if (skipRefresh !== true) this.refresh(newItem.id) + }) + if (skipRefresh !== true) this.resize() + } + remove() { + let effected = 0 + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it || String(item).indexOf(':') != -1) return + effected++ + // remove from screen + query(this.box).find('#tb_'+ this.name +'_item_'+ w2utils.escapeId(it.id)).remove() + // remove from array + let ind = this.get(it.id, true) + if (ind != null) this.items.splice(ind, 1) + }) + this.resize() + return effected + } + set(id, newOptions) { + let item = this.get(id) + if (item == null) return false + Object.assign(item, newOptions) + this.refresh(String(id).split(':')[0]) + return true + } + get(id, returnIndex) { + if (arguments.length === 0) { + let all = [] + for (let i1 = 0; i1 < this.items.length; i1++) if (this.items[i1].id != null) all.push(this.items[i1].id) + return all + } + let tmp = String(id).split(':') + for (let i2 = 0; i2 < this.items.length; i2++) { + let it = this.items[i2] + // find a menu item + if (['menu', 'menu-radio', 'menu-check'].includes(it.type) && tmp.length == 2 && it.id == tmp[0]) { + let subItems = it.items + if (typeof subItems == 'function') subItems = subItems(this) + for (let i = 0; i < subItems.length; i++) { + let item = subItems[i] + if (item.id == tmp[1] || (item.id == null && item.text == tmp[1])) { + if (returnIndex == true) return i; else return item + } + if (Array.isArray(item.items)) { + for (let j = 0; j < item.items.length; j++) { + if (item.items[j].id == tmp[1] || (item.items[j].id == null && item.items[j].text == tmp[1])) { + if (returnIndex == true) return i; else return item.items[j] + } + } + } + } + } else if (it.id == tmp[0]) { + if (returnIndex == true) return i2; else return it + } + } + return null + } + setCount(id, count, className, style) { + let btn = query(this.box).find(`#tb_${this.name}_item_${w2utils.escapeId(id)} .w2ui-tb-count > span`) + if (btn.length > 0) { + btn.removeClass() + .addClass(className ?? '') + .text(count) + .get(0).style.cssText = style ?? '' + this.last.badge[id] = { + className: className ?? '', + style: style ?? '' + } + let item = this.get(id) + item.count = count + } else { + this.set(id, { count: count }) + this.setCount(...arguments) // to update styles + } + } + show() { + let effected = [] + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it) return + it.hidden = false + effected.push(String(item).split(':')[0]) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it); this.resize() }) }, 15) // needs timeout + return effected + } + hide() { + let effected = [] + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it) return + it.hidden = true + effected.push(String(item).split(':')[0]) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it); this.tooltipHide(it); this.resize() }) }, 15) // needs timeout + return effected + } + enable() { + let effected = [] + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it) return + it.disabled = false + effected.push(String(item).split(':')[0]) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it) }) }, 15) // needs timeout + return effected + } + disable() { + let effected = [] + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it) return + it.disabled = true + effected.push(String(item).split(':')[0]) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it); this.tooltipHide(it) }) }, 15) // needs timeout + return effected + } + check() { + let effected = [] + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it || String(item).indexOf(':') != -1) return + it.checked = true + effected.push(String(item).split(':')[0]) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it) }) }, 15) // needs timeout + return effected + } + uncheck() { + let effected = [] + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it || String(item).indexOf(':') != -1) return + // remove overlay + if (['menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(it.type) && it.checked) { + w2tooltip.hide(this.name + '-drop') + } + it.checked = false + effected.push(String(item).split(':')[0]) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it) }) }, 15) // needs timeout + return effected + } + click(id, event) { + // click on menu items + let tmp = String(id).split(':') + let it = this.get(tmp[0]) + let items = (it && it.items ? w2utils.normMenu.call(this, it.items, it) : []) + if (tmp.length > 1) { + let subItem = this.get(id) + if (subItem && !subItem.disabled) { + this.menuClick({ name: this.name, item: it, subItem: subItem, originalEvent: event }) + } + return + } + if (it && !it.disabled) { + // event before + let edata = this.trigger('click', { + target: (id != null ? id : this.name), + item: it, object: it, originalEvent: event + }) + if (edata.isCancelled === true) return + // read items again, they might have been changed in the click event handler + items = (it && it.items ? w2utils.normMenu.call(this, it.items, it) : []) + let btn = '#tb_'+ this.name +'_item_'+ w2utils.escapeId(it.id) + query(this.box).find(btn).removeClass('down') // need to re-query at the moment -- as well as elsewhere in this function + if (it.type == 'radio') { + for (let i = 0; i < this.items.length; i++) { + let itt = this.items[i] + if (itt == null || itt.id == it.id || itt.type !== 'radio') continue + if (itt.group == it.group && itt.checked) { + itt.checked = false + this.refresh(itt.id) + } + } + it.checked = true + query(this.box).find(btn).addClass('checked') + } + if (['menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(it.type)) { + this.tooltipHide(id) + if (it.checked) { + w2tooltip.hide(this.name + '-drop') + return + } else { + // timeout is needed to make sure previous overlay hides + setTimeout(() => { + let hideDrop = (id, btn) => { + // need a closure to capture id variable + let self = this + return function () { + self.set(id, { checked: false }) + } + } + let el = query(this.box).find('#tb_'+ this.name +'_item_'+ w2utils.escapeId(it.id)) + if (!w2utils.isPlainObject(it.overlay)) it.overlay = {} + if (it.type == 'drop') { + w2tooltip.show(w2utils.extend({ + html: it.html, + class: 'w2ui-white', + hideOn: ['doc-click'] + }, it.overlay, { + anchor: el[0], + name: this.name + '-drop', + data: { item: it, btn } + })) + .hide(hideDrop(it.id, btn)) + } + if (['menu', 'menu-radio', 'menu-check'].includes(it.type)) { + let menuType = 'normal' + if (it.type == 'menu-radio') { + menuType = 'radio' + items.forEach((item) => { + if (it.selected == item.id) item.checked = true; else item.checked = false + }) + } + if (it.type == 'menu-check') { + menuType = 'check' + items.forEach((item) => { + if (Array.isArray(it.selected) && it.selected.includes(item.id)) item.checked = true; else item.checked = false + }) + } + w2menu.show(w2utils.extend({ + items, + }, it.overlay, { + type: menuType, + name : this.name + '-drop', + anchor: el[0], + data: { item: it, btn } + })) + .hide(hideDrop(it.id, btn)) + .remove(event => { + this.menuClick({ name: this.name, remove: true, item: it, subItem: event.detail.item, + originalEvent: event }) + }) + .select(event => { + this.menuClick({ name: this.name, item: it, subItem: event.detail.item, + originalEvent: event }) + }) + } + if (['color', 'text-color'].includes(it.type)) { + w2color.show(w2utils.extend({ + color: it.color + }, it.overlay, { + anchor: el[0], + name: this.name + '-drop', + data: { item: it, btn } + })) + .hide(hideDrop(it.id, btn)) + .select(event => { + if (event.detail.color != null) { + this.colorClick({ name: this.name, item: it, color: event.detail.color }) + } + }) + } + }, 0) + } + } + if (['check', 'menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(it.type)) { + it.checked = !it.checked + if (it.checked) { + query(this.box).find(btn).addClass('checked') + } else { + query(this.box).find(btn).removeClass('checked') + } + } + // route processing + if (it.route) { + let route = String('/'+ it.route).replace(/\/{2,}/g, '/') + let info = w2utils.parseRoute(route) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]) + } + } + setTimeout(() => { window.location.hash = route }, 1) + } + // need to refresh toolbar as it might be dynamic + this.tooltipShow(id) + // event after + edata.finish() + } + } + scroll(direction, line, instant) { + return new Promise((resolve, reject) => { + let scrollBox = query(this.box).find(`.w2ui-tb-line:nth-child(${line}) .w2ui-scroll-wrapper`) + let scrollLeft = scrollBox.get(0).scrollLeft + let right = scrollBox.find('.w2ui-tb-right').get(0) + let width1 = scrollBox.parent().get(0).getBoundingClientRect().width + let width2 = scrollLeft + parseInt(right.offsetLeft) + parseInt(right.clientWidth ) + switch (direction) { + case 'left': { + scroll = scrollLeft - width1 + 50 // 35 is width of both button + if (scroll <= 0) scroll = 0 + scrollBox.get(0).scrollTo({ top: 0, left: scroll, behavior: instant ? 'atuo' : 'smooth' }) + break + } + case 'right': { + scroll = scrollLeft + width1 - 50 // 35 is width of both button + if (scroll >= width2 - width1) scroll = width2 - width1 + scrollBox.get(0).scrollTo({ top: 0, left: scroll, behavior: instant ? 'atuo' : 'smooth' }) + break + } + } + setTimeout(() => { this.resize(); resolve() }, instant ? 0 : 500) + }) + } + render(box) { + let time = Date.now() + if (typeof box == 'string') box = query(box).get(0) + // event before + let edata = this.trigger('render', { target: this.name, box: box ?? this.box }) + if (edata.isCancelled === true) return + // defaul action + if (box != null) { + // clean previous box + if (query(this.box).find('.w2ui-scroll-wrapper .w2ui-tb-right').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-toolbar') + .html('') + } + this.box = box + } + if (!this.box) return + if (!Array.isArray(this.right)) { + this.right = [this.right] + } + // render all buttons + let html = '' + let line = 0 + for (let i = 0; i < this.items.length; i++) { + let it = this.items[i] + if (it == null) continue + if (it.id == null) it.id = 'item_' + i + if (it.caption != null) { + console.log('NOTICE: toolbar item.caption property is deprecated, please use item.text. Item -> ', it) + } + if (it.hint != null) { + console.log('NOTICE: toolbar item.hint property is deprecated, please use item.tooltip. Item -> ', it) + } + if (i === 0 || it.type == 'new-line') { + line++ + html += ` +
    +
    +
    ${this.right[line-1] ?? ''}
    +
    +
    +
    +
    + ` + } + it.line = line + } + query(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-toolbar') + .html(html) + if (query(this.box).length > 0) { + query(this.box)[0].style.cssText += this.style + } + w2utils.bindEvents(query(this.box).find('.w2ui-tb-line .w2ui-eaction'), this) + // observe div resize + this.last.observeResize = new ResizeObserver(() => { this.resize() }) + this.last.observeResize.observe(this.box) + // refresh all + this.refresh() + this.resize() + // event after + edata.finish() + return Date.now() - time + } + refresh(id) { + let time = Date.now() + // event before + let edata = this.trigger('refresh', { target: (id != null ? id : this.name), item: this.get(id) }) + if (edata.isCancelled === true) return + let edata2 + // refresh all + if (id == null) { + for (let i = 0; i < this.items.length; i++) { + let it1 = this.items[i] + if (it1.id == null) it1.id = 'item_' + i + this.refresh(it1.id) + } + return + } + // create or refresh only one item + let it = this.get(id) + if (it == null) return false + if (typeof it.onRefresh == 'function') { + edata2 = this.trigger('refresh', { target: id, item: it, object: it }) + if (edata2.isCancelled === true) return + } + let selector = `#tb_${this.name}_item_${w2utils.escapeId(it.id)}` + let btn = query(this.box).find(selector) + let html = this.getItemHTML(it) + // hide tooltip + this.tooltipHide(id) + // if there is a spacer, then right HTML is not 100% + if (it.type == 'spacer') { + query(this.box).find(`.w2ui-tb-line:nth-child(${it.line}`).find('.w2ui-tb-right').css('width', 'auto') + } + if (btn.length === 0) { + let next = parseInt(this.get(id, true)) + 1 + let $next = query(this.box).find(`#tb_${this.name}_item_${w2utils.escapeId(this.items[next] ? this.items[next].id : '')}`) + if ($next.length == 0) { + $next = query(this.box).find(`.w2ui-tb-line:nth-child(${it.line}`).find('.w2ui-tb-right').before(html) + } else { + $next.after(html) + } + w2utils.bindEvents(query(this.box).find(selector), this) + } else { + // refresh + query(this.box).find(selector).replace(query.html(html)) + let newBtn = query(this.box).find(selector).get(0) + w2utils.bindEvents(newBtn, this) + // update overlay's anchor if changed + let overlays = w2tooltip.get(true) + Object.keys(overlays).forEach(key => { + if (overlays[key].anchor == btn.get(0)) { + overlays[key].anchor = newBtn + } + }) + } + if (['menu', 'menu-radio', 'menu-check'].includes(it.type) && it.checked) { + // check selected items + let selected = Array.isArray(it.selected) ? it.selected : [it.selected] + it.items.forEach((item) => { + if (selected.includes(item.id)) item.checked = true; else item.checked = false + }) + w2menu.update(this.name + '-drop', it.items) + } + // event after + if (typeof it.onRefresh == 'function') { + edata2.finish() + } + edata.finish() + return Date.now() - time + } + resize() { + let time = Date.now() + // event before + let edata = this.trigger('resize', { target: this.name }) + if (edata.isCancelled === true) return + query(this.box).find('.w2ui-tb-line').each(el => { + // show hide overflow buttons + let box = query(el) + box.find('.w2ui-scroll-left, .w2ui-scroll-right').hide() + let scrollBox = box.find('.w2ui-scroll-wrapper').get(0) + let $right = box.find('.w2ui-tb-right') + let boxWidth = box.get(0).getBoundingClientRect().width + let itemsWidth = ($right.length > 0 ? $right[0].offsetLeft + $right[0].clientWidth : 0) + if (boxWidth < itemsWidth) { + // we have overflown content + if (scrollBox.scrollLeft > 0) { + box.find('.w2ui-scroll-left').show() + } + if (boxWidth < itemsWidth - scrollBox.scrollLeft) { + box.find('.w2ui-scroll-right').show() + } + } + }) + // event after + edata.finish() + return Date.now() - time + } + destroy() { + // event before + let edata = this.trigger('destroy', { target: this.name }) + if (edata.isCancelled === true) return + // clean up + if (query(this.box).find('.w2ui-scroll-wrapper .w2ui-tb-right').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-toolbar') + .html('') + } + query(this.box).html('') + this.last.observeResize?.disconnect() + delete w2ui[this.name] + // event after + edata.finish() + } + // ======================================== + // --- Internal Functions + getItemHTML(item) { + let html = '' + if (item.caption != null && item.text == null) item.text = item.caption // for backward compatibility + if (item.text == null) item.text = '' + if (item.tooltip == null && item.hint != null) item.tooltip = item.hint // for backward compatibility + if (item.tooltip == null) item.tooltip = '' + if (typeof item.get !== 'function' && (Array.isArray(item.items) || typeof item.items == 'function')) { + item.get = function get(id) { // need scope, cannot be arrow func + let tmp = item.items + if (typeof tmp == 'function') tmp = item.items(item) + return tmp.find(it => it.id == id ? true : false) + } + } + let icon = '' + let text = (typeof item.text == 'function' ? item.text.call(this, item) : item.text) + if (item.icon) { + icon = item.icon + if (typeof item.icon == 'function') { + icon = item.icon.call(this, item) + } + if (String(icon).slice(0, 1) !== '<') { + icon = `` + } + icon = `
    ${icon}
    ` + } + let classes = ['w2ui-tb-button'] + if (item.checked) classes.push('checked') + if (item.disabled) classes.push('disabled') + if (item.hidden) classes.push('hidden') + if (!icon) classes.push('no-icon') + switch (item.type) { + case 'color': + case 'text-color': + if (typeof item.color == 'string') { + if (item.color.slice(0, 1) == '#') item.color = item.color.slice(1) + if ([3, 6, 8].includes(item.color.length)) item.color = '#' + item.color + } + if (item.type == 'color') { + text = ` + ${(item.text ? `
    ${w2utils.lang(item.text)}
    ` : '')}` + } + if (item.type == 'text-color') { + text = ''+ + (item.text ? w2utils.lang(item.text) : 'Aa') + + '' + } + case 'menu': + case 'menu-check': + case 'menu-radio': + case 'button': + case 'check': + case 'radio': + case 'drop': { + let arrow = (item.arrow === true + || (item.arrow !== false && ['menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(item.type))) + html = ` +
    + ${ icon } + ${ text != '' + ? `
    + ${ w2utils.lang(text) } + ${ item.count != null + ? w2utils.stripSpaces(` + ${item.count} + `) + : '' + } + ${ arrow + ? '' + : '' + } +
    ` + : ''} +
    + ` + break + } + case 'break': + html = `
    +   +
    ` + break + case 'spacer': + html = `
    +
    ` + break + case 'html': + html = `
    + ${(typeof item.html == 'function' ? item.html.call(this, item) : item.html)} +
    ` + break + } + return html + } + tooltipShow(id) { + if (this.tooltip == null) return + let el = query(this.box).find('#tb_'+ this.name + '_item_'+ w2utils.escapeId(id)).get(0) + let item = this.get(id) + let pos = this.tooltip + let txt = item.tooltip + if (typeof txt == 'function') txt = txt.call(this, item) + // not for opened drop downs + if (['menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(item.type) + && item.checked == true) { + return + } + w2tooltip.show({ + anchor: el, + name: this.name + '-tooltip', + html: txt, + position: pos + }) + return + } + tooltipHide(id) { + if (this.tooltip == null) return + w2tooltip.hide(this.name + '-tooltip') + } + menuClick(event) { + if (event.item && !event.item.disabled) { + // event before + let edata = this.trigger((event.remove !== true ? 'click' : 'remove'), { + target: event.item.id + ':' + event.subItem.id, item: event.item, + subItem: event.subItem, originalEvent: event.originalEvent + }) + if (edata.isCancelled === true) return + // route processing + let it = event.subItem + let item = this.get(event.item.id) + let items = item.items + if (typeof items == 'function') items = item.items() + if (item.type == 'menu') { + item.selected = it.id + } + if (item.type == 'menu-radio') { + item.selected = it.id + if (Array.isArray(items)) { + items.forEach((item) => { + if (item.checked === true) delete item.checked + if (Array.isArray(item.items)) { + item.items.forEach((item) => { + if (item.checked === true) delete item.checked + }) + } + }) + } + it.checked = true + } + if (item.type == 'menu-check') { + if (!Array.isArray(item.selected)) item.selected = [] + if (it.group == null) { + let ind = item.selected.indexOf(it.id) + if (ind == -1) { + item.selected.push(it.id) + it.checked = true + } else { + item.selected.splice(ind, 1) + it.checked = false + } + } else if (it.group === false) { + // if group is false, then it is not part of checkboxes + } else { + let unchecked = [] + let ind = item.selected.indexOf(it.id) + let checkNested = (items) => { + items.forEach((sub) => { + if (sub.group === it.group) { + let ind = item.selected.indexOf(sub.id) + if (ind != -1) { + if (sub.id != it.id) unchecked.push(sub.id) + item.selected.splice(ind, 1) + } + } + if (Array.isArray(sub.items)) checkNested(sub.items) + }) + } + checkNested(items) + if (ind == -1) { + item.selected.push(it.id) + it.checked = true + } + } + } + if (typeof it.route == 'string') { + let route = it.route !== '' ? String('/'+ it.route).replace(/\/{2,}/g, '/') : '' + let info = w2utils.parseRoute(route) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + if (this.routeData[info.keys[k].name] == null) continue + route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]) + } + } + setTimeout(() => { window.location.hash = route }, 1) + } + this.refresh(event.item.id) + // event after + edata.finish() + } + } + colorClick(event) { + let obj = this + if (event.item && !event.item.disabled) { + // event before + let edata = this.trigger('click', { + target: event.item.id, item: event.item, + color: event.color, final: event.final, originalEvent: event.originalEvent + }) + if (edata.isCancelled === true) return + // default behavior + event.item.color = event.color + obj.refresh(event.item.id) + // event after + edata.finish() + } + } + mouseAction(event, target, action, id) { + let btn = this.get(id) + let edata = this.trigger('mouse' + action, { target: id, item: btn, object: btn, originalEvent: event }) + if (edata.isCancelled === true || btn.disabled || btn.hidden) return + switch (action) { + case 'Enter': + query(target).addClass('over') + this.tooltipShow(id) + break + case 'Leave': + query(target).removeClass('over down') + this.tooltipHide(id) + break + case 'Down': + query(target).addClass('down') + break + case 'Up': + query(target).removeClass('down') + break + } + edata.finish() + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2tooltip, w2menu + * + * == TODO == + * - dbl click should be like it is in grid (with timer not HTML dbl click event) + * - node.style is misleading - should be there to apply color for example + * - node.plus - is not working + * + * == 2.0 changes + * - remove jQuery dependency + * - deprecarted obj.img, node.img + * - CSP - fixed inline events + * - observeResize for the box + * - handleTooltip and handle.tooltip - text/function + * - added onMouseEntter, onMouseLeave events + */ + +class w2sidebar extends w2base { + constructor(options) { + super(options.name) + this.name = null + this.box = null + this.sidebar = null + this.parent = null + this.nodes = [] // Sidebar child nodes + this.menu = [] + this.routeData = {} // data for dynamic routes + this.selected = null // current selected node (readonly) + this.icon = null + this.style = '' + this.topHTML = '' + this.bottomHTML = '' + this.flatButton = false + this.keyboard = true + this.flat = false + this.hasFocus = false + this.levelPadding = 12 + this.skipRefresh = false + this.tabIndex = null // will only be set if > 0 and not null + this.handle = { size: 0, style: '', html: '', tooltip: '' }, + this.onClick = null // Fire when user click on Node Text + this.onDblClick = null // Fire when user dbl clicks + this.onMouseEnter = null // mouse enter/leave over an item + this.onMouseLeave = null + this.onContextMenu = null + this.onMenuClick = null // when context menu item selected + this.onExpand = null // Fire when node expands + this.onCollapse = null // Fire when node collapses + this.onKeydown = null + this.onRender = null + this.onRefresh = null + this.onResize = null + this.onDestroy = null + this.onFocus = null + this.onBlur = null + this.onFlat = null + this.node_template = { + id: null, + text: '', + order: null, + count: null, + icon: null, + nodes: [], + style: '', // additional style for subitems + route: null, + selected: false, + expanded: false, + hidden: false, + disabled: false, + group: false, // if true, it will build as a group + groupShowHide: true, + collapsible: false, + plus: false, // if true, plus will be shown even if there is no sub nodes + // events + onClick: null, + onDblClick: null, + onContextMenu: null, + onExpand: null, + onCollapse: null, + // internal + parent: null, // node object + sidebar: null + } + this.last = { + badge: {} + } + let nodes = options.nodes + delete options.nodes + // mix in options + Object.assign(this, options) + // add item via method to makes sure item_template is applied + if (Array.isArray(nodes)) this.add(nodes) + // need to reassign back to keep it in config + options.nodes = nodes + // render if box specified + if (typeof this.box == 'string') this.box = query(this.box).get(0) + if (this.box) this.render(this.box) + } + add(parent, nodes) { + if (arguments.length == 1) { + // need to be in reverse order + nodes = arguments[0] + parent = this + } + if (typeof parent == 'string') parent = this.get(parent) + if (parent == null || parent == '') parent = this + return this.insert(parent, null, nodes) + } + insert(parent, before, nodes) { + let txt, ind, tmp, node, nd + if (arguments.length == 2 && typeof parent == 'string') { + // need to be in reverse order + nodes = arguments[1] + before = arguments[0] + if (before != null) { + ind = this.get(before) + if (ind == null) { + if (!Array.isArray(nodes)) nodes = [nodes] + if (nodes[0].caption != null && nodes[0].text == null) { + console.log('NOTICE: sidebar node.caption property is deprecated, please use node.text. Node -> ', nodes[0]) + nodes[0].text = nodes[0].caption + } + txt = nodes[0].text + console.log('ERROR: Cannot insert node "'+ txt +'" because cannot find node "'+ before +'" to insert before.') + return null + } + parent = this.get(before).parent + } else { + parent = this + } + } + if (typeof parent == 'string') parent = this.get(parent) + if (parent == null || parent == '') parent = this + if (!Array.isArray(nodes)) nodes = [nodes] + for (let o = 0; o < nodes.length; o++) { + node = nodes[o] + if (node.caption != null && node.text == null) { + console.log('NOTICE: sidebar node.caption property is deprecated, please use node.text') + node.text = node.caption + } + if (typeof node.id == null) { + txt = node.text + console.log('ERROR: Cannot insert node "'+ txt +'" because it has no id.') + continue + } + if (this.get(this, node.id) != null) { + console.log('ERROR: Cannot insert node with id='+ node.id +' (text: '+ node.text + ') because another node with the same id already exists.') + continue + } + tmp = Object.assign({}, this.node_template, node) + tmp.sidebar = this + tmp.parent = parent + nd = tmp.nodes || [] + tmp.nodes = [] // very important to re-init empty nodes array + if (before == null) { // append to the end + parent.nodes.push(tmp) + } else { + ind = this.get(parent, before, true) + if (ind == null) { + console.log('ERROR: Cannot insert node "'+ node.text +'" because cannot find node "'+ before +'" to insert before.') + return null + } + parent.nodes.splice(ind, 0, tmp) + } + if (nd.length > 0) { + this.insert(tmp, null, nd) + } + } + if (!this.skipRefresh) this.refresh(parent.id) + return tmp + } + remove() { // multiple arguments + let effected = 0 + let node + Array.from(arguments).forEach(arg => { + node = this.get(arg) + if (node == null) return + if (this.selected != null && this.selected === node.id) { + this.selected = null + } + let ind = this.get(node.parent, arg, true) + if (ind == null) return + if (node.parent.nodes[ind].selected) node.sidebar.unselect(node.id) + node.parent.nodes.splice(ind, 1) + node.parent.collapsible = node.parent.nodes.length > 0 + effected++ + }) + if (!this.skipRefresh) { + if (effected > 0 && arguments.length == 1) this.refresh(node.parent.id); else this.refresh() + } + return effected + } + set(parent, id, node) { + if (arguments.length == 2) { + // need to be in reverse order + node = id + id = parent + parent = this + } + // searches all nested nodes + if (typeof parent == 'string') parent = this.get(parent) + if (parent.nodes == null) return null + for (let i = 0; i < parent.nodes.length; i++) { + if (parent.nodes[i].id === id) { + // see if quick update is possible + let res = this.update(id, node) + if (Object.keys(res).length != 0) { + // make sure nodes inserted correctly + let nodes = node.nodes + w2utils.extend(parent.nodes[i], node, { nodes: [] }) + if (nodes != null) { + this.add(parent.nodes[i], nodes) + } + if (!this.skipRefresh) this.refresh(id) + } + return true + } else { + let rv = this.set(parent.nodes[i], id, node) + if (rv) return true + } + } + return false + } + get(parent, id, returnIndex) { // can be just called get(id) or get(id, true) + if (arguments.length === 0) { + let all = [] + let tmp = this.find({}) + for (let t = 0; t < tmp.length; t++) { + if (tmp[t].id != null) all.push(tmp[t].id) + } + return all + } else { + if (arguments.length == 1 || (arguments.length == 2 && id === true) ) { + // need to be in reverse order + returnIndex = id + id = parent + parent = this + } + // searches all nested nodes + if (typeof parent == 'string') parent = this.get(parent) + if (parent.nodes == null) return null + for (let i = 0; i < parent.nodes.length; i++) { + if (parent.nodes[i].id == id) { + if (returnIndex === true) return i; else return parent.nodes[i] + } else { + let rv = this.get(parent.nodes[i], id, returnIndex) + if (rv || rv === 0) return rv + } + } + return null + } + } + setCount(id, count, className, style) { + let btn = query(this.box).find(`#node_${w2utils.escapeId(id)} .w2ui-node-count`) + if (btn.length > 0) { + btn.removeClass() + .addClass(`w2ui-node-count ${className || ''}`) + .text(count) + .get(0).style.cssText = style || '' + this.last.badge[id] = { + className: className || '', + style: style || '' + } + let item = this.get(id) + item.count = count + } else { + this.set(id, { count: count }) + this.setCount(...arguments) // to update styles + } + } + find(parent, params, results) { // can be just called find({ selected: true }) + // TODO: rewrite with this.each() + if (arguments.length == 1) { + // need to be in reverse order + params = parent + parent = this + } + if (!results) results = [] + // searches all nested nodes + if (typeof parent == 'string') parent = this.get(parent) + if (parent.nodes == null) return results + for (let i = 0; i < parent.nodes.length; i++) { + let match = true + for (let prop in params) { // params is an object + if (parent.nodes[i][prop] != params[prop]) match = false + } + if (match) results.push(parent.nodes[i]) + if (parent.nodes[i].nodes.length > 0) results = this.find(parent.nodes[i], params, results) + } + return results + } + sort(options, nodes) { + // default options + if (!options || typeof options != 'object') options = {} + if (options.foldersFirst == null) options.foldersFirst = true + if (options.caseSensitive == null) options.caseSensitive = false + if (options.reverse == null) options.reverse = false + if (nodes == null) { + nodes = this.nodes + } + nodes.sort((a, b) => { + // folders first + let isAfolder = (a.nodes && a.nodes.length > 0) + let isBfolder = (b.nodes && b.nodes.length > 0) + // both folder or both not folders + if (options.foldersFirst === false || (!isAfolder && !isBfolder) || (isAfolder && isBfolder)) { + let aText = a.text + let bText = b.text + if (!options.caseSensitive) { + aText = aText.toLowerCase() + bText = bText.toLowerCase() + } + if (a.order != null) aText = a.order + if (b.order != null) bText = b.order + let cmp = w2utils.naturalCompare(aText, bText) + return (cmp === 1 || cmp === -1) & options.reverse ? -cmp : cmp + } + if (isAfolder && !isBfolder) { + return !options.reverse ? -1 : 1 + } + if (!isAfolder && isBfolder) { + return !options.reverse ? 1 : -1 + } + }) + nodes.forEach(node => { + if (node.nodes && node.nodes.length > 0) { + this.sort(options, node.nodes) + } + }) + } + each(fn, nodes) { + if (nodes == null) nodes = this.nodes + nodes.forEach((node) => { + fn.call(this, node) + if (node.nodes && node.nodes.length > 0) { + this.each(fn, node.nodes) + } + }) + } + search(str) { + let count = 0 + let str2 = str.toLowerCase() + this.each((node) => { + if (node.text.toLowerCase().indexOf(str2) === -1) { + node.hidden = true + } else { + count++ + showParents(node) + node.hidden = false + } + }) + this.refresh() + return count + function showParents(node) { + if (node.parent) { + node.parent.hidden = false + showParents(node.parent) + } + } + } + show() { // multiple arguments + let effected = [] + Array.from(arguments).forEach(it => { + let node = this.get(it) + if (node == null || node.hidden === false) return + node.hidden = false + effected.push(node.id) + }) + if (effected.length > 0) { + if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh() + } + return effected + } + hide() { // multiple arguments + let effected = [] + Array.from(arguments).forEach(it => { + let node = this.get(it) + if (node == null || node.hidden === true) return + node.hidden = true + effected.push(node.id) + }) + if (effected.length > 0) { + if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh() + } + return effected + } + enable() { // multiple arguments + let effected = [] + Array.from(arguments).forEach(it => { + let node = this.get(it) + if (node == null || node.disabled === false) return + node.disabled = false + effected.push(node.id) + }) + if (effected.length > 0) { + if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh() + } + return effected + } + disable() { // multiple arguments + let effected = [] + Array.from(arguments).forEach(it => { + let node = this.get(it) + if (node == null || node.disabled === true) return + node.disabled = true + if (node.selected) this.unselect(node.id) + effected.push(node.id) + }) + if (effected.length > 0) { + if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh() + } + return effected + } + select(id) { + let new_node = this.get(id) + if (!new_node) return false + if (this.selected == id && new_node.selected) return false + this.unselect(this.selected) + let $el = query(this.box).find('#node_'+ w2utils.escapeId(id)) + $el.addClass('w2ui-selected') + .find('.w2ui-icon') + .addClass('w2ui-icon-selected') + if ($el.length > 0) { + if (!this.inView(id)) this.scrollIntoView(id) + } + new_node.selected = true + this.selected = id + return true + } + unselect(id) { + // if no arguments provided, unselect selected node + if (arguments.length === 0) { + id = this.selected + } + let current = this.get(id) + if (!current) return false + current.selected = false + query(this.box).find('#node_'+ w2utils.escapeId(id)) + .removeClass('w2ui-selected') + .find('.w2ui-icon').removeClass('w2ui-icon-selected') + if (this.selected == id) this.selected = null + return true + } + toggle(id) { + let nd = this.get(id) + if (nd == null) return false + if (nd.plus) { + this.set(id, { plus: false }) + this.expand(id) + this.refresh(id) + return + } + if (nd.nodes.length === 0) return false + if (!nd.collapsible) return false + if (this.get(id).expanded) return this.collapse(id); else return this.expand(id) + } + collapse(id) { + let self = this + let nd = this.get(id) + if (nd == null) return false + // event before + let edata = this.trigger('collapse', { target: id, object: nd }) + if (edata.isCancelled === true) return + // default action + query(this.box).find('#node_'+ w2utils.escapeId(id) +'_sub').hide() + query(this.box).find('#node_'+ w2utils.escapeId(id) +' .w2ui-expanded') + .removeClass('w2ui-expanded') + .addClass('w2ui-collapsed') + nd.expanded = false + // event after + edata.finish() + setTimeout(() => { self.refresh(id) }, 0) + return true + } + expand(id) { + let self = this + let nd = this.get(id) + // event before + let edata = this.trigger('expand', { target: id, object: nd }) + if (edata.isCancelled === true) return + // default action + query(this.box).find('#node_'+ w2utils.escapeId(id) +'_sub') + .show() + query(this.box).find('#node_'+ w2utils.escapeId(id) +' .w2ui-collapsed') + .removeClass('w2ui-collapsed') + .addClass('w2ui-expanded') + nd.expanded = true + // event after + edata.finish() + self.refresh(id) + return true + } + collapseAll(parent) { + if (parent == null) parent = this + if (typeof parent == 'string') parent = this.get(parent) + if (parent.nodes == null) return false + for (let i = 0; i < parent.nodes.length; i++) { + if (parent.nodes[i].expanded === true) parent.nodes[i].expanded = false + if (parent.nodes[i].nodes && parent.nodes[i].nodes.length > 0) this.collapseAll(parent.nodes[i]) + } + this.refresh(parent.id) + return true + } + expandAll(parent) { + if (parent == null) parent = this + if (typeof parent == 'string') parent = this.get(parent) + if (parent.nodes == null) return false + for (let i = 0; i < parent.nodes.length; i++) { + if (parent.nodes[i].expanded === false) parent.nodes[i].expanded = true + if (parent.nodes[i].nodes && parent.nodes[i].nodes.length > 0) this.expandAll(parent.nodes[i]) + } + this.refresh(parent.id) + } + expandParents(id) { + let node = this.get(id) + if (node == null) return false + if (node.parent) { + if (!node.parent.expanded) { + node.parent.expanded = true + this.refresh(node.parent.id) + } + this.expandParents(node.parent.id) + } + return true + } + click(id, event) { + let obj = this + let nd = this.get(id) + if (nd == null) return + if (nd.disabled || nd.group) return // should click event if already selected + // unselect all previously + query(obj.box).find('.w2ui-node.w2ui-selected').each(el => { + let oldID = query(el).attr('id').replace('node_', '') + let oldNode = obj.get(oldID) + if (oldNode != null) oldNode.selected = false + query(el).removeClass('w2ui-selected').find('.w2ui-icon').removeClass('w2ui-icon-selected') + }) + // select new one + let newNode = query(obj.box).find('#node_'+ w2utils.escapeId(id)) + let oldNode = query(obj.box).find('#node_'+ w2utils.escapeId(obj.selected)) + newNode.addClass('w2ui-selected').find('.w2ui-icon').addClass('w2ui-icon-selected') + // need timeout to allow rendering + setTimeout(() => { + // event before + let edata = obj.trigger('click', { target: id, originalEvent: event, node: nd, object: nd }) + if (edata.isCancelled === true) { + // restore selection + newNode.removeClass('w2ui-selected').find('.w2ui-icon').removeClass('w2ui-icon-selected') + oldNode.addClass('w2ui-selected').find('.w2ui-icon').addClass('w2ui-icon-selected') + return + } + // default action + if (oldNode != null) oldNode.selected = false + obj.get(id).selected = true + obj.selected = id + // route processing + if (typeof nd.route == 'string') { + let route = nd.route !== '' ? String('/'+ nd.route).replace(/\/{2,}/g, '/') : '' + let info = w2utils.parseRoute(route) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + if (obj.routeData[info.keys[k].name] == null) continue + route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), obj.routeData[info.keys[k].name]) + } + } + setTimeout(() => { window.location.hash = route }, 1) + } + // event after + edata.finish() + }, 1) + } + focus(event) { + let self = this + // event before + let edata = this.trigger('focus', { target: this.name, originalEvent: event }) + if (edata.isCancelled === true) return false + // default behaviour + this.hasFocus = true + query(this.box).find('.w2ui-sidebar-body').addClass('w2ui-focus') + setTimeout(() => { + let input = query(self.box).find('#sidebar_'+ self.name + '_focus').get(0) + if (document.activeElement != input) input.focus() + }, 10) + // event after + edata.finish() + } + blur(event) { + // event before + let edata = this.trigger('blur', { target: this.name, originalEvent: event }) + if (edata.isCancelled === true) return false + // default behaviour + this.hasFocus = false + query(this.box).find('.w2ui-sidebar-body').removeClass('w2ui-focus') + // event after + edata.finish() + } + keydown(event) { + let obj = this + let nd = obj.get(obj.selected) + if (obj.keyboard !== true) return + if (!nd) nd = obj.nodes[0] + // trigger event + let edata = obj.trigger('keydown', { target: obj.name, originalEvent: event }) + if (edata.isCancelled === true) return + // default behaviour + if (event.keyCode == 13 || event.keyCode == 32) { // enter or space + if (nd.nodes.length > 0) obj.toggle(obj.selected) + } + if (event.keyCode == 37) { // left + if (nd.nodes.length > 0 && nd.expanded) { + obj.collapse(obj.selected) + } else { + selectNode(nd.parent) + if (!nd.parent.group) obj.collapse(nd.parent.id) + } + } + if (event.keyCode == 39) { // right + if ((nd.nodes.length > 0 || nd.plus) && !nd.expanded) obj.expand(obj.selected) + } + if (event.keyCode == 38) { // up + if (obj.get(obj.selected) == null) { + selectNode(this.nodes[0] || null) + } else { + selectNode(neighbor(nd, prev)) + } + } + if (event.keyCode == 40) { // down + if (obj.get(obj.selected) == null) { + selectNode(this.nodes[0] || null) + } else { + selectNode(neighbor(nd, next)) + } + } + // cancel event if needed + if ([13, 32, 37, 38, 39, 40].includes(event.keyCode)) { + if (event.preventDefault) event.preventDefault() + if (event.stopPropagation) event.stopPropagation() + } + // event after + edata.finish() + function selectNode(node, event) { + if (node != null && !node.hidden && !node.disabled && !node.group) { + obj.click(node.id, event) + if (!obj.inView(node.id)) obj.scrollIntoView(node.id) + } + } + function neighbor(node, neighborFunc) { + node = neighborFunc(node) + while (node != null && (node.hidden || node.disabled)) { + if (node.group) break; else node = neighborFunc(node) + } + return node + } + function next(node, noSubs) { + if (node == null) return null + let parent = node.parent + let ind = obj.get(node.id, true) + let nextNode = null + // jump inside + if (node.expanded && node.nodes.length > 0 && noSubs !== true) { + let t = node.nodes[0] + if (t.hidden || t.disabled || t.group) nextNode = next(t); else nextNode = t + } else { + if (parent && ind + 1 < parent.nodes.length) { + nextNode = parent.nodes[ind + 1] + } else { + nextNode = next(parent, true) // jump to the parent + } + } + if (nextNode != null && (nextNode.hidden || nextNode.disabled || nextNode.group)) nextNode = next(nextNode) + return nextNode + } + function prev(node) { + if (node == null) return null + let parent = node.parent + let ind = obj.get(node.id, true) + let prevNode = (ind > 0) ? lastChild(parent.nodes[ind - 1]) : parent + if (prevNode != null && (prevNode.hidden || prevNode.disabled || prevNode.group)) prevNode = prev(prevNode) + return prevNode + } + function lastChild(node) { + if (node.expanded && node.nodes.length > 0) { + let t = node.nodes[node.nodes.length - 1] + if (t.hidden || t.disabled || t.group) return prev(t); else return lastChild(t) + } + return node + } + } + inView(id) { + let item = query(this.box).find('#node_'+ w2utils.escapeId(id)).get(0) + if (!item) { + return false + } + let div = query(this.box).find('.w2ui-sidebar-body').get(0) + if (item.offsetTop < div.scrollTop || (item.offsetTop + item.clientHeight > div.clientHeight + div.scrollTop)) { + return false + } + return true + } + scrollIntoView(id, instant) { + return new Promise((resolve, reject) => { + if (id == null) id = this.selected + let nd = this.get(id) + if (nd == null) return + let item = query(this.box).find('#node_'+ w2utils.escapeId(id)).get(0) + item.scrollIntoView({ block: 'center', inline: 'center', behavior: instant ? 'atuo' : 'smooth' }) + setTimeout(() => { this.resize(); resolve() }, instant ? 0 : 500) + }) + } + dblClick(id, event) { + let nd = this.get(id) + // event before + let edata = this.trigger('dblClick', { target: id, originalEvent: event, object: nd }) + if (edata.isCancelled === true) return + // default action + this.toggle(id) + // event after + edata.finish() + } + contextMenu(id, event) { + let nd = this.get(id) + if (id != this.selected) this.click(id) + // event before + let edata = this.trigger('contextMenu', { target: id, originalEvent: event, object: nd, allowOnDisabled: false }) + if (edata.isCancelled === true) return + // default action + if (nd.disabled && !edata.allowOnDisabled) return + if (this.menu.length > 0) { + w2menu.show({ + name: this.name + '_menu', + anchor: document.body, + items: this.menu, + originalEvent: event + }) + .select(evt => { + this.menuClick(id, parseInt(evt.detail.index), event) + }) + } + // prevent default context menu + if (event.preventDefault) event.preventDefault() + // event after + edata.finish() + } + menuClick(itemId, index, event) { + // event before + let edata = this.trigger('menuClick', { target: itemId, originalEvent: event, menuIndex: index, menuItem: this.menu[index] }) + if (edata.isCancelled === true) return + // default action + // -- empty + // event after + edata.finish() + } + goFlat() { + // event before + let edata = this.trigger('flat', { goFlat: !this.flat }) + if (edata.isCancelled === true) return + // default action + this.flat = !this.flat + this.refresh() + // event after + edata.finish() + } + render(box) { + let time = Date.now() + let obj = this + if (typeof box == 'string') box = query(box).get(0) + // event before + let edata = this.trigger('render', { target: this.name, box: box ?? this.box }) + if (edata.isCancelled === true) return + // default action + if (box != null) { + // clean previous box + if (query(this.box).find('.w2ui-sidebar-body').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-sidebar') + .html('') + } + this.box = box + } + if (!this.box) return + query(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-sidebar') + .html(`
    +
    + +
    +
    +
    `) + let rect = query(this.box).get(0).getBoundingClientRect() + query(this.box).find(':scope > div').css({ + width : rect.width + 'px', + height : rect.height + 'px' + }) + query(this.box).get(0).style.cssText += this.style + // focus + let kbd_timer + query(this.box).find('#sidebar_'+ this.name + '_focus') + .on('focus', function(event) { + clearTimeout(kbd_timer) + if (!obj.hasFocus) obj.focus(event) + }) + .on('blur', function(event) { + kbd_timer = setTimeout(() => { + if (obj.hasFocus) { obj.blur(event) } + }, 100) + }) + .on('keydown', function(event) { + if (event.keyCode != 9) { // not tab + w2ui[obj.name].keydown.call(w2ui[obj.name], event) + } + }) + query(this.box).off('mousedown') + .on('mousedown', function(event) { + // set focus to grid + setTimeout(() => { + // if input then do not focus + if (['INPUT', 'TEXTAREA', 'SELECT'].indexOf(event.target.tagName.toUpperCase()) == -1) { + let $input = query(obj.box).find('#sidebar_'+ obj.name + '_focus') + if (document.activeElement != $input.get(0)) { + $input.get(0).focus() + } + } + }, 1) + }) + // observe div resize + this.last.observeResize = new ResizeObserver(() => { this.resize() }) + this.last.observeResize.observe(this.box) + // event after + edata.finish() + // --- + this.refresh() + return Date.now() - time + } + update(id, options) { + // quick function to refresh just this item (not sub nodes) + // - icon, class, style, text, count + let nd = this.get(id) + let level + if (nd) { + let $el = query(this.box).find('#node_'+ w2utils.escapeId(nd.id)) + if (nd.group) { + if (options.text) { + nd.text = options.text + $el.find('.w2ui-group-text').replace(typeof nd.text == 'function' + ? nd.text.call(this, nd) + : ''+ nd.text +'') + delete options.text + } + if (options.class) { + nd.class = options.class + level = $el.data('level') + $el.get(0).className = 'w2ui-node-group w2ui-level-'+ level +(nd.class ? ' ' + nd.class : '') + delete options.class + } + if (options.style) { + nd.style = options.style + $el.get(0).nextElementSibling.style = nd.style +';'+ (!nd.hidden && nd.expanded ? '' : 'display: none;') + delete options.style + } + } else { + if (options.icon) { + let $icon = $el.find('.w2ui-node-image > span') + if ($icon.length > 0) { + nd.icon = options.icon + $icon[0].className = (typeof nd.icon == 'function' ? nd.icon.call(this, nd) : nd.icon) + delete options.icon + } + } + if (options.count) { + nd.count = options.count + $el.find('.w2ui-node-count').html(nd.count) + if ($el.find('.w2ui-node-count').length > 0) delete options.count + } + if (options.class && $el.length > 0) { + nd.class = options.class + level = $el.data('level') + $el[0].className = 'w2ui-node w2ui-level-'+ level + (nd.selected ? ' w2ui-selected' : '') + (nd.disabled ? ' w2ui-disabled' : '') + (nd.class ? ' ' + nd.class : '') + delete options.class + } + if (options.text) { + nd.text = options.text + $el.find('.w2ui-node-text').html(typeof nd.text == 'function' ? nd.text.call(this, nd) : nd.text) + delete options.text + } + if (options.style && $el.length > 0) { + let $txt = $el.find('.w2ui-node-text') + nd.style = options.style + $txt[0].style = nd.style + delete options.style + } + } + } + // return what was not set + return options + } + refresh(id, noBinding) { + if (this.box == null) return + let time = Date.now() + // event before + let edata = this.trigger('refresh', { + target: (id != null ? id : this.name), + nodeId: (id != null ? id : null), + fullRefresh: (id != null ? false : true) + }) + if (edata.isCancelled === true) return + // adjust top and bottom + let flatHTML = '' + if (this.flatButton == true) { + flatHTML = `
    ` + } + if (id == null && (this.topHTML !== '' || flatHTML !== '')) { + query(this.box).find('.w2ui-sidebar-top').html(this.topHTML + flatHTML) + query(this.box).find('.w2ui-sidebar-body') + .css('top', query(this.box).find('.w2ui-sidebar-top').get(0)?.clientHeight + 'px') + query(this.box).find('.w2ui-flat') + .off('click') + .on('click', event => { this.goFlat() }) + } + if (id != null && this.bottomHTML !== '') { + query(this.box).find('.w2ui-sidebar-bottom').html(this.bottomHTML) + query(this.box).find('.w2ui-sidebar-body') + .css('bottom', query(this.box).find('.w2ui-sidebar-bottom').get(0)?.clientHeight + 'px') + } + // default action + query(this.box).find(':scope > div').removeClass('w2ui-sidebar-flat').addClass(this.flat ? 'w2ui-sidebar-flat' : '').css({ + width : query(this.box).get(0)?.clientWidth + 'px', + height: query(this.box).get(0)?.clientHeight + 'px' + }) + // if no parent - reset nodes + if (this.nodes.length > 0 && this.nodes[0].parent == null) { + let tmp = this.nodes + this.nodes = [] + this.add(this, tmp) + } + let obj = this + let node + let nodeSubId + if (id == null) { + node = this + nodeSubId = '.w2ui-sidebar-body' + } else { + node = this.get(id) + if (node == null) return + nodeSubId = '#node_'+ w2utils.escapeId(node.id) + '_sub' + } + let nodeId = '#node_'+ w2utils.escapeId(node.id) + let nodeHTML + if (node !== this) { + nodeHTML = getNodeHTML(node) + query(this.box).find(nodeId).before('') + query(this.box).find(nodeId).remove() + query(this.box).find(nodeSubId).remove() + query(this.box).find('#sidebar_'+ this.name + '_tmp').before(nodeHTML) + query(this.box).find('#sidebar_'+ this.name + '_tmp').remove() + } + // remember scroll position + let div = query(this.box).find(':scope > div').get(0) + let scroll = { + top: div?.scrollTop, + left: div?.scrollLeft + } + // refresh sub nodes + query(this.box).find(nodeSubId).html('') + for (let i = 0; i < node.nodes.length; i++) { + let subNode = node.nodes[i] + nodeHTML = getNodeHTML(subNode) + query(this.box).find(nodeSubId).append(nodeHTML) + if (subNode.nodes.length !== 0) { + this.refresh(subNode.id, true) + } else { + // trigger event + let edata2 = this.trigger('refresh', { target: subNode.id }) + if (edata2.isCancelled === true) return + // event after + edata2.finish() + } + } + // reset scroll + if (div) { + div.scrollTop = scroll.top + div.scrollLeft = scroll.left + } + // bind events + if (!noBinding) { + let els = query(this.box).find(`${nodeId}.w2ui-eaction, ${nodeSubId} .w2ui-eaction`) + w2utils.bindEvents(els, this) + } + // event after + edata.finish() + return Date.now() - time + function getNodeHTML(nd) { + let html = '' + let icon = nd.icon + if (icon == null) icon = obj.icon + // -- find out level + let tmp = nd.parent + let level = 0 + while (tmp && tmp.parent != null) { + // if (tmp.group) level--; + tmp = tmp.parent + level++ + } + if (nd.caption != null && nd.text == null) nd.text = nd.caption + if (nd.caption != null) { + console.log('NOTICE: sidebar node.caption property is deprecated, please use node.text. Node -> ', nd) + nd.text = nd.caption + } + if (Array.isArray(nd.nodes) && nd.nodes.length > 0) nd.collapsible = true + if (nd.group) { + let text = w2utils.lang(typeof nd.text == 'function' ? nd.text.call(obj, nd) : nd.text) + if (String(text).substr(0, 5) != '${text}` + } + html = ` +
    + ${nd.groupShowHide && nd.collapsible + ? `${!nd.hidden && nd.expanded ? w2utils.lang('Hide') : w2utils.lang('Show')}` + : '' + } ${text} +
    +
    +
    ` + if (obj.flat) { + html = ` +
     
    +
    ` + } + } else { + if (nd.selected && !nd.disabled) obj.selected = nd.id + tmp = '' + if (icon) { + tmp = ` +
    + +
    ` + } + let expand = '' + let counts = (nd.count != null + ? `
    + ${nd.count} +
    ` + : '') + if (nd.collapsible === true) { + expand = `
    ` + } + let text = w2utils.lang(typeof nd.text == 'function' ? nd.text.call(obj, nd) : nd.text) + // array with classes + let classes = ['w2ui-node', `w2ui-level-${level}`, 'w2ui-eaction'] + if (nd.selected) classes.push('w2ui-selected') + if (nd.disabled) classes.push('w2ui-disabled') + if (nd.class) classes.push(nd.class) + html = ` +
    + ${obj.handle.html + ? `
    + ${typeof obj.handle.html == 'function' ? obj.handle.html.call(obj, nd) : obj.handle.html} +
    ` + : '' + } +
    + ${expand} ${tmp} ${counts} +
    ${text}
    +
    +
    +
    ` + if (obj.flat) { + html = ` +
    +
    ${tmp}
    +
    +
    ` + } + } + return html + } + } + mouseAction(action, el, id, event, type) { + let node = this.get(id) + let text = w2utils.lang(typeof node.text == 'function' ? node.text.call(this, node) : node.text) + let tooltip = text + (node.count || node.count === 0 ? ' - '+ node.count +'' : '') + let edata = this.trigger('mouse' + action, { target: id, node, tooltip, originalEvent: event }) + if (type == 'tooltip') { + this.tooltip(el, tooltip, id) + } + if (type == 'handle') { + this.handleTooltip(el, id) + } + edata.finish() + } + tooltip(el, text, id) { + let $el = query(el).find('.w2ui-node-data') + if (text !== '') { + w2tooltip.show({ + anchor: $el.get(0), + name: this.name + '_tooltip', + html: text, + position: 'right|left' + }) + } else { + w2tooltip.hide(this.name + '_tooltip') + } + } + handleTooltip(anchor, id) { + let text = this.handle.tooltip + if (typeof text == 'function') { + text = text(id) + } + if (text !== '' && id != null) { + w2tooltip.show({ + anchor: anchor, + name: this.name + '_tooltip', + html: text, + position: 'top|bottom' + }) + } else { + w2tooltip.hide(this.name + '_tooltip') + } + } + showPlus(el, color) { + query(el).find('span:nth-child(1)').css('color', color) + } + resize() { + let time = Date.now() + // event before + let edata = this.trigger('resize', { target: this.name }) + if (edata.isCancelled === true) return + // default action + let rect = query(this.box).get(0).getBoundingClientRect() + query(this.box).css('overflow', 'hidden') // container should have no overflow + query(this.box).find(':scope > div').css({ + width : rect.width + 'px', + height : rect.height + 'px' + }) + // event after + edata.finish() + return Date.now() - time + } + destroy() { + // event before + let edata = this.trigger('destroy', { target: this.name }) + if (edata.isCancelled === true) return + // clean up + if (query(this.box).find('.w2ui-sidebar-body').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-sidebar') + .html('') + } + this.last.observeResize?.disconnect() + delete w2ui[this.name] + // event after + edata.finish() + } + lock(msg, showSpinner) { + let args = Array.from(arguments) + args.unshift(this.box) + w2utils.lock(...args) + } + unlock(speed) { + w2utils.unlock(this.box, speed) + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2tooltip + * + * == 2.0 changes + * - CSP - fixed inline events + * - removed jQuery dependency + * - observeResize for the box + * - refactored w2events + * - scrollIntoView - removed callback + * - scroll, scrollIntoView return promise + * - animateInsert, animateClose - returns a promise + * - add, insert return a promise + * - onMouseEnter, onMouseLeave, onMouseDown, onMouseUp + */ + +class w2tabs extends w2base { + constructor(options) { + super(options.name) + this.box = null // DOM Element that holds the element + this.name = null // unique name for w2ui + this.active = null + this.reorder = false + this.flow = 'down' // can be down or up + this.tooltip = 'top|left' // can be top, bottom, left, right + this.tabs = [] + this.routeData = {} // data for dynamic routes + this.last = {} // placeholder for internal variables + this.right = '' + this.style = '' + this.onClick = null + this.onMouseEnter = null // mouse enter and lease + this.onMouseLeave = null + this.onMouseDown = null + this.onMouseUp = null + this.onClose = null + this.onRender = null + this.onRefresh = null + this.onResize = null + this.onDestroy = null + this.tab_template = { + id: null, + text: null, + route: null, + hidden: false, + disabled: false, + closable: false, + tooltip: null, + style: '', + onClick: null, + onRefresh: null, + onClose: null + } + let tabs = options.tabs + delete options.tabs + // mix in options + Object.assign(this, options) + // add item via method to makes sure item_template is applied + if (Array.isArray(tabs)) this.add(tabs) + // need to reassign back to keep it in config + options.tabs = tabs + // render if box specified + if (typeof this.box == 'string') this.box = query(this.box).get(0) + if (this.box) this.render(this.box) + } + add(tab) { + return this.insert(null, tab) + } + insert(id, tabs) { + if (!Array.isArray(tabs)) tabs = [tabs] + // assume it is array + let proms = [] + tabs.forEach(tab => { + // checks + if (tab.id == null) { + console.log(`ERROR: The parameter "id" is required but not supplied. (obj: ${this.name})`) + return + } + if (!w2utils.checkUniqueId(tab.id, this.tabs, 'tabs', this.name)) return + // add tab + let it = Object.assign({}, this.tab_template, tab) + if (id == null) { + this.tabs.push(it) + proms.push(this.animateInsert(null, it)) + } else { + let middle = this.get(id, true) + let before = this.tabs[middle].id + this.tabs.splice(middle, 0, it) + proms.push(this.animateInsert(before, it)) + } + }) + return Promise.all(proms) + } + remove() { + let effected = 0 + Array.from(arguments).forEach(it => { + let tab = this.get(it) + if (!tab) return + effected++ + // remove from array + this.tabs.splice(this.get(tab.id, true), 1) + // remove from screen + query(this.box).find(`#tabs_${this.name}_tab_${w2utils.escapeId(tab.id)}`).remove() + }) + this.resize() + return effected + } + select(id) { + if (this.active == id || this.get(id) == null) return false + this.active = id + this.refresh() + return true + } + set(id, tab) { + let index = this.get(id, true) + if (index == null) return false + w2utils.extend(this.tabs[index], tab) + this.refresh(id) + return true + } + get(id, returnIndex) { + if (arguments.length === 0) { + let all = [] + for (let i1 = 0; i1 < this.tabs.length; i1++) { + if (this.tabs[i1].id != null) { + all.push(this.tabs[i1].id) + } + } + return all + } else { + for (let i2 = 0; i2 < this.tabs.length; i2++) { + if (this.tabs[i2].id == id) { // need to be == since id can be numeric + return (returnIndex === true ? i2 : this.tabs[i2]) + } + } + } + return null + } + show() { + let effected = [] + Array.from(arguments).forEach(it => { + let tab = this.get(it) + if (!tab || tab.hidden === false) return + tab.hidden = false + effected.push(tab.id) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it); this.resize() }) }, 15) // needs timeout + return effected + } + hide() { + let effected = [] + Array.from(arguments).forEach(it => { + let tab = this.get(it) + if (!tab || tab.hidden === true) return + tab.hidden = true + effected.push(tab.id) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it); this.resize() }) }, 15) // needs timeout + return effected + } + enable() { + let effected = [] + Array.from(arguments).forEach(it => { + let tab = this.get(it) + if (!tab || tab.disabled === false) return + tab.disabled = false + effected.push(tab.id) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it) }) }, 15) // needs timeout + return effected + } + disable() { + let effected = [] + Array.from(arguments).forEach(it => { + let tab = this.get(it) + if (!tab || tab.disabled === true) return + tab.disabled = true + effected.push(tab.id) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it) }) }, 15) // needs timeout + return effected + } + dragMove(event) { + if (!this.last.reordering) return + let self = this + let info = this.last.moving + let tab = this.tabs[info.index] + let next = _find(info.index, 1) + let prev = _find(info.index, -1) + let $el = query(this.box).find('#tabs_'+ this.name + '_tab_'+ w2utils.escapeId(tab.id)) + if (info.divX > 0 && next) { + let $nextEl = query(this.box).find('#tabs_'+ this.name + '_tab_'+ w2utils.escapeId(next.id)) + let width1 = parseInt($el.get(0).clientWidth) + let width2 = parseInt($nextEl.get(0).clientWidth) + if (width1 < width2) { + width1 = Math.floor(width1 / 3) + width2 = width2 - width1 + } else { + width1 = Math.floor(width2 / 3) + width2 = width2 - width1 + } + if (info.divX > width2) { + let index = this.tabs.indexOf(next) + this.tabs.splice(info.index, 0, this.tabs.splice(index, 1)[0]) // reorder in the array + info.$tab.before($nextEl.get(0)) + info.$tab.css('opacity', 0) + Object.assign(this.last.moving, { + index: index, + divX: -width1, + x: event.pageX + width1, + left: info.left + info.divX + width1 + }) + return + } + } + if (info.divX < 0 && prev) { + let $prevEl = query(this.box).find('#tabs_'+ this.name + '_tab_'+ w2utils.escapeId(prev.id)) + let width1 = parseInt($el.get(0).clientWidth) + let width2 = parseInt($prevEl.get(0).clientWidth) + if (width1 < width2) { + width1 = Math.floor(width1 / 3) + width2 = width2 - width1 + } else { + width1 = Math.floor(width2 / 3) + width2 = width2 - width1 + } + if (Math.abs(info.divX) > width2) { + let index = this.tabs.indexOf(prev) + this.tabs.splice(info.index, 0, this.tabs.splice(index, 1)[0]) // reorder in the array + $prevEl.before(info.$tab) + info.$tab.css('opacity', 0) + Object.assign(info, { + index: index, + divX: width1, + x: event.pageX - width1, + left: info.left + info.divX - width1 + }) + return + } + } + function _find(ind, inc) { + ind += inc + let tab = self.tabs[ind] + if (tab && tab.hidden) { + tab = _find(ind, inc) + } + return tab + } + } + mouseAction(action, id, event) { + let tab = this.get(id) + let edata = this.trigger('mouse' + action, { target: id, tab, object: tab, originalEvent: event }) + if (edata.isCancelled === true || tab.disabled || tab.hidden) return + switch (action) { + case 'Enter': + this.tooltipShow(id) + break + case 'Leave': + this.tooltipHide(id) + break + case 'Down': + this.initReorder(id, event) + break + case 'Up': + break + } + edata.finish() + } + tooltipShow(id) { + let item = this.get(id) + let el = query(this.box).find('#tabs_'+ this.name + '_tab_'+ w2utils.escapeId(id)).get(0) + if (this.tooltip == null || item.disabled || this.last.reordering) { + return + } + let pos = this.tooltip + let txt = item.tooltip + if (typeof txt == 'function') txt = txt.call(this, item) + w2tooltip.show({ + anchor: el, + name: this.name + '_tooltip', + html: txt, + position: pos + }) + } + tooltipHide(id) { + if (this.tooltip == null) return + w2tooltip.hide(this.name + '_tooltip') + } + getTabHTML(id) { + let index = this.get(id, true) + let tab = this.tabs[index] + if (tab == null) return false + if (tab.text == null && tab.caption != null) tab.text = tab.caption + if (tab.tooltip == null && tab.hint != null) tab.tooltip = tab.hint // for backward compatibility + if (tab.caption != null) { + console.log('NOTICE: tabs tab.caption property is deprecated, please use tab.text. Tab -> ', tab) + } + if (tab.hint != null) { + console.log('NOTICE: tabs tab.hint property is deprecated, please use tab.tooltip. Tab -> ', tab) + } + let text = tab.text + if (typeof text == 'function') text = text.call(this, tab) + if (text == null) text = '' + let closable = '' + let addStyle = '' + if (tab.hidden) { addStyle += 'display: none;' } + if (tab.disabled) { addStyle += 'opacity: 0.2;' } + if (tab.closable && !tab.disabled) { + closable = `
    +
    ` + } + return ` +
    + ${w2utils.lang(text) + closable} +
    ` + } + refresh(id) { + let time = Date.now() + if (this.flow == 'up') { + query(this.box).addClass('w2ui-tabs-up') + } else { + query(this.box).removeClass('w2ui-tabs-up') + } + // event before + let edata = this.trigger('refresh', { target: (id != null ? id : this.name), object: this.get(id) }) + if (edata.isCancelled === true) return + if (id == null) { + // refresh all + for (let i = 0; i < this.tabs.length; i++) { + this.refresh(this.tabs[i].id) + } + } else { + // create or refresh only one item + let selector = '#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(id) + let $tab = query(this.box).find(selector) + let tabHTML = this.getTabHTML(id) + if ($tab.length === 0) { + query(this.box).find('#tabs_'+ this.name +'_right').before(tabHTML) + } else { + if (query(this.box).find('.tab-animate-insert').length == 0) { + $tab.replace(tabHTML) + } + } + w2utils.bindEvents(query(this.box).find(`${selector}, ${selector} .w2ui-eaction`), this) + } + // right html + query(this.box).find('#tabs_'+ this.name +'_right').html(this.right) + // event after + edata.finish() + // this.resize(); + return Date.now() - time + } + render(box) { + let time = Date.now() + if (typeof box == 'string') box = query(box).get(0) + // event before + let edata = this.trigger('render', { target: this.name, box: box ?? this.box }) + if (edata.isCancelled === true) return + // default action + if (box != null) { + // clean previous box + if (query(this.box).find('#tabs_'+ this.name + '_right').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-tabs') + .html('') + } + this.box = box + } + if (!this.box) return false + // render all buttons + let html =` +
    +
    +
    ${this.right}
    +
    +
    +
    ` + query(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-tabs') + .html(html) + if (query(this.box).length > 0) { + query(this.box)[0].style.cssText += this.style + } + w2utils.bindEvents(query(this.box).find('.w2ui-eaction'), this) + // observe div resize + this.last.observeResize = new ResizeObserver(() => { this.resize() }) + this.last.observeResize.observe(this.box) + // event after + edata.finish() + this.refresh() + this.resize() + return Date.now() - time + } + initReorder(id, event) { + if (!this.reorder) return + let self = this + let $tab = query(this.box).find('#tabs_' + this.name + '_tab_' + w2utils.escapeId(id)) + let tabIndex = this.get(id, true) + let $ghost = query($tab.get(0).cloneNode(true)) + let edata + $ghost.attr('id', '#tabs_' + this.name + '_tab_ghost') + this.last.moving = { + index: tabIndex, + indexFrom: tabIndex, + $tab: $tab, + $ghost: $ghost, + divX: 0, + left: $tab.get(0).getBoundingClientRect().left, + parentX: query(this.box).get(0).getBoundingClientRect().left, + x: event.pageX, + opacity: $tab.css('opacity') + } + query(document) + .off('.w2uiTabReorder') + .on('mousemove.w2uiTabReorder', function (event) { + if (!self.last.reordering) { + // event before + edata = self.trigger('reorder', { target: self.tabs[tabIndex].id, indexFrom: tabIndex, tab: self.tabs[tabIndex] }) + if (edata.isCancelled === true) return + w2tooltip.hide(this.name + '_tooltip') + self.last.reordering = true + $ghost.addClass('moving') + $ghost.css({ + 'pointer-events': 'none', + 'position': 'absolute', + 'left': $tab.get(0).getBoundingClientRect().left + }) + $tab.css('opacity', 0) + query(self.box).find('.w2ui-scroll-wrapper').append($ghost.get(0)) + query(self.box).find('.w2ui-tab-close').hide() + } + self.last.moving.divX = event.pageX - self.last.moving.x + $ghost.css('left', (self.last.moving.left - self.last.moving.parentX + self.last.moving.divX) + 'px') + self.dragMove(event) + }) + .on('mouseup.w2uiTabReorder', function () { + query(document).off('.w2uiTabReorder') + $ghost.css({ + 'transition': '0.1s', + 'left': self.last.moving.$tab.get(0).getBoundingClientRect().left - self.last.moving.parentX + }) + query(self.box).find('.w2ui-tab-close').show() + setTimeout(() => { + $ghost.remove() + $tab.css({ opacity: self.last.moving.opacity }) + // self.render() + if (self.last.reordering) { + edata.finish({ indexTo: self.last.moving.index }) + } + self.last.reordering = false + }, 100) + }) + } + scroll(direction, instant) { + return new Promise((resolve, reject) => { + let scrollBox = query(this.box).find('.w2ui-scroll-wrapper') + let scrollLeft = scrollBox.get(0).scrollLeft + let right = scrollBox.find('.w2ui-tabs-right').get(0) + let width1 = scrollBox.parent().get(0).getBoundingClientRect().width + let width2 = scrollLeft + parseInt(right.offsetLeft) + parseInt(right.clientWidth ) + switch (direction) { + case 'left': { + let scroll = scrollLeft - width1 + 50 // 35 is width of both button + if (scroll <= 0) scroll = 0 + scrollBox.get(0).scrollTo({ top: 0, left: scroll, behavior: instant ? 'atuo' : 'smooth' }) + break + } + case 'right': { + let scroll = scrollLeft + width1 - 50 // 35 is width of both button + if (scroll >= width2 - width1) scroll = width2 - width1 + scrollBox.get(0).scrollTo({ top: 0, left: scroll, behavior: instant ? 'atuo' : 'smooth' }) + break + } + } + setTimeout(() => { this.resize(); resolve() }, instant ? 0 : 350) + }) + } + scrollIntoView(id, instant) { + return new Promise((resolve, reject) => { + if (id == null) id = this.active + let tab = this.get(id) + if (tab == null) return + let tabEl = query(this.box).find('#tabs_' + this.name + '_tab_' + w2utils.escapeId(id)).get(0) + tabEl.scrollIntoView({ block: 'start', inline: 'center', behavior: instant ? 'atuo' : 'smooth' }) + setTimeout(() => { this.resize(); resolve() }, instant ? 0 : 500) + }) + } + resize() { + let time = Date.now() + if (this.box == null) return + // event before + let edata = this.trigger('resize', { target: this.name }) + if (edata.isCancelled === true) return + // show hide overflow buttons + let box = query(this.box) + box.find('.w2ui-scroll-left, .w2ui-scroll-right').hide() + let scrollBox = box.find('.w2ui-scroll-wrapper').get(0) + let $right = box.find('.w2ui-tabs-right') + let boxWidth = box.get(0).getBoundingClientRect().width + let itemsWidth = ($right.length > 0 ? $right[0].offsetLeft + $right[0].clientWidth : 0) + if (boxWidth < itemsWidth) { + // we have overflown content + if (scrollBox.scrollLeft > 0) { + box.find('.w2ui-scroll-left').show() + } + if (boxWidth < itemsWidth - scrollBox.scrollLeft) { + box.find('.w2ui-scroll-right').show() + } + } + // event after + edata.finish() + return Date.now() - time + } + destroy() { + // event before + let edata = this.trigger('destroy', { target: this.name }) + if (edata.isCancelled === true) return + // clean up + if (query(this.box).find('#tabs_'+ this.name + '_right').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-tabs') + .html('') + } + this.last.observeResize?.disconnect() + delete w2ui[this.name] + // event after + edata.finish() + } + // =================================================== + // -- Internal Event Handlers + click(id, event) { + let tab = this.get(id) + if (tab == null || tab.disabled || this.last.reordering) return false + // event before + let edata = this.trigger('click', { target: id, tab: tab, object: tab, originalEvent: event }) + if (edata.isCancelled === true) return + // default action + query(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(this.active)).removeClass('active') + this.active = tab.id + query(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(this.active)).addClass('active') + // route processing + if (typeof tab.route == 'string') { + let route = tab.route !== '' ? String('/'+ tab.route).replace(/\/{2,}/g, '/') : '' + let info = w2utils.parseRoute(route) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + if (this.routeData[info.keys[k].name] == null) continue + route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]) + } + } + setTimeout(() => { window.location.hash = route }, 1) + } + // event after + edata.finish() + } + clickClose(id, event) { + let tab = this.get(id) + if (tab == null || tab.disabled) return false + // event before + let edata = this.trigger('close', { target: id, object: tab, tab, originalEvent: event }) + if (edata.isCancelled === true) return + this.animateClose(id).then(() => { + this.remove(id) + edata.finish() + this.refresh() + }) + if (event) event.stopPropagation() + } + animateClose(id) { + return new Promise((resolve, reject) => { + let $tab = query(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(id)) + let width = parseInt($tab.get(0).clientWidth || 0) + let anim = `
    ` + let $anim = $tab.replace(anim) + setTimeout(() => { $anim.css({ width: '0px' }) }, 1) + setTimeout(() => { + $anim.remove() + this.resize() + resolve() + }, 500) + }) + } + animateInsert(id, tab) { + return new Promise((resolve, reject) => { + let $before = query(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(id)) + let $tab = query.html(this.getTabHTML(tab.id)) + if ($before.length == 0) { + $before = query(this.box).find('#tabs_tabs_right') + $before.before($tab) + this.resize() + } else { + $tab.css({ opacity: 0 }) + // first insert tab on the right to get its proper dimentions + query(this.box).find('#tabs_tabs_right').before($tab.get(0)) + let $tmp = query(this.box).find('#' + $tab.attr('id')) + let width = $tmp.get(0).clientWidth ?? 0 + // insert animation div + let $anim = query.html('
    ') + $before.before($anim) + // hide tab and move it in the right position + $tab.hide() + $anim.before($tab[0]) + setTimeout(() => { $anim.css({ width: width + 'px' }) }, 1) + setTimeout(() => { + $anim.remove() + $tab.css({ opacity: 1 }).show() + this.refresh(tab.id) + this.resize() + resolve() + }, 500) + } + }) + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2tabs, w2toolbar + * + * == 2.0 changes + * - CSP - fixed inline events + * - remove jQuery dependency + * - layout.confirm - refactored + * - layout.message - refactored + * - panel.removed + */ + +let w2panels = ['top', 'left', 'main', 'preview', 'right', 'bottom'] +class w2layout extends w2base { + constructor(options) { + super(options.name) + this.box = null // DOM Element that holds the element + this.name = null // unique name for w2ui + this.panels = [] + this.last = {} + this.padding = 1 // panel padding + this.resizer = 4 // resizer width or height + this.style = '' + this.onShow = null + this.onHide = null + this.onResizing = null + this.onResizerClick = null + this.onRender = null + this.onRefresh = null + this.onChange = null + this.onResize = null + this.onDestroy = null + this.panel_template = { + type: null, // left, right, top, bottom + title: '', + size: 100, // width or height depending on panel name + minSize: 20, + maxSize: false, + hidden: false, + resizable: false, + overflow: 'auto', + style: '', + html: '', // can be String or Object with .render(box) method + tabs: null, + toolbar: null, + width: null, // read only + height: null, // read only + show: { + toolbar: false, + tabs: false + }, + removed: null, // function to call when content is overwritten + onRefresh: null, + onShow: null, + onHide: null + } + // mix in options + Object.assign(this, options) + if (!Array.isArray(this.panels)) this.panels = [] + // add defined panels + this.panels.forEach((panel, ind) => { + this.panels[ind] = w2utils.extend({}, this.panel_template, panel) + if (w2utils.isPlainObject(panel.tabs) || Array.isArray(panel.tabs)) initTabs(this, panel.type) + if (w2utils.isPlainObject(panel.toolbar) || Array.isArray(panel.toolbar)) initToolbar(this, panel.type) + }) + // add all other panels + w2panels.forEach(tab => { + if (this.get(tab) != null) return + this.panels.push(w2utils.extend({}, this.panel_template, { type: tab, hidden: (tab !== 'main'), size: 50 })) + }) + // render if box specified + if (typeof this.box == 'string') this.box = query(this.box).get(0) + if (this.box) this.render(this.box) + function initTabs(object, panel, tabs) { + let pan = object.get(panel) + if (pan != null && tabs == null) tabs = pan.tabs + if (pan == null || tabs == null) return false + // instantiate tabs + if (Array.isArray(tabs)) tabs = { tabs: tabs } + let name = object.name + '_' + panel + '_tabs' + if (w2ui[name]) w2ui[name].destroy() // destroy if existed + pan.tabs = new w2tabs(w2utils.extend({}, tabs, { owner: object, name: object.name + '_' + panel + '_tabs' })) + pan.show.tabs = true + return true + } + function initToolbar(object, panel, toolbar) { + let pan = object.get(panel) + if (pan != null && toolbar == null) toolbar = pan.toolbar + if (pan == null || toolbar == null) return false + // instantiate toolbar + if (Array.isArray(toolbar)) toolbar = { items: toolbar } + let name = object.name + '_' + panel + '_toolbar' + if (w2ui[name]) w2ui[name].destroy() // destroy if existed + pan.toolbar = new w2toolbar(w2utils.extend({}, toolbar, { owner: object, name: object.name + '_' + panel + '_toolbar' })) + pan.show.toolbar = true + return true + } + } + html(panel, data, transition) { + let p = this.get(panel) + let promise = { + panel: panel, + html: p.html, + error: false, + cancelled: false, + removed(cb) { + if (typeof cb == 'function') { + p.removed = cb + } + } + } + if (typeof p.removed == 'function') { + p.removed({ panel: panel, html: p.html, html_new: data, transition: transition || 'none' }) + p.removed = null // this is one time call back only + } + // if it is CSS panel + if (panel == 'css') { + query(this.box).find('#layout_'+ this.name +'_panel_css').html('') + promise.status = true + return promise + } + if (p == null) { + console.log('ERROR: incorrect panel name. Panel name can be main, left, right, top, bottom, preview or css') + promise.error = true + return promise + } + if (data == null) { + return promise + } + // event before + let edata = this.trigger('change', { target: panel, panel: p, html_new: data, transition: transition }) + if (edata.isCancelled === true) { + promise.cancelled = true + return promise + } + let pname = '#layout_'+ this.name + '_panel_'+ p.type + let current = query(this.box).find(pname + '> .w2ui-panel-content') + let panelTop = 0 + if (current.length > 0) { + query(this.box).find(pname).get(0).scrollTop = 0 + panelTop = query(current).css('top') + } + if (p.html === '') { + p.html = data + this.refresh(panel) + } else { + p.html = data + if (!p.hidden) { + if (transition != null && transition !== '') { + // apply transition + query(this.box).addClass('animating') + let div1 = query(this.box).find(pname + '> .w2ui-panel-content') + div1.after('
    ') + let div2 = query(this.box).find(pname + '> .w2ui-panel-content.new-panel') + div1.css('top', panelTop) + div2.css('top', panelTop) + if (typeof data == 'object') { + data.box = div2[0] // do not do .render(box); + data.render() + } else { + div2.hide().html(data) + } + w2utils.transition(div1[0], div2[0], transition, () => { + div1.remove() + div2.removeClass('new-panel') + div2.css('overflow', p.overflow) + // make sure only one content left + query(query(this.box).find(pname + '> .w2ui-panel-content').get(1)).remove() + query(this.box).removeClass('animating') + this.refresh(panel) + }) + } else { + this.refresh(panel) + } + } + } + // event after + edata.finish() + return promise + } + message(panel, options) { + let p = this.get(panel) + let box = query(this.box).find('#layout_'+ this.name + '_panel_'+ p.type) + let oldOverflow = box.css('overflow') + box.css('overflow', 'hidden') + let prom = w2utils.message({ + owner: this, + box : box.get(0), + after: '.w2ui-panel-title', + param: panel + }, options) + if (prom) { + prom.self.on('close:after', () => { + box.css('overflow', oldOverflow) + }) + } + return prom + } + confirm(panel, options) { + let p = this.get(panel) + let box = query(this.box).find('#layout_'+ this.name + '_panel_'+ p.type) + let oldOverflow = box.css('overflow') + box.css('overflow', 'hidden') + let prom = w2utils.confirm({ + owner : this, + box : box.get(0), + after : '.w2ui-panel-title', + param : panel + }, options) + if (prom) { + prom.self.on('close:after', () => { + box.css('overflow', oldOverflow) + }) + } + return prom + } + load(panel, url, transition) { + return new Promise((resolve, reject) => { + if ((panel == 'css' || this.get(panel) != null) && url != null) { + fetch(url) + .then(resp => resp.text()) + .then(text => { + this.resize() + resolve(this.html(panel, text, transition)) + }) + } else { + reject() + } + }) + } + sizeTo(panel, size, instant) { + let pan = this.get(panel) + if (pan == null) return false + // resize + query(this.box).find(':scope > div > .w2ui-panel') + .css('transition', (instant !== true ? '.2s' : '0s')) + setTimeout(() => { this.set(panel, { size: size }) }, 1) + // clean + setTimeout(() => { + query(this.box).find(':scope > div > .w2ui-panel').css('transition', '0s') + this.resize() + }, 300) + return true + } + show(panel, immediate) { + // event before + let edata = this.trigger('show', { target: panel, thisect: this.get(panel), immediate: immediate }) + if (edata.isCancelled === true) return + let p = this.get(panel) + if (p == null) return false + p.hidden = false + if (immediate === true) { + query(this.box).find('#layout_'+ this.name +'_panel_'+panel) + .css({ 'opacity': '1' }) + edata.finish() + this.resize() + } else { + // resize + query(this.box).addClass('animating') + query(this.box).find('#layout_'+ this.name +'_panel_'+panel) + .css({ 'opacity': '0' }) + query(this.box).find(':scope > div > .w2ui-panel') + .css('transition', '.2s') + setTimeout(() => { this.resize() }, 1) + // show + setTimeout(() => { + query(this.box).find('#layout_'+ this.name +'_panel_'+ panel).css({ 'opacity': '1' }) + }, 250) + // clean + setTimeout(() => { + query(this.box).find(':scope > div > .w2ui-panel') + .css('transition', '0s') + query(this.box).removeClass('animating') + edata.finish() + this.resize() + }, 300) + } + return true + } + hide(panel, immediate) { + // event before + let edata = this.trigger('hide', { target: panel, object: this.get(panel), immediate: immediate }) + if (edata.isCancelled === true) return + let p = this.get(panel) + if (p == null) return false + p.hidden = true + if (immediate === true) { + query(this.box).find('#layout_'+ this.name +'_panel_'+panel) + .css({ 'opacity': '0' }) + edata.finish() + this.resize() + } else { + // hide + query(this.box).addClass('animating') + query(this.box).find(':scope > div > .w2ui-panel') + .css('transition', '.2s') + query(this.box).find('#layout_'+ this.name +'_panel_'+panel) + .css({ 'opacity': '0' }) + setTimeout(() => { this.resize() }, 1) + // clean + setTimeout(() => { + query(this.box).find(':scope > div > .w2ui-panel') + .css('transition', '0s') + query(this.box).removeClass('animating') + edata.finish() + this.resize() + }, 300) + } + return true + } + toggle(panel, immediate) { + let p = this.get(panel) + if (p == null) return false + if (p.hidden) return this.show(panel, immediate); else return this.hide(panel, immediate) + } + set(panel, options) { + let ind = this.get(panel, true) + if (ind == null) return false + w2utils.extend(this.panels[ind], options) + // refresh only when content changed + if (options.html != null || options.resizable != null) { + this.refresh(panel) + } + // show/hide resizer + this.resize() // resize is needed when panel size is changed + return true + } + get(panel, returnIndex) { + for (let p = 0; p < this.panels.length; p++) { + if (this.panels[p].type == panel) { + if (returnIndex === true) return p; else return this.panels[p] + } + } + return null + } + el(panel) { + let el = query(this.box).find('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-content') + if (el.length != 1) return null + return el[0] + } + hideToolbar(panel) { + let pan = this.get(panel) + if (!pan) return + pan.show.toolbar = false + query(this.box).find('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-toolbar').hide() + this.resize() + } + showToolbar(panel) { + let pan = this.get(panel) + if (!pan) return + pan.show.toolbar = true + query(this.box).find('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-toolbar').show() + this.resize() + } + toggleToolbar(panel) { + let pan = this.get(panel) + if (!pan) return + if (pan.show.toolbar) this.hideToolbar(panel); else this.showToolbar(panel) + } + assignToolbar(panel, toolbar) { + if (typeof toolbar == 'string' && w2ui[toolbar] != null) toolbar = w2ui[toolbar] + let pan = this.get(panel) + pan.toolbar = toolbar + let tmp = query(this.box).find(panel +'> .w2ui-panel-toolbar') + if (pan.toolbar != null) { + if (tmp.find('[name='+ pan.toolbar.name +']').length === 0) { + pan.toolbar.render(tmp.get(0)) + } else if (pan.toolbar != null) { + pan.toolbar.refresh() + } + toolbar.owner = this + this.showToolbar(panel) + this.refresh(panel) + } else { + tmp.html('') + this.hideToolbar(panel) + } + } + hideTabs(panel) { + let pan = this.get(panel) + if (!pan) return + pan.show.tabs = false + query(this.box).find('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-tabs').hide() + this.resize() + } + showTabs(panel) { + let pan = this.get(panel) + if (!pan) return + pan.show.tabs = true + query(this.box).find('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-tabs').show() + this.resize() + } + toggleTabs(panel) { + let pan = this.get(panel) + if (!pan) return + if (pan.show.tabs) this.hideTabs(panel); else this.showTabs(panel) + } + render(box) { + let time = Date.now() + let self = this + if (typeof box == 'string') box = query(box).get(0) + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + // event before + let edata = this.trigger('render', { target: this.name, box: box ?? this.box }) + if (edata.isCancelled === true) return + // default action + if (box != null) { + // clean previous box + if (query(this.box).find('#layout_'+ this.name +'_panel_main').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-layout') + .html('') + } + this.box = box + } + if (!this.box) return false + // render layout + query(this.box) + .attr('name', this.name) + .addClass('w2ui-layout') + .html('
    ') + if (query(this.box).length > 0) { + query(this.box)[0].style.cssText += this.style + } + // create all panels + for (let p1 = 0; p1 < w2panels.length; p1++) { + let html = '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    ' + query(this.box).find(':scope > div').append(html) + } + query(this.box).find(':scope > div') + .append('
    ') + this.refresh() // if refresh is not called here, the layout will not be available right after initialization + // observe div resize + this.last.observeResize = new ResizeObserver(() => { this.resize() }) + this.last.observeResize.observe(this.box) + // process event + edata.finish() + // re-init events + setTimeout(() => { // needed this timeout to allow browser to render first if there are tabs or toolbar + self.last.events = { resizeStart, mouseMove, mouseUp } + this.resize() + }, 0) + return Date.now() - time + function resizeStart(type, evnt) { + if (!self.box) return + if (!evnt) evnt = window.event + query(document) + .off('mousemove', self.last.events.mouseMove) + .on('mousemove', self.last.events.mouseMove) + query(document) + .off('mouseup', self.last.events.mouseUp) + .on('mouseup', self.last.events.mouseUp) + self.last.resize = { + type : type, + x : evnt.screenX, + y : evnt.screenY, + diff_x : 0, + diff_y : 0, + value : 0 + } + // lock all panels + w2panels.forEach(panel => { + let $tmp = query(self.el(panel)).find('.w2ui-lock') + if ($tmp.length > 0) { + $tmp.data('locked', 'yes') + } else { + self.lock(panel, { opacity: 0 }) + } + }) + let el = query(self.box).find('#layout_'+ self.name +'_resizer_'+ type).get(0) + if (type == 'left' || type == 'right') { + self.last.resize.value = parseInt(el.style.left) + } + if (type == 'top' || type == 'preview' || type == 'bottom') { + self.last.resize.value = parseInt(el.style.top) + } + } + function mouseUp(evnt) { + if (!self.box) return + if (!evnt) evnt = window.event + query(document).off('mousemove', self.last.events.mouseMove) + query(document).off('mouseup', self.last.events.mouseUp) + if (self.last.resize == null) return + // unlock all panels + w2panels.forEach(panel => { + let $tmp = query(self.el(panel)).find('.w2ui-lock') + if ($tmp.data('locked') == 'yes') { + $tmp.removeData('locked') + } else { + self.unlock(panel) + } + }) + // set new size + if (self.last.diff_x !== 0 || self.last.resize.diff_y !== 0) { // only recalculate if changed + let ptop = self.get('top') + let pbottom = self.get('bottom') + let panel = self.get(self.last.resize.type) + let width = w2utils.getSize(query(self.box), 'width') + let height = w2utils.getSize(query(self.box), 'height') + let str = String(panel.size) + let ns, nd + switch (self.last.resize.type) { + case 'top': + ns = parseInt(panel.sizeCalculated) + self.last.resize.diff_y + nd = 0 + break + case 'bottom': + ns = parseInt(panel.sizeCalculated) - self.last.resize.diff_y + nd = 0 + break + case 'preview': + ns = parseInt(panel.sizeCalculated) - self.last.resize.diff_y + nd = (ptop && !ptop.hidden ? ptop.sizeCalculated : 0) + + (pbottom && !pbottom.hidden ? pbottom.sizeCalculated : 0) + break + case 'left': + ns = parseInt(panel.sizeCalculated) + self.last.resize.diff_x + nd = 0 + break + case 'right': + ns = parseInt(panel.sizeCalculated) - self.last.resize.diff_x + nd = 0 + break + } + // set size + if (str.substr(str.length-1) == '%') { + panel.size = Math.floor(ns * 100 / (panel.type == 'left' || panel.type == 'right' ? width : height - nd) * 100) / 100 + '%' + } else { + if (String(panel.size).substr(0, 1) == '-') { + panel.size = parseInt(panel.size) - panel.sizeCalculated + ns + } else { + panel.size = ns + } + } + self.resize() + } + query(self.box) + .find('#layout_'+ self.name + '_resizer_'+ self.last.resize.type) + .removeClass('active') + delete self.last.resize + } + function mouseMove(evnt) { + if (!self.box) return + if (!evnt) evnt = window.event + if (self.last.resize == null) return + let panel = self.get(self.last.resize.type) + // event before + let tmp = self.last.resize + let edata = self.trigger('resizing', { target: self.name, object: panel, originalEvent: evnt, + panel: tmp ? tmp.type : 'all', diff_x: tmp ? tmp.diff_x : 0, diff_y: tmp ? tmp.diff_y : 0 }) + if (edata.isCancelled === true) return + let p = query(self.box).find('#layout_'+ self.name + '_resizer_'+ tmp.type) + let resize_x = (evnt.screenX - tmp.x) + let resize_y = (evnt.screenY - tmp.y) + let mainPanel = self.get('main') + if (!p.hasClass('active')) p.addClass('active') + switch (tmp.type) { + case 'left': + if (panel.minSize - resize_x > panel.width) { + resize_x = panel.minSize - panel.width + } + if (panel.maxSize && (panel.width + resize_x > panel.maxSize)) { + resize_x = panel.maxSize - panel.width + } + if (mainPanel.minSize + resize_x > mainPanel.width) { + resize_x = mainPanel.width - mainPanel.minSize + } + break + case 'right': + if (panel.minSize + resize_x > panel.width) { + resize_x = panel.width - panel.minSize + } + if (panel.maxSize && (panel.width - resize_x > panel.maxSize)) { + resize_x = panel.width - panel.maxSize + } + if (mainPanel.minSize - resize_x > mainPanel.width) { + resize_x = mainPanel.minSize - mainPanel.width + } + break + case 'top': + if (panel.minSize - resize_y > panel.height) { + resize_y = panel.minSize - panel.height + } + if (panel.maxSize && (panel.height + resize_y > panel.maxSize)) { + resize_y = panel.maxSize - panel.height + } + if (mainPanel.minSize + resize_y > mainPanel.height) { + resize_y = mainPanel.height - mainPanel.minSize + } + break + case 'preview': + case 'bottom': + if (panel.minSize + resize_y > panel.height) { + resize_y = panel.height - panel.minSize + } + if (panel.maxSize && (panel.height - resize_y > panel.maxSize)) { + resize_y = panel.height - panel.maxSize + } + if (mainPanel.minSize - resize_y > mainPanel.height) { + resize_y = mainPanel.minSize - mainPanel.height + } + break + } + tmp.diff_x = resize_x + tmp.diff_y = resize_y + switch (tmp.type) { + case 'top': + case 'preview': + case 'bottom': + tmp.diff_x = 0 + if (p.length > 0) p[0].style.top = (tmp.value + tmp.diff_y) + 'px' + break + case 'left': + case 'right': + tmp.diff_y = 0 + if (p.length > 0) p[0].style.left = (tmp.value + tmp.diff_x) + 'px' + break + } + // event after + edata.finish() + } + } + refresh(panel) { + let self = this + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + if (panel == null) panel = null + let time = Date.now() + // event before + let edata = self.trigger('refresh', { target: (panel != null ? panel : self.name), object: self.get(panel) }) + if (edata.isCancelled === true) return + // self.unlock(panel); + if (typeof panel == 'string') { + let p = self.get(panel) + if (p == null) return + let pname = '#layout_'+ self.name + '_panel_'+ p.type + let rname = '#layout_'+ self.name +'_resizer_'+ p.type + // apply properties to the panel + query(self.box).find(pname).css({ display: p.hidden ? 'none' : 'block' }) + if (p.resizable) { + query(self.box).find(rname).show() + } else { + query(self.box).find(rname).hide() + } + // insert content + if (typeof p.html == 'object' && typeof p.html.render === 'function') { + p.html.box = query(self.box).find(pname +'> .w2ui-panel-content')[0] + setTimeout(() => { + // need to remove unnecessary classes + if (query(self.box).find(pname +'> .w2ui-panel-content').length > 0) { + query(self.box).find(pname +'> .w2ui-panel-content') + .removeClass() + .removeAttr('name') + .addClass('w2ui-panel-content') + .css('overflow', p.overflow)[0].style.cssText += ';' + p.style + } + if (p.html && typeof p.html.render == 'function') { + p.html.render() // do not do .render(box); + } + }, 1) + } else { + // need to remove unnecessary classes + if (query(self.box).find(pname +'> .w2ui-panel-content').length > 0) { + query(self.box).find(pname +'> .w2ui-panel-content') + .removeClass() + .removeAttr('name') + .addClass('w2ui-panel-content') + .html(p.html) + .css('overflow', p.overflow)[0].style.cssText += ';' + p.style + } + } + // if there are tabs and/or toolbar - render it + let tmp = query(self.box).find(pname +'> .w2ui-panel-tabs') + if (p.show.tabs) { + if (tmp.find('[name='+ p.tabs.name +']').length === 0 && p.tabs != null) { + p.tabs.render(tmp.get(0)) + } else { + p.tabs.refresh() + } + } else { + tmp.html('').removeClass('w2ui-tabs').hide() + } + tmp = query(self.box).find(pname +'> .w2ui-panel-toolbar') + if (p.show.toolbar) { + if (tmp.find('[name='+ p.toolbar.name +']').length === 0 && p.toolbar != null) { + p.toolbar.render(tmp.get(0)) + } else { + p.toolbar.refresh() + } + } else { + tmp.html('').removeClass('w2ui-toolbar').hide() + } + // show title + tmp = query(self.box).find(pname +'> .w2ui-panel-title') + if (p.title) { + tmp.html(p.title).show() + } else { + tmp.html('').hide() + } + } else { + if (query(self.box).find('#layout_'+ self.name +'_panel_main').length === 0) { + self.render() + return + } + self.resize() + // refresh all of them + for (let p1 = 0; p1 < this.panels.length; p1++) { self.refresh(this.panels[p1].type) } + } + edata.finish() + return Date.now() - time + } + resize() { + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + if (!this.box) return false + let time = Date.now() + // event before + let tmp = this.last.resize + let edata = this.trigger('resize', { target: this.name, + panel: tmp ? tmp.type : 'all', diff_x: tmp ? tmp.diff_x : 0, diff_y: tmp ? tmp.diff_y : 0 }) + if (edata.isCancelled === true) return + if (this.padding < 0) this.padding = 0 + // layout itself + // width includes border and padding, we need to exclude that so panels + // are sized correctly + let width = w2utils.getSize(query(this.box), 'width') + let height = w2utils.getSize(query(this.box), 'height') + let self = this + // panels + let pmain = this.get('main') + let pprev = this.get('preview') + let pleft = this.get('left') + let pright = this.get('right') + let ptop = this.get('top') + let pbottom = this.get('bottom') + let sprev = (pprev != null && pprev.hidden !== true ? true : false) + let sleft = (pleft != null && pleft.hidden !== true ? true : false) + let sright = (pright != null && pright.hidden !== true ? true : false) + let stop = (ptop != null && ptop.hidden !== true ? true : false) + let sbottom = (pbottom != null && pbottom.hidden !== true ? true : false) + let l, t, w, h + // calculate % + for (let p = 0; p < w2panels.length; p++) { + if (w2panels[p] === 'main') continue + tmp = this.get(w2panels[p]) + if (!tmp) continue + let str = String(tmp.size || 0) + if (str.substr(str.length-1) == '%') { + let tmph = height + if (tmp.type == 'preview') { + tmph = tmph - + (ptop && !ptop.hidden ? ptop.sizeCalculated : 0) - + (pbottom && !pbottom.hidden ? pbottom.sizeCalculated : 0) + } + tmp.sizeCalculated = parseInt((tmp.type == 'left' || tmp.type == 'right' ? width : tmph) * parseFloat(tmp.size) / 100) + } else { + tmp.sizeCalculated = parseInt(tmp.size) + } + tmp.sizeCalculated = Math.max(tmp.sizeCalculated, parseInt(tmp.minSize)) + } + // negative size + if (String(pright.size).substr(0, 1) == '-') { + if (sleft && String(pleft.size).substr(0, 1) == '-') { + console.log('ERROR: you cannot have both left panel.size and right panel.size be negative.') + } else { + pright.sizeCalculated = width - (sleft ? pleft.sizeCalculated : 0) + parseInt(pright.size) + } + } + if (String(pleft.size).substr(0, 1) == '-') { + if (sright && String(pright.size).substr(0, 1) == '-') { + console.log('ERROR: you cannot have both left panel.size and right panel.size be negative.') + } else { + pleft.sizeCalculated = width - (sright ? pright.sizeCalculated : 0) + parseInt(pleft.size) + } + } + // top if any + if (ptop != null && ptop.hidden !== true) { + l = 0 + t = 0 + w = width + h = ptop.sizeCalculated + query(this.box).find('#layout_'+ this.name +'_panel_top') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }) + ptop.width = w + ptop.height = h + // resizer + if (ptop.resizable) { + t = ptop.sizeCalculated - (this.padding === 0 ? this.resizer : 0) + h = (this.resizer > this.padding ? this.resizer : this.padding) + query(this.box).find('#layout_'+ this.name +'_resizer_top') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ns-resize' + }) + .off('mousedown') + .on('mousedown', function(event) { + event.preventDefault() + // event before + let edata = self.trigger('resizerClick', { target: 'top', originalEvent: event }) + if (edata.isCancelled === true) return + // default action + w2ui[self.name].last.events.resizeStart('top', event) + // event after + edata.finish() + return false + }) + } + } else { + query(this.box).find('#layout_'+ this.name +'_panel_top').hide() + query(this.box).find('#layout_'+ this.name +'_resizer_top').hide() + } + // left if any + if (pleft != null && pleft.hidden !== true) { + l = 0 + t = 0 + (stop ? ptop.sizeCalculated + this.padding : 0) + w = pleft.sizeCalculated + h = height - (stop ? ptop.sizeCalculated + this.padding : 0) - + (sbottom ? pbottom.sizeCalculated + this.padding : 0) + query(this.box).find('#layout_'+ this.name +'_panel_left') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }) + pleft.width = w + pleft.height = h + // resizer + if (pleft.resizable) { + l = pleft.sizeCalculated - (this.padding === 0 ? this.resizer : 0) + w = (this.resizer > this.padding ? this.resizer : this.padding) + query(this.box).find('#layout_'+ this.name +'_resizer_left') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ew-resize' + }) + .off('mousedown') + .on('mousedown', function(event) { + event.preventDefault() + // event before + let edata = self.trigger('resizerClick', { target: 'left', originalEvent: event }) + if (edata.isCancelled === true) return + // default action + w2ui[self.name].last.events.resizeStart('left', event) + // event after + edata.finish() + return false + }) + } + } else { + query(this.box).find('#layout_'+ this.name +'_panel_left').hide() + query(this.box).find('#layout_'+ this.name +'_resizer_left').hide() + } + // right if any + if (pright != null && pright.hidden !== true) { + l = width - pright.sizeCalculated + t = 0 + (stop ? ptop.sizeCalculated + this.padding : 0) + w = pright.sizeCalculated + h = height - (stop ? ptop.sizeCalculated + this.padding : 0) - + (sbottom ? pbottom.sizeCalculated + this.padding : 0) + query(this.box).find('#layout_'+ this.name +'_panel_right') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }) + pright.width = w + pright.height = h + // resizer + if (pright.resizable) { + l = l - this.padding + w = (this.resizer > this.padding ? this.resizer : this.padding) + query(this.box).find('#layout_'+ this.name +'_resizer_right') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ew-resize' + }) + .off('mousedown') + .on('mousedown', function(event) { + event.preventDefault() + // event before + let edata = self.trigger('resizerClick', { target: 'right', originalEvent: event }) + if (edata.isCancelled === true) return + // default action + w2ui[self.name].last.events.resizeStart('right', event) + // event after + edata.finish() + return false + }) + } + } else { + query(this.box).find('#layout_'+ this.name +'_panel_right').hide() + query(this.box).find('#layout_'+ this.name +'_resizer_right').hide() + } + // bottom if any + if (pbottom != null && pbottom.hidden !== true) { + l = 0 + t = height - pbottom.sizeCalculated + w = width + h = pbottom.sizeCalculated + query(this.box).find('#layout_'+ this.name +'_panel_bottom') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }) + pbottom.width = w + pbottom.height = h + // resizer + if (pbottom.resizable) { + t = t - (this.padding === 0 ? 0 : this.padding) + h = (this.resizer > this.padding ? this.resizer : this.padding) + query(this.box).find('#layout_'+ this.name +'_resizer_bottom') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ns-resize' + }) + .off('mousedown') + .on('mousedown', function(event) { + event.preventDefault() + // event before + let edata = self.trigger('resizerClick', { target: 'bottom', originalEvent: event }) + if (edata.isCancelled === true) return + // default action + w2ui[self.name].last.events.resizeStart('bottom', event) + // event after + edata.finish() + return false + }) + } + } else { + query(this.box).find('#layout_'+ this.name +'_panel_bottom').hide() + query(this.box).find('#layout_'+ this.name +'_resizer_bottom').hide() + } + // main - always there + l = 0 + (sleft ? pleft.sizeCalculated + this.padding : 0) + t = 0 + (stop ? ptop.sizeCalculated + this.padding : 0) + w = width - (sleft ? pleft.sizeCalculated + this.padding : 0) - + (sright ? pright.sizeCalculated + this.padding: 0) + h = height - (stop ? ptop.sizeCalculated + this.padding : 0) - + (sbottom ? pbottom.sizeCalculated + this.padding : 0) - + (sprev ? pprev.sizeCalculated + this.padding : 0) + query(this.box) + .find('#layout_'+ this.name +'_panel_main') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }) + pmain.width = w + pmain.height = h + // preview if any + if (pprev != null && pprev.hidden !== true) { + l = 0 + (sleft ? pleft.sizeCalculated + this.padding : 0) + t = height - (sbottom ? pbottom.sizeCalculated + this.padding : 0) - pprev.sizeCalculated + w = width - (sleft ? pleft.sizeCalculated + this.padding : 0) - + (sright ? pright.sizeCalculated + this.padding : 0) + h = pprev.sizeCalculated + query(this.box).find('#layout_'+ this.name +'_panel_preview') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }) + pprev.width = w + pprev.height = h + // resizer + if (pprev.resizable) { + t = t - (this.padding === 0 ? 0 : this.padding) + h = (this.resizer > this.padding ? this.resizer : this.padding) + query(this.box).find('#layout_'+ this.name +'_resizer_preview') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ns-resize' + }) + .off('mousedown') + .on('mousedown', function(event) { + event.preventDefault() + // event before + let edata = self.trigger('resizerClick', { target: 'preview', originalEvent: event }) + if (edata.isCancelled === true) return + // default action + w2ui[self.name].last.events.resizeStart('preview', event) + // event after + edata.finish() + return false + }) + } + } else { + query(this.box).find('#layout_'+ this.name +'_panel_preview').hide() + query(this.box).find('#layout_'+ this.name +'_resizer_preview').hide() + } + // display tabs and toolbar if needed + for (let p1 = 0; p1 < w2panels.length; p1++) { + let pan = this.get(w2panels[p1]) + let tmp2 = '#layout_'+ this.name +'_panel_'+ w2panels[p1] +' > .w2ui-panel-' + let tabHeight = 0 + if (pan) { + if (pan.title) { + let el = query(this.box).find(tmp2 + 'title').css({ top: tabHeight + 'px', display: 'block' }) + tabHeight += w2utils.getSize(el, 'height') + } + if (pan.show.tabs) { + let el = query(this.box).find(tmp2 + 'tabs').css({ top: tabHeight + 'px', display: 'block' }) + tabHeight += w2utils.getSize(el, 'height') + } + if (pan.show.toolbar) { + let el = query(this.box).find(tmp2 + 'toolbar').css({ top: tabHeight + 'px', display: 'block' }) + tabHeight += w2utils.getSize(el, 'height') + } + } + query(this.box).find(tmp2 + 'content').css({ display: 'block' }).css({ top: tabHeight + 'px' }) + } + edata.finish() + return Date.now() - time + } + destroy() { + // event before + let edata = this.trigger('destroy', { target: this.name }) + if (edata.isCancelled === true) return + if (w2ui[this.name] == null) return false + // clean up + if (query(this.box).find('#layout_'+ this.name +'_panel_main').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-layout') + .html('') + } + this.last.observeResize?.disconnect() + delete w2ui[this.name] + // event after + edata.finish() + if (this.last.events && this.last.events.resize) { + query(window).off('resize', this.last.events.resize) + } + return true + } + lock(panel, msg, showSpinner) { + if (w2panels.indexOf(panel) == -1) { + console.log('ERROR: First parameter needs to be the a valid panel name.') + return + } + let args = Array.from(arguments) + args[0] = '#layout_'+ this.name + '_panel_' + panel + w2utils.lock(...args) + } + unlock(panel, speed) { + if (w2panels.indexOf(panel) == -1) { + console.log('ERROR: First parameter needs to be the a valid panel name.') + return + } + let nm = '#layout_'+ this.name + '_panel_' + panel + w2utils.unlock(nm, speed) + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: jQuery, w2utils, w2base, w2toolbar, w2field + * + * == TODO == + * - problem with .set() and arrays, array get extended too, but should be replaced + * - allow functions in routeData (also add routeData to list/enum) + * - send parsed URL to the event if there is routeData + * - add selectType: 'none' so that no selection can be make but with mouse + * - focus/blur for selectType = cell not display grayed out selection + * - allow enum in inline edit (see https://github.com/vitmalina/w2ui/issues/911#issuecomment-107341193) + * - remote source, but localSort/localSearch + * - promise for request, load, save, etc. + * - onloadmore event (so it will be easy to implement remote data source with local sort) + * - status() - clears on next select, etc. Should not if it is off + * + * == DEMOS To create == + * - batch for disabled buttons + * - natural sort + * - resize on max content + * + * == 2.0 changes + * - toolbarInput - deprecated, toolbarSearch stays + * - searchSuggest + * - searchSave, searchSelected, savedSearches, defaultSearches, useLocalStorage, searchFieldTooltip + * - cache, cacheSave + * - onSearchSave, onSearchRemove, onSearchSelect + * - show.searchLogic + * - show.searchSave + * - refreshSearch + * - initAllFields -> searchInitInput + * - textSearch - deprecated in favor of defaultOperator + * - grid.confirm - refactored + * - grid.message - refactored + * - search.type == 'text' can have 'in' and 'not in' operators, then it will switch to enum + * - grid.find(..., displayedOnly) + * - column.render(..., this) - added + * - observeResize for the box + * - remove edit.type == 'select' + * - editDone(...) + * - liveSearch + * - deprecated onUnselect event + * - requestComplete(data, action, callBack, resolve, reject) - new argument list + * - msgAJAXError -> msgHTTPError + * - aded msgServerError + * - deleted grid.method + * - added grid.prepareParams + * - added mouseEnter/mouseLeave + * - grid.show.columnReorder -> grid.reorderRows + * - updagte docs search.label (not search.text) + */ + +class w2grid extends w2base { + constructor(options) { + super(options.name) + this.name = null + this.box = null // HTML element that hold this element + this.columns = [] // { field, text, size, attr, render, hidden, gridMinWidth, editable } + this.columnGroups = [] // { span: int, text: 'string', main: true/false, style: 'string' } + this.records = [] // { recid: int(required), field1: 'value1', ... fieldN: 'valueN', style: 'string', changes: object } + this.summary = [] // array of summary records, same structure as records array + this.searches = [] // { type, label, field, attr, text, hidden } + this.toolbar = {} // if not empty object; then it is toolbar object + this.ranges = [] + this.contextMenu = [] + this.searchMap = {} // re-map search fields + this.searchData = [] + this.sortMap = {} // re-map sort fields + this.sortData = [] + this.savedSearches = [] + this.defaultSearches = [] + this.total = 0 // server total + this.recid = null // field from records to be used as recid + // internal + this.last = { + field : '', // last search field, e.g. 'all' + label : '', // last search field label, e.g. 'All Fields' + logic : 'AND', // last search logic, e.g. 'AND' or 'OR' + search : '', // last search text + searchIds : [], // last search IDs + selection : { // last selection details + indexes : [], + columns : {} + }, + saved_sel : null, // last result of selectionSave() + multi : false, // last multi flag, true when searching for multiple fields + scrollTop : 0, // last scrollTop position + scrollLeft : 0, // last scrollLeft position + colStart : 0, // for column virtual scrolling + colEnd : 0, // for column virtual scrolling + fetch: { + action : '', // last fetch command, e.g. 'load' + offset : null, // last fetch offset, integer + start : 0, // timestamp of start of last fetch request + response : 0, // time it took to complete the last fetch request in seconds + options : null, + controller: null, + loaded : false, // data is loaded from the server + hasMore : false // flag to indicate if there are more items to pull from the server + }, + pull_more : false, + pull_refresh : true, + range_start : null, // last range start cell + range_end : null, // last range end cell + sel_ind : null, // last selected cell index + sel_col : null, // last selected column + sel_type : null, // last selection type, e.g. 'click' or 'key' + sel_recid : null, // last selected record id + idCache : {}, // object, id cache for get() + move : null, // object, move details + cancelClick : null, // boolean flag to indicate if the click event should be ignored, set during mouseMove() + inEditMode : false, // flag to indicate if we're currently in edit mode during inline editing + _edit : null, // object with details on the last edited cell, { value, index, column, recid } + kbd_timer : null, // last id of blur() timer + marker_timer : null, // last id of markSearch() timer + click_time : null, // timestamp of last click + click_recid : null, // last clicked record id + bubbleEl : null, // last bubble element + colResizing : false, // flag to indicate that a column is currently being resized + tmp : null, // object with last column resizing details + copy_event : null, // last copy event + userSelect : '', // last user select type, e.g. 'text' + columnDrag : false, // false or an object with a remove() method + state : null, // last grid state + show_extra : 0, // last show extra for virtual scrolling + toolbar_height: 0, // height of grid's toolbar + } + this.header = '' + this.url = '' + this.limit = 100 + this.offset = 0 // how many records to skip (for infinite scroll) when pulling from server + this.postData = {} + this.routeData = {} + this.httpHeaders = {} + this.show = { + header : false, + toolbar : false, + footer : false, + columnMenu : true, + columnHeaders : true, + lineNumbers : false, + expandColumn : false, + selectColumn : false, + emptyRecords : true, + toolbarReload : true, + toolbarColumns : false, + toolbarSearch : true, + toolbarAdd : false, + toolbarEdit : false, + toolbarDelete : false, + toolbarSave : false, + searchAll : true, + searchLogic : true, + searchHiddenMsg : false, + searchSave : true, + statusRange : true, + statusBuffered : false, + statusRecordID : true, + statusSelection : true, + statusResponse : true, + statusSort : false, + statusSearch : false, + recordTitles : false, + selectionBorder : true, + skipRecords : true, + saveRestoreState: true + } + this.stateId = null // Custom state name for stateSave, stateRestore and stateReset + this.hasFocus = false + this.autoLoad = true // for infinite scroll + this.fixedBody = true // if false; then grid grows with data + this.recordHeight = 32 + this.lineNumberWidth = 34 + this.keyboard = true + this.selectType = 'row' // can be row|cell + this.liveSearch = false // if true, it will auto search if typed in search_all + this.multiSearch = true + this.multiSelect = true + this.multiSort = true + this.reorderColumns = false + this.reorderRows = false + this.showExtraOnSearch = 0 // show extra records before and after on search + this.markSearch = true + this.columnTooltip = 'top|bottom' // can be top, bottom, left, right + this.disableCVS = false // disable Column Virtual Scroll + this.nestedFields = true // use field name containing dots as separator to look into object + this.vs_start = 150 + this.vs_extra = 5 + this.style = '' + this.tabIndex = null + this.dataType = null // if defined, then overwrites w2utils.settings.dataType + this.parser = null + this.advanceOnEdit = true // automatically begin editing the next cell after submitting an inline edit? + this.useLocalStorage = true + // default values for the column + this.colTemplate = { + text : '', // column text (can be a function) + field : '', // field name to map the column to a record + size : null, // size of column in px or % + min : 20, // minimum width of column in px + max : null, // maximum width of column in px + gridMinWidth : null, // minimum width of the grid when column is visible + sizeCorrected : null, // read only, corrected size (see explanation below) + sizeCalculated : null, // read only, size in px (see explanation below) + sizeOriginal : null, // size as defined + sizeType : null, // px or % + hidden : false, // indicates if column is hidden + sortable : false, // indicates if column is sortable + sortMode : null, // sort mode ('default'|'natural'|'i18n') or custom compare function + searchable : false, // bool/string: int,float,date,... or an object to create search field + resizable : true, // indicates if column is resizable + hideable : true, // indicates if column can be hidden + autoResize : null, // indicates if column can be auto-resized by double clicking on the resizer + attr : '', // string that will be inside the tag + style : '', // additional style for the td tag + render : null, // string or render function + title : null, // string or function for the title property for the column cells + tooltip : null, // string for the title property for the column header + editable : {}, // editable object (see explanation below) + frozen : false, // indicates if the column is fixed to the left + info : null, // info bubble, can be bool/object + clipboardCopy : false, // if true (or string or function), it will display clipboard copy icon + } + // these column properties will be saved in stateSave() + this.stateColProps = { + text : false, + field : true, + size : true, + min : false, + max : false, + gridMinWidth : false, + sizeCorrected : false, + sizeCalculated : true, + sizeOriginal : true, + sizeType : true, + hidden : true, + sortable : false, + sortMode : true, + searchable : false, + resizable : false, + hideable : false, + autoResize : false, + attr : false, + style : false, + render : false, + title : false, + tooltip : false, + editable : false, + frozen : true, + info : false, + clipboardCopy : false + } + this.msgDelete = 'Are you sure you want to delete ${count} ${records}?' + this.msgNotJSON = 'Returned data is not in valid JSON format.' + this.msgHTTPError = 'HTTP error. See console for more details.' + this.msgServerError= 'Server error' + this.msgRefresh = 'Refreshing...' + this.msgNeedReload = 'Your remote data source record count has changed, reloading from the first record.' + this.msgEmpty = '' // if not blank, then it is message when server returns no records + this.buttons = { + 'reload' : { type: 'button', id: 'w2ui-reload', icon: 'w2ui-icon-reload', tooltip: 'Reload data in the list' }, + 'columns' : { type: 'menu-check', id: 'w2ui-column-on-off', icon: 'w2ui-icon-columns', tooltip: 'Show/hide columns', + overlay: { align: 'none' } + }, + 'search' : { type: 'html', id: 'w2ui-search', + html: '' + }, + 'add' : { type: 'button', id: 'w2ui-add', text: 'Add New', tooltip: 'Add new record', icon: 'w2ui-icon-plus' }, + 'edit' : { type: 'button', id: 'w2ui-edit', text: 'Edit', tooltip: 'Edit selected record', icon: 'w2ui-icon-pencil', batch: 1, disabled: true }, + 'delete' : { type: 'button', id: 'w2ui-delete', text: 'Delete', tooltip: 'Delete selected records', icon: 'w2ui-icon-cross', batch: true, disabled: true }, + 'save' : { type: 'button', id: 'w2ui-save', text: 'Save', tooltip: 'Save changed records', icon: 'w2ui-icon-check' } + } + this.operators = { // for search fields + 'text' : ['is', 'begins', 'contains', 'ends'], // could have "in" and "not in" + 'number' : ['=', 'between', '>', '<', '>=', '<='], + 'date' : ['is', { oper: 'less', text: 'before'}, { oper: 'more', text: 'since' }, 'between'], + 'list' : ['is'], + 'hex' : ['is', 'between'], + 'color' : ['is', 'begins', 'contains', 'ends'], + 'enum' : ['in', 'not in'] + // -- all possible + // "text" : ['is', 'begins', 'contains', 'ends'], + // "number" : ['is', 'between', 'less:less than', 'more:more than', 'null:is null', 'not null:is not null'], + // "list" : ['is', 'null:is null', 'not null:is not null'], + // "enum" : ['in', 'not in', 'null:is null', 'not null:is not null'] + } + this.defaultOperator = { + 'text' : 'begins', + 'number' : '=', + 'date' : 'is', + 'list' : 'is', + 'enum' : 'in', + 'hex' : 'begins', + 'color' : 'begins' + } + // map search field type to operator + this.operatorsMap = { + 'text' : 'text', + 'int' : 'number', + 'float' : 'number', + 'money' : 'number', + 'currency' : 'number', + 'percent' : 'number', + 'hex' : 'hex', + 'alphanumeric' : 'text', + 'color' : 'color', + 'date' : 'date', + 'time' : 'date', + 'datetime' : 'date', + 'list' : 'list', + 'combo' : 'text', + 'enum' : 'enum', + 'file' : 'enum', + 'select' : 'list', + 'radio' : 'list', + 'checkbox' : 'list', + 'toggle' : 'list' + } + // events + this.onAdd = null + this.onEdit = null + this.onRequest = null // called on any server event + this.onLoad = null + this.onDelete = null + this.onSave = null + this.onSelect = null + this.onClick = null + this.onDblClick = null + this.onContextMenu = null + this.onContextMenuClick = null // when context menu item selected + this.onColumnClick = null + this.onColumnDblClick = null + this.onColumnResize = null + this.onColumnAutoResize = null + this.onSort = null + this.onSearch = null + this.onSearchOpen = null + this.onChange = null // called when editable record is changed + this.onRestore = null // called when editable record is restored + this.onExpand = null + this.onCollapse = null + this.onError = null + this.onKeydown = null + this.onToolbar = null // all events from toolbar + this.onColumnOnOff = null + this.onCopy = null + this.onPaste = null + this.onSelectionExtend = null + this.onEditField = null + this.onRender = null + this.onRefresh = null + this.onReload = null + this.onResize = null + this.onDestroy = null + this.onStateSave = null + this.onStateRestore = null + this.onFocus = null + this.onBlur = null + this.onReorderRow = null + this.onSearchSave = null + this.onSearchRemove = null + this.onSearchSelect = null + this.onColumnSelect = null + this.onColumnDragStart = null + this.onColumnDragEnd = null + this.onResizerDblClick = null + this.onMouseEnter = null // mouse enter over record event + this.onMouseLeave = null + // need deep merge, should be extend, not objectAssign + w2utils.extend(this, options) + // check if there are records without recid + if (Array.isArray(this.records)) { + let remove = [] // remove from records as they are summary + this.records.forEach((rec, ind) => { + if (rec[this.recid] != null) { + rec.recid = rec[this.recid] + } + if (rec.recid == null) { + console.log('ERROR: Cannot add records without recid. (obj: '+ this.name +')') + } + if (rec.w2ui?.summary === true) { + this.summary.push(rec) + remove.push(ind) // cannot remove here as it will mess up array walk thru + } + }) + remove.sort() + for (let t = remove.length-1; t >= 0; t--) { + this.records.splice(remove[t], 1) + } + } + // add searches + if (Array.isArray(this.columns)) { + this.columns.forEach((col, ind) => { + col = w2utils.extend({}, this.colTemplate, col) + this.columns[ind] = col + let search = col.searchable + if (search == null || search === false || this.getSearch(col.field) != null) return + if (w2utils.isPlainObject(search)) { + this.addSearch(w2utils.extend({ field: col.field, label: col.text, type: 'text' }, search)) + } else { + let stype = col.searchable + let attr = '' + if (col.searchable === true) { + stype = 'text' + attr = 'size="20"' + } + this.addSearch({ field: col.field, label: col.text, type: stype, attr: attr }) + } + }) + } + // add icon to default searches if not defined + if (Array.isArray(this.defaultSearches)) { + this.defaultSearches.forEach((search, ind) => { + search.id = 'default-'+ ind + search.icon ??= 'w2ui-icon-search' + }) + } + // check if there are saved searches in localStorage + let data = this.cache('searches') + if (Array.isArray(data)) { + data.forEach(search => { + this.savedSearches.push({ + id: search.id ?? 'none', + text: search.text ?? 'none', + icon: 'w2ui-icon-search', + remove: true, + logic: search.logic ?? 'AND', + data: search.data ?? [] + }) + }) + } + // render if box specified + if (typeof this.box == 'string') this.box = query(this.box).get(0) + if (this.box) this.render(this.box) + } + add(record, first) { + if (!Array.isArray(record)) record = [record] + let added = 0 + for (let i = 0; i < record.length; i++) { + let rec = record[i] + if (rec[this.recid] != null) { + rec.recid = rec[this.recid] + } + if (rec.recid == null) { + console.log('ERROR: Cannot add record without recid. (obj: '+ this.name +')') + continue + } + if (rec.w2ui?.summary === true) { + if (first) this.summary.unshift(rec); else this.summary.push(rec) + } else { + if (first) this.records.unshift(rec); else this.records.push(rec) + } + added++ + } + let url = this.url?.get ?? this.url + if (!url) { + this.total = this.records.length + this.localSort(false, true) + this.localSearch() + // do not call this.refresh(), this is unnecessary, heavy, and messes with the toolbar. + // this.refreshBody() + // this.resizeRecords() + this.refresh() + } else { + this.refresh() // ?? should it be reload? + } + return added + } + find(obj, returnIndex, displayedOnly) { + if (obj == null) obj = {} + let recs = [] + let hasDots = false + // check if property is nested - needed for speed + for (let o in obj) if (String(o).indexOf('.') != -1) hasDots = true + // look for an item + let start = displayedOnly ? this.last.range_start : 0 + let end = displayedOnly ? this.last.range_end + 1: this.records.length + if (end > this.records.length) end = this.records.length + for (let i = start; i < end; i++) { + let match = true + for (let o in obj) { + let val = this.records[i][o] + if (hasDots && String(o).indexOf('.') != -1) val = this.parseField(this.records[i], o) + if (obj[o] == 'not-null') { + if (val == null || val === '') match = false + } else { + if (obj[o] != val) match = false + } + } + if (match && returnIndex !== true) recs.push(this.records[i].recid) + if (match && returnIndex === true) recs.push(i) + } + return recs + } + set(recid, record, noRefresh) { // does not delete existing, but overrides on top of it + if ((typeof recid == 'object') && (recid !== null)) { + noRefresh = record + record = recid + recid = null + } + // update all records + if (recid == null) { + for (let i = 0; i < this.records.length; i++) { + w2utils.extend(this.records[i], record) // recid is the whole record + } + if (noRefresh !== true) this.refresh() + } else { // find record to update + let ind = this.get(recid, true) + if (ind == null) return false + let isSummary = (this.records[ind] && this.records[ind].recid == recid ? false : true) + if (isSummary) { + w2utils.extend(this.summary[ind], record) + } else { + w2utils.extend(this.records[ind], record) + } + if (noRefresh !== true) this.refreshRow(recid, ind) // refresh only that record + } + return true + } + get(recid, returnIndex) { + // search records + if (Array.isArray(recid)) { + let recs = [] + for (let i = 0; i < recid.length; i++) { + let v = this.get(recid[i], returnIndex) + if (v !== null) + recs.push(v) + } + return recs + } else { + // get() must be fast, implements a cache to bypass loop over all records + // most of the time. + let idCache = this.last.idCache + if (!idCache) { + this.last.idCache = idCache = {} + } + let i = idCache[recid] + if (typeof(i) === 'number') { + if (i >= 0 && i < this.records.length && this.records[i].recid == recid) { + if (returnIndex === true) return i; else return this.records[i] + } + // summary indexes are stored as negative numbers, try them now. + i = ~i + if (i >= 0 && i < this.summary.length && this.summary[i].recid == recid) { + if (returnIndex === true) return i; else return this.summary[i] + } + // wrong index returned, clear cache + this.last.idCache = idCache = {} + } + for (let i = 0; i < this.records.length; i++) { + if (this.records[i].recid == recid) { + idCache[recid] = i + if (returnIndex === true) return i; else return this.records[i] + } + } + // search summary + for (let i = 0; i < this.summary.length; i++) { + if (this.summary[i].recid == recid) { + idCache[recid] = ~i + if (returnIndex === true) return i; else return this.summary[i] + } + } + return null + } + } + getFirst(offset) { + if (this.records.length == 0) return null + let rec = this.records[0] + let tmp = this.last.searchIds + if (this.searchData.length > 0) { + if (Array.isArray(tmp) && tmp.length > 0) { + rec = this.records[tmp[offset || 0]] + } else { + rec = null + } + } + return rec + } + remove() { + let removed = 0 + for (let a = 0; a < arguments.length; a++) { + for (let r = this.records.length-1; r >= 0; r--) { + if (this.records[r].recid == arguments[a]) { this.records.splice(r, 1); removed++ } + } + for (let r = this.summary.length-1; r >= 0; r--) { + if (this.summary[r].recid == arguments[a]) { this.summary.splice(r, 1); removed++ } + } + } + let url = this.url?.get ?? this.url + if (!url) { + this.localSort(false, true) + this.localSearch() + } + this.refresh() + return removed + } + addColumn(before, columns) { + let added = 0 + if (arguments.length == 1) { + columns = before + before = this.columns.length + } else { + if (typeof before == 'string') before = this.getColumn(before, true) + if (before == null) before = this.columns.length + } + if (!Array.isArray(columns)) columns = [columns] + for (let i = 0; i < columns.length; i++) { + let col = w2utils.extend({}, this.colTemplate, columns[i]) + this.columns.splice(before, 0, col) + // if column is searchable, add search field + if (columns[i].searchable) { + let stype = columns[i].searchable + let attr = '' + if (columns[i].searchable === true) { stype = 'text'; attr = 'size="20"' } + this.addSearch({ field: columns[i].field, label: columns[i].text, type: stype, attr: attr }) + } + before++ + added++ + } + this.refresh() + return added + } + removeColumn() { + let removed = 0 + for (let a = 0; a < arguments.length; a++) { + for (let r = this.columns.length-1; r >= 0; r--) { + if (this.columns[r].field == arguments[a]) { + if (this.columns[r].searchable) this.removeSearch(arguments[a]) + this.columns.splice(r, 1) + removed++ + } + } + } + this.refresh() + return removed + } + getColumn(field, returnIndex) { + // no arguments - return fields of all columns + if (arguments.length === 0) { + let ret = [] + for (let i = 0; i < this.columns.length; i++) ret.push(this.columns[i].field) + return ret + } + // find column + for (let i = 0; i < this.columns.length; i++) { + if (this.columns[i].field == field) { + if (returnIndex === true) return i; else return this.columns[i] + } + } + return null + } + updateColumn(fields, updates) { + let effected = 0 + fields = (Array.isArray(fields) ? fields : [fields]) + fields.forEach((colName) => { + this.columns.forEach((col) => { + if (col.field == colName) { + let _updates = w2utils.clone(updates) + Object.keys(_updates).forEach((key) => { + // if it is a function + if (typeof _updates[key] == 'function') { + _updates[key] = _updates[key](col) + } + if (col[key] != _updates[key]) effected++ + }) + w2utils.extend(col, _updates) + } + }) + }) + if (effected > 0) { + this.refresh() // need full refresh due to colgroups not reassigning properly + } + return effected + } + toggleColumn() { + return this.updateColumn(Array.from(arguments), { hidden(col) { return !col.hidden } }) + } + showColumn() { + return this.updateColumn(Array.from(arguments), { hidden: false }) + } + hideColumn() { + return this.updateColumn(Array.from(arguments), { hidden: true }) + } + addSearch(before, search) { + let added = 0 + if (arguments.length == 1) { + search = before + before = this.searches.length + } else { + if (typeof before == 'string') before = this.getSearch(before, true) + if (before == null) before = this.searches.length + } + if (!Array.isArray(search)) search = [search] + for (let i = 0; i < search.length; i++) { + this.searches.splice(before, 0, search[i]) + before++ + added++ + } + this.searchClose() + return added + } + removeSearch() { + let removed = 0 + for (let a = 0; a < arguments.length; a++) { + for (let r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a]) { this.searches.splice(r, 1); removed++ } + } + } + this.searchClose() + return removed + } + getSearch(field, returnIndex) { + // no arguments - return fields of all searches + if (arguments.length === 0) { + let ret = [] + for (let i = 0; i < this.searches.length; i++) ret.push(this.searches[i].field) + return ret + } + // find search + for (let i = 0; i < this.searches.length; i++) { + if (this.searches[i].field == field) { + if (returnIndex === true) return i; else return this.searches[i] + } + } + return null + } + toggleSearch() { + let effected = 0 + for (let a = 0; a < arguments.length; a++) { + for (let r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a]) { + this.searches[r].hidden = !this.searches[r].hidden + effected++ + } + } + } + this.searchClose() + return effected + } + showSearch() { + let shown = 0 + for (let a = 0; a < arguments.length; a++) { + for (let r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a] && this.searches[r].hidden !== false) { + this.searches[r].hidden = false + shown++ + } + } + } + this.searchClose() + return shown + } + hideSearch() { + let hidden = 0 + for (let a = 0; a < arguments.length; a++) { + for (let r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a] && this.searches[r].hidden !== true) { + this.searches[r].hidden = true + hidden++ + } + } + } + this.searchClose() + return hidden + } + getSearchData(field) { + for (let i = 0; i < this.searchData.length; i++) { + if (this.searchData[i].field == field) return this.searchData[i] + } + return null + } + localSort(silent, noResetRefresh) { + let obj = this + let url = this.url?.get ?? this.url + if (url) { + console.log('ERROR: grid.localSort can only be used on local data source, grid.url should be empty.') + return + } + if (Object.keys(this.sortData).length === 0) return + let time = Date.now() + // process date fields + this.selectionSave() + this.prepareData() + if (!noResetRefresh) { + this.reset() + } + // process sortData + for (let i = 0; i < this.sortData.length; i++) { + let column = this.getColumn(this.sortData[i].field) + if (!column) return // TODO: ability to sort columns when they are not part of colums array + if (typeof column.render == 'string') { + if (['date', 'age'].indexOf(column.render.split(':')[0]) != -1) { + this.sortData[i].field_ = column.field + '_' + } + if (['time'].indexOf(column.render.split(':')[0]) != -1) { + this.sortData[i].field_ = column.field + '_' + } + } + } + // prepare paths and process sort + preparePaths() + this.records.sort((a, b) => { + return compareRecordPaths(a, b) + }) + cleanupPaths() + this.selectionRestore(noResetRefresh) + time = Date.now() - time + if (silent !== true && this.show.statusSort) { + setTimeout(() => { + this.status(w2utils.lang('Sorting took ${count} seconds', { count: time/1000 })) + }, 10) + } + return time + // grab paths before sorting for efficiency and because calling obj.get() + // while sorting 'obj.records' is unsafe, at least on webkit + function preparePaths() { + for (let i = 0; i < obj.records.length; i++) { + let rec = obj.records[i] + if (rec.w2ui?.parent_recid != null) { + rec.w2ui._path = getRecordPath(rec) + } + } + } + // cleanup and release memory allocated by preparePaths() + function cleanupPaths() { + for (let i = 0; i < obj.records.length; i++) { + let rec = obj.records[i] + if (rec.w2ui?.parent_recid != null) { + rec.w2ui._path = null + } + } + } + // compare two paths, from root of tree to given records + function compareRecordPaths(a, b) { + if ((!a.w2ui || a.w2ui.parent_recid == null) && (!b.w2ui || b.w2ui.parent_recid == null)) { + return compareRecords(a, b) // no tree, fast path + } + let pa = getRecordPath(a) + let pb = getRecordPath(b) + for (let i = 0; i < Math.min(pa.length, pb.length); i++) { + let diff = compareRecords(pa[i], pb[i]) + if (diff !== 0) return diff // different subpath + } + if (pa.length > pb.length) return 1 + if (pa.length < pb.length) return -1 + console.log('ERROR: two paths should not be equal.') + return 0 + } + // return an array of all records from root to and including 'rec' + function getRecordPath(rec) { + if (!rec.w2ui || rec.w2ui.parent_recid == null) return [rec] + if (rec.w2ui._path) + return rec.w2ui._path + // during actual sort, we should never reach this point + let subrec = obj.get(rec.w2ui.parent_recid) + if (!subrec) { + console.log('ERROR: no parent record: ' + rec.w2ui.parent_recid) + return [rec] + } + return (getRecordPath(subrec).concat(rec)) + } + // compare two records according to sortData and finally recid + function compareRecords(a, b) { + if (a === b) return 0 // optimize, same object + for (let i = 0; i < obj.sortData.length; i++) { + let fld = obj.sortData[i].field + let sortFld = (obj.sortData[i].field_) ? obj.sortData[i].field_ : fld + let aa = a[sortFld] + let bb = b[sortFld] + if (String(fld).indexOf('.') != -1) { + aa = obj.parseField(a, sortFld) + bb = obj.parseField(b, sortFld) + } + let col = obj.getColumn(fld) + if (col && Object.keys(col.editable).length > 0) { // for drop editable fields and drop downs + if (w2utils.isPlainObject(aa) && aa.text) aa = aa.text + if (w2utils.isPlainObject(bb) && bb.text) bb = bb.text + } + let ret = compareCells(aa, bb, i, obj.sortData[i].direction, col.sortMode || 'default') + if (ret !== 0) return ret + } + // break tie for similar records, + // required to have consistent ordering for tree paths + let ret = compareCells(a.recid, b.recid, -1, 'asc') + return ret + } + // compare two values, aa and bb, producing consistent ordering + function compareCells(aa, bb, i, direction, sortMode) { + // if both objects are strictly equal, we're done + if (aa === bb) + return 0 + // all nulls, empty and undefined on bottom + if ((aa == null || aa === '') && (bb != null && bb !== '')) + return 1 + if ((aa != null && aa !== '') && (bb == null || bb === '')) + return -1 + let dir = (direction.toLowerCase() === 'asc') ? 1 : -1 + // for different kind of objects, sort by object type + if (typeof aa != typeof bb) + return (typeof aa > typeof bb) ? dir : -dir + // for different kind of classes, sort by classes + if (aa.constructor.name != bb.constructor.name) + return (aa.constructor.name > bb.constructor.name) ? dir : -dir + // if we're dealing with non-null objects, call valueOf(). + // this mean that Date() or custom objects will compare properly. + if (aa && typeof aa == 'object') + aa = aa.valueOf() + if (bb && typeof bb == 'object') + bb = bb.valueOf() + // if we're still dealing with non-null objects that have + // a useful Object => String conversion, convert to string. + let defaultToString = {}.toString + if (aa && typeof aa == 'object' && aa.toString != defaultToString) + aa = String(aa) + if (bb && typeof bb == 'object' && bb.toString != defaultToString) + bb = String(bb) + // do case-insensitive string comparison + if (typeof aa == 'string') + aa = aa.toLowerCase().trim() + if (typeof bb == 'string') + bb = bb.toLowerCase().trim() + switch (sortMode) { + case 'natural': + sortMode = w2utils.naturalCompare + break + case 'i18n': + sortMode = w2utils.i18nCompare + break + } + if (typeof sortMode == 'function') { + return sortMode(aa,bb) * dir + } + // compare both objects + if (aa > bb) + return dir + if (aa < bb) + return -dir + return 0 + } + } + localSearch(silent) { + let obj = this + let url = this.url?.get ?? this.url + if (url) { + console.log('ERROR: grid.localSearch can only be used on local data source, grid.url should be empty.') + return + } + let time = Date.now() + let defaultToString = {}.toString + let duplicateMap = {} + this.total = this.records.length + // mark all records as shown + this.last.searchIds = [] + // prepare date/time fields + this.prepareData() + // hide records that did not match + if (this.searchData.length > 0 && !url) { + this.total = 0 + for (let i = 0; i < this.records.length; i++) { + let rec = this.records[i] + let match = searchRecord(rec) + if (match) { + if (rec?.w2ui) addParent(rec.w2ui.parent_recid) + if (this.showExtraOnSearch > 0) { + let before = this.showExtraOnSearch + let after = this.showExtraOnSearch + if (i < before) before = i + if (i + after > this.records.length) after = this.records.length - i + if (before > 0) { + for (let j = i - before; j < i; j++) { + if (this.last.searchIds.indexOf(j) < 0) + this.last.searchIds.push(j) + } + } + if (this.last.searchIds.indexOf(i) < 0) this.last.searchIds.push(i) + if (after > 0) { + for (let j = (i + 1) ; j <= (i + after) ; j++) { + if (this.last.searchIds.indexOf(j) < 0) this.last.searchIds.push(j) + } + } + } else { + this.last.searchIds.push(i) + } + } + } + this.total = this.last.searchIds.length + } + time = Date.now() - time + if (silent !== true && this.show.statusSearch) { + setTimeout(() => { + this.status(w2utils.lang('Search took ${count} seconds', { count: time/1000 })) + }, 10) + } + return time + // check if a record (or one of its closed children) matches the search data + function searchRecord(rec) { + let fl = 0, val1, val2, val3, tmp + let orEqual = false + for (let j = 0; j < obj.searchData.length; j++) { + let sdata = obj.searchData[j] + let search = obj.getSearch(sdata.field) + if (sdata == null) continue + if (search == null) search = { field: sdata.field, type: sdata.type } + let val1b = obj.parseField(rec, search.field) + val1 = (val1b !== null && val1b !== undefined && + (typeof val1b != 'object' || val1b.toString != defaultToString)) ? + String(val1b).toLowerCase() : '' // do not match a bogus string + if (sdata.value != null) { + if (!Array.isArray(sdata.value)) { + val2 = String(sdata.value).toLowerCase() + } else { + val2 = sdata.value[0] + val3 = sdata.value[1] + } + } + switch (sdata.operator) { + case '=': + case 'is': + if (obj.parseField(rec, search.field) == sdata.value) fl++ // do not hide record + else if (search.type == 'date') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatDate(tmp, 'yyyy-mm-dd') + val2 = w2utils.formatDate(w2utils.isDate(val2, w2utils.settings.dateFormat, true), 'yyyy-mm-dd') + if (val1 == val2) fl++ + } + else if (search.type == 'time') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatTime(tmp, 'hh24:mi') + val2 = w2utils.formatTime(val2, 'hh24:mi') + if (val1 == val2) fl++ + } + else if (search.type == 'datetime') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatDateTime(tmp, 'yyyy-mm-dd|hh24:mm:ss') + val2 = w2utils.formatDateTime(w2utils.isDateTime(val2, w2utils.settings.datetimeFormat, true), 'yyyy-mm-dd|hh24:mm:ss') + if (val1 == val2) fl++ + } + break + case 'between': + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + if (parseFloat(obj.parseField(rec, search.field)) >= parseFloat(val2) && parseFloat(obj.parseField(rec, search.field)) <= parseFloat(val3)) fl++ + } + else if (search.type == 'date') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.isDate(tmp, w2utils.settings.dateFormat, true) + val2 = w2utils.isDate(val2, w2utils.settings.dateFormat, true) + val3 = w2utils.isDate(val3, w2utils.settings.dateFormat, true) + if (val3 != null) val3 = new Date(val3.getTime() + 86400000) // 1 day + if (val1 >= val2 && val1 < val3) fl++ + } + else if (search.type == 'time') { + val1 = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val2 = w2utils.isTime(val2, true) + val3 = w2utils.isTime(val3, true) + val2 = (new Date()).setHours(val2.hours, val2.minutes, val2.seconds ? val2.seconds : 0, 0) + val3 = (new Date()).setHours(val3.hours, val3.minutes, val3.seconds ? val3.seconds : 0, 0) + if (val1 >= val2 && val1 < val3) fl++ + } + else if (search.type == 'datetime') { + val1 = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val2 = w2utils.isDateTime(val2, w2utils.settings.datetimeFormat, true) + val3 = w2utils.isDateTime(val3, w2utils.settings.datetimeFormat, true) + if (val3) val3 = new Date(val3.getTime() + 86400000) // 1 day + if (val1 >= val2 && val1 < val3) fl++ + } + break + case '<=': + orEqual = true + case '<': + case 'less': + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + val1 = parseFloat(obj.parseField(rec, search.field)) + val2 = parseFloat(sdata.value) + if (val1 < val2 || (orEqual && val1 === val2)) fl++ + } + else if (search.type == 'date') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.isDate(tmp, w2utils.settings.dateFormat, true) + val2 = w2utils.isDate(val2, w2utils.settings.dateFormat, true) + if (val1 < val2 || (orEqual && val1 === val2)) fl++ + } + else if (search.type == 'time') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatTime(tmp, 'hh24:mi') + val2 = w2utils.formatTime(val2, 'hh24:mi') + if (val1 < val2 || (orEqual && val1 === val2)) fl++ + } + else if (search.type == 'datetime') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatDateTime(tmp, 'yyyy-mm-dd|hh24:mm:ss') + val2 = w2utils.formatDateTime(w2utils.isDateTime(val2, w2utils.settings.datetimeFormat, true), 'yyyy-mm-dd|hh24:mm:ss') + if (val1.length == val2.length && (val1 < val2 || (orEqual && val1 === val2))) fl++ + } + break + case '>=': + orEqual = true + case '>': + case 'more': + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + val1 = parseFloat(obj.parseField(rec, search.field)) + val2 = parseFloat(sdata.value) + if (val1 > val2 || (orEqual && val1 === val2)) fl++ + } + else if (search.type == 'date') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.isDate(tmp, w2utils.settings.dateFormat, true) + val2 = w2utils.isDate(val2, w2utils.settings.dateFormat, true) + if (val1 > val2 || (orEqual && val1 === val2)) fl++ + } + else if (search.type == 'time') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatTime(tmp, 'hh24:mi') + val2 = w2utils.formatTime(val2, 'hh24:mi') + if (val1 > val2 || (orEqual && val1 === val2)) fl++ + } + else if (search.type == 'datetime') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatDateTime(tmp, 'yyyy-mm-dd|hh24:mm:ss') + val2 = w2utils.formatDateTime(w2utils.isDateTime(val2, w2utils.settings.datetimeFormat, true), 'yyyy-mm-dd|hh24:mm:ss') + if (val1.length == val2.length && (val1 > val2 || (orEqual && val1 === val2))) fl++ + } + break + case 'in': + tmp = sdata.value + if (sdata.svalue) tmp = sdata.svalue + if ((tmp.indexOf(w2utils.isFloat(val1b) ? parseFloat(val1b) : val1b) !== -1) || tmp.indexOf(val1) !== -1) fl++ + break + case 'not in': + tmp = sdata.value + if (sdata.svalue) tmp = sdata.svalue + if (!((tmp.indexOf(w2utils.isFloat(val1b) ? parseFloat(val1b) : val1b) !== -1) || tmp.indexOf(val1) !== -1)) fl++ + break + case 'begins': + case 'begins with': // need for back compatibility + if (val1.indexOf(val2) === 0) fl++ // do not hide record + break + case 'contains': + if (val1.indexOf(val2) >= 0) fl++ // do not hide record + break + case 'null': + if (obj.parseField(rec, search.field) == null) fl++ // do not hide record + break + case 'not null': + if (obj.parseField(rec, search.field) != null) fl++ // do not hide record + break + case 'ends': + case 'ends with': // need for back compatibility + let lastIndex = val1.lastIndexOf(val2) + if (lastIndex !== -1 && lastIndex == val1.length - val2.length) fl++ // do not hide record + break + } + } + if ((obj.last.logic == 'OR' && fl !== 0) || + (obj.last.logic == 'AND' && fl == obj.searchData.length)) + return true + if (rec.w2ui?.children && rec.w2ui?.expanded !== true) { + // there are closed children, search them too. + for (let r = 0; r < rec.w2ui.children.length; r++) { + let subRec = rec.w2ui.children[r] + if (searchRecord(subRec)) + return true + } + } + return false + } + // add parents nodes recursively + function addParent(recid) { + let i = obj.get(recid, true) + if (i == null || recid == null || duplicateMap[recid] || obj.last.searchIds.includes(i)) { + return + } + duplicateMap[recid] = true + let rec = obj.records[i] + if (rec?.w2ui) { + addParent(rec.w2ui.parent_recid) + } + obj.last.searchIds.push(i) + } + } + getRangeData(range, extra) { + let rec1 = this.get(range[0].recid, true) + let rec2 = this.get(range[1].recid, true) + let col1 = range[0].column + let col2 = range[1].column + let res = [] + if (col1 == col2) { // one row + for (let r = rec1; r <= rec2; r++) { + let record = this.records[r] + let dt = record[this.columns[col1].field] || null + if (extra !== true) { + res.push(dt) + } else { + res.push({ data: dt, column: col1, index: r, record: record }) + } + } + } else if (rec1 == rec2) { // one line + let record = this.records[rec1] + for (let i = col1; i <= col2; i++) { + let dt = record[this.columns[i].field] || null + if (extra !== true) { + res.push(dt) + } else { + res.push({ data: dt, column: i, index: rec1, record: record }) + } + } + } else { + for (let r = rec1; r <= rec2; r++) { + let record = this.records[r] + res.push([]) + for (let i = col1; i <= col2; i++) { + let dt = record[this.columns[i].field] + if (extra !== true) { + res[res.length-1].push(dt) + } else { + res[res.length-1].push({ data: dt, column: i, index: r, record: record }) + } + } + } + } + return res + } + addRange(ranges) { + let added = 0, first, last + if (this.selectType == 'row') return added + if (!Array.isArray(ranges)) ranges = [ranges] + // if it is selection + for (let i = 0; i < ranges.length; i++) { + if (typeof ranges[i] != 'object') ranges[i] = { name: 'selection' } + if (ranges[i].name == 'selection') { + if (this.show.selectionBorder === false) continue + let sel = this.getSelection() + if (sel.length === 0) { + this.removeRange('selection') + continue + } else { + first = sel[0] + last = sel[sel.length-1] + } + } else { // other range + first = ranges[i].range[0] + last = ranges[i].range[1] + } + if (first) { + let rg = { + name: ranges[i].name, + range: [{ recid: first.recid, column: first.column }, { recid: last.recid, column: last.column }], + style: ranges[i].style || '' + } + // add range + let ind = false + for (let j = 0; j < this.ranges.length; j++) if (this.ranges[j].name == ranges[i].name) { ind = j; break } + if (ind !== false) { + this.ranges[ind] = rg + } else { + this.ranges.push(rg) + } + added++ + } + } + this.refreshRanges() + return added + } + removeRange() { + let removed = 0 + for (let a = 0; a < arguments.length; a++) { + let name = arguments[a] + query(this.box).find('#grid_'+ this.name +'_'+ name).remove() + query(this.box).find('#grid_'+ this.name +'_f'+ name).remove() + for (let r = this.ranges.length-1; r >= 0; r--) { + if (this.ranges[r].name == name) { + this.ranges.splice(r, 1) + removed++ + } + } + } + return removed + } + refreshRanges() { + if (this.ranges.length === 0) return + let self = this + let range + let time = Date.now() + let rec1 = query(this.box).find(`#grid_${this.name}_frecords`) + let rec2 = query(this.box).find(`#grid_${this.name}_records`) + for (let i = 0; i < this.ranges.length; i++) { + let rg = this.ranges[i] + let first = rg.range[0] + let last = rg.range[1] + if (first.index == null) first.index = this.get(first.recid, true) + if (last.index == null) last.index = this.get(last.recid, true) + let td1 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(first.recid) + ' td[col="'+ first.column +'"]') + let td2 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(last.recid) + ' td[col="'+ last.column +'"]') + let td1f = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(first.recid) + ' td[col="'+ first.column +'"]') + let td2f = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(last.recid) + ' td[col="'+ last.column +'"]') + let _lastColumn = last.column + // adjustment due to column virtual scroll + if (first.column < this.last.colStart && last.column > this.last.colStart) { + td1 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(first.recid) + ' td[col="start"]') + } + if (first.column < this.last.colEnd && last.column > this.last.colEnd) { + td2 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(last.recid) + ' td[col="end"]') + _lastColumn = '"end"' + } + // if virtual scrolling kicked in + let index_top = parseInt(query(this.box).find('#grid_'+ this.name +'_rec_top').next().attr('index')) + let index_bottom = parseInt(query(this.box).find('#grid_'+ this.name +'_rec_bottom').prev().attr('index')) + let index_ftop = parseInt(query(this.box).find('#grid_'+ this.name +'_frec_top').next().attr('index')) + let index_fbottom = parseInt(query(this.box).find('#grid_'+ this.name +'_frec_bottom').prev().attr('index')) + if (td1.length === 0 && first.index < index_top && last.index > index_top) { + td1 = query(this.box).find('#grid_'+ this.name +'_rec_top').next().find('td[col="'+ first.column +'"]') + } + if (td2.length === 0 && last.index > index_bottom && first.index < index_bottom) { + td2 = query(this.box).find('#grid_'+ this.name +'_rec_bottom').prev().find('td[col="'+ _lastColumn +'"]') + } + if (td1f.length === 0 && first.index < index_ftop && last.index > index_ftop) { // frozen + td1f = query(this.box).find('#grid_'+ this.name +'_frec_top').next().find('td[col="'+ first.column +'"]') + } + if (td2f.length === 0 && last.index > index_fbottom && first.index < index_fbottom) { // frozen + td2f = query(this.box).find('#grid_'+ this.name +'_frec_bottom').prev().find('td[col="'+ last.column +'"]') + } + // do not show selection cell if it is editable + let edit = query(this.box).find('#grid_'+ this.name + '_editable') + let tmp = edit.find('.w2ui-input') + let tmp1 = tmp.attr('recid') + let tmp2 = tmp.attr('column') + if (rg.name == 'selection' && rg.range[0].recid == tmp1 && rg.range[0].column == tmp2) continue + // frozen regular columns range + range = query(this.box).find('#grid_'+ this.name +'_f'+ rg.name) + if (td1f.length > 0 || td2f.length > 0) { + if (range.length === 0) { + rec1.append('
    '+ + (rg.name == 'selection' ? '
    ' : '')+ + '
    ') + range = query(this.box).find('#grid_'+ this.name +'_f'+ rg.name) + } else { + range.attr('style', rg.style) + range.find('.w2ui-selection-resizer').show() + } + if (td2f.length === 0) { + td2f = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(last.recid) +' td:last-child') + if (td2f.length === 0) td2f = query(this.box).find('#grid_'+ this.name +'_frec_bottom td:first-child') + range.css('border-right', '0px') + range.find('.w2ui-selection-resizer').hide() + } + if (first.recid != null && last.recid != null && td1f.length > 0 && td2f.length > 0) { + let style = getComputedStyle(td2f[0]) + let top1 = (td1f.prop('offsetTop') - td1f.prop('scrollTop')) + let left1 = (td1f.prop('offsetLeft') + td1f.prop('scrollLeft')) + let top2 = (td2f.prop('offsetTop') - td2f.prop('scrollTop')) + let left2 = (td2f.prop('offsetLeft') + td2f.prop('scrollLeft')) + range.show().css({ + top : (top1 > 0 ? top1 : 0) + 'px', + left : (left1 > 0 ? left1 : 0) + 'px', + width : (left2 - left1 + parseFloat(style.width) + 2) + 'px', + height : (top2 - top1 + parseFloat(style.height) + 1) + 'px' + }) + } else { + range.hide() + } + } else { + range.hide() + } + // regular columns range + range = query(this.box).find('#grid_'+ this.name +'_'+ rg.name) + if (td1.length > 0 || td2.length > 0) { + if (range.length === 0) { + rec2.append('
    '+ + (rg.name == 'selection' ? '
    ' : '')+ + '
    ') + range = query(this.box).find('#grid_'+ this.name +'_'+ rg.name) + } else { + range.attr('style', rg.style) + } + if (td1.length === 0) { + td1 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(first.recid) +' td:first-child') + if (td1.length === 0) td1 = query(this.box).find('#grid_'+ this.name +'_rec_top td:first-child') + } + if (td2f.length !== 0) { + range.css('border-left', '0px') + } + if (first.recid != null && last.recid != null && td1.length > 0 && td2.length > 0) { + let style = getComputedStyle(td2[0]) + let top1 = (td1.prop('offsetTop') - td1.prop('scrollTop')) + let left1 = (td1.prop('offsetLeft') + td1.prop('scrollLeft')) + let top2 = (td2.prop('offsetTop') - td2.prop('scrollTop')) + let left2 = (td2.prop('offsetLeft') + td2.prop('scrollLeft')) + range.show().css({ + top : (top1 > 0 ? top1 : 0) + 'px', + left : (left1 > 0 ? left1 : 0) + 'px', + width : (left2 - left1 + parseFloat(style.width) + 2) + 'px', + height : (top2 - top1 + parseFloat(style.height) + 1) + 'px' + }) + } else { + range.hide() + } + } else { + range.hide() + } + } + // add resizer events + query(this.box).find('.w2ui-selection-resizer') + .off('.resizer') + .on('mousedown.resizer', mouseStart) + .on('dblclick.resizer', (event) => { + let edata = this.trigger('resizerDblClick', { target: this.name, originalEvent: event }) + if (edata.isCancelled === true) return + edata.finish() + }) + let edata = { target: this.name, originalRange: null, newRange: null } + return Date.now() - time + function mouseStart(event) { + let sel = self.getSelection() + self.last.move = { + type : 'expand', + x : event.screenX, + y : event.screenY, + divX : 0, + divY : 0, + recid : sel[0].recid, + column : sel[0].column, + originalRange : [w2utils.clone(sel[0]), w2utils.clone(sel[sel.length-1]) ], + newRange : [w2utils.clone(sel[0]), w2utils.clone(sel[sel.length-1]) ] + } + query('body') + .off('.w2ui-' + self.name) + .on('mousemove.w2ui-' + self.name, mouseMove) + .on('mouseup.w2ui-' + self.name, mouseStop) + // do not blur grid + event.preventDefault() + } + function mouseMove(event) { + let mv = self.last.move + if (!mv || mv.type != 'expand') return + mv.divX = (event.screenX - mv.x) + mv.divY = (event.screenY - mv.y) + // find new cell + let recid, column + let tmp = event.target + if (tmp.tagName.toUpperCase() != 'TD') tmp = query(tmp).closest('td')[0] + if (query(tmp).attr('col') != null) column = parseInt(query(tmp).attr('col')) + if (column == null) { + return + } + tmp = query(tmp).closest('tr')[0] + recid = self.records[query(tmp).attr('index')].recid + // new range + if (mv.newRange[1].recid == recid && mv.newRange[1].column == column) return + let prevNewRange = w2utils.clone(mv.newRange) + mv.newRange = [{ recid: mv.recid, column: mv.column }, { recid: recid, column: column }] + // event before + if (edata.detail) { + edata.detail.newRange = w2utils.clone(mv.newRange) + edata.detail.originalRange = w2utils.clone(mv.originalRange) + } + edata = self.trigger('selectionExtend', edata) + if (edata.isCancelled === true) { + mv.newRange = prevNewRange + edata.detail.newRange = prevNewRange + return + } else { + // default behavior + self.removeRange('grid-selection-expand') + self.addRange({ + name : 'grid-selection-expand', + range : mv.newRange, + style : 'background-color: rgba(100,100,100,0.1); border: 2px dotted rgba(100,100,100,0.5);' + }) + } + } + function mouseStop(event) { + // default behavior + self.removeRange('grid-selection-expand') + delete self.last.move + query('body').off('.w2ui-' + self.name) + // event after + if (edata.finish) edata.finish() + } + } + select() { + if (arguments.length === 0) return 0 + let selected = 0 + let sel = this.last.selection + if (!this.multiSelect) this.selectNone(true) + // if too many arguments > 150k, then it errors off + let args = Array.from(arguments) + if (Array.isArray(args[0])) args = args[0] + // event before + let tmp = { target: this.name } + if (args.length == 1) { + tmp.multiple = false + if (w2utils.isPlainObject(args[0])) { + tmp.clicked = { + recid: args[0].recid, + column: args[0].column + } + } else { + tmp.recid = args[0] + } + } else { + tmp.multiple = true + tmp.clicked = { recids: args } + } + let edata = this.trigger('select', tmp) + if (edata.isCancelled === true) return 0 + // default action + if (this.selectType == 'row') { + for (let a = 0; a < args.length; a++) { + let recid = typeof args[a] == 'object' ? args[a].recid : args[a] + let index = this.get(recid, true) + if (index == null) continue + let recEl1 = null + let recEl2 = null + if (this.searchData.length !== 0 || (index + 1 >= this.last.range_start && index + 1 <= this.last.range_end)) { + recEl1 = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)) + recEl2 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)) + } + if (this.selectType == 'row') { + if (sel.indexes.indexOf(index) != -1) continue + sel.indexes.push(index) + if (recEl1 && recEl2) { + recEl1.addClass('w2ui-selected').find('.w2ui-col-number').addClass('w2ui-row-selected') + recEl2.addClass('w2ui-selected').find('.w2ui-col-number').addClass('w2ui-row-selected') + recEl1.find('.w2ui-grid-select-check').prop('checked', true) + } + selected++ + } + } + } else { + // normalize for performance + let new_sel = {} + for (let a = 0; a < args.length; a++) { + let recid = typeof args[a] == 'object' ? args[a].recid : args[a] + let column = typeof args[a] == 'object' ? args[a].column : null + new_sel[recid] = new_sel[recid] || [] + if (Array.isArray(column)) { + new_sel[recid] = column + } else if (w2utils.isInt(column)) { + new_sel[recid].push(column) + } else { + for (let i = 0; i < this.columns.length; i++) { if (this.columns[i].hidden) continue; new_sel[recid].push(parseInt(i)) } + } + } + // add all + let col_sel = [] + for (let recid in new_sel) { + let index = this.get(recid, true) + if (index == null) continue + let recEl1 = null + let recEl2 = null + if (index + 1 >= this.last.range_start && index + 1 <= this.last.range_end) { + recEl1 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)) + recEl2 = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)) + } + let s = sel.columns[index] || [] + // default action + if (sel.indexes.indexOf(index) == -1) { + sel.indexes.push(index) + } + // only only those that are new + for (let t = 0; t < new_sel[recid].length; t++) { + if (s.indexOf(new_sel[recid][t]) == -1) s.push(new_sel[recid][t]) + } + s.sort((a, b) => { return a-b }) // sort function must be for numerical sort + for (let t = 0; t < new_sel[recid].length; t++) { + let col = new_sel[recid][t] + if (col_sel.indexOf(col) == -1) col_sel.push(col) + if (recEl1) { + recEl1.find('#grid_'+ this.name +'_data_'+ index +'_'+ col).addClass('w2ui-selected') + recEl1.find('.w2ui-col-number').addClass('w2ui-row-selected') + recEl1.find('.w2ui-grid-select-check').prop('checked', true) + } + if (recEl2) { + recEl2.find('#grid_'+ this.name +'_data_'+ index +'_'+ col).addClass('w2ui-selected') + recEl2.find('.w2ui-col-number').addClass('w2ui-row-selected') + recEl2.find('.w2ui-grid-select-check').prop('checked', true) + } + selected++ + } + // save back to selection object + sel.columns[index] = s + } + // select columns (need here for speed) + for (let c = 0; c < col_sel.length; c++) { + query(this.box).find('#grid_'+ this.name +'_column_'+ col_sel[c] +' .w2ui-col-header').addClass('w2ui-col-selected') + } + } + // need to sort new selection for speed + sel.indexes.sort((a, b) => { return a-b }) + // all selected? + let areAllSelected = (this.records.length > 0 && sel.indexes.length == this.records.length), + areAllSearchedSelected = (sel.indexes.length > 0 && this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length) + if (areAllSelected || areAllSearchedSelected) { + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', true) + } else { + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', false) + } + this.status() + this.addRange('selection') + this.updateToolbar(sel, areAllSelected) + // event after + edata.finish() + return selected + } + unselect() { + let unselected = 0 + let sel = this.last.selection + // if too many arguments > 150k, then it errors off + let args = Array.from(arguments) + if (Array.isArray(args[0])) args = args[0] + // event before + let tmp = { target: this.name } + if (args.length == 1) { + tmp.multiple = false + if (w2utils.isPlainObject(args[0])) { + tmp.clicked = { + recid: args[0].recid, + column: args[0].column + } + } else { + tmp.clicked = { recid: args[0] } + } + } else { + tmp.multiple = true + tmp.recids = args + } + let edata = this.trigger('select', tmp) + if (edata.isCancelled === true) return 0 + for (let a = 0; a < args.length; a++) { + let recid = typeof args[a] == 'object' ? args[a].recid : args[a] + let record = this.get(recid) + if (record == null) continue + let index = this.get(record.recid, true) + let recEl1 = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)) + let recEl2 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)) + if (this.selectType == 'row') { + if (sel.indexes.indexOf(index) == -1) continue + // default action + sel.indexes.splice(sel.indexes.indexOf(index), 1) + recEl1.removeClass('w2ui-selected w2ui-inactive').find('.w2ui-col-number').removeClass('w2ui-row-selected') + recEl2.removeClass('w2ui-selected w2ui-inactive').find('.w2ui-col-number').removeClass('w2ui-row-selected') + if (recEl1.length != 0) { + recEl1[0].style.cssText = 'height: '+ this.recordHeight +'px; ' + recEl1.attr('custom_style') + recEl2[0].style.cssText = 'height: '+ this.recordHeight +'px; ' + recEl2.attr('custom_style') + } + recEl1.find('.w2ui-grid-select-check').prop('checked', false) + unselected++ + } else { + let col = args[a].column + if (!w2utils.isInt(col)) { // unselect all columns + let cols = [] + for (let i = 0; i < this.columns.length; i++) { if (this.columns[i].hidden) continue; cols.push({ recid: recid, column: i }) } + return this.unselect(cols) + } + let s = sel.columns[index] + if (!Array.isArray(s) || s.indexOf(col) == -1) continue + // default action + s.splice(s.indexOf(col), 1) + query(this.box).find(`#grid_${this.name}_rec_${w2utils.escapeId(recid)} > td[col="${col}"]`).removeClass('w2ui-selected w2ui-inactive') + query(this.box).find(`#grid_${this.name}_frec_${w2utils.escapeId(recid)} > td[col="${col}"]`).removeClass('w2ui-selected w2ui-inactive') + // check if any row/column still selected + let isColSelected = false + let isRowSelected = false + let tmp = this.getSelection() + for (let i = 0; i < tmp.length; i++) { + if (tmp[i].column == col) isColSelected = true + if (tmp[i].recid == recid) isRowSelected = true + } + if (!isColSelected) { + query(this.box).find(`.w2ui-grid-columns td[col="${col}"] .w2ui-col-header, .w2ui-grid-fcolumns td[col="${col}"] .w2ui-col-header`).removeClass('w2ui-col-selected') + } + if (!isRowSelected) { + query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)).find('.w2ui-col-number').removeClass('w2ui-row-selected') + } + unselected++ + if (s.length === 0) { + delete sel.columns[index] + sel.indexes.splice(sel.indexes.indexOf(index), 1) + recEl1.find('.w2ui-grid-select-check').prop('checked', false) + } + } + } + // all selected? + let areAllSelected = (this.records.length > 0 && sel.indexes.length == this.records.length), + areAllSearchedSelected = (sel.indexes.length > 0 && this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length) + if (areAllSelected || areAllSearchedSelected) { + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', true) + } else { + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', false) + } + // show number of selected + this.status() + this.addRange('selection') + this.updateToolbar(sel, areAllSelected) + // event after + edata.finish() + return unselected + } + selectAll() { + let time = Date.now() + if (this.multiSelect === false) return + // default action + let url = this.url?.get ?? this.url + let sel = w2utils.clone(this.last.selection) + let cols = [] + for (let i = 0; i < this.columns.length; i++) cols.push(i) + // if local data source and searched + sel.indexes = [] + if (!url && this.searchData.length !== 0) { + // local search applied + for (let i = 0; i < this.last.searchIds.length; i++) { + sel.indexes.push(this.last.searchIds[i]) + if (this.selectType != 'row') sel.columns[this.last.searchIds[i]] = cols.slice() // .slice makes copy of the array + } + } else { + let buffered = this.records.length + if (this.searchData.length != 0 && !url) buffered = this.last.searchIds.length + for (let i = 0; i < buffered; i++) { + sel.indexes.push(i) + if (this.selectType != 'row') sel.columns[i] = cols.slice() // .slice makes copy of the array + } + } + // event before + let edata = this.trigger('select', { target: this.name, multiple: true, all: true, clicked: sel }) + if (edata.isCancelled === true) return + this.last.selection = sel + // add selected class + if (this.selectType == 'row') { + query(this.box).find('.w2ui-grid-records tr:not(.w2ui-empty-record)') + .addClass('w2ui-selected').find('.w2ui-col-number').addClass('w2ui-row-selected') + query(this.box).find('.w2ui-grid-frecords tr:not(.w2ui-empty-record)') + .addClass('w2ui-selected').find('.w2ui-col-number').addClass('w2ui-row-selected') + query(this.box).find('input.w2ui-grid-select-check').prop('checked', true) + } else { + query(this.box).find('.w2ui-grid-columns td .w2ui-col-header, .w2ui-grid-fcolumns td .w2ui-col-header').addClass('w2ui-col-selected') + query(this.box).find('.w2ui-grid-records tr .w2ui-col-number').addClass('w2ui-row-selected') + query(this.box).find('.w2ui-grid-records tr:not(.w2ui-empty-record)') + .find('.w2ui-grid-data:not(.w2ui-col-select)').addClass('w2ui-selected') + query(this.box).find('.w2ui-grid-frecords tr .w2ui-col-number').addClass('w2ui-row-selected') + query(this.box).find('.w2ui-grid-frecords tr:not(.w2ui-empty-record)') + .find('.w2ui-grid-data:not(.w2ui-col-select)').addClass('w2ui-selected') + query(this.box).find('input.w2ui-grid-select-check').prop('checked', true) + } + // enable/disable toolbar buttons + sel = this.getSelection(true) + this.addRange('selection') + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', true) + this.status() + this.updateToolbar({ indexes: sel }, true) + // event after + edata.finish() + return Date.now() - time + } + selectNone(skipEvent) { + let time = Date.now() + // event before + let edata + if (!skipEvent) { + edata = this.trigger('select', { target: this.name, clicked: [] }) + if (edata.isCancelled === true) return + } + // default action + let sel = this.last.selection + // remove selected class + if (this.selectType == 'row') { + query(this.box).find('.w2ui-grid-records tr.w2ui-selected').removeClass('w2ui-selected w2ui-inactive') + .find('.w2ui-col-number').removeClass('w2ui-row-selected') + query(this.box).find('.w2ui-grid-frecords tr.w2ui-selected').removeClass('w2ui-selected w2ui-inactive') + .find('.w2ui-col-number').removeClass('w2ui-row-selected') + query(this.box).find('input.w2ui-grid-select-check').prop('checked', false) + } else { + query(this.box).find('.w2ui-grid-columns td .w2ui-col-header, .w2ui-grid-fcolumns td .w2ui-col-header').removeClass('w2ui-col-selected') + query(this.box).find('.w2ui-grid-records tr .w2ui-col-number').removeClass('w2ui-row-selected') + query(this.box).find('.w2ui-grid-frecords tr .w2ui-col-number').removeClass('w2ui-row-selected') + query(this.box).find('.w2ui-grid-data.w2ui-selected').removeClass('w2ui-selected w2ui-inactive') + query(this.box).find('input.w2ui-grid-select-check').prop('checked', false) + } + sel.indexes = [] + sel.columns = {} + this.removeRange('selection') + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', false) + this.status() + this.updateToolbar(sel, false) + // event after + if (!skipEvent) { + edata.finish() + } + return Date.now() - time + } + updateToolbar(sel) { + let obj = this + let cnt = sel && sel.indexes ? sel.indexes.length : 0 + this.toolbar.items.forEach((item) => { + _checkItem(item, '') + if (Array.isArray(item.items)) { + item.items.forEach((it) => { + _checkItem(it, item.id + ':') + }) + } + }) + // enable/disable toolbar search button + if (this.show.toolbarSave) { + if (this.getChanges().length > 0) { + this.toolbar.enable('w2ui-save') + } else { + this.toolbar.disable('w2ui-save') + } + } + function _checkItem(item, prefix) { + if (item.batch != null) { + let enabled = false + if (item.batch === true) { + if (cnt > 0) enabled = true + } else if (typeof item.batch == 'number') { + if (cnt === item.batch) enabled = true + } else if (typeof item.batch == 'function') { + enabled = item.batch({ cnt, sel }) + } + if (enabled) { + obj.toolbar.enable(prefix + item.id) + } else { + obj.toolbar.disable(prefix + item.id) + } + } + } + } + getSelection(returnIndex) { + let ret = [] + let sel = this.last.selection + if (this.selectType == 'row') { + for (let i = 0; i < sel.indexes.length; i++) { + if (!this.records[sel.indexes[i]]) continue + if (returnIndex === true) ret.push(sel.indexes[i]); else ret.push(this.records[sel.indexes[i]].recid) + } + return ret + } else { + for (let i = 0; i < sel.indexes.length; i++) { + let cols = sel.columns[sel.indexes[i]] + if (!this.records[sel.indexes[i]]) continue + for (let j = 0; j < cols.length; j++) { + ret.push({ recid: this.records[sel.indexes[i]].recid, index: parseInt(sel.indexes[i]), column: cols[j] }) + } + } + return ret + } + } + search(field, value) { + let url = this.url?.get ?? this.url + let searchData = [] + let last_multi = this.last.multi + let last_logic = this.last.logic + let last_field = this.last.field + let last_search = this.last.search + let hasHiddenSearches = false + let overlay = query(`#w2overlay-${this.name}-search-overlay`) + // add hidden searches + for (let i = 0; i < this.searches.length; i++) { + if (!this.searches[i].hidden || this.searches[i].value == null) continue + searchData.push({ + field : this.searches[i].field, + operator : this.searches[i].operator || 'is', + type : this.searches[i].type, + value : this.searches[i].value || '' + }) + hasHiddenSearches = true + } + if (arguments.length === 0 && overlay.length === 0) { + if (this.multiSearch) { + field = this.searchData + value = this.last.logic + } else { + field = this.last.field + value = this.last.search + } + } + // 1: search() - advanced search (reads from popup) + if (arguments.length === 0 && overlay.length !== 0) { + this.focus() // otherwise search drop down covers searches + last_logic = overlay.find(`#grid_${this.name}_logic`).val() + last_search = '' + // advanced search + for (let i = 0; i < this.searches.length; i++) { + let search = this.searches[i] + let operator = overlay.find('#grid_'+ this.name + '_operator_'+ i).val() + let field1 = overlay.find('#grid_'+ this.name + '_field_'+ i) + let field2 = overlay.find('#grid_'+ this.name + '_field2_'+ i) + let value1 = field1.val() + let value2 = field2.val() + let svalue = null + let text = null + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + let fld1 = field1[0]._w2field + let fld2 = field2[0]._w2field + if (fld1) value1 = fld1.clean(value1) + if (fld2) value2 = fld2.clean(value2) + } + if (['list', 'enum'].indexOf(search.type) != -1 || ['in', 'not in'].indexOf(operator) != -1) { + value1 = field1[0]._w2field.selected || {} + if (Array.isArray(value1)) { + svalue = [] + for (let j = 0; j < value1.length; j++) { + svalue.push(w2utils.isFloat(value1[j].id) ? parseFloat(value1[j].id) : String(value1[j].id).toLowerCase()) + delete value1[j].hidden + } + if (Object.keys(value1).length === 0) value1 = '' + } else { + text = value1.text || '' + value1 = value1.id || '' + } + } + if ((value1 !== '' && value1 != null) || (value2 != null && value2 !== '')) { + let tmp = { + field : search.field, + type : search.type, + operator : operator + } + if (operator == 'between') { + w2utils.extend(tmp, { value: [value1, value2] }) + } else if (operator == 'in' && typeof value1 == 'string') { + w2utils.extend(tmp, { value: value1.split(',') }) + } else if (operator == 'not in' && typeof value1 == 'string') { + w2utils.extend(tmp, { value: value1.split(',') }) + } else { + w2utils.extend(tmp, { value: value1 }) + } + if (svalue) w2utils.extend(tmp, { svalue: svalue }) + if (text) w2utils.extend(tmp, { text: text }) + // convert date to unix time + try { + if (search.type == 'date' && operator == 'between') { + tmp.value[0] = value1 // w2utils.isDate(value1, w2utils.settings.dateFormat, true).getTime(); + tmp.value[1] = value2 // w2utils.isDate(value2, w2utils.settings.dateFormat, true).getTime(); + } + if (search.type == 'date' && operator == 'is') { + tmp.value = value1 // w2utils.isDate(value1, w2utils.settings.dateFormat, true).getTime(); + } + } catch (e) { + } + searchData.push(tmp) + last_multi = true // if only hidden searches, then do not set + } + } + } + // 2: search(field, value) - regular search + if (typeof field == 'string') { + // if only one argument - search all + if (arguments.length == 1) { + value = field + field = 'all' + } + last_field = field + last_search = value + last_multi = false + last_logic = (hasHiddenSearches ? 'AND' : 'OR') + // loop through all searches and see if it applies + if (value != null) { + if (field.toLowerCase() == 'all') { + // if there are search fields loop thru them + if (this.searches.length > 0) { + for (let i = 0; i < this.searches.length; i++) { + let search = this.searches[i] + if (search.type == 'text' || (search.type == 'alphanumeric' && w2utils.isAlphaNumeric(value)) + || (search.type == 'int' && w2utils.isInt(value)) || (search.type == 'float' && w2utils.isFloat(value)) + || (search.type == 'percent' && w2utils.isFloat(value)) || ((search.type == 'hex' || search.type == 'color') && w2utils.isHex(value)) + || (search.type == 'currency' && w2utils.isMoney(value)) || (search.type == 'money' && w2utils.isMoney(value)) + || (search.type == 'date' && w2utils.isDate(value)) || (search.type == 'time' && w2utils.isTime(value)) + || (search.type == 'datetime' && w2utils.isDateTime(value)) || (search.type == 'datetime' && w2utils.isDate(value)) + || (search.type == 'enum' && w2utils.isAlphaNumeric(value)) || (search.type == 'list' && w2utils.isAlphaNumeric(value)) + ) { + let def = this.defaultOperator[this.operatorsMap[search.type]] + let tmp = { + field : search.field, + type : search.type, + operator : (search.operator != null ? search.operator : def), + value : value + } + if (String(value).trim() != '') searchData.push(tmp) + } + // range in global search box + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1 && String(value).trim().split('-').length == 2) { + let t = String(value).trim().split('-') + let tmp = { + field : search.field, + type : search.type, + operator : (search.operator != null ? search.operator : 'between'), + value : [t[0], t[1]] + } + searchData.push(tmp) + } + // lists fields + if (['list', 'enum'].indexOf(search.type) != -1) { + let new_values = [] + if (search.options == null) search.options = {} + if (!Array.isArray(search.options.items)) search.options.items = [] + for (let j = 0; j < search.options.items; j++) { + let tmp = search.options.items[j] + try { + let re = new RegExp(value, 'i') + if (re.test(tmp)) new_values.push(j) + if (tmp.text && re.test(tmp.text)) new_values.push(tmp.id) + } catch (e) {} + } + if (new_values.length > 0) { + let tmp = { + field : search.field, + type : search.type, + operator : (search.operator != null ? search.operator : 'in'), + value : new_values + } + searchData.push(tmp) + } + } + } + } else { + // no search fields, loop thru columns + for (let i = 0; i < this.columns.length; i++) { + let tmp = { + field : this.columns[i].field, + type : 'text', + operator : this.defaultOperator.text, + value : value + } + searchData.push(tmp) + } + } + } else { + let el = overlay.find('#grid_'+ this.name +'_search_all') + let search = this.getSearch(field) + if (search == null) search = { field: field, type: 'text' } + if (search.field == field) this.last.label = search.label + if (value !== '') { + let op = this.defaultOperator[this.operatorsMap[search.type]] + let val = value + if (['date', 'time', 'datetime'].indexOf(search.type) != -1) op = 'is' + if (['list', 'enum'].indexOf(search.type) != -1) { + op = 'is' + let tmp = el._w2field.get() + if (tmp && Object.keys(tmp).length > 0) val = tmp.id; else val = '' + } + if (search.type == 'int' && value !== '') { + op = 'is' + if (String(value).indexOf('-') != -1) { + let tmp = value.split('-') + if (tmp.length == 2) { + op = 'between' + val = [parseInt(tmp[0]), parseInt(tmp[1])] + } + } + if (String(value).indexOf(',') != -1) { + let tmp = value.split(',') + op = 'in' + val = [] + for (let i = 0; i < tmp.length; i++) val.push(tmp[i]) + } + } + if (search.operator != null) op = search.operator + let tmp = { + field : search.field, + type : search.type, + operator : op, + value : val + } + searchData.push(tmp) + } + } + } + } + // 3: search([{ field, value, [operator,] [type] }, { field, value, [operator,] [type] } ], logic) - submit whole structure + if (Array.isArray(field)) { + let logic = 'AND' + if (typeof value == 'string') { + logic = value.toUpperCase() + if (logic != 'OR' && logic != 'AND') logic = 'AND' + } + last_search = '' + last_multi = true + last_logic = logic + for (let i = 0; i < field.length; i++) { + let data = field[i] + if (typeof data.value == 'number' && data.operator == null) data.operator = this.defaultOperator.number + if (typeof data.value == 'string' && data.operator == null) data.operator = this.defaultOperator.text + if (Array.isArray(data.value) && data.operator == null) data.operator = this.defaultOperator.enum + if (w2utils.isDate(data.value) && data.operator == null) data.operator = this.defaultOperator.date + // merge current field and search if any + searchData.push(data) + } + } + // event before + let edata = this.trigger('search', { + target: this.name, + multi: (arguments.length === 0 ? true : false), + searchField: (field ? field : 'multi'), + searchValue: (field ? value : 'multi'), + searchData: searchData, + searchLogic: last_logic + }) + if (edata.isCancelled === true) return + // default action + this.searchData = edata.detail.searchData + this.last.field = last_field + this.last.search = last_search + this.last.multi = last_multi + this.last.logic = edata.detail.searchLogic + this.last.scrollTop = 0 + this.last.scrollLeft = 0 + this.last.selection.indexes = [] + this.last.selection.columns = {} + // -- clear all search field + this.searchClose() + // apply search + if (url) { + this.last.fetch.offset = 0 + this.reload() + } else { + // local search + this.localSearch() + this.refresh() + } + // event after + edata.finish() + } + // open advanced search popover + searchOpen() { + if (!this.box) return + if (this.searches.length === 0) return + // event before + let edata = this.trigger('searchOpen', { target: this.name }) + if (edata.isCancelled === true) { + return + } + let $btn = query(this.toolbar.box).find('.w2ui-grid-search-input .w2ui-search-drop') + $btn.addClass('checked') + // show search + w2tooltip.show({ + name: this.name + '-search-overlay', + anchor: query(this.box).find('#grid_'+ this.name +'_search_all').get(0), + position: 'bottom|top', + html: this.getSearchesHTML(), + align: 'left', + arrowSize: 12, + class: 'w2ui-grid-search-advanced', + hideOn: ['doc-click'] + }) + .then(event => { + this.initSearches() + this.last.search_opened = true + let overlay = query(`#w2overlay-${this.name}-search-overlay`) + overlay + .data('gridName', this.name) + .off('.grid-search') + .on('click.grid-search', () => { + // hide any tooltip opened by searches + overlay.find('input, select').each(el => { + let names = query(el).data('tooltipName') + if (names) names.forEach(name => { + w2tooltip.hide(name) + }) + }) + }) + w2utils.bindEvents(overlay.find('select, input, button'), this) + // init first field + let sfields = query(`#w2overlay-${this.name}-search-overlay *[rel=search]`) + if (sfields.length > 0) sfields[0].focus() + // event after + edata.finish() + }) + .hide(event => { + $btn.removeClass('checked') + this.last.search_opened = false + }) + } + searchClose() { + w2tooltip.hide(this.name + '-search-overlay') + } + // if clicked on a field in the search strip + searchFieldTooltip(ind, sd_ind, el) { + let sf = this.searches[ind] + let sd = this.searchData[sd_ind] + let oper = sd.operator + if (oper == 'more' && sd.type == 'date') oper = 'since' + if (oper == 'less' && sd.type == 'date') oper = 'before' + let options = '' + let val = sd.value + if (Array.isArray(sd.value)) { // && Array.isArray(sf.options.items)) { + sd.value.forEach(opt => { + options += `${opt.text || opt}` + }) + if (sd.type == 'date') { + options = '' + sd.value.forEach(opt => { + options += `${w2utils.formatDate(opt)}` + }) + } + } else { + if (sd.type == 'date') { + val = w2utils.formatDateTime(val) + } + } + w2tooltip.hide(this.name + '-search-props') + w2tooltip.show({ + name: this.name + '-search-props', + anchor: el, + class: 'w2ui-white', + hideOn: 'doc-click', + html: ` +
    + ${sf.label} + ${w2utils.lang(oper)} + ${Array.isArray(sd.value) + ? `${options}` + : `${val}` + } +
    + +
    +
    ` + }).then(event => { + query(event.detail.overlay.box).find('#remove').on('click', () => { + this.searchData.splice(`${sd_ind}`, 1) + this.reload() + this.localSearch() + w2tooltip.hide(this.name + '-search-props') + }) + }) + } + // drop down with save searches + searchSuggest(imediate, forceHide, input) { + clearTimeout(this.last.kbd_timer) + clearTimeout(this.last.overlay_timer) + this.searchShowFields(true) + this.searchClose() + if (forceHide === true) { + w2tooltip.hide(this.name + '-search-suggest') + return + } + if (query(`#w2overlay-${this.name}-search-suggest`).length > 0) { + // already shown + return + } + if (!imediate) { + this.last.overlay_timer = setTimeout(() => { this.searchSuggest(true) }, 100) + return + } + let el = query(this.box).find(`#grid_${this.name}_search_all`).get(0) + let searches = [ + ...this.defaultSearches ?? [], + ...this.defaultSearches?.length > 0 && this.savedSearches?.length > 0 ? ['--'] : [], + ...this.savedSearches ?? [] + ] + if (Array.isArray(searches) && searches.length > 0) { + w2menu.show({ + name: this.name + '-search-suggest', + anchor: el, + align: 'both', + items: searches, + hideOn: ['doc-click', 'sleect', 'remove'], + render(item) { + let ret = item.text + if (item.isDefault) ret = `${ret}` + return ret + } + }) + .select(event => { + let edata = this.trigger('searchSelect', { + target: this.name, + index: event.detail.index, + item: event.detail.item + }) + if (edata.isCancelled === true) { + event.preventDefault() + return + } + event.detail.overlay.hide() + this.last.logic = event.detail.item.logic || 'AND' + this.last.search = '' + this.last.label = '[Multiple Fields]' + this.searchData = w2utils.clone(event.detail.item.data) + this.searchSelected = w2utils.clone(event.detail.item, { exclude: ['icon', 'remove'] }) + this.reload() + edata.finish() + }) + .remove(event => { + let item = event.detail.item + let edata = this.trigger('searchRemove', { target: this.name, index: event.detail.index, item }) + if (edata.isCancelled === true) { + event.preventDefault() + return + } + event.detail.overlay.hide() + this.confirm(w2utils.lang('Do you want to delete search "${item}"?', { item: item.text })) + .yes(evt => { + // remove from searches + let search = this.savedSearches.findIndex((s) => s.id == item.id ? true : false) + if (search !== -1) { + this.savedSearches.splice(search, 1) + } + this.cacheSave('searches', this.savedSearches.map(s => w2utils.clone(s, { exclude: ['remove', 'icon'] }))) + evt.detail.self.close() + // evt after + edata.finish() + }) + .no(evt => { + evt.detail.self.close() + }) + }) + } + } + searchSave() { + let value = '' + if (this.searchSelected) { + value = this.searchSelected.text + } + let ind = this.savedSearches.findIndex(s => { return s.id == this.searchSelected?.id ? true : false }) + // event before + let edata = this.trigger('searchSave', { target: this.name, saveLocalStorage: true }) + if (edata.isCancelled === true) return + this.message({ + width: 350, + height: 150, + body: ``, + buttons: ` + + + ` + }).open(async (event) => { + query(event.detail.box).find('input, button').eq(0).val(value) + await event.complete + query(event.detail.box).find('#grid-search-cancel').on('click', () => { + this.message() + }) + query(event.detail.box).find('#grid-search-save').on('click', () => { + let name = query(event.detail.box).find('.w2ui-message .search-name').val() + // save in savedSearches + if (this.searchSelected && ind != -1) { + Object.assign(this.savedSearches[ind], { + id: name, + text: name, + logic: this.last.logic, + data: w2utils.clone(this.searchData) + }) + } else { + this.savedSearches.push({ + id: name, + text: name, + icon: 'w2ui-icon-search', + remove: true, + logic: this.last.logic, + data: this.searchData + }) + } + // save local storage + this.cacheSave('searches', this.savedSearches.map(s => w2utils.clone(s, { exclude: ['remove', 'icon'] }))) + this.message() + // update on screen + if (this.searchSelected) { + this.searchSelected.text = name + query(this.box).find(`#grid_${this.name}_search_name .name-text`).html(name) + } else { + this.searchSelected = { + text: name, + logic: this.last.logic, + data: w2utils.clone(this.searchData) + } + query(event.detail.box).find(`#grid_${this.name}_search_all`).val(' ').prop('readOnly', true) + query(event.detail.box).find(`#grid_${this.name}_search_name`).show().find('.name-text').html(name) + } + edata.finish({ name }) + }) + query(event.detail.box).find('input, button') + .off('.message') + .on('keydown.message', evt => { + let val = String(query(event.detail.box).find('.w2ui-message-body input').val()).trim() + if (evt.keyCode == 13 && val != '') { + query(event.detail.box).find('#grid-search-save').trigger('click') // enter + } + if (evt.keyCode == 27) { // escape + this.message() + } + }) + .eq(0) + .on('input.message', evt => { + let $save = query(event.detail.box).closest('.w2ui-message').find('#grid-search-save') + if (String(query(event.detail.box).val()).trim() === '') { + $save.prop('disabled', true) + } else { + $save.prop('disabled', false) + } + }) + .get(0) + .focus() + }) + } + cache(type) { + if (w2utils.hasLocalStorage && this.useLocalStorage) { + try { + let data = JSON.parse(localStorage.w2ui || '{}') + data[(this.stateId || this.name)] ??= {} + return data[(this.stateId || this.name)][type] + } catch (e) { + } + } + return null + } + cacheSave(type, value) { + if (w2utils.hasLocalStorage && this.useLocalStorage) { + try { + let data = JSON.parse(localStorage.w2ui || '{}') + data[(this.stateId || this.name)] ??= {} + data[(this.stateId || this.name)][type] = value + localStorage.w2ui = JSON.stringify(data) + return true + } catch (e) { + delete localStorage.w2ui + } + } + return false + } + searchReset(noReload) { + let searchData = [] + let hasHiddenSearches = false + // add hidden searches + for (let i = 0; i < this.searches.length; i++) { + if (!this.searches[i].hidden || this.searches[i].value == null) continue + searchData.push({ + field : this.searches[i].field, + operator : this.searches[i].operator || 'is', + type : this.searches[i].type, + value : this.searches[i].value || '' + }) + hasHiddenSearches = true + } + // event before + let edata = this.trigger('search', { reset: true, target: this.name, searchData: searchData }) + if (edata.isCancelled === true) return + // default action + let input = query(this.box).find('#grid_'+ this.name +'_search_all') + this.searchData = edata.detail.searchData + this.searchSelected = null + this.last.search = '' + this.last.logic = (hasHiddenSearches ? 'AND' : 'OR') + // --- do not reset to All Fields (I think) + input.next().hide() // advanced search button + if (this.searches.length > 0) { + if (!this.multiSearch || !this.show.searchAll) { + let tmp = 0 + while (tmp < this.searches.length && (this.searches[tmp].hidden || this.searches[tmp].simple === false)) tmp++ + if (tmp >= this.searches.length) { + // all searches are hidden + this.last.field = '' + this.last.label = '' + } else { + this.last.field = this.searches[tmp].field + this.last.label = this.searches[tmp].label + } + } else { + this.last.field = 'all' + this.last.label = 'All Fields' + input.next().show() // advanced search button + } + } + this.last.multi = false + this.last.fetch.offset = 0 + // reset scrolling position + this.last.scrollTop = 0 + this.last.scrollLeft = 0 + this.last.selection.indexes = [] + this.last.selection.columns = {} + // -- clear all search field + this.searchClose() + let all = input.val('').get(0) + if (all?._w2field) { all._w2field.reset() } + // apply search + if (!noReload) this.reload() + // event after + edata.finish() + } + searchShowFields(forceHide) { + if (forceHide === true) { + w2tooltip.hide(this.name + '-search-fields') + return + } + let items = [] + for (let s = -1; s < this.searches.length; s++) { + let search = this.searches[s] + let sField = (search ? search.field : null) + let column = this.getColumn(sField) + let disabled = false + let tooltip = null + if (this.show.searchHiddenMsg == true && s != -1 + && (column == null || (column.hidden === true && column.hideable !== false))) { + disabled = true + tooltip = w2utils.lang(`This column ${column == null ? 'does not exist' : 'is hidden'}`) + } + if (s == -1) { // -1 is All Fields search + if (!this.multiSearch || !this.show.searchAll) continue + search = { field: 'all', label: 'All Fields' } + } else { + if (column != null && column.hideable === false) continue + if (search.hidden === true) { + tooltip = w2utils.lang('This column is hidden') + // don't show hidden (not simple) searches + if (search.simple === false) continue + } + } + if (search.label == null && search.caption != null) { + console.log('NOTICE: grid search.caption property is deprecated, please use search.label. Search ->', search) + search.label = search.caption + } + items.push({ + id: search.field, + text: w2utils.lang(search.label), + search, + tooltip, + disabled, + checked: (search.field == this.last.field) + }) + } + w2menu.show({ + type: 'radio', + name: this.name + '-search-fields', + anchor: query(this.box).find('#grid_'+ this.name +'_search_name').parent().find('.w2ui-search-down').get(0), + items, + align: 'none', + hideOn: ['doc-click', 'select'] + }) + .select(event => { + this.searchInitInput(event.detail.item.search.field) + }) + } + searchInitInput(field, value) { + let search + let el = query(this.box).find('#grid_'+ this.name +'_search_all') + if (field == 'all') { + search = { field: 'all', label: w2utils.lang('All Fields') } + } else { + search = this.getSearch(field) + if (search == null) return + } + // update field + if (this.last.search != '') { + this.last.label = search.label + this.search(search.field, this.last.search) + } else { + this.last.field = search.field + this.last.label = search.label + } + el.attr('placeholder', w2utils.lang('Search') + ' ' + w2utils.lang(search.label || search.caption || search.field, true)) + } + // clears records and related params + clear(noRefresh) { + this.total = 0 + this.records = [] + this.summary = [] + this.last.fetch.offset = 0 // need this for reload button to work on remote data set + this.last.idCache = {} // optimization to free memory + this.last.selection = { indexes: [], columns: {} } + this.reset(true) + // refresh + if (!noRefresh) this.refresh() + } + // clears scroll position, selection, ranges + reset(noRefresh) { + // position + this.last.scrollTop = 0 + this.last.scrollLeft = 0 + this.last.range_start = null + this.last.range_end = null + // additional + query(this.box).find(`#grid_${this.name}_records`).prop('scrollTop', 0) + // refresh + if (!noRefresh) this.refresh() + } + skip(offset, callBack) { + let url = this.url?.get ?? this.url + if (url) { + this.offset = parseInt(offset) + if (this.offset > this.total) this.offset = this.total - this.limit + if (this.offset < 0 || !w2utils.isInt(this.offset)) this.offset = 0 + this.clear(true) + this.reload(callBack) + } else { + console.log('ERROR: grid.skip() can only be called when you have remote data source.') + } + } + load(url, callBack) { + if (url == null) { + console.log('ERROR: You need to provide url argument when calling .load() method of "'+ this.name +'" object.') + return new Promise((resolve, reject) => { reject() }) + } + // default action + this.clear(true) + return this.request('load', {}, url, callBack) + } + reload(callBack) { + let grid = this + let url = this.url?.get ?? this.url + grid.selectionSave() + if (url) { + // need to remember selection (not just last.selection object) + return this.load(url, () => { + grid.selectionRestore() + if (typeof callBack == 'function') callBack() + }) + } else { + this.reset(true) + this.localSearch() + this.selectionRestore() + if (typeof callBack == 'function') callBack({ status: 'success' }) + return new Promise(resolve => { resolve() }) + } + } + prepareParams(url, fetchOptions) { + let dataType = this.dataType ?? w2utils.settings.dataType + let postParams = fetchOptions.body + switch (dataType) { + case 'HTTPJSON': + postParams = { request: postParams } + if (['PUT', 'DELETE'].includes(fetchOptions.method)) { + fetchOptions.method = 'POST' + } + body2params() + break + case 'HTTP': + if (['PUT', 'DELETE'].includes(fetchOptions.method)) { + fetchOptions.method = 'POST' + } + body2params() + break + case 'RESTFULL': + if (['PUT', 'DELETE'].includes(fetchOptions.method)) { + fetchOptions.headers['Content-Type'] = 'application/json' + } else { + body2params() + } + break + case 'JSON': + if (fetchOptions.method == 'GET') { + postParams = { request: postParams } + body2params() + } else { + fetchOptions.headers['Content-Type'] = 'application/json' + fetchOptions.method = 'POST' + } + break + } + fetchOptions.body = typeof fetchOptions.body == 'string' ? fetchOptions.body : JSON.stringify(fetchOptions.body) + return fetchOptions + function body2params() { + Object.keys(postParams).forEach(key => { + let param = postParams[key] + if (typeof param == 'object') param = JSON.stringify(param) + url.searchParams.append(key, param) + }) + delete fetchOptions.body + } + } + request(action, postData, url, callBack) { + let self = this + let resolve, reject + let requestProm = new Promise((res, rej) => { resolve = res; reject = rej }) + if (postData == null) postData = {} + if (!url) url = this.url + if (!url) return new Promise((resolve, reject) => { reject() }) + // build parameters list + if (!w2utils.isInt(this.offset)) this.offset = 0 + if (!w2utils.isInt(this.last.fetch.offset)) this.last.fetch.offset = 0 + // add list params + let edata + let params = { + limit: this.limit, + offset: parseInt(this.offset) + parseInt(this.last.fetch.offset), + searchLogic: this.last.logic, + search: this.searchData.map((search) => { + let _search = w2utils.clone(search) + if (this.searchMap && this.searchMap[_search.field]) _search.field = this.searchMap[_search.field] + return _search + }), + sort: this.sortData.map((sort) => { + let _sort = w2utils.clone(sort) + if (this.sortMap && this.sortMap[_sort.field]) _sort.field = this.sortMap[_sort.field] + return _sort + }) + } + if (this.searchData.length === 0) { + delete params.search + delete params.searchLogic + } + if (this.sortData.length === 0) { + delete params.sort + } + // append other params + w2utils.extend(params, this.postData) + w2utils.extend(params, postData) + // other actions + if (action == 'delete' || action == 'save') { + delete params.limit + delete params.offset + params.action = action + if (action == 'delete') { + params[this.recid || 'recid'] = this.getSelection() + } + } + // event before + if (action == 'load') { + edata = this.trigger('request', { target: this.name, url, postData: params, httpMethod: 'GET', + httpHeaders: this.httpHeaders }) + if (edata.isCancelled === true) return new Promise((resolve, reject) => { reject() }) + } else { + edata = { detail: { + url, + postData: params, + httpMethod: action == 'save' ? 'PUT' : 'DELETE', + httpHeaders: this.httpHeaders + }} + } + // call server to get data + if (this.last.fetch.offset === 0) { + this.lock(w2utils.lang(this.msgRefresh), true) + } + if (this.last.fetch.controller) try { this.last.fetch.controller.abort() } catch (e) {} + // URL + url = edata.detail.url + switch (action) { + case 'save': + if (url?.save) url = url.save + break + case 'delete': + if (url?.remove) url = url.remove + break + default: + url = url?.get ?? url + } + // process url with routeData + if (Object.keys(this.routeData).length > 0) { + let info = w2utils.parseRoute(url) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + if (this.routeData[info.keys[k].name] == null) continue + url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]) + } + } + } + url = new URL(url, location) + // ajax options + let fetchOptions = this.prepareParams(url, { + method: edata.detail.httpMethod, + headers: edata.detail.httpHeaders, + body: edata.detail.postData + }) + Object.assign(this.last.fetch, { + action: action, + options: fetchOptions, + controller: new AbortController(), + start: Date.now(), + loaded: false + }) + fetchOptions.signal = this.last.fetch.controller.signal + fetch(url, fetchOptions) + .catch(processError) + .then(resp => { + if (resp == null) return // request aborted + if (resp?.status != 200) { + processError(resp ?? {}) + return + } + self.unlock() + resp.json() + .catch(processError) + .then(data => { + this.requestComplete(data, action, callBack, resolve, reject) + }) + }) + if (action == 'load') { + // event after + edata.finish() + } + return requestProm + function processError(response) { + if (response?.name === 'AbortError') { + // request was aborted by the grid + return + } + self.unlock() + // trigger event + let edata2 = self.trigger('error', { response, lastFetch: self.last.fetch }) + if (edata2.isCancelled === true) return + // default behavior + if (response.status && response.status != 200) { + self.error(response.status + ': ' + response.statusText) + } else { + console.log('ERROR: Server communication failed.', + '\n EXPECTED:', { total: 5, records: [{ recid: 1, field: 'value' }] }, + '\n OR:', { error: true, message: 'error message' }) + self.requestComplete({ error: true, message: w2utils.lang(this.msgHTTPError), response }, action, callBack, resolve, reject) + } + // event after + edata2.finish() + } + } + requestComplete(data, action, callBack, resolve, reject) { + let error = data.error ?? false + if (data.error == null && data.status === 'error') error = true + this.last.fetch.response = (Date.now() - this.last.fetch.start) / 1000 + setTimeout(() => { + if (this.show.statusResponse) { + this.status(w2utils.lang('Server Response ${count} seconds', { count: this.last.fetch.response })) + } + }, 10) + this.last.pull_more = false + this.last.pull_refresh = true + // event before + let event_name = 'load' + if (this.last.fetch.action == 'save') event_name = 'save' + if (this.last.fetch.action == 'delete') event_name = 'delete' + let edata = this.trigger(event_name, { target: this.name, error, data, lastFetch: this.last.fetch }) + if (edata.isCancelled === true) { + reject() + return + } + // parse server response + if (!error) { + // default action + if (typeof this.parser == 'function') { + data = this.parser(data) + if (typeof data != 'object') { + console.log('ERROR: Your parser did not return proper object') + } + } else { + if (data == null) { + data = { + error: true, + message: w2utils.lang(this.msgNotJSON), + } + } else if (Array.isArray(data)) { + // if it is plain array, assume these are records + data = { + error, + records: data, + total: data.length + } + } + } + if (action == 'load') { + if (data.total == null) data.total = -1 + if (data.records == null) { + data.records = [] + } + if (data.records.length == this.limit) { + let loaded = this.records.length + data.records.length + this.last.fetch.hasMore = (loaded == this.total ? false : true) + } else { + this.last.fetch.hasMore = false + this.total = this.offset + this.last.fetch.offset + data.records.length + } + if (!this.last.fetch.hasMore) { + // if no more records, then hide spinner + query(this.box).find('#grid_'+ this.name +'_rec_more, #grid_'+ this.name +'_frec_more').hide() + } + if (this.last.fetch.offset === 0) { + this.records = [] + this.summary = [] + } else { + if (data.total != -1 && parseInt(data.total) != parseInt(this.total)) { + let grid = this + this.message(w2utils.lang(this.msgNeedReload)) + .ok(() => { + delete grid.last.fetch.offset + grid.reload() + }) + return new Promise(resolve => { resolve() }) + } + } + if (w2utils.isInt(data.total)) this.total = parseInt(data.total) + // records + if (data.records) { + data.records.forEach(rec => { + if (this.recid) { + rec.recid = this.parseField(rec, this.recid) + } + if (rec.recid == null) { + rec.recid = 'recid-' + this.records.length + } + if (rec.w2ui?.summary === true) { + this.summary.push(rec) + } else { + this.records.push(rec) + } + }) + } + // summary records (if any) + if (data.summary) { + this.summary = [] // reset summary with each call + data.summary.forEach(rec => { + if (this.recid) { + rec.recid = this.parseField(rec, this.recid) + } + if (rec.recid == null) { + rec.recid = 'recid-' + this.summary.length + } + this.summary.push(rec) + }) + } + } else if (action == 'delete') { + this.reset() // unselect old selections + return this.reload() + } + } else { + this.error(w2utils.lang(data.message ?? this.msgServerError)) + reject(data) + } + // event after + let url = this.url?.get ?? this.url + if (!url) { + this.localSort() + this.localSearch() + } + this.total = parseInt(this.total) + // do not refresh if loading on infinite scroll + if (this.last.fetch.offset === 0) { + this.refresh() + } else { + this.scroll() + this.resize() + } + // call back + if (typeof callBack == 'function') callBack(data) // need to be before event:after + resolve(data) + // after event + edata.finish() + this.last.fetch.loaded = true + } + error(msg) { + // let the management of the error outside of the grid + let edata = this.trigger('error', { target: this.name, message: msg }) + if (edata.isCancelled === true) { + return + } + this.message(msg) + // event after + edata.finish() + } + getChanges(recordsBase) { + let changes = [] + if (typeof recordsBase == 'undefined') { + recordsBase = this.records + } + for (let r = 0; r < recordsBase.length; r++) { + let rec = recordsBase[r] + if (rec?.w2ui) { + if (rec.w2ui.changes != null) { + let obj = {} + obj[this.recid || 'recid'] = rec.recid + changes.push(w2utils.extend(obj, rec.w2ui.changes)) + } + // recursively look for changes in non-expanded children + if (rec.w2ui.expanded !== true && rec.w2ui.children && rec.w2ui.children.length) { + changes.push(...this.getChanges(rec.w2ui.children)) + } + } + } + return changes + } + mergeChanges() { + let changes = this.getChanges() + for (let c = 0; c < changes.length; c++) { + let record = this.get(changes[c][this.recid || 'recid']) + for (let s in changes[c]) { + if (s == 'recid' || (this.recid && s == this.recid)) continue // do not allow to change recid + if (typeof changes[c][s] === 'object') changes[c][s] = changes[c][s].text + try { + _setValue(record, s, changes[c][s]) + } catch (e) { + console.log('ERROR: Cannot merge. ', e.message || '', e) + } + if (record.w2ui) delete record.w2ui.changes + } + } + this.refresh() + function _setValue(obj, field, value) { + let fld = field.split('.') + if (fld.length == 1) { + obj[field] = value + } else { + obj = obj[fld[0]] + fld.shift() + _setValue(obj, fld.join('.'), value) + } + } + } + save(callBack) { + let changes = this.getChanges() + let url = this.url?.save ?? this.url + // event before + let edata = this.trigger('save', { target: this.name, changes: changes }) + if (edata.isCancelled === true) return + if (url) { + this.request('save', { 'changes' : edata.detail.changes }, null, + (data) => { + if (!data.error) { + // only merge changes, if save was successful + this.mergeChanges() + } + // event after + edata.finish() + // call back + if (typeof callBack == 'function') callBack(data) + } + ) + } else { + this.mergeChanges() + // event after + edata.finish() + } + } + editField(recid, column, value, event) { + let self = this + if (this.last.inEditMode === true) { + // This is triggerign when user types fast + if (event && event.keyCode == 13) { + let { index, column, value } = this.last._edit + this.editChange({ type: 'custom', value }, index, column, event) + this.editDone(index, column, event) + } else { + // when 2 chars entered fast (spreadsheet) + let input = query(this.box).find('div.w2ui-edit-box .w2ui-input') + if (input.length > 0) { + if (input.get(0).tagName == 'DIV') { + input.text(input.text() + value) + w2utils.setCursorPosition(input.get(0), input.text().length) + } else { + input.val(input.val() + value) + w2utils.setCursorPosition(input.get(0), input.val().length) + } + } + } + return + } + let index = this.get(recid, true) + let edit = this.getCellEditable(index, column) + if (!edit || ['checkbox', 'check'].includes(edit.type)) return + let rec = this.records[index] + let col = this.columns[column] + let prefix = (col.frozen === true ? '_f' : '_') + if (['list', 'enum', 'file'].indexOf(edit.type) != -1) { + console.log('ERROR: input types "list", "enum" and "file" are not supported in inline editing.') + return + } + // event before + let edata = this.trigger('editField', { target: this.name, recid, column, value, index, originalEvent: event }) + if (edata.isCancelled === true) return + value = edata.detail.value + // default behaviour + this.last.inEditMode = true + this.last.editColumn = column + this.last._edit = { value: value, index: index, column: column, recid: recid } + this.selectNone(true) // no need to trigger select event + this.select({ recid: recid, column: column }) + // create input element + let tr = query(this.box).find('#grid_'+ this.name + prefix +'rec_' + w2utils.escapeId(recid)) + let div = tr.find('[col="'+ column +'"] > div') // TD -> DIV + this.last._edit.tr = tr + this.last._edit.div = div + // clear previous if any (spreadsheet) + query(this.box).find('div.w2ui-edit-box').remove() + // for spreadsheet - insert into selection + if (this.selectType != 'row') { + query(this.box).find('#grid_'+ this.name + prefix + 'selection') + .attr('id', 'grid_'+ this.name + '_editable') + .removeClass('w2ui-selection') + .addClass('w2ui-edit-box') + .prepend('
    ') + .find('.w2ui-selection-resizer') + .remove() + div = query(this.box).find('#grid_'+ this.name + '_editable > div:first-child') + } + edit.attr = edit.attr ?? '' + edit.text = edit.text ?? '' + edit.style = edit.style ?? '' + edit.items = edit.items ?? [] + let val = (rec.w2ui?.changes?.[col.field] != null + ? w2utils.stripTags(rec.w2ui.changes[col.field]) + : w2utils.stripTags(self.parseField(rec, col.field))) + if (val == null) val = '' + let prevValue = (typeof val != 'object' ? val : '') + if (edata.detail.prevValue != null) prevValue = edata.detail.prevValue + if (value != null) val = value + let addStyle = (col.style != null ? col.style + ';' : '') + if (typeof col.render == 'string' + && ['number', 'int', 'float', 'money', 'percent', 'size'].includes(col.render.split(':')[0])) { + addStyle += 'text-align: right;' + } + // normalize items, if not yet normlized + if (edit.items.length > 0 && !w2utils.isPlainObject(edit.items[0])) { + edit.items = w2utils.normMenu(edit.items) + } + let input + let dropTypes = ['date', 'time', 'datetime', 'color', 'list', 'combo'] + let styles = getComputedStyle(tr.find('[col="'+ column +'"] > div').get(0)) + let font = `font-family: ${styles['font-family']}; font-size: ${styles['font-size']};` + switch (edit.type) { + case 'div': { + div.addClass('w2ui-editable') + .html(w2utils.stripSpaces(`
    +
    ${edit.text}`)) + input = div.find('div.w2ui-input').get(0) + input.innerText = (typeof val != 'object' ? val : '') + if (value != null) { + w2utils.setCursorPosition(input, input.innerText.length) + } else { + w2utils.setCursorPosition(input, 0, input.innerText.length) + } + break + } + default: { + div.addClass('w2ui-editable') + .html(w2utils.stripSpaces(`${edit.text}`)) + input = div.find('input').get(0) + // issue #499 + if (edit.type == 'number') { + val = w2utils.formatNumber(val) + } + if (edit.type == 'date') { + val = w2utils.formatDate(w2utils.isDate(val, edit.format, true) || new Date(), edit.format) + } + input.value = (typeof val != 'object' ? val : '') + // init w2field, attached to input._w2field + let doHide = (event) => { + let escKey = this.last._edit?.escKey + // check if any element is selected in drop down + let selected = false + let name = query(input).data('tooltipName') + if (name && w2tooltip.get(name[0])?.selected != null) { + selected = true + } + // trigger change on new value if selected from overlay + if (this.last.inEditMode && !escKey && dropTypes.includes(edit.type) // drop down types + && (event.detail.overlay.anchor?.id == this.last._edit.input?.id || edit.type == 'list')) { + this.editChange() + this.editDone(undefined, undefined, { keyCode: selected ? 13 : 0 }) // advance on select + } + } + new w2field(w2utils.extend({}, edit, { + el: input, + selected: val, + onSelect: doHide, + onHide: doHide + })) + if (value == null && input) { + // if no new value, then select content + input.select() + } + } + } + Object.assign(this.last._edit, { input, edit }) + query(input) + .off('.w2ui-editable') + .on('blur.w2ui-editable', (event) => { + if (this.last.inEditMode) { + let type = this.last._edit.edit.type + let name = query(input).data('tooltipName') // if popup is open + if (dropTypes.includes(type) && name) { + // drop downs finish edit when popover is closed + return + } + this.editChange(input, index, column, event) + this.editDone() + } + }) + .on('mousedown.w2ui-editable', (event) => { + event.stopPropagation() + }) + .on('click.w2ui-editable', (event) => { + expand.call(input, event) + }) + .on('paste.w2ui-editable', (event) => { + // clean paste to be plain text + event.preventDefault() + let text = event.clipboardData.getData('text/plain') + document.execCommand('insertHTML', false, text) + }) + .on('keyup.w2ui-editable', (event) => { + expand.call(input, event) + }) + .on('keydown.w2ui-editable', (event) => { + switch (event.keyCode) { + case 8: // backspace; + if (edit.type == 'list' && !input._w2field) { // cancel backspace when deleting element + event.preventDefault() + } + break + case 9: + case 13: + event.preventDefault() + break + case 27: // esc button exits edit mode, but if in a popup, it will also close the popup, hence + // if tooltip is open - hide it + let name = query(input).data('tooltipName') + if (name && name.length > 0) { + this.last._edit.escKey = true + w2tooltip.hide(name[0]) + event.preventDefault() + } + event.stopPropagation() + break + } + // need timeout so, this handler is executed after key is processed by browser + setTimeout(() => { + switch (event.keyCode) { + case 9: { // tab + let next = event.shiftKey + ? self.prevCell(index, column, true) + : self.nextCell(index, column, true) + if (next != null) { + let recid = self.records[next.index].recid + this.editChange(input, index, column, event) + this.editDone(index, column, event) + if (self.selectType != 'row') { + self.selectNone(true) // no need to trigger select event + self.select({ recid, column: next.colIndex }) + } else { + self.editField(recid, next.colIndex, null, event) + } + if (event.preventDefault) event.preventDefault() + } + break + } + case 13: { // enter + // check if any element is selected in drop down + let selected = false + let name = query(input).data('tooltipName') + if (name && w2tooltip.get(name[0]).selected != null) { + selected = true + } + // if tooltip is not open or no element is selected + if (!name || !selected) { + this.editChange(input, index, column, event) + this.editDone(index, column, event) + } + break + } + case 27: { // escape + this.last._edit.escKey = false + let old = self.parseField(rec, col.field) + if (rec.w2ui?.changes?.[col.field] != null) old = rec.w2ui.changes[col.field] + if (input._prevValue != null) old = input._prevValue + if (input.tagName == 'DIV') { + input.innerText = old != null ? old : '' + } else { + input.value = old != null ? old : '' + } + this.editDone(index, column, event) + setTimeout(() => { self.select({ recid: recid, column: column }) }, 1) + break + } + } + // if input too small - expand + expand(input) + }, 1) + }) + // save previous value + if (input) input._prevValue = prevValue + // focus and select + setTimeout(() => { + if (!this.last.inEditMode) return + if (input) { + input.focus() + clearTimeout(this.last.kbd_timer) // keep focus + input.resize = expand + expand(input) + } + }, 50) + // event after + edata.finish({ input }) + return + function expand(input) { + try { + let styles = getComputedStyle(input) + let val = (input.tagName.toUpperCase() == 'DIV' ? input.innerText : input.value) + let editBox = query(self.box).find('#grid_'+ self.name + '_editable').get(0) + let style = `font-family: ${styles['font-family']}; font-size: ${styles['font-size']}; white-space: no-wrap;` + let width = w2utils.getStrWidth(val, style) + if (width + 20 > editBox.clientWidth) { + query(editBox).css('width', width + 20 + 'px') + } + } catch (e) { + } + } + } + editChange(input, index, column, event) { + // if params are not specified + input = input ?? this.last._edit.input + index = index ?? this.last._edit.index + column = column ?? this.last._edit.column + event = event ?? {} + // all other fields + let summary = index < 0 + index = index < 0 ? -index - 1 : index + let records = summary ? this.summary : this.records + let rec = records[index] + let col = this.columns[column] + let new_val = (input?.tagName == 'DIV' ? input.innerText : input.value) + let fld = input._w2field + if (fld) { + if (fld.type == 'list') { + new_val = fld.selected + } + if (Object.keys(new_val).length === 0 || new_val == null) new_val = '' + if (!w2utils.isPlainObject(new_val)) new_val = fld.clean(new_val) + } + if (input.type == 'checkbox') { + if (rec.w2ui?.editable === false) input.checked = !input.checked + new_val = input.checked + } + let old_val = this.parseField(rec, col.field) + let prev_val = (rec.w2ui?.changes && rec.w2ui.changes.hasOwnProperty(col.field) ? rec.w2ui.changes[col.field]: old_val) + // change/restore event + let edata = { + target: this.name, input, + recid: rec.recid, index, column, + originalEvent: event, + value: { + new: new_val, + previous: prev_val, + original: old_val, + } + } + if (event.target?._prevValue != null) edata.value.previous = event.target._prevValue + let count = 0 // just in case to avoid infinite loop + while (count < 20) { + count++ + new_val = edata.value.new + if ((typeof new_val != 'object' && String(old_val) != String(new_val)) || + (typeof new_val == 'object' && new_val && new_val.id != old_val + && (typeof old_val != 'object' || old_val == null || new_val.id != old_val.id))) { + // change event + edata = this.trigger('change', edata) + if (edata.isCancelled !== true) { + if (new_val !== edata.detail.value.new) { + // re-evaluate the type of change to be made + continue + } + // default action + if ((edata.detail.value.new === '' || edata.detail.value.new == null) && (prev_val === '' || prev_val == null)) { + // value did not change, was empty is empty + } else { + rec.w2ui = rec.w2ui ?? {} + rec.w2ui.changes = rec.w2ui.changes ?? {} + rec.w2ui.changes[col.field] = edata.detail.value.new + } + // event after + edata.finish() + } + } else { + // restore event + edata = this.trigger('restore', edata) + if (edata.isCancelled !== true) { + if (new_val !== edata.detail.value.new) { + // re-evaluate the type of change to be made + continue + } + // default action + if (rec.w2ui?.changes) { + delete rec.w2ui.changes[col.field] + if (Object.keys(rec.w2ui.changes).length === 0) { + delete rec.w2ui.changes + } + } + // event after + edata.finish() + } + } + break + } + } + editDone(index, column, event) { + // if params are not specified + index = index ?? this.last._edit.index + column = column ?? this.last._edit.column + event = event ?? {} + // removal of input happens when TR is redrawn + if (this.advanceOnEdit && event.keyCode == 13) { + let next = event.shiftKey ? this.prevRow(index, column, 1) : this.nextRow(index, column, 1) + if (next == null) next = index // keep the same + setTimeout(() => { + if (this.selectType != 'row') { + this.selectNone(true) // no need to trigger select event + this.select({ recid: this.records[next].recid, column: column }) + } else { + this.editField(this.records[next].recid, column, null, event) + } + }, 1) + } + let summary = index < 0 + let cell = query(this.last._edit.tr).find('[col="'+ column +'"]') + let rec = this.records[index] + let col = this.columns[column] + // need to set before remove, as remove will trigger blur + this.last.inEditMode = false + this.last._edit = null + // remove - by updating cell data + if (!summary) { + if (rec.w2ui?.changes?.[col.field] != null) { + cell.addClass('w2ui-changed') + } else { + cell.removeClass('w2ui-changed') + } + cell.replace(this.getCellHTML(index, column, summary)) + } + // remove - spreadsheet + query(this.box).find('div.w2ui-edit-box').remove() + // update toolbar buttons + this.updateToolbar() + // keep grid in focus if needed + setTimeout(() => { + let input = query(this.box).find(`#grid_${this.name}_focus`).get(0) + if (document.activeElement !== input && !this.last.inEditMode) { + input.focus() + } + }, 10) + } + 'delete'(force) { + // event before + let edata = this.trigger('delete', { target: this.name, force: force }) + if (force) this.message() // close message + if (edata.isCancelled === true) return + force = edata.detail.force + // default action + let recs = this.getSelection() + if (recs.length === 0) return + if (this.msgDelete != '' && !force) { + this.confirm({ + text: w2utils.lang(this.msgDelete, { + count: recs.length, + records: w2utils.lang( recs.length == 1 ? 'record' : 'records') + }), + width: 380, + height: 170, + yes_text: 'Delete', + yes_class: 'w2ui-btn-red', + no_text: 'Cancel', + }) + .yes(event => { + event.detail.self.close() + this.delete(true) + }) + .no(event => { + event.detail.self.close() + }) + return + } + // call delete script + let url = (typeof this.url != 'object' ? this.url : this.url.remove) + if (url) { + this.request('delete') + } else { + if (typeof recs[0] != 'object') { + this.selectNone() + this.remove.apply(this, recs) + } else { + // clear cells + for (let r = 0; r < recs.length; r++) { + let fld = this.columns[recs[r].column].field + let ind = this.get(recs[r].recid, true) + let rec = this.records[ind] + if (ind != null && fld != 'recid') { + this.records[ind][fld] = '' + if (rec.w2ui?.changes) delete rec.w2ui.changes[fld] + // -- style should not be deleted + // if (rec.style != null && w2utils.isPlainObject(rec.style) && rec.style[recs[r].column]) { + // delete rec.style[recs[r].column]; + // } + } + } + this.update() + } + } + // event after + edata.finish() + } + click(recid, event) { + let time = Date.now() + let column = null + if (this.last.cancelClick == true || (event && event.altKey)) return + if ((typeof recid == 'object') && (recid !== null)) { + column = recid.column + recid = recid.recid + } + if (event == null) event = {} + // check for double click + if (time - parseInt(this.last.click_time) < 350 && this.last.click_recid == recid && event.type == 'click') { + this.dblClick(recid, event) + return + } + // hide bubble + if (this.last.bubbleEl) { + this.last.bubbleEl = null + } + this.last.click_time = time + let last_recid = this.last.click_recid + this.last.click_recid = recid + // column user clicked on + if (column == null && event.target) { + let trg = event.target + if (trg.tagName != 'TD') trg = query(trg).closest('td')[0] + if (query(trg).attr('col') != null) column = parseInt(query(trg).attr('col')) + } + // event before + let edata = this.trigger('click', { target: this.name, recid, column, originalEvent: event }) + if (edata.isCancelled === true) return + // default action + let sel = this.getSelection() + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', false) + let ind = this.get(recid, true) + let selectColumns = [] + this.last.sel_ind = ind + this.last.sel_col = column + this.last.sel_recid = recid + this.last.sel_type = 'click' + // multi select with shift key + let start, end, t1, t2 + if (event.shiftKey && sel.length > 0 && this.multiSelect) { + if (sel[0].recid) { + start = this.get(sel[0].recid, true) + end = this.get(recid, true) + if (column > sel[0].column) { + t1 = sel[0].column + t2 = column + } else { + t1 = column + t2 = sel[0].column + } + for (let c = t1; c <= t2; c++) selectColumns.push(c) + } else { + start = this.get(last_recid, true) + end = this.get(recid, true) + } + let sel_add = [] + if (start > end) { let tmp = start; start = end; end = tmp } + let url = this.url?.get ? this.url.get : this.url + for (let i = start; i <= end; i++) { + if (this.searchData.length > 0 && !url && !this.last.searchIds.includes(i)) continue + if (this.selectType == 'row') { + sel_add.push(this.records[i].recid) + } else { + for (let sc = 0; sc < selectColumns.length; sc++) { + sel_add.push({ recid: this.records[i].recid, column: selectColumns[sc] }) + } + } + //sel.push(this.records[i].recid); + } + this.select(sel_add) + } else { + let last = this.last.selection + let flag = (last.indexes.indexOf(ind) != -1 ? true : false) + let fselect = false + // if clicked on the checkbox + if (query(event.target).closest('td').hasClass('w2ui-col-select')) fselect = true + // clear other if necessary + if (((!event.ctrlKey && !event.shiftKey && !event.metaKey && !fselect) || !this.multiSelect) && !this.showSelectColumn) { + if (this.selectType != 'row' && !last.columns[ind]?.includes(column)) flag = false + this.selectNone(true) // no need to trigger select event + if (flag === true && sel.length == 1) { + this.unselect({ recid: recid, column: column }) + } else { + this.select({ recid: recid, column: column }) + } + } else { + if (this.selectType != 'row' && !last.columns[ind]?.includes(column)) flag = false + if (flag === true) { + this.unselect({ recid: recid, column: column }) + } else { + this.select({ recid: recid, column: column }) + } + } + } + this.status() + this.initResize() + // event after + edata.finish() + } + columnClick(field, event) { + // ignore click if column was resized + if (this.last.colResizing === true) { + return + } + // event before + let edata = this.trigger('columnClick', { target: this.name, field: field, originalEvent: event }) + if (edata.isCancelled === true) return + // default behaviour + if (this.selectType == 'row') { + let column = this.getColumn(field) + if (column && column.sortable) this.sort(field, null, (event && (event.ctrlKey || event.metaKey) ? true : false)) + if (edata.detail.field == 'line-number') { + if (this.getSelection().length >= this.records.length) { + this.selectNone() + } else { + this.selectAll() + } + } + } else { + if (event.altKey){ + let column = this.getColumn(field) + if (column && column.sortable) this.sort(field, null, (event && (event.ctrlKey || event.metaKey) ? true : false)) + } + // select entire column + if (edata.detail.field == 'line-number') { + if (this.getSelection().length >= this.records.length) { + this.selectNone() + } else { + this.selectAll() + } + } else { + if (!event.shiftKey && !event.metaKey && !event.ctrlKey) { + this.selectNone(true) + } + let tmp = this.getSelection() + let column = this.getColumn(edata.detail.field, true) + let sel = [] + let cols = [] + // check if there was a selection before + if (tmp.length != 0 && event.shiftKey) { + let start = column + let end = tmp[0].column + if (start > end) { + start = tmp[0].column + end = column + } + for (let i = start; i<=end; i++) cols.push(i) + } else { + cols.push(column) + } + edata = this.trigger('columnSelect', { target: this.name, columns: cols }) + if (edata.isCancelled !== true) { + for (let i = 0; i < this.records.length; i++) { + sel.push({ recid: this.records[i].recid, column: cols }) + } + this.select(sel) + } + edata.finish() + } + } + // event after + edata.finish() + } + columnDblClick(field, event) { + // event before + let edata = this.trigger('columnDblClick', { target: this.name, field: field, originalEvent: event }) + if (edata.isCancelled === true) return + // event after + edata.finish() + } + focus(event) { + // event before + let edata = this.trigger('focus', { target: this.name, originalEvent: event }) + if (edata.isCancelled === true) return false + // default behaviour + this.hasFocus = true + query(this.box).removeClass('w2ui-inactive').find('.w2ui-inactive').removeClass('w2ui-inactive') + setTimeout(() => { + let txt = query(this.box).find(`#grid_${this.name}_focus`).get(0) + if (txt && document.activeElement != txt) { + txt.focus() + } + }, 10) + // event after + edata.finish() + } + blur(event) { + // event before + let edata = this.trigger('blur', { target: this.name, originalEvent: event }) + if (edata.isCancelled === true) return false + // default behaviour + this.hasFocus = false + query(this.box).addClass('w2ui-inactive').find('.w2ui-selected').addClass('w2ui-inactive') + query(this.box).find('.w2ui-selection').addClass('w2ui-inactive') + // event after + edata.finish() + } + keydown(event) { + // this method is called from w2utils + let obj = this + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (obj.keyboard !== true) return + // trigger event + let edata = obj.trigger('keydown', { target: obj.name, originalEvent: event }) + if (edata.isCancelled === true) return + // default behavior + if (query(this.box).find('.w2ui-message').length > 0) { + // if there are messages + if (event.keyCode == 27) this.message() + return + } + let empty = false + let records = query(obj.box).find('#grid_'+ obj.name +'_records') + let sel = obj.getSelection() + if (sel.length === 0) empty = true + let recid = sel[0] || null + let columns = [] + let recid2 = sel[sel.length-1] + if (typeof recid == 'object' && recid != null) { + recid = sel[0].recid + columns = [] + let ii = 0 + while (true) { + if (!sel[ii] || sel[ii].recid != recid) break + columns.push(sel[ii].column) + ii++ + } + recid2 = sel[sel.length-1].recid + } + let ind = obj.get(recid, true) + let ind2 = obj.get(recid2, true) + let recEL = query(obj.box).find(`#grid_${obj.name}_rec_${(ind != null ? w2utils.escapeId(obj.records[ind].recid) : 'none')}`) + let pageSize = Math.floor(records[0].clientHeight / obj.recordHeight) + let cancel = false + let key = event.keyCode + let shiftKey = event.shiftKey + switch (key) { + case 8: // backspace + case 46: // delete + // delete if button is visible + obj.delete() + cancel = true + event.stopPropagation() + break + case 27: // escape + obj.selectNone() + cancel = true + break + case 65: // cmd + A + if (!event.metaKey && !event.ctrlKey) break + obj.selectAll() + cancel = true + break + case 13: // enter + // if expandable columns - expand it + if (this.selectType == 'row' && obj.show.expandColumn === true) { + if (recEL.length <= 0) break + obj.toggle(recid, event) + cancel = true + } else { // or enter edit + for (let c = 0; c < this.columns.length; c++) { + let edit = this.getCellEditable(ind, c) + if (edit) { + columns.push(parseInt(c)) + break + } + } + // edit last column that was edited + if (this.selectType == 'row' && this.last._edit && this.last._edit.column) { + columns = [this.last._edit.column] + } + if (columns.length > 0) { + obj.editField(recid, this.last.editColumn || columns[0], null, event) + cancel = true + } + } + break + case 37: // left + moveLeft() + break + case 39: // right + moveRight() + break + case 33: // + moveUp(pageSize) + break + case 34: // + moveDown(pageSize) + break + case 35: // + moveDown(-1) + break + case 36: // + moveUp(-1) + break + case 38: // up + // ctrl (or cmd) + up -> same as home + moveUp(event.metaKey || event.ctrlKey ? -1 : 1) + break + case 40: // down + // ctrl (or cmd) + up -> same as end + moveDown(event.metaKey || event.ctrlKey ? -1 : 1) + break + // copy & paste + case 17: // ctrl key + case 91: // cmd key + // SLOW: 10k records take 7.0 + if (empty) break + // in Safari need to copy to buffer on cmd or ctrl key (otherwise does not work) + if (w2utils.isSafari) { + obj.last.copy_event = obj.copy(false, event) + let focus = query(obj.box).find('#grid_'+ obj.name + '_focus') + focus.val(obj.last.copy_event.detail.text) + focus[0].select() + } + break + case 67: // - c + // this fill trigger event.onComplete + if (event.metaKey || event.ctrlKey) { + if (w2utils.isSafari) { + obj.copy(obj.last.copy_event, event) + } else { + obj.last.copy_event = obj.copy(false, event) + let focus = query(obj.box).find('#grid_'+ obj.name + '_focus') + focus.val(obj.last.copy_event.detail.text) + focus[0].select() + obj.copy(obj.last.copy_event, event) + } + } + break + case 88: // x - cut + if (empty) break + if (event.ctrlKey || event.metaKey) { + if (w2utils.isSafari) { + obj.copy(obj.last.copy_event, event) + } else { + obj.last.copy_event = obj.copy(false, event) + let focus = query(obj.box).find('#grid_'+ obj.name + '_focus') + focus.val(obj.last.copy_event.detail.text) + focus[0].select() + obj.copy(obj.last.copy_event, event) + } + } + break + } + let tmp = [32, 187, 189, 192, 219, 220, 221, 186, 222, 188, 190, 191] // other typeable chars + for (let i = 48; i <= 111; i++) tmp.push(i) // 0-9,a-z,A-Z,numpad + if (tmp.indexOf(key) != -1 && !event.ctrlKey && !event.metaKey && !cancel) { + if (columns.length === 0) columns.push(0) + cancel = false + // move typed key into edit + setTimeout(() => { + let focus = query(obj.box).find('#grid_'+ obj.name + '_focus') + let key = focus.val() + focus.val('') + obj.editField(recid, columns[0], key, event) + }, 1) + } + if (cancel) { // cancel default behaviour + if (event.preventDefault) event.preventDefault() + } + // event after + edata.finish() + function moveLeft() { + if (empty) { // no selection + selectTopRecord() + return + } + if (obj.selectType == 'row') { + if (recEL.length <= 0) return + let tmp = obj.records[ind].w2ui || {} + if (tmp && tmp.parent_recid != null && (!Array.isArray(tmp.children) || tmp.children.length === 0 || !tmp.expanded)) { + obj.unselect(recid) + obj.collapse(tmp.parent_recid, event) + obj.select(tmp.parent_recid) + } else { + obj.collapse(recid, event) + } + } else { + let prev = obj.prevCell(ind, columns[0]) + if (prev?.index != ind) { + prev = null + } else { + prev = prev?.colIndex + } + if (!shiftKey && prev == null) { + obj.selectNone(true) + prev = 0 + } + if (prev != null) { + if (shiftKey && obj.multiSelect) { + if (tmpUnselect()) return + let tmp = [] + let newSel = [] + let unSel = [] + if (columns.indexOf(obj.last.sel_col) === 0 && columns.length > 1) { + for (let i = 0; i < sel.length; i++) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid) + unSel.push({ recid: sel[i].recid, column: columns[columns.length-1] }) + } + obj.unselect(unSel) + obj.scrollIntoView(ind, columns[columns.length-1], true) + } else { + for (let i = 0; i < sel.length; i++) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid) + newSel.push({ recid: sel[i].recid, column: prev }) + } + obj.select(newSel) + obj.scrollIntoView(ind, prev, true) + } + } else { + obj.click({ recid: recid, column: prev }, event) + obj.scrollIntoView(ind, prev, true) + } + } else { + // if selected more then one, then select first + if (!shiftKey) { + obj.selectNone(true) + } + } + } + cancel = true + } + function moveRight() { + if (empty) { + selectTopRecord() + return + } + if (obj.selectType == 'row') { + if (recEL.length <= 0) return + obj.expand(recid, event) + } else { + let next = obj.nextCell(ind, columns[columns.length-1]) // columns is an array of selected columns + if (next.index != ind) { + next = null + } else { + next = next.colIndex + } + if (!shiftKey && next == null) { + obj.selectNone(true) + next = obj.columns.length-1 + } + if (next != null) { + if (shiftKey && key == 39 && obj.multiSelect) { + if (tmpUnselect()) return + let tmp = [] + let newSel = [] + let unSel = [] + if (columns.indexOf(obj.last.sel_col) == columns.length-1 && columns.length > 1) { + for (let i = 0; i < sel.length; i++) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid) + unSel.push({ recid: sel[i].recid, column: columns[0] }) + } + obj.unselect(unSel) + obj.scrollIntoView(ind, columns[0], true) + } else { + for (let i = 0; i < sel.length; i++) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid) + newSel.push({ recid: sel[i].recid, column: next }) + } + obj.select(newSel) + obj.scrollIntoView(ind, next, true) + } + } else { + obj.click({ recid: recid, column: next }, event) + obj.scrollIntoView(ind, next, true) + } + } else { + // if selected more then one, then select first + if (!shiftKey) { + obj.selectNone(true) + } + } + } + cancel = true + } + function moveUp(numRows) { + if (empty) selectTopRecord() + if (recEL.length <= 0) return + // move to the previous record + let prev = obj.prevRow(ind, obj.selectType == 'row' ? 0 : sel[0].column, numRows) + if (!shiftKey && prev == null) { + if (obj.searchData.length != 0 && !url) { + prev = obj.last.searchIds[0] + } else { + prev = 0 + } + } + if (prev != null) { + if (shiftKey && obj.multiSelect) { // expand selection + if (tmpUnselect()) return + if (obj.selectType == 'row') { + if (obj.last.sel_ind > prev && obj.last.sel_ind != ind2) { + obj.unselect(obj.records[ind2].recid) + } else { + obj.select(obj.records[prev].recid) + } + } else { + if (obj.last.sel_ind > prev && obj.last.sel_ind != ind2) { + prev = ind2 + let tmp = [] + for (let c = 0; c < columns.length; c++) tmp.push({ recid: obj.records[prev].recid, column: columns[c] }) + obj.unselect(tmp) + } else { + let tmp = [] + for (let c = 0; c < columns.length; c++) tmp.push({ recid: obj.records[prev].recid, column: columns[c] }) + obj.select(tmp) + } + } + } else { // move selected record + obj.selectNone(true) // no need to trigger select event + obj.click({ recid: obj.records[prev].recid, column: columns[0] }, event) + } + obj.scrollIntoView(prev, null, true, numRows != 1) // top align record + if (event.preventDefault) event.preventDefault() + } else { + // if selected more then one, then select first + if (!shiftKey) { + obj.selectNone(true) + } + } + } + function moveDown(numRows) { + if (empty) selectTopRecord() + if (recEL.length <= 0) return + // move to the next record + let next = obj.nextRow(ind2, obj.selectType == 'row' ? 0 : sel[0].column, numRows) + if (!shiftKey && next == null) { + if (obj.searchData.length != 0 && !url) { + next = obj.last.searchIds[obj.last.searchIds.length - 1] + } else { + next = obj.records.length - 1 + } + } + if (next != null) { + if (shiftKey && obj.multiSelect) { // expand selection + if (tmpUnselect()) return + if (obj.selectType == 'row') { + if (obj.last.sel_ind < next && obj.last.sel_ind != ind) { + obj.unselect(obj.records[ind].recid) + } else { + obj.select(obj.records[next].recid) + } + } else { + if (obj.last.sel_ind < next && obj.last.sel_ind != ind) { + next = ind + let tmp = [] + for (let c = 0; c < columns.length; c++) tmp.push({ recid: obj.records[next].recid, column: columns[c] }) + obj.unselect(tmp) + } else { + let tmp = [] + for (let c = 0; c < columns.length; c++) tmp.push({ recid: obj.records[next].recid, column: columns[c] }) + obj.select(tmp) + } + } + } else { // move selected record + obj.selectNone(true) // no need to trigger select event + obj.click({ recid: obj.records[next].recid, column: columns[0] }, event) + } + obj.scrollIntoView(next, null, true, numRows != 1) // top align record + cancel = true + } else { + // if selected more then one, then select first + if (!shiftKey) { + obj.selectNone(true) // no need to trigger select event + } + } + } + function selectTopRecord() { + if (!obj.records || obj.records.length === 0) return + let ind = Math.floor(records[0].scrollTop / obj.recordHeight) + 1 + if (!obj.records[ind] || ind < 2) ind = 0 + if (typeof obj.records[ind] === 'undefined') return + obj.select({ recid: obj.records[ind].recid, column: 0}) + } + function tmpUnselect () { + if (obj.last.sel_type != 'click') return false + if (obj.selectType != 'row') { + obj.last.sel_type = 'key' + if (sel.length > 1) { + for (let s = 0; s < sel.length; s++) { + if (sel[s].recid == obj.last.sel_recid && sel[s].column == obj.last.sel_col) { + sel.splice(s, 1) + break + } + } + obj.unselect(sel) + return true + } + return false + } else { + obj.last.sel_type = 'key' + if (sel.length > 1) { + sel.splice(sel.indexOf(obj.records[obj.last.sel_ind].recid), 1) + obj.unselect(sel) + return true + } + return false + } + } + } + scrollIntoView(ind, column, instant, recTop) { + let buffered = this.records.length + if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length + if (buffered === 0) return + if (ind == null) { + let sel = this.getSelection() + if (sel.length === 0) return + if (w2utils.isPlainObject(sel[0])) { + ind = sel[0].index + column = sel[0].column + } else { + ind = this.get(sel[0], true) + } + } + let records = query(this.box).find(`#grid_${this.name}_records`) + let recWidth = records[0].clientWidth + let recHeight = records[0].clientHeight + let recSTop = records[0].scrollTop + let recSLeft = records[0].scrollLeft + // if all records in view + let len = this.last.searchIds.length + if (len > 0) ind = this.last.searchIds.indexOf(ind) // if search is applied + // smooth or instant + records.css({ 'scroll-behavior': instant ? 'auto' : 'smooth' }) + // vertical + if (recHeight < this.recordHeight * (len > 0 ? len : buffered) && records.length > 0) { + // scroll to correct one + let t1 = Math.floor(recSTop / this.recordHeight) + let t2 = t1 + Math.floor(recHeight / this.recordHeight) + if (ind == t1) { + records.prop('scrollTop', recSTop - recHeight / 1.3) + } + if (ind == t2) { + records.prop('scrollTop', recSTop + recHeight / 1.3) + } + if (ind < t1 || ind > t2) { + records.prop('scrollTop', (ind - 1) * this.recordHeight) + } + if (recTop === true) { + records.prop('scrollTop', ind * this.recordHeight) + } + } + // horizontal + if (column != null) { + let x1 = 0 + let x2 = 0 + let sb = w2utils.scrollBarSize() + for (let i = 0; i <= column; i++) { + let col = this.columns[i] + if (col.frozen || col.hidden) continue + x1 = x2 + x2 += parseInt(col.sizeCalculated) + } + if (recWidth < x2 - recSLeft) { // right + records.prop('scrollLeft', x1 - sb) + } else if (x1 < recSLeft) { // left + records.prop('scrollLeft', x2 - recWidth + sb * 2) + } + } + } + scrollToColumn(field) { + if (field == null) + return + let sWidth = 0 + let found = false + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + if (col.field == field) { + found = true + break + } + if (col.frozen || col.hidden) + continue + let cSize = parseInt(col.sizeCalculated ? col.sizeCalculated : col.size) + sWidth += cSize + } + if (!found) + return + this.last.scrollLeft = sWidth+1 + this.scroll() + } + + dblClick(recid, event) { + // find columns + let column = null + if ((typeof recid == 'object') && (recid !== null)) { + column = recid.column + recid = recid.recid + } + if (event == null) event = {} + // column user clicked on + if (column == null && event.target) { + let tmp = event.target + if (tmp.tagName.toUpperCase() != 'TD') tmp = query(tmp).closest('td')[0] + column = parseInt(query(tmp).attr('col')) + } + let index = this.get(recid, true) + let rec = this.records[index] + // event before + let edata = this.trigger('dblClick', { target: this.name, recid: recid, column: column, originalEvent: event }) + if (edata.isCancelled === true) return + // default action + this.selectNone(true) // no need to trigger select event + let edit = this.getCellEditable(index, column) + if (edit) { + this.editField(recid, column, null, event) + } else { + this.select({ recid: recid, column: column }) + if (this.show.expandColumn || (rec && rec.w2ui && Array.isArray(rec.w2ui.children))) this.toggle(recid) + } + // event after + edata.finish() + } + showContextMenu(recid, column, event) { + if (this.last.userSelect == 'text') return + if (event == null) { + event = { offsetX: 0, offsetY: 0, target: query(this.box).find(`#grid_${this.name}_rec_${recid}`)[0] } + } + if (event.offsetX == null) { + event.offsetX = event.layerX - event.target.offsetLeft + event.offsetY = event.layerY - event.target.offsetTop + } + if (w2utils.isFloat(recid)) recid = parseFloat(recid) + let sel = this.getSelection() + if (this.selectType == 'row') { + if (sel.indexOf(recid) == -1) this.click(recid) + } else { + let selected = false + // check if any selected sel in the right row/column + for (let i = 0; i < sel.length; i++) { + if (sel[i].recid == recid || sel[i].column == column) selected = true + } + if (!selected && recid != null) this.click({ recid: recid, column: column }) + if (!selected && column != null) this.columnClick(this.columns[column].field, event) + } + // event before + let edata = this.trigger('contextMenu', { target: this.name, originalEvent: event, recid, column }) + if (edata.isCancelled === true) return + // default action + if (this.contextMenu.length > 0) { + w2menu.show({ + anchor: document.body, + originalEvent: event, + items: this.contextMenu + }) + .select((event) => { + clearTimeout(this.last.kbd_timer) // keep grid in focus + this.contextMenuClick(recid, event) + }) + clearTimeout(this.last.kbd_timer) // keep grid in focus + } + // cancel browser context menu + event.preventDefault() + // event after + edata.finish() + } + contextMenuClick(recid, event) { + // event before + let edata = this.trigger('contextMenuClick', { target: this.name, recid, originalEvent: event.detail.originalEvent, + menuEvent: event, menuIndex: event.detail.index, menuItem: event.detail.item + }) + if (edata.isCancelled === true) return + // no default action + edata.finish() + } + toggle(recid) { + let rec = this.get(recid) + if (rec == null) return + rec.w2ui = rec.w2ui ?? {} + if (rec.w2ui.expanded === true) return this.collapse(recid); else return this.expand(recid) + } + expand(recid, noRefresh) { + let ind = this.get(recid, true) + let rec = this.records[ind] + rec.w2ui = rec.w2ui ?? {} + let id = w2utils.escapeId(recid) + let children = rec.w2ui.children + let edata + if (Array.isArray(children)) { + if (rec.w2ui.expanded === true || children.length === 0) return false // already shown + edata = this.trigger('expand', { target: this.name, recid: recid }) + if (edata.isCancelled === true) return false + rec.w2ui.expanded = true + children.forEach((child) => { + child.w2ui = child.w2ui ?? {} + child.w2ui.parent_recid = rec.recid + if (child.w2ui.children == null) child.w2ui.children = [] + }) + this.records.splice.apply(this.records, [ind + 1, 0].concat(children)) + if (this.total !== -1) { + this.total += children.length + } + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (!url) { + this.localSort(true, true) + if (this.searchData.length > 0) { + this.localSearch(true) + } + } + if (noRefresh !== true) this.refresh() + edata.finish() + } else { + if (query(this.box).find('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').length > 0 || this.show.expandColumn !== true) return false + if (rec.w2ui.expanded == 'none') return false + // insert expand row + query(this.box).find('#grid_'+ this.name +'_rec_'+ id).after( + ` + +
    + + + `) + query(this.box).find('#grid_'+ this.name +'_frec_'+ id).after( + ` + ${this.show.lineNumbers ? '' : ''} + +
    + + `) + // event before + edata = this.trigger('expand', { target: this.name, recid: recid, + box_id: 'grid_'+ this.name +'_rec_'+ recid +'_expanded', fbox_id: 'grid_'+ this.name +'_frec_'+ recid +'_expanded' }) + if (edata.isCancelled === true) { + query(this.box).find('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').remove() + query(this.box).find('#grid_'+ this.name +'_frec_'+ id +'_expanded_row').remove() + return false + } + // expand column + let row1 = query(this.box).find('#grid_'+ this.name +'_rec_'+ recid +'_expanded') + let row2 = query(this.box).find('#grid_'+ this.name +'_frec_'+ recid +'_expanded') + let innerHeight = row1.find(':scope div:first-child')[0]?.clientHeight ?? 50 + if (row1[0].clientHeight < innerHeight) { + row1.css({ height: innerHeight + 'px' }) + } + if (row2[0].clientHeight < innerHeight) { + row2.css({ height: innerHeight + 'px' }) + } + // default action + query(this.box).find('#grid_'+ this.name +'_rec_'+ id).attr('expanded', 'yes').addClass('w2ui-expanded') + query(this.box).find('#grid_'+ this.name +'_frec_'+ id).attr('expanded', 'yes').addClass('w2ui-expanded') + query(this.box).find('#grid_'+ this.name +'_cell_'+ this.get(recid, true) +'_expand div').html('-') + rec.w2ui.expanded = true + // event after + edata.finish() + this.resizeRecords() + } + return true + } + collapse(recid, noRefresh) { + let ind = this.get(recid, true) + let rec = this.records[ind] + rec.w2ui = rec.w2ui || {} + let id = w2utils.escapeId(recid) + let children = rec.w2ui.children + let edata + if (Array.isArray(children)) { + if (rec.w2ui.expanded !== true) return false // already hidden + edata = this.trigger('collapse', { target: this.name, recid: recid }) + if (edata.isCancelled === true) return false + clearExpanded(rec) + let stops = [] + for (let r = rec; r != null; r = this.get(r.w2ui.parent_recid)) + stops.push(r.w2ui.parent_recid) + // stops contains 'undefined' plus the ID of all nodes in the path from 'rec' to the tree root + let start = ind + 1 + let end = start + while (true) { + if (this.records.length <= end + 1 || this.records[end+1].w2ui == null || + stops.indexOf(this.records[end+1].w2ui.parent_recid) >= 0) { + break + } + end++ + } + this.records.splice(start, end - start + 1) + if (this.total !== -1) { + this.total -= end - start + 1 + } + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (!url) { + if (this.searchData.length > 0) { + this.localSearch(true) + } + } + if (noRefresh !== true) this.refresh() + edata.finish() + } else { + if (query(this.box).find('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').length === 0 || this.show.expandColumn !== true) return false + // event before + edata = this.trigger('collapse', { target: this.name, recid: recid, + box_id: 'grid_'+ this.name +'_rec_'+ recid +'_expanded', fbox_id: 'grid_'+ this.name +'_frec_'+ recid +'_expanded' }) + if (edata.isCancelled === true) return false + // default action + query(this.box).find('#grid_'+ this.name +'_rec_'+ id).removeAttr('expanded').removeClass('w2ui-expanded') + query(this.box).find('#grid_'+ this.name +'_frec_'+ id).removeAttr('expanded').removeClass('w2ui-expanded') + query(this.box).find('#grid_'+ this.name +'_cell_'+ this.get(recid, true) +'_expand div').html('+') + query(this.box).find('#grid_'+ this.name +'_rec_'+ id +'_expanded').css('height', '0px') + query(this.box).find('#grid_'+ this.name +'_frec_'+ id +'_expanded').css('height', '0px') + setTimeout(() => { + query(this.box).find('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').remove() + query(this.box).find('#grid_'+ this.name +'_frec_'+ id +'_expanded_row').remove() + rec.w2ui.expanded = false + // event after + edata.finish() + this.resizeRecords() + }, 300) + } + return true + function clearExpanded(rec) { + rec.w2ui.expanded = false + for (let i = 0; i < rec.w2ui.children.length; i++) { + let subRec = rec.w2ui.children[i] + if (subRec.w2ui.expanded) { + clearExpanded(subRec) + } + } + } + } + sort(field, direction, multiField) { // if no params - clears sort + // event before + let edata = this.trigger('sort', { target: this.name, field: field, direction: direction, multiField: multiField }) + if (edata.isCancelled === true) return + // check if needed to quit + if (field != null) { + // default action + let sortIndex = this.sortData.length + for (let s = 0; s < this.sortData.length; s++) { + if (this.sortData[s].field == field) { sortIndex = s; break } + } + if (direction == null) { + if (this.sortData[sortIndex] == null) { + direction = 'asc' + } else { + if (this.sortData[sortIndex].direction == null) { + this.sortData[sortIndex].direction = '' + } + switch (this.sortData[sortIndex].direction.toLowerCase()) { + case 'asc' : direction = 'desc'; break + case 'desc' : direction = 'asc'; break + default : direction = 'asc'; break + } + } + } + if (this.multiSort === false) { this.sortData = []; sortIndex = 0 } + if (multiField != true) { this.sortData = []; sortIndex = 0 } + // set new sort + if (this.sortData[sortIndex] == null) this.sortData[sortIndex] = {} + this.sortData[sortIndex].field = field + this.sortData[sortIndex].direction = direction + } else { + this.sortData = [] + } + // if local + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (!url) { + this.localSort(false, true) + if (this.searchData.length > 0) this.localSearch(true) + // reset vertical scroll + this.last.scrollTop = 0 + query(this.box).find(`#grid_${this.name}_records`).prop('scrollTop', 0) + // event after + edata.finish({ direction }) + this.refresh() + } else { + // event after + edata.finish({ direction }) + this.last.fetch.offset = 0 + this.reload() + } + } + copy(flag, oEvent) { + if (w2utils.isPlainObject(flag)) { + // event after + flag.finish() + return flag.text + } + // generate text to copy + let sel = this.getSelection() + if (sel.length === 0) return '' + let text = '' + if (typeof sel[0] == 'object') { // cell copy + // find min/max column + let minCol = sel[0].column + let maxCol = sel[0].column + let recs = [] + for (let s = 0; s < sel.length; s++) { + if (sel[s].column < minCol) minCol = sel[s].column + if (sel[s].column > maxCol) maxCol = sel[s].column + if (recs.indexOf(sel[s].index) == -1) recs.push(sel[s].index) + } + recs.sort((a, b) => { return a-b }) // sort function must be for numerical sort + for (let r = 0 ; r < recs.length; r++) { + let ind = recs[r] + for (let c = minCol; c <= maxCol; c++) { + let col = this.columns[c] + if (col.hidden === true) continue + text += this.getCellCopy(ind, c) + '\t' + } + text = text.substr(0, text.length-1) // remove last \t + text += '\n' + } + } else { // row copy + // copy headers + for (let c = 0; c < this.columns.length; c++) { + let col = this.columns[c] + if (col.hidden === true) continue + let colName = (col.text ? col.text : col.field) + if (col.text && col.text.length < 3 && col.tooltip) colName = col.tooltip // if column name is less then 3 char and there is tooltip - use it + text += '"' + w2utils.stripTags(colName) + '"\t' + } + text = text.substr(0, text.length-1) // remove last \t + text += '\n' + // copy selected text + for (let s = 0; s < sel.length; s++) { + let ind = this.get(sel[s], true) + for (let c = 0; c < this.columns.length; c++) { + let col = this.columns[c] + if (col.hidden === true) continue + text += '"' + this.getCellCopy(ind, c) + '"\t' + } + text = text.substr(0, text.length-1) // remove last \t + text += '\n' + } + } + text = text.substr(0, text.length - 1) + // if called without params + let edata + if (flag == null) { + // before event + edata = this.trigger('copy', { target: this.name, text: text, + cut: (oEvent.keyCode == 88 ? true : false), originalEvent: oEvent }) + if (edata.isCancelled === true) return '' + text = edata.detail.text + // event after + edata.finish() + return text + } else if (flag === false) { // only before event + // before event + edata = this.trigger('copy', { target: this.name, text: text, + cut: (oEvent.keyCode == 88 ? true : false), originalEvent: oEvent }) + if (edata.isCancelled === true) return '' + text = edata.detail.text + return edata + } + } + /** + * Gets value to be copied to the clipboard + * @param ind index of the record + * @param col_ind index of the column + * @returns the displayed value of the field's record associated with the cell + */ + getCellCopy(ind, col_ind) { + return w2utils.stripTags(this.getCellHTML(ind, col_ind)) + } + paste(text, event) { + let sel = this.getSelection() + let ind = this.get(sel[0].recid, true) + let col = sel[0].column + // before event + let edata = this.trigger('paste', { target: this.name, text: text, index: ind, column: col, originalEvent: event }) + if (edata.isCancelled === true) return + text = edata.detail.text + // default action + if (this.selectType == 'row' || sel.length === 0) { + console.log('ERROR: You can paste only if grid.selectType = \'cell\' and when at least one cell selected.') + // event after + edata.finish() + return + } + if (typeof text !== 'object') { + let newSel = [] + text = text.split('\n') + for (let t = 0; t < text.length; t++) { + let tmp = text[t].split('\t') + let cnt = 0 + let rec = this.records[ind] + let cols = [] + if (rec == null) continue + for (let dt = 0; dt < tmp.length; dt++) { + if (!this.columns[col + cnt]) continue + setCellPaste(rec, this.columns[col + cnt].field, tmp[dt]) + cols.push(col + cnt) + cnt++ + } + for (let c = 0; c < cols.length; c++) newSel.push({ recid: rec.recid, column: cols[c] }) + ind++ + } + this.selectNone(true) // no need to trigger select event + this.select(newSel) + } else { + this.selectNone(true) // no need to trigger select event + this.select([{ recid: this.records[ind], column: col }]) + } + this.refresh() + // event after + edata.finish() + function setCellPaste(rec, field, paste) { + rec.w2ui = rec.w2ui ?? {} + rec.w2ui.changes = rec.w2ui.changes || {} + rec.w2ui.changes[field] = paste + } + } + // ================================================== + // --- Common functions + resize() { + let time = Date.now() + // make sure the box is right + if (!this.box || query(this.box).attr('name') != this.name) return + // event before + let edata = this.trigger('resize', { target: this.name }) + if (edata.isCancelled === true) return + // resize + this.resizeBoxes() + this.resizeRecords() + // event after + edata.finish() + return Date.now() - time + } + update({ cells, fullCellRefresh, ignoreColumns } = {}) { + let time = Date.now() + let self = this + if (this.box == null) return 0 + if (Array.isArray(cells)) { + for (let i = 0; i < cells.length; i++) { + let index = cells[i].index + let column = cells[i].column + if (index < 0) continue + if (index == null || column == null) { + console.log('ERROR: Wrong argument for grid.update({ cells }), cells should be [{ index: X, column: Y }, ...]') + continue + } + let rec = this.records[index] ?? {} + rec.w2ui = rec.w2ui ?? {} + rec.w2ui._update = rec.w2ui._update ?? { cells: [] } + let row1 = rec.w2ui._update.row1 + let row2 = rec.w2ui._update.row2 + if (row1 == null || !row1.isConnected || row2 == null || !row2.isColSelected) { + row1 = this.box.querySelector(`#grid_${this.name}_rec_${w2utils.escapeId(rec.recid)}`) + row2 = this.box.querySelector(`#grid_${this.name}_frec_${w2utils.escapeId(rec.recid)}`) + rec.w2ui._update.row1 = row1 + rec.w2ui._update.row2 = row2 + } + _update(rec, row1, row2, index, column) + } + } else { + for (let i = this.last.range_start-1; i <= this.last.range_end; i++) { + let index = i + if (this.last.searchIds.length > 0) { // if search is applied + index = this.last.searchIds[i] + } else { + index = i + } + let rec = this.records[index] + if (index < 0 || rec == null) continue + rec.w2ui = rec.w2ui ?? {} + rec.w2ui._update = rec.w2ui._update ?? { cells: [] } + let row1 = rec.w2ui._update.row1 + let row2 = rec.w2ui._update.row2 + if (row1 == null || !row1.isConnected || row2 == null || !row2.isColSelected) { + row1 = this.box.querySelector(`#grid_${this.name}_rec_${w2utils.escapeId(rec.recid)}`) + row2 = this.box.querySelector(`#grid_${this.name}_frec_${w2utils.escapeId(rec.recid)}`) + rec.w2ui._update.row1 = row1 + rec.w2ui._update.row2 = row2 + } + for (let column = 0; column < this.columns.length; column++) { + _update(rec, row1, row2, index, column) + } + } + } + return Date.now() - time + function _update(rec, row1, row2, index, column) { + let pcol = self.columns[column] + if (Array.isArray(ignoreColumns) && (ignoreColumns.includes(column) || ignoreColumns.includes(pcol.field))) { + return + } + let cell = rec.w2ui._update.cells[column] + if (cell == null || !cell.isConnected) { + cell = self.box.querySelector(`#grid_${self.name}_data_${index}_${column}`) + rec.w2ui._update.cells[column] = cell + } + if (cell == null) return + if (fullCellRefresh) { + query(cell).replace(self.getCellHTML(index, column, false)) + // need to reselect as it was replaced + cell = self.box.querySelector(`#grid_${self.name}_data_${index}_${column}`) + rec.w2ui._update.cells[column] = cell + } else { + let div = cell.children[0] // there is always a div inside a cell + // value, attr, style, className, divAttr -- all on TD level except divAttr + let { value, style, className } = self.getCellValue(index, column, false, true) + if (div.innerHTML != value) { + div.innerHTML = value + } + if (style != '' && cell.style.cssText != style) { + cell.style.cssText = style + } + if (className != '') { + let ignore = ['w2ui-grid-data'] + let remove = [] + let add = className.split(' ').filter(cl => !!cl) // remove empty + cell.classList.forEach(cl => { if (!ignore.includes(cl)) remove.push(cl)}) + cell.classList.remove(...remove) + cell.classList.add(...add) + } + } + // column styles if any (lower priority) + if (self.columns[column].style && self.columns[column].style != cell.style.cssText) { + cell.style.cssText = self.columns[column].style ?? '' + } + // record class if any + if (rec.w2ui.class != null) { + if (typeof rec.w2ui.class == 'string') { + let ignore = ['w2ui-odd', 'w2ui-even', 'w2ui-record'] + let remove = [] + let add = rec.w2ui.class.split(' ').filter(cl => !!cl) // remove empty + if (row1 && row2) { + row1.classList.forEach(cl => { if (!ignore.includes(cl)) remove.push(cl)}) + row1.classList.remove(...remove) + row1.classList.add(...add) + row2.classList.remove(...remove) + row2.classList.add(...add) + } + } + if (w2utils.isPlainObject(rec.w2ui.class) && typeof rec.w2ui.class[pcol.field] == 'string') { + let ignore = ['w2ui-grid-data'] + let remove = [] + let add = rec.w2ui.class[pcol.field].split(' ').filter(cl => !!cl) + cell.classList.forEach(cl => { if (!ignore.includes(cl)) remove.push(cl)}) + cell.classList.remove(...remove) + cell.classList.add(...add) + } + } + // record styles if any + if (rec.w2ui.style != null) { + if (row1 && row2 && typeof rec.w2ui.style == 'string' && row1.style.cssText !== rec.w2ui.style) { + row1.style.cssText = 'height: '+ self.recordHeight + 'px;' + rec.w2ui.style + row1.setAttribute('custom_style', rec.w2ui.style) + row2.style.cssText = 'height: '+ self.recordHeight + 'px;' + rec.w2ui.style + row2.setAttribute('custom_style', rec.w2ui.style) + } + if (w2utils.isPlainObject(rec.w2ui.style) && typeof rec.w2ui.style[pcol.field] == 'string' + && cell.style.cssText !== rec.w2ui.style[pcol.field]) { + cell.style.cssText = rec.w2ui.style[pcol.field] + } + } + } + } + refreshCell(recid, field) { + let index = this.get(recid, true) + let col_ind = this.getColumn(field, true) + let isSummary = (this.records[index] && this.records[index].recid == recid ? false : true) + let cell = query(this.box).find(`${isSummary ? '.w2ui-grid-summary ' : ''}#grid_${this.name}_data_${index}_${col_ind}`) + if (cell.length == 0) return false + // set cell html and changed flag + cell.replace(this.getCellHTML(index, col_ind, isSummary)) + return true + } + refreshRow(recid, ind = null) { + let tr1 = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)) + let tr2 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)) + if (tr1.length > 0) { + if (ind == null) ind = this.get(recid, true) + let line = tr1.attr('line') + let isSummary = (this.records[ind] && this.records[ind].recid == recid ? false : true) + // if it is searched, find index in search array + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (this.searchData.length > 0 && !url) for (let s = 0; s < this.last.searchIds.length; s++) if (this.last.searchIds[s] == ind) ind = s + let rec_html = this.getRecordHTML(ind, line, isSummary) + tr1.replace(rec_html[0]) + tr2.replace(rec_html[1]) + // apply style to row if it was changed in render functions + let st = (this.records[ind].w2ui ? this.records[ind].w2ui.style : '') + if (typeof st == 'string') { + tr1 = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)) + tr2 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)) + tr1.attr('custom_style', st) + tr2.attr('custom_style', st) + if (tr1.hasClass('w2ui-selected')) { + st = st.replace('background-color', 'none') + } + tr1[0].style.cssText = 'height: '+ this.recordHeight + 'px;' + st + tr2[0].style.cssText = 'height: '+ this.recordHeight + 'px;' + st + } + if (isSummary) { + this.resize() + } + return true + } + return false + } + refresh() { + let time = Date.now() + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (this.total <= 0 && !url && this.searchData.length === 0) { + this.total = this.records.length + } + if (!this.box) return + // event before + let edata = this.trigger('refresh', { target: this.name }) + if (edata.isCancelled === true) return + // -- header + if (this.show.header) { + query(this.box).find(`#grid_${this.name}_header`).html(w2utils.lang(this.header) +' ').show() + } else { + query(this.box).find(`#grid_${this.name}_header`).hide() + } + // -- toolbar + if (this.show.toolbar) { + query(this.box).find('#grid_'+ this.name +'_toolbar').show() + } else { + query(this.box).find('#grid_'+ this.name +'_toolbar').hide() + } + // -- make sure search is closed + this.searchClose() + // search placeholder + let sInput = query(this.box).find('#grid_'+ this.name +'_search_all') + if (!this.multiSearch && this.last.field == 'all' && this.searches.length > 0) { + this.last.field = this.searches[0].field + this.last.label = this.searches[0].label + } + for (let s = 0; s < this.searches.length; s++) { + if (this.searches[s].field == this.last.field) this.last.label = this.searches[s].label + } + if (this.last.multi) { + sInput.attr('placeholder', '[' + w2utils.lang('Multiple Fields') + ']') + } else { + sInput.attr('placeholder', w2utils.lang('Search') + ' ' + w2utils.lang(this.last.label, true)) + } + if (sInput.val() != this.last.search) { + let val = this.last.search + let tmp = sInput._w2field + if (tmp) val = tmp.format(val) + sInput.val(val) + } + this.refreshSearch() + this.refreshBody() + // -- footer + if (this.show.footer) { + query(this.box).find(`#grid_${this.name}_footer`).html(this.getFooterHTML()).show() + } else { + query(this.box).find(`#grid_${this.name}_footer`).hide() + } + // all selected? + let sel = this.last.selection, + areAllSelected = (this.records.length > 0 && sel.indexes.length == this.records.length), + areAllSearchedSelected = (sel.indexes.length > 0 && this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length) + if (areAllSelected || areAllSearchedSelected) { + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', true) + } else { + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', false) + } + // show number of selected + this.status() + // collapse all records + let rows = this.find({ 'w2ui.expanded': true }, true, true) + for (let r = 0; r < rows.length; r++) { + let tmp = this.records[rows[r]].w2ui + if (tmp && !Array.isArray(tmp.children)) { + tmp.expanded = false + } + } + // mark selection + if (this.markSearch) { + setTimeout(() => { + // mark all search strings + let search = [] + for (let s = 0; s < this.searchData.length; s++) { + let sdata = this.searchData[s] + let fld = this.getSearch(sdata.field) + if (!fld || fld.hidden) continue + let ind = this.getColumn(sdata.field, true) + search.push({ field: sdata.field, search: sdata.value, col: ind }) + } + if (search.length > 0) { + search.forEach((item) => { + let el = query(this.box).find('td[col="'+ item.col +'"]:not(.w2ui-head)') + w2utils.marker(el, item.search) + }) + } + }, 50) + } + this.updateToolbar() + // event after + edata.finish() + this.resize() + this.addRange('selection') + setTimeout(() => { // allow to render first + this.resize() // needed for horizontal scroll to show (do not remove) + this.scroll() + }, 1) + if (this.reorderColumns && !this.last.columnDrag) { + this.last.columnDrag = this.initColumnDrag() + } else if (!this.reorderColumns && this.last.columnDrag) { + this.last.columnDrag.remove() + } + return Date.now() - time + } + refreshSearch() { + if (this.multiSearch && this.searchData.length > 0) { + if (query(this.box).find('.w2ui-grid-searches').length == 0) { + query(this.box).find('.w2ui-grid-toolbar') + .css('height', (this.last.toolbar_height + 35) + 'px') + .append(`
    `) + } + let searches = ` + +
    ` + this.searchData.forEach((sd, sd_ind) => { + let ind = this.getSearch(sd.field, true) + let sf = this.searches[ind] + let display + if (Array.isArray(sd.value)) { + display = `${sd.value.length}` + } else { + display = `: ${sd.value}` + } + if (sf && sf.type == 'date') { + if (sd.operator == 'between') { + let dsp1 = sd.value[0] + let dsp2 = sd.value[1] + if (Number(dsp1) === dsp1) { + dsp1 = w2utils.formatDate(dsp1) + } + if (Number(dsp2) === dsp2) { + dsp2 = w2utils.formatDate(dsp2) + } + display = `: ${dsp1} - ${dsp2}` + } else { + let dsp = sd.value + if (Number(dsp) == dsp) { + dsp = w2utils.formatDate(dsp) + } + let oper = sd.operator + if (oper == 'more') oper = 'since' + if (oper == 'less') oper = 'before' + if (oper.substr(0, 5) == 'more:') { + oper = 'since' + } + display = `: ${oper} ${dsp}` + } + } + searches += ` + ${sf ? sf.label : ''} + ${display} + + ` + }) + // clear and save + searches += ` + ${this.show.searchSave + ? `
    + + ` + : '' +} + + ` + query(this.box).find(`#grid_${this.name}_searches`).html(searches) + query(this.box).find(`#grid_${this.name}_search_logic`).html(w2utils.lang(this.last.logic == 'AND' ? 'All' : 'Any')) + } else { + query(this.box).find('.w2ui-grid-toolbar') + .css('height', this.last.toolbar_height + 'px') + .find('.w2ui-grid-searches') + .remove() + } + if (this.searchSelected) { + query(this.box).find(`#grid_${this.name}_search_all`).val(' ').prop('readOnly', true) + query(this.box).find(`#grid_${this.name}_search_name`).show().find('.name-text').html(this.searchSelected.text) + } else { + query(this.box).find(`#grid_${this.name}_search_all`).prop('readOnly', false) + query(this.box).find(`#grid_${this.name}_search_name`).hide().find('.name-text').html('') + } + w2utils.bindEvents(query(this.box).find(`#grid_${this.name}_searches .w2ui-action, #grid_${this.name}_searches button`), this) + } + refreshBody() { + this.scroll() // need to calculate virtual scrolling for columns + let recHTML = this.getRecordsHTML() + let colHTML = this.getColumnsHTML() + let bodyHTML = + '
    '+ + recHTML[0] + + '
    '+ + '
    ' + + recHTML[1] + + '
    '+ + '
    '+ + // Columns need to be after to be able to overlap + '
    '+ + ' '+ colHTML[0] +'
    '+ + '
    '+ + '
    '+ + ' '+ colHTML[1] +'
    '+ + '
    '+ + `` + let gridBody = query(this.box).find(`#grid_${this.name}_body`, this.box).html(bodyHTML) + let records = query(this.box).find(`#grid_${this.name}_records`, this.box) + let frecords = query(this.box).find(`#grid_${this.name}_frecords`, this.box) + if (this.selectType == 'row') { + records.on('mouseover mouseout', { delegate: 'tr' }, (event) => { + let recid = query(event.delegate).attr('recid') + query(this.box).find(`#grid_${this.name}_frec_${w2utils.escapeId(recid)}`) + .toggleClass('w2ui-record-hover', event.type == 'mouseover') + }) + frecords.on('mouseover mouseout', { delegate: 'tr' }, (event) => { + let recid = query(event.delegate).attr('recid') + query(this.box).find(`#grid_${this.name}_rec_${w2utils.escapeId(recid)}`) + .toggleClass('w2ui-record-hover', event.type == 'mouseover') + }) + } + if (w2utils.isIOS) { + records.append(frecords) + .on('click', { delegate: 'tr' }, (event) => { + let recid = query(event.delegate).attr('recid') + this.dblClick(recid, event) + }) + } else { + records.add(frecords) + .on('click', { delegate: 'tr' }, (event) => { + let recid = query(event.delegate).attr('recid') + // do not generate click if empty record is clicked + if (recid != '-none-') { + this.click(recid, event) + } + }) + .on('contextmenu', { delegate: 'tr' }, (event) => { + let recid = query(event.delegate).attr('recid') + let td = query(event.target).closest('td') + let column = parseInt(td.attr('col') ?? -1) + this.showContextMenu(recid, column, event) + }) + .on('mouseover', { delegate: 'tr' }, (event) => { + this.last.rec_out = false + let index = query(event.delegate).attr('index') + let recid = query(event.delegate).attr('recid') + if (index !== this.last.rec_over) { + this.last.rec_over = index + // setTimeout is needed for correct event order enter/leave + setTimeout(() => { + delete this.last.rec_out + let edata = this.trigger('mouseEnter', { target: this.name, originalEvent: event, index, recid }) + edata.finish() + }) + } + }) + .on('mouseout', { delegate: 'tr' }, (event) => { + let index = query(event.delegate).attr('index') + let recid = query(event.delegate).attr('recid') + this.last.rec_out = true + // setTimeouts are needed for correct event order enter/leave + setTimeout(() => { + let recLeave = () => { + let edata = this.trigger('mouseLeave', { target: this.name, originalEvent: event, index, recid }) + edata.finish() + } + if (index !== this.last.rec_over) { + recLeave() + } + setTimeout(() => { + if (this.last.rec_out) { + delete this.last.rec_out + delete this.last.rec_over + recLeave() + } + }) + }) + }) + } + // enable scrolling on frozen records, + gridBody + .data('scroll', { lastDelta: 0, lastTime: 0 }) + .find('.w2ui-grid-frecords') + .on('mousewheel DOMMouseScroll ', (event) => { + event.preventDefault() + // TODO: improve, scroll is not smooth, if scrolled to the end, it takes a while to return + let scroll = gridBody.data('scroll') + let container = gridBody.find('.w2ui-grid-records') + let amount = typeof event.wheelDelta != null ? -event.wheelDelta : (event.detail || event.deltaY) + let newScrollTop = container.prop('scrollTop') + scroll.lastDelta += amount + amount = Math.round(scroll.lastDelta) + gridBody.data('scroll', scroll) + // make scroll amount dependent on visible rows + // amount *= (Math.round(records.prop('clientHeight') / self.recordHeight) - 1) * self.recordHeight / 4 + container.get(0).scroll({ top: newScrollTop + amount, behavior: 'smooth' }) + }) + // scroll on records (and frozen records) + records.off('.body-global') + .on('scroll.body-global', { delegate: '.w2ui-grid-records' }, event => { + this.scroll(event) + }) + query(this.box).find('.w2ui-grid-body') // gridBody + .off('.body-global') + // header column click + .on('click.body-global dblclick.body-global contextmenu.body-global', { delegate: 'td.w2ui-head' }, event => { + let col_ind = query(event.delegate).attr('col') + let col = this.columns[col_ind] ?? { field: col_ind } // it could be line number + switch (event.type) { + case 'click': + this.columnClick(col.field, event) + break + case 'dblclick': + this.columnDblClick(col.field, event) + break + case 'contextmenu': + if (this.show.columnMenu) { + w2menu.show({ + type: 'check', + anchor: document.body, + originalEvent: event, + items: this.initColumnOnOff() + }) + .then(() => { + query('#w2overlay-context-menu .w2ui-grid-skip') + .off('.w2ui-grid') + .on('click.w2ui-grid', evt => { + evt.stopPropagation() + }) + .on('keypress', evt => { + if (evt.keyCode == 13) { + this.skip(evt.target.value) + this.toolbar.click('w2ui-column-on-off') // close menu + } + }) + }) + .select((event) => { + let id = event.detail.item.id + if (['w2ui-stateSave', 'w2ui-stateReset'].includes(id)) { + this[id.substring(5)]() + } else if (id == 'w2ui-skip') { + // empty + } else { + this.columnOnOff(event, event.detail.item.id) + } + clearTimeout(this.last.kbd_timer) // keep grid in focus + }) + clearTimeout(this.last.kbd_timer) // keep grid in focus + } + event.preventDefault() + break + } + }) + .on('mouseover.body-global', { delegate: '.w2ui-col-header' }, event => { + let col = query(event.delegate).parent().attr('col') + this.columnTooltipShow(col, event) + query(event.delegate) + .off('.tooltip') + .on('mouseleave.tooltip', () => { + this.columnTooltipHide(col, event) + }) + }) + // select all + .on('click.body-global', { delegate: 'input.w2ui-select-all' }, event => { + if (event.delegate.checked) { this.selectAll() } else { this.selectNone() } + event.stopPropagation() + clearTimeout(this.last.kbd_timer) // keep grid in focus + }) + // tree-like grid (or expandable column) expand/collapse + .on('click.body-global', { delegate: '.w2ui-show-children, .w2ui-col-expand' }, event => { + event.stopPropagation() + this.toggle(query(event.target).parents('tr').attr('recid')) + }) + // info bubbles + .on('click.body-global mouseover.body-global', { delegate: '.w2ui-info' }, event => { + let td = query(event.delegate).closest('td') + let tr = td.parent() + let col = this.columns[td.attr('col')] + let isSummary = tr.parents('.w2ui-grid-body').hasClass('w2ui-grid-summary') + if (['mouseenter', 'mouseover'].includes(col.info?.showOn?.toLowerCase()) && event.type == 'mouseover') { + this.showBubble(tr.attr('index'), td.attr('col'), isSummary) + .then(() => { + query(event.delegate) + .off('.tooltip') + .on('mouseleave.tooltip', () => { w2tooltip.hide(this.name + '-bubble') }) + }) + } else if (event.type == 'click') { + w2tooltip.hide(this.name + '-bubble') + this.showBubble(tr.attr('index'), td.attr('col'), isSummary) + } + }) + // clipborad copy icon + .on('mouseover.body-global', { delegate: '.w2ui-clipboard-copy' }, event => { + if (event.delegate._tooltipShow) return + let td = query(event.delegate).parent() + let tr = td.parent() + let col = this.columns[td.attr('col')] + let isSummary = tr.parents('.w2ui-grid-body').hasClass('w2ui-grid-summary') + w2tooltip.show({ + name: this.name + '-bubble', + anchor: event.delegate, + html: w2utils.lang(typeof col.clipboardCopy == 'string' ? col.clipboardCopy : 'Copy to clipboard'), + position: 'top|bottom', + offsetY: -2 + }) + .hide(evt => { + event.delegate._tooltipShow = false + query(event.delegate).off('.tooltip') + }) + query(event.delegate) + .off('.tooltip') + .on('mouseleave.tooltip', evt => { + w2tooltip.hide(this.name + '-bubble') + }) + .on('click.tooltip', evt => { + evt.stopPropagation() + w2tooltip.update(this.name + '-bubble', w2utils.lang('Copied')) + this.clipboardCopy(tr.attr('index'), td.attr('col'), isSummary) + }) + event.delegate._tooltipShow = true + }) + .on('click.body-global', { delegate: '.w2ui-editable-checkbox' }, event => { + let dt = query(event.delegate).data() + this.editChange.call(this, event.delegate, dt.changeind, dt.colind, event) + this.updateToolbar() + }) + // show empty message + if (this.records.length === 0 && this.msgEmpty) { + query(this.box).find(`#grid_${this.name}_body`) + .append(`
    ${w2utils.lang(this.msgEmpty)}
    `) + } else if (query(this.box).find(`#grid_${this.name}_empty_msg`).length > 0) { + query(this.box).find(`#grid_${this.name}_empty_msg`).remove() + } + // show summary records + if (this.summary.length > 0) { + let sumHTML = this.getSummaryHTML() + query(this.box).find(`#grid_${this.name}_fsummary`).html(sumHTML[0]).show() + query(this.box).find(`#grid_${this.name}_summary`).html(sumHTML[1]).show() + } else { + query(this.box).find(`#grid_${this.name}_fsummary`).hide() + query(this.box).find(`#grid_${this.name}_summary`).hide() + } + } + render(box) { + let time = Date.now() + let obj = this + if (typeof box == 'string') box = query(box).get(0) + // event before + let edata = this.trigger('render', { target: this.name, box: box ?? this.box }) + if (edata.isCancelled === true) return + // default action + if (box != null) { + // clean previous box + if (query(this.box).find(`#grid_${this.name}_body`).length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-grid w2ui-inactive') + .html('') + } + this.box = box + } + if (!this.box) return + let url = (typeof this.url != 'object' ? this.url : this.url.get) + // reset needed if grid existed + this.reset(true) + // --- default search field + if (!this.last.field) { + if (!this.multiSearch || !this.show.searchAll) { + let tmp = 0 + while (tmp < this.searches.length && (this.searches[tmp].hidden || this.searches[tmp].simple === false)) tmp++ + if (tmp >= this.searches.length) { + // all searches are hidden + this.last.field = '' + this.last.label = '' + } else { + this.last.field = this.searches[tmp].field + this.last.label = this.searches[tmp].label + } + } else { + this.last.field = 'all' + this.last.label = 'All Fields' + } + } + // insert elements + query(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-grid w2ui-inactive') + .html('
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ' '+ + ' '+ // readonly needed on android not to open keyboard + '
    ') + if (this.selectType != 'row') query(this.box).addClass('w2ui-ss') + if (query(this.box).length > 0) query(this.box)[0].style.cssText += this.style + // init toolbar + this.initToolbar() + if (this.toolbar != null) this.toolbar.render(query(this.box).find('#grid_'+ this.name +'_toolbar')[0]) + this.last.toolbar_height = query(this.box).find(`#grid_${this.name}_toolbar`).prop('offsetHeight') + // re-init search_all + if (this.last.field && this.last.field != 'all') { + let sd = this.searchData + setTimeout(() => { this.searchInitInput(this.last.field, (sd.length == 1 ? sd[0].value : null)) }, 1) + } + // init footer + query(this.box).find(`#grid_${this.name}_footer`).html(this.getFooterHTML()) + // refresh + if (!this.last.state) this.last.state = this.stateSave(true) // initial default state + this.stateRestore() + if (url) { this.clear(); this.refresh() } // show empty grid (need it) - should it be only for remote data source + // if hidden searches - apply it + let hasHiddenSearches = false + for (let i = 0; i < this.searches.length; i++) { + if (this.searches[i].hidden) { hasHiddenSearches = true; break } + } + if (hasHiddenSearches) { + this.searchReset(false) // will call reload + if (!url) setTimeout(() => { this.searchReset() }, 1) + } else { + this.reload() + } + // focus + query(this.box).find(`#grid_${this.name}_focus`) + .on('focus', (event) => { + clearTimeout(this.last.kbd_timer) + if (!this.hasFocus) this.focus() + }) + .on('blur', (event) => { + clearTimeout(this.last.kbd_timer) + this.last.kbd_timer = setTimeout(() => { + if (this.hasFocus) { this.blur() } + }, 100) // need this timer to be 100 ms + }) + .on('paste', (event) => { + let cd = (event.clipboardData ? event.clipboardData : null) + if (cd) { + let items = cd.items + if (items.length == 2) { + if (items.length == 2 && items[1].kind == 'file') { + items = [items[1]] + } + if (items.length == 2 && items[0].type == 'text/plain' && items[1].type == 'text/html') { + items = [items[1]] + } + } + let items2send = [] + // might contain data in different formats, but it is a single paste + for (let index in items) { + let item = items[index] + if (item.kind === 'file') { + let file = item.getAsFile() + items2send.push({ kind: 'file', data: file }) + } else if (item.kind === 'string' && (item.type === 'text/plain' || item.type === 'text/html')) { + event.preventDefault() + let text = cd.getData('text/plain') + if (text.indexOf('\r') != -1 && text.indexOf('\n') == -1) { + text = text.replace(/\r/g, '\n') + } + items2send.push({ kind: (item.type == 'text/html' ? 'html' : 'text'), data: text }) + } + } + if (items2send.length === 1 && items2send[0].kind != 'file') { + items2send = items2send[0].data + } + w2ui[this.name].paste(items2send, event) + event.preventDefault() + } + }) + .on('keydown', function (event) { + w2ui[obj.name].keydown.call(w2ui[obj.name], event) + }) + // init mouse events for mouse selection + let edataCol // event for column select + query(this.box).off('mousedown.mouseStart').on('mousedown.mouseStart', mouseStart) + this.updateToolbar() + // event after + edata.finish() + // observe div resize + this.last.observeResize = new ResizeObserver(() => { this.resize() }) + this.last.observeResize.observe(this.box) + return Date.now() - time + function mouseStart (event) { + if (event.which != 1) return // if not left mouse button + // restore css user-select + if (obj.last.userSelect == 'text') { + obj.last.userSelect = '' + query(obj.box).find('.w2ui-grid-body').css('user-select', 'none') + } + // regular record select + if (obj.selectType == 'row' && (query(event.target).parents().hasClass('w2ui-head') || query(event.target).hasClass('w2ui-head'))) return + if (obj.last.move && obj.last.move.type == 'expand') return + // if altKey - alow text selection + if (event.altKey) { + query(obj.box).find('.w2ui-grid-body').css('user-select', 'text') + obj.selectNone() + obj.last.move = { type: 'text-select' } + obj.last.userSelect = 'text' + } else { + let tmp = event.target + let pos = { + x: event.offsetX - 10, + y: event.offsetY - 10 + } + let tmps = false + while (tmp) { + if (tmp.classList && tmp.classList.contains('w2ui-grid')) break + if (tmp.tagName && tmp.tagName.toUpperCase() == 'TD') tmps = true + if (tmp.tagName && tmp.tagName.toUpperCase() != 'TR' && tmps == true) { + pos.x += tmp.offsetLeft + pos.y += tmp.offsetTop + } + tmp = tmp.parentNode + } + obj.last.move = { + x : event.screenX, + y : event.screenY, + divX : 0, + divY : 0, + focusX : pos.x, + focusY : pos.y, + recid : query(event.target).parents('tr').attr('recid'), + column : parseInt(event.target.tagName.toUpperCase() == 'TD' ? query(event.target).attr('col') : query(event.target).parents('td').attr('col')), + type : 'select', + ghost : false, + start : true + } + if (obj.last.move.recid == null) obj.last.move.type = 'select-column' + // set focus to grid + let target = event.target + let $input = query(obj.box).find('#grid_'+ obj.name + '_focus') + // move input next to cursor so screen does not jump + if (obj.last.move) { + let sLeft = obj.last.move.focusX + let sTop = obj.last.move.focusY + let $owner = query(target).parents('table').parent() + if ($owner.hasClass('w2ui-grid-records') || $owner.hasClass('w2ui-grid-frecords') + || $owner.hasClass('w2ui-grid-columns') || $owner.hasClass('w2ui-grid-fcolumns') + || $owner.hasClass('w2ui-grid-summary')) { + sLeft = obj.last.move.focusX - query(obj.box).find('#grid_'+ obj.name +'_records').prop('scrollLeft') + sTop = obj.last.move.focusY - query(obj.box).find('#grid_'+ obj.name +'_records').prop('scrollTop') + } + if (query(target).hasClass('w2ui-grid-footer') || query(target).parents('div.w2ui-grid-footer').length > 0) { + sTop = query(obj.box).find('#grid_'+ obj.name +'_footer').get(0).offsetTop + } + // if clicked on toolbar + if ($owner.hasClass('w2ui-scroll-wrapper') && $owner.parent().hasClass('w2ui-toolbar')) { + sLeft = obj.last.move.focusX - $owner.prop('scrollLeft') + } + $input.css({ + left: sLeft - 10, + top : sTop + }) + } + // if toolbar input is clicked + setTimeout(() => { + if (!obj.last.inEditMode) { + if (['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName)) { + target.focus() + } else { + if ($input.get(0) !== document.active) $input.get(0)?.focus({ preventScroll: true }) + } + } + }, 50) + // disable click select for this condition + if (!obj.multiSelect && !obj.reorderRows && obj.last.move.type == 'drag') { + delete obj.last.move + } + } + if (obj.reorderRows == true) { + let el = event.target + if (el.tagName.toUpperCase() != 'TD') el = query(el).parents('td')[0] + if (query(el).hasClass('w2ui-col-number') || query(el).hasClass('w2ui-col-order')) { + obj.selectNone() + obj.last.move.reorder = true + // suppress hover + let eColor = query(obj.box).find('.w2ui-even.w2ui-empty-record').css('background-color') + let oColor = query(obj.box).find('.w2ui-odd.w2ui-empty-record').css('background-color') + query(obj.box).find('.w2ui-even td').filter(':not(.w2ui-col-number)').css('background-color', eColor) + query(obj.box).find('.w2ui-odd td').filter(':not(.w2ui-col-number)').css('background-color', oColor) + // display empty record and ghost record + let mv = obj.last.move + let recs = query(obj.box).find('.w2ui-grid-records') + if (!mv.ghost) { + let row = query(obj.box).find(`#grid_${obj.name}_rec_${mv.recid}`) + let tmp = row.parents('table').find('tr:first-child').get(0).cloneNode(true) + mv.offsetY = event.offsetY + mv.from = mv.recid + mv.pos = { top: row.get(0).offsetTop-1, left: row.get(0).offsetLeft } + mv.ghost = query(row.get(0).cloneNode(true)) + mv.ghost.removeAttr('id') + mv.ghost.find('td').css({ + 'border-top': '1px solid silver', + 'border-bottom': '1px solid silver' + }) + row.find('td').remove() + row.append(`
    `) + recs.append('
    ') + recs.append('
    ') + query(obj.box).find('#grid_'+ obj.name + '_ghost').append(tmp).append(mv.ghost) + } + let ghost = query(obj.box).find('#grid_'+ obj.name + '_ghost') + ghost.css({ + top : mv.pos.top + 'px', + left : mv.pos.left + 'px' + }) + } else { + obj.last.move.reorder = false + } + } + query(document) + .on('mousemove.w2ui-' + obj.name, mouseMove) + .on('mouseup.w2ui-' + obj.name, mouseStop) + // needed when grid grids are nested, see issue #1275 + event.stopPropagation() + } + function mouseMove(event) { + if (!event.target.tagName) { + // element has no tagName - most likely the target is the #document itself + // this can happen is you click+drag and move the mouse out of the DOM area, + // e.g. into the browser's toolbar area + return + } + let mv = obj.last.move + if (!mv || ['select', 'select-column'].indexOf(mv.type) == -1) return + mv.divX = (event.screenX - mv.x) + mv.divY = (event.screenY - mv.y) + if (Math.abs(mv.divX) <= 1 && Math.abs(mv.divY) <= 1) return // only if moved more then 1px + obj.last.cancelClick = true + if (obj.reorderRows == true && obj.last.move.reorder) { + let tmp = query(event.target).parents('tr') + let recid = tmp.attr('recid') + if (recid == '-none-') recid = 'bottom' + if (recid != mv.from) { + // let row1 = query(obj.box).find('#grid_'+ obj.name + '_rec_'+ mv.recid) + let row2 = query(obj.box).find('#grid_'+ obj.name + '_rec_'+ recid) + query(obj.box).find('.insert-before') + row2.addClass('insert-before') + // MOVABLE GHOST + // if (event.screenY - mv.lastY < 0) row1.after(row2); else row2.after(row1); + mv.lastY = event.screenY + mv.to = recid + // line to insert before + let pos = { top: row2.get(0)?.offsetTop, left: row2.get(0)?.offsetLeft } + let ghost_line = query(obj.box).find('#grid_'+ obj.name + '_ghost_line') + if (pos) { + ghost_line.css({ + top : pos.top + 'px', + left : mv.pos.left + 'px', + 'border-top': '2px solid #769EFC' + }) + } else { + ghost_line.css({ + 'border-top': '2px solid transparent' + }) + } + } + let ghost = query(obj.box).find('#grid_'+ obj.name + '_ghost') + ghost.css({ + top : (mv.pos.top + mv.divY) + 'px', + left : mv.pos.left + 'px' + }) + return + } + if (mv.start && mv.recid) { + obj.selectNone() + mv.start = false + } + let newSel = [] + let recid = (event.target.tagName.toUpperCase() == 'TR' ? query(event.target).attr('recid') : query(event.target).parents('tr').attr('recid')) + if (recid == null) { + // select by dragging columns + if (obj.selectType == 'row') return + if (obj.last.move && obj.last.move.type == 'select') return + let col = parseInt(query(event.target).parents('td').attr('col')) + if (isNaN(col)) { + obj.removeRange('column-selection') + query(obj.box).find('.w2ui-grid-columns .w2ui-col-header, .w2ui-grid-fcolumns .w2ui-col-header').removeClass('w2ui-col-selected') + query(obj.box).find('.w2ui-col-number').removeClass('w2ui-row-selected') + delete mv.colRange + } else { + // add all columns in between + let newRange = col + '-' + col + if (mv.column < col) newRange = mv.column + '-' + col + if (mv.column > col) newRange = col + '-' + mv.column + // array of selected columns + let cols = [] + let tmp = newRange.split('-') + for (let ii = parseInt(tmp[0]); ii <= parseInt(tmp[1]); ii++) { + cols.push(ii) + } + if (mv.colRange != newRange) { + edataCol = obj.trigger('columnSelect', { target: obj.name, columns: cols }) + if (edataCol.isCancelled !== true) { + if (mv.colRange == null) obj.selectNone() + // highlight columns + let tmp = newRange.split('-') + query(obj.box).find('.w2ui-grid-columns .w2ui-col-header, .w2ui-grid-fcolumns .w2ui-col-header').removeClass('w2ui-col-selected') + for (let j = parseInt(tmp[0]); j <= parseInt(tmp[1]); j++) { + query(obj.box).find('#grid_'+ obj.name +'_column_' + j + ' .w2ui-col-header').addClass('w2ui-col-selected') + } + query(obj.box).find('.w2ui-col-number').not('.w2ui-head').addClass('w2ui-row-selected') + // show new range + mv.colRange = newRange + obj.removeRange('column-selection') + obj.addRange({ + name : 'column-selection', + range : [{ recid: obj.records[0].recid, column: tmp[0] }, { recid: obj.records[obj.records.length-1].recid, column: tmp[1] }], + style : 'background-color: rgba(90, 145, 234, 0.1)' + }) + } + } + } + } else { // regular selection + let ind1 = obj.get(mv.recid, true) + // this happens when selection is started on summary row + if (ind1 == null || (obj.records[ind1] && obj.records[ind1].recid != mv.recid)) return + let ind2 = obj.get(recid, true) + // this happens when selection is extended into summary row (a good place to implement scrolling) + if (ind2 == null) return + let col1 = parseInt(mv.column) + let col2 = parseInt(event.target.tagName.toUpperCase() == 'TD' ? query(event.target).attr('col') : query(event.target).parents('td').attr('col')) + if (isNaN(col1) && isNaN(col2)) { // line number select entire record + col1 = 0 + col2 = obj.columns.length-1 + } + if (ind1 > ind2) { let tmp = ind1; ind1 = ind2; ind2 = tmp } + // check if need to refresh + let tmp = 'ind1:'+ ind1 +',ind2;'+ ind2 +',col1:'+ col1 +',col2:'+ col2 + if (mv.range == tmp) return + mv.range = tmp + for (let i = ind1; i <= ind2; i++) { + if (obj.last.searchIds.length > 0 && obj.last.searchIds.indexOf(i) == -1) continue + if (obj.selectType != 'row') { + if (col1 > col2) { let tmp = col1; col1 = col2; col2 = tmp } + for (let c = col1; c <= col2; c++) { + if (obj.columns[c].hidden) continue + newSel.push({ recid: obj.records[i].recid, column: parseInt(c) }) + } + } else { + newSel.push(obj.records[i].recid) + } + } + if (obj.selectType != 'row') { + let sel = obj.getSelection() + // add more items + let tmp = [] + for (let ns = 0; ns < newSel.length; ns++) { + let flag = false + for (let s = 0; s < sel.length; s++) if (newSel[ns].recid == sel[s].recid && newSel[ns].column == sel[s].column) flag = true + if (!flag) tmp.push({ recid: newSel[ns].recid, column: newSel[ns].column }) + } + obj.select(tmp) + // remove items + tmp = [] + for (let s = 0; s < sel.length; s++) { + let flag = false + for (let ns = 0; ns < newSel.length; ns++) if (newSel[ns].recid == sel[s].recid && newSel[ns].column == sel[s].column) flag = true + if (!flag) tmp.push({ recid: sel[s].recid, column: sel[s].column }) + } + obj.unselect(tmp) + } else { + if (obj.multiSelect) { + let sel = obj.getSelection() + for (let ns = 0; ns < newSel.length; ns++) { + if (sel.indexOf(newSel[ns]) == -1) obj.select(newSel[ns]) // add more items + } + for (let s = 0; s < sel.length; s++) { + if (newSel.indexOf(sel[s]) == -1) obj.unselect(sel[s]) // remove items + } + } + } + } + } + function mouseStop (event) { + let mv = obj.last.move + setTimeout(() => { delete obj.last.cancelClick }, 1) + if (query(event.target).parents().hasClass('.w2ui-head') || query(event.target).hasClass('.w2ui-head')) return + if (mv && ['select', 'select-column'].indexOf(mv.type) != -1) { + if (mv.colRange != null && edataCol.isCancelled !== true) { + let tmp = mv.colRange.split('-') + let sel = [] + for (let i = 0; i < obj.records.length; i++) { + let cols = [] + for (let j = parseInt(tmp[0]); j <= parseInt(tmp[1]); j++) cols.push(j) + sel.push({ recid: obj.records[i].recid, column: cols }) + } + obj.removeRange('column-selection') + edataCol.finish() + obj.select(sel) + } + if (obj.reorderRows == true && obj.last.move.reorder) { + if (mv.to != null) { + // event + let edata = obj.trigger('reorderRow', { target: obj.name, recid: mv.from, moveBefore: mv.to }) + if (edata.isCancelled === true) { + resetRowReorder() + delete obj.last.move + return + } + // default behavior + let ind1 = obj.get(mv.from, true) + let ind2 = obj.get(mv.to, true) + if (mv.to == 'bottom') ind2 = obj.records.length // end of list + let tmp = obj.records[ind1] + // swap records + if (ind1 != null && ind2 != null) { + obj.records.splice(ind1, 1) + if (ind1 > ind2) { + obj.records.splice(ind2, 0, tmp) + } else { + obj.records.splice(ind2 - 1, 0, tmp) + } + } + // clear sortData + obj.sortData = [] + query(obj.box) + .find(`#grid_${obj.name}_columns .w2ui-col-header`) + .removeClass('w2ui-col-sorted') + resetRowReorder() + // event after + edata.finish() + } else { + resetRowReorder() + } + } + } + delete obj.last.move + query(document).off('.w2ui-' + obj.name) + } + function resetRowReorder() { + query(obj.box).find(`#grid_${obj.name}_ghost`).remove() + query(obj.box).find(`#grid_${obj.name}_ghost_line`).remove() + obj.refresh() + delete obj.last.move + } + } + destroy() { + // event before + let edata = this.trigger('destroy', { target: this.name }) + if (edata.isCancelled === true) return + // remove all events + query(this.box).off() + // clean up + if (typeof this.toolbar == 'object' && this.toolbar.destroy) this.toolbar.destroy() + if (query(this.box).find(`#grid_${this.name}_body`).length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-grid w2ui-inactive') + .html('') + } + this.last.observeResize?.disconnect() + delete w2ui[this.name] + // event after + edata.finish() + } + // =========================================== + // --- Internal Functions + initColumnOnOff() { + let items = [ + { id: 'line-numbers', text: 'Line #', checked: this.show.lineNumbers } + ] + // columns + for (let c = 0; c < this.columns.length; c++) { + let col = this.columns[c] + let text = this.columns[c].text + if (col.hideable === false) continue + if (!text && this.columns[c].tooltip) text = this.columns[c].tooltip + if (!text) text = '- column '+ (parseInt(c) + 1) +' -' + items.push({ id: col.field, text: w2utils.stripTags(text), checked: !col.hidden }) + } + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if ((url && this.show.skipRecords) || this.show.saveRestoreState) { + items.push({ text: '--' }) + } + // skip records + if (this.show.skipRecords) { + let skip = w2utils.lang('Skip') + + `` + + w2utils.lang('records') + items.push({ id: 'w2ui-skip', text: skip, group: false, icon: 'w2ui-icon-empty' }) + } + // save/restore state + if (this.show.saveRestoreState) { + items.push( + { id: 'w2ui-stateSave', text: w2utils.lang('Save Grid State'), icon: 'w2ui-icon-empty', group: false }, + { id: 'w2ui-stateReset', text: w2utils.lang('Restore Default State'), icon: 'w2ui-icon-empty', group: false } + ) + } + let selected = [] + items.forEach(item => { + item.text = w2utils.lang(item.text) // translate + if (item.checked) selected.push(item.id) + }) + this.toolbar.set('w2ui-column-on-off', { selected, items }) + return items + } + initColumnDrag(box) { + // throw error if using column groups + if (this.columnGroups && this.columnGroups.length) { + throw 'Draggable columns are not currently supported with column groups.' + } + let self = this + let dragData = { + pressed: false, + targetPos: null, + columnHead: null + } + let hasInvalidClass = (target, lastColumn) => { + let iClass = ['w2ui-col-number', 'w2ui-col-expand', 'w2ui-col-select'] + if (lastColumn !== true) iClass.push('w2ui-head-last') + for (let i = 0; i < iClass.length; i++) { + if (query(target).closest('.w2ui-head').hasClass(iClass[i])) { + return true + } + } + return false + } + // attach original event listener + query(self.box) + .off('.colDrag') + .on('mousedown.colDrag', dragColStart) + function dragColStart(event) { + if (dragData.pressed || dragData.numberPreColumnsPresent === 0 || event.button !== 0) return + let edata, columns, origColumn, origColumnNumber + let preColHeadersSelector = '.w2ui-head.w2ui-col-number, .w2ui-head.w2ui-col-expand, .w2ui-head.w2ui-col-select' + // do nothing if it is not a header + if (!query(event.target).parents().hasClass('w2ui-head') || hasInvalidClass(event.target)) return + dragData.pressed = true + dragData.initialX = event.pageX + dragData.initialY = event.pageY + dragData.numberPreColumnsPresent = query(self.box).find(preColHeadersSelector).length + // start event for drag start + dragData.columnHead = origColumn = query(event.target).closest('.w2ui-head') + dragData.originalPos = origColumnNumber = parseInt(origColumn.attr('col'), 10) + edata = self.trigger('columnDragStart', { originalEvent: event, origColumnNumber, target: origColumn[0] }) + if (edata.isCancelled === true) return false + columns = dragData.columns = query(self.box).find('.w2ui-head:not(.w2ui-head-last)') + // add events + query(document).on('mouseup.colDrag', dragColEnd) + query(document).on('mousemove.colDrag', dragColOver) + let col = self.columns[dragData.originalPos] + let colText = w2utils.lang(typeof col.text == 'function' ? col.text(col) : col.text) + dragData.ghost = query.html(`${colText}`)[0] + query(document.body).append(dragData.ghost) + query(dragData.ghost) + .css({ + display: 'none', + left: event.pageX, + top: event.pageY, + opacity: 1, + margin: '3px 0 0 20px', + padding: '3px', + 'background-color': 'white', + position: 'fixed', + 'z-index': 999999, + }) + .addClass('.w2ui-grid-ghost') + + // establish current offsets + dragData.offsets = [] + for (let i = 0, l = columns.length; i < l; i++) { + let rect = columns[i].getBoundingClientRect() + dragData.offsets.push(rect.left) + } + // conclude event + edata.finish() + } + function dragColOver(event) { + if (!dragData.pressed || !dragData.columnHead) return + let cursorX = event.pageX + let cursorY = event.pageY + if (!hasInvalidClass(event.target, true)) { + markIntersection(event) + } + trackGhost(cursorX, cursorY) + } + function dragColEnd(event) { + if (!dragData.pressed || !dragData.columnHead) return + dragData.pressed = false + let edata, target, selected, columnConfig + let finish = () => { + let ghosts = query(self.box).find('.w2ui-grid-ghost') + query(self.box).find('.w2ui-intersection-marker').hide() + query(dragData.ghost).remove() + ghosts.remove() + // dragData.columns.css({ overflow: '' }).children('div').css({ overflow: '' }); + query(document).off('.colDrag') + dragData = {} + } + // if no move, then click event for sorting + if (event.pageX == dragData.initialX && event.pageY == dragData.initialY) { + self.columnClick(self.columns[dragData.originalPos].field, event) + finish() + return + } + // start event for drag start + edata = self.trigger('columnDragEnd', { originalEvent: event, target: dragData.columnHead[0], dragData }) + if (edata.isCancelled === true) return false + selected = self.columns[dragData.originalPos] + columnConfig = self.columns + if (dragData.originalPos != dragData.targetPos && dragData.targetPos != null) { + columnConfig.splice(dragData.targetPos, 0, w2utils.clone(selected)) + columnConfig.splice(columnConfig.indexOf(selected), 1) + } + finish() + self.refresh() + edata.finish({ targetColumn: target - 1 }) + } + function markIntersection(event) { + // if mouse over is not over table + if (query(event.target).closest('td').length == 0) { + return + } + // if mouse over invalid column + let rect1 = query(self.box).find('.w2ui-grid-body').get(0).getBoundingClientRect() + let rect2 = query(event.target).closest('td').get(0).getBoundingClientRect() + query(self.box).find('.w2ui-intersection-marker') + .show() + .css({ + left: (rect2.left - rect1.left) + 'px' + }) + let td = query(event.target).closest('td') + dragData.targetPos = td.hasClass('w2ui-head-last') ? self.columns.length : parseInt(td.attr('col')) + return + } + function trackGhost(cursorX, cursorY){ + query(dragData.ghost) + .css({ + left : (cursorX - 10) + 'px', + top : (cursorY - 10) + 'px' + }) + .show() + } + // return an object to remove drag if it has ever been enabled + return { + remove() { + query(self.box).off('.colDrag') + self.last.columnDrag = false + } + } + } + columnOnOff(event, field) { + // event before + let edata = this.trigger('columnOnOff', { target: this.name, field: field, originalEvent: event }) + if (edata.isCancelled === true) return + // collapse expanded rows + let rows = this.find({ 'w2ui.expanded': true }, true) + for (let r = 0; r < rows.length; r++) { + let tmp = this.records[r].w2ui + if (tmp && !Array.isArray(tmp.children)) { + this.records[r].w2ui.expanded = false + } + } + // show/hide + if (field == 'line-numbers') { + this.show.lineNumbers = !this.show.lineNumbers + this.refresh() + } else { + let col = this.getColumn(field) + if (col.hidden) { + this.showColumn(col.field) + } else { + this.hideColumn(col.field) + } + } + // event after + edata.finish() + } + initToolbar() { + // if it is already initiazlied + if (this.toolbar.render != null) { + return + } + let tb_items = this.toolbar.items || [] + this.toolbar.items = [] + this.toolbar = new w2toolbar(w2utils.extend({}, this.toolbar, { name: this.name +'_toolbar', owner: this })) + if (this.show.toolbarReload) { + this.toolbar.items.push(w2utils.extend({}, this.buttons.reload)) + } + if (this.show.toolbarColumns) { + this.toolbar.items.push(w2utils.extend({}, this.buttons.columns)) + } + if (this.show.toolbarSearch) { + let html =` +
    + ${this.buttons.search.html} +
    + + + x +
    + +
    + +
    +
    ` + this.toolbar.items.push({ + id: 'w2ui-search', + type: 'html', + html, + onRefresh: async (event) => { + await event.complete + let input = query(this.box).find(`#grid_${this.name}_search_all`) + w2utils.bindEvents(query(this.box).find(`#grid_${this.name}_search_all, .w2ui-action`), this) + // slow down live search calls + let slowSearch = w2utils.debounce((event) => { + let val = event.target.value + if (this.liveSearch && this.last.liveText != val) { + this.last.liveText = val + this.search(this.last.field, val) + } + if (event.keyCode == 40) { // arrow down + this.searchSuggest(true) + } + }, 250) + input + .on('change', event => { + if (!this.liveSearch) { + this.search(this.last.field, event.target.value) + this.searchSuggest(true, true, this) + } + }) + .on('blur', () => { this.last.liveText = '' }) + .on('keyup', slowSearch) + } + }) + } + if (Array.isArray(tb_items)) { + let ids = tb_items.map(item => item.id) + if (this.show.toolbarAdd && !ids.includes(this.buttons.add.id)) { + this.toolbar.items.push(w2utils.extend({}, this.buttons.add)) + } + if (this.show.toolbarEdit && !ids.includes(this.buttons.edit.id)) { + this.toolbar.items.push(w2utils.extend({}, this.buttons.edit)) + } + if (this.show.toolbarDelete && !ids.includes(this.buttons.delete.id)) { + this.toolbar.items.push(w2utils.extend({}, this.buttons.delete)) + } + if (this.show.toolbarSave && !ids.includes(this.buttons.save.id)) { + if (this.show.toolbarAdd || this.show.toolbarDelete || this.show.toolbarEdit) { + this.toolbar.items.push({ type: 'break', id: 'w2ui-break2' }) + } + this.toolbar.items.push(w2utils.extend({}, this.buttons.save)) + } + // fill in overwritten items with default buttons + // ids are w2ui-* but in this.buttons the map is just [add, edit, delete] + // must specify at least {id, name} in this.toolbar.items if you want to keep order + tb_items = tb_items.map(item => this.buttons[item.name] + ? w2utils.extend({}, this.buttons[item.name], item) : item) + } + // add original buttons + this.toolbar.items.push(...tb_items) + // ============================================= + // ------ Toolbar onClick processing + this.toolbar.on('click', (event) => { + let edata = this.trigger('toolbar', { target: event.target, originalEvent: event }) + if (edata.isCancelled === true) return + let edata2 + switch (event.detail.item.id) { + case 'w2ui-reload': + edata2 = this.trigger('reload', { target: this.name }) + if (edata2.isCancelled === true) return false + this.reload() + edata2.finish() + break + case 'w2ui-column-on-off': + // TODO: tap on columns will hide menu before opening, only in grid not in toolbar + if (event.detail.subItem) { + let id = event.detail.subItem.id + if (['w2ui-stateSave', 'w2ui-stateReset'].includes(id)) { + this[id.substring(5)]() + } else if (id == 'w2ui-skip') { + // empty + } else { + this.columnOnOff(event, event.detail.subItem.id) + } + } else { + this.initColumnOnOff() + // init input control with records to skip + setTimeout(() => { + query(`#w2overlay-${this.name}_toolbar-drop .w2ui-grid-skip`) + .off('.w2ui-grid') + .on('click.w2ui-grid', evt => { + evt.stopPropagation() + }) + .on('keypress', evt => { + if (evt.keyCode == 13) { + this.skip(evt.target.value) + this.toolbar.click('w2ui-column-on-off') // close menu + } + }) + }, 100) + } + break + case 'w2ui-add': + // events + edata2 = this.trigger('add', { target: this.name, recid: null }) + if (edata2.isCancelled === true) return false + edata2.finish() + break + case 'w2ui-edit': { + let sel = this.getSelection() + let recid = null + if (sel.length == 1) recid = sel[0] + // events + edata2 = this.trigger('edit', { target: this.name, recid: recid }) + if (edata2.isCancelled === true) return false + edata2.finish() + break + } + case 'w2ui-delete': + this.delete() + break + case 'w2ui-save': + this.save() + break + } + // no default action + edata.finish() + }) + this.toolbar.on('refresh', (event) => { + if (event.target == 'w2ui-search') { + let sd = this.searchData + setTimeout(() => { + this.searchInitInput(this.last.field, (sd.length == 1 ? sd[0].value : null)) + }, 1) + } + }) + } + initResize() { + let obj = this + query(this.box).find('.w2ui-resizer') + .off('.grid-col-resize') + .on('click.grid-col-resize', function(event) { + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true + if (event.preventDefault) event.preventDefault() + }) + .on('mousedown.grid-col-resize', function(event) { + if (!event) event = window.event + obj.last.colResizing = true + obj.last.tmp = { + x : event.screenX, + y : event.screenY, + gx : event.screenX, + gy : event.screenY, + col : parseInt(query(this).attr('name')) + } + // find tds that will be resized + obj.last.tmp.tds = query(obj.box).find('#grid_'+ obj.name +'_body table tr:first-child td[col="'+ obj.last.tmp.col +'"]') + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true + if (event.preventDefault) event.preventDefault() + // fix sizes + for (let c = 0; c < obj.columns.length; c++) { + if (obj.columns[c].hidden) continue + if (obj.columns[c].sizeOriginal == null) obj.columns[c].sizeOriginal = obj.columns[c].size + obj.columns[c].size = obj.columns[c].sizeCalculated + } + let edata = { phase: 'before', type: 'columnResize', target: obj.name, column: obj.last.tmp.col, field: obj.columns[obj.last.tmp.col].field } + edata = obj.trigger(w2utils.extend(edata, { resizeBy: 0, originalEvent: event })) + // set move event + let timer + let mouseMove = function(event) { + if (obj.last.colResizing != true) return + if (!event) event = window.event + // event before + edata = obj.trigger(w2utils.extend(edata, { resizeBy: (event.screenX - obj.last.tmp.gx), originalEvent: event })) + if (edata.isCancelled === true) { edata.isCancelled = false; return } + // default action + obj.last.tmp.x = (event.screenX - obj.last.tmp.x) + obj.last.tmp.y = (event.screenY - obj.last.tmp.y) + let newWidth = (parseInt(obj.columns[obj.last.tmp.col].size) + obj.last.tmp.x) + 'px' + obj.columns[obj.last.tmp.col].size = newWidth + if (timer) clearTimeout(timer) + timer = setTimeout(() => { + obj.resizeRecords() + obj.scroll() + }, 100) + // quick resize + obj.last.tmp.tds.css({ width: newWidth }) + // reset + obj.last.tmp.x = event.screenX + obj.last.tmp.y = event.screenY + } + let mouseUp = function(event) { + query(document).off('.grid-col-resize') + obj.resizeRecords() + obj.scroll() + // event after + edata.finish({ originalEvent: event }) + // need timeout to finish processing events + setTimeout(() => { obj.last.colResizing = false }, 1) + } + query(document) + .off('.grid-col-resize') + .on('mousemove.grid-col-resize', mouseMove) + .on('mouseup.grid-col-resize', mouseUp) + }) + .on('dblclick.grid-col-resize', function(event) { + let colId = parseInt(query(this).attr('name')), + col = obj.columns[colId], + maxDiff = 0 + if (col.autoResize === false) { + return true + } + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true + if (event.preventDefault) event.preventDefault() + query(obj.box).find('.w2ui-grid-records td[col="' + colId + '"] > div', obj.box).each(() => { + let thisDiff = this.offsetWidth - this.scrollWidth + if (thisDiff < maxDiff) { + maxDiff = thisDiff - 3 // 3px buffer needed for Firefox + } + }) + // event before + let edata = { phase: 'before', type: 'columnAutoResize', target: obj.name, column: col, field: col.field } + edata = obj.trigger(w2utils.extend(edata, { resizeBy: Math.abs(maxDiff), originalEvent: event })) + if (edata.isCancelled === true) { edata.isCancelled = false; return } + if (maxDiff < 0) { + col.size = Math.min(parseInt(col.size) + Math.abs(maxDiff), col.max || Infinity) + 'px' + obj.resizeRecords() + obj.resizeRecords() // Why do we have to call it twice in order to show the scrollbar? + obj.scroll() + } + // event after + edata.finish({ originalEvent: event }) + }) + .each(el => { + let td = query(el).get(0).parentNode + query(el).css({ + 'height' : td.clientHeight + 'px', + 'margin-left' : (td.clientWidth - 3) + 'px' + }) + }) + } + resizeBoxes() { + // elements + let header = query(this.box).find(`#grid_${this.name}_header`) + let toolbar = query(this.box).find(`#grid_${this.name}_toolbar`) + let fsummary = query(this.box).find(`#grid_${this.name}_fsummary`) + let summary = query(this.box).find(`#grid_${this.name}_summary`) + let footer = query(this.box).find(`#grid_${this.name}_footer`) + let body = query(this.box).find(`#grid_${this.name}_body`) + if (this.show.header) { + header.css({ + top: '0px', + left: '0px', + right: '0px' + }) + } + if (this.show.toolbar) { + toolbar.css({ + top: (0 + (this.show.header ? w2utils.getSize(header, 'height') : 0)) + 'px', + left: '0px', + right: '0px' + }) + } + if (this.summary.length > 0) { + fsummary.css({ + bottom: (0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0)) + 'px' + }) + summary.css({ + bottom: (0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0)) + 'px', + right: '0px' + }) + } + if (this.show.footer) { + footer.css({ + bottom: '0px', + left: '0px', + right: '0px' + }) + } + body.css({ + top: (0 + (this.show.header ? w2utils.getSize(header, 'height') : 0) + (this.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0)) + 'px', + bottom: (0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0) + (this.summary.length > 0 ? w2utils.getSize(summary, 'height') : 0)) + 'px', + left: '0px', + right: '0px' + }) + } + resizeRecords() { + let obj = this + // remove empty records + query(this.box).find('.w2ui-empty-record').remove() + // -- Calculate Column size in PX + let box = query(this.box) + let grid = query(this.box).find(':scope > div.w2ui-grid-box') + let header = query(this.box).find(`#grid_${this.name}_header`) + let toolbar = query(this.box).find(`#grid_${this.name}_toolbar`) + let summary = query(this.box).find(`#grid_${this.name}_summary`) + let fsummary = query(this.box).find(`#grid_${this.name}_fsummary`) + let footer = query(this.box).find(`#grid_${this.name}_footer`) + let body = query(this.box).find(`#grid_${this.name}_body`) + let columns = query(this.box).find(`#grid_${this.name}_columns`) + let fcolumns = query(this.box).find(`#grid_${this.name}_fcolumns`) + let records = query(this.box).find(`#grid_${this.name}_records`) + let frecords = query(this.box).find(`#grid_${this.name}_frecords`) + let scroll1 = query(this.box).find(`#grid_${this.name}_scroll1`) + let lineNumberWidth = String(this.total).length * 8 + 10 + if (lineNumberWidth < 34) lineNumberWidth = 34 // 3 digit width + if (this.lineNumberWidth != null) lineNumberWidth = this.lineNumberWidth + let bodyOverflowX = false + let bodyOverflowY = false + let sWidth = 0 + for (let i = 0; i < this.columns.length; i++) { + if (this.columns[i].frozen || this.columns[i].hidden) continue + let cSize = parseInt(this.columns[i].sizeCalculated ? this.columns[i].sizeCalculated : this.columns[i].size) + sWidth += cSize + } + if (records[0]?.clientWidth < sWidth) bodyOverflowX = true + if (body[0]?.clientHeight - (columns[0]?.clientHeight ?? 0) + < (query(records).find(':scope > table')[0]?.clientHeight ?? 0) + (bodyOverflowX ? w2utils.scrollBarSize() : 0)) { + bodyOverflowY = true + } + // body might be expanded by data + if (!this.fixedBody) { + // allow it to render records, then resize + let bodyHeight = w2utils.getSize(columns, 'height') + + w2utils.getSize(query(this.box).find('#grid_'+ this.name +'_records table'), 'height') + + (bodyOverflowX ? w2utils.scrollBarSize() : 0) + let calculatedHeight = bodyHeight + + (this.show.header ? w2utils.getSize(header, 'height') : 0) + + (this.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0) + + (summary.css('display') != 'none' ? w2utils.getSize(summary, 'height') : 0) + + (this.show.footer ? w2utils.getSize(footer, 'height') : 0) + grid.css('height', calculatedHeight + 'px') + body.css('height', bodyHeight + 'px') + box.css('height', w2utils.getSize(grid, 'height') + 'px') + } else { + // fixed body height + let calculatedHeight = grid[0]?.clientHeight + - (this.show.header ? w2utils.getSize(header, 'height') : 0) + - (this.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0) + - (summary.css('display') != 'none' ? w2utils.getSize(summary, 'height') : 0) + - (this.show.footer ? w2utils.getSize(footer, 'height') : 0) + body.css('height', calculatedHeight + 'px') + } + let buffered = this.records.length + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (this.searchData.length != 0 && !url) buffered = this.last.searchIds.length + // apply overflow + if (!this.fixedBody) { bodyOverflowY = false } + if (bodyOverflowX || bodyOverflowY) { + columns.find(':scope > table > tbody > tr:nth-child(1) td.w2ui-head-last') + .css('width', w2utils.scrollBarSize() + 'px') + .show() + records.css({ + top: ((this.columnGroups.length > 0 && this.show.columns ? 1 : 0) + w2utils.getSize(columns, 'height')) +'px', + '-webkit-overflow-scrolling': 'touch', + 'overflow-x': (bodyOverflowX ? 'auto' : 'hidden'), + 'overflow-y': (bodyOverflowY ? 'auto' : 'hidden') + }) + } else { + columns.find(':scope > table > tbody > tr:nth-child(1) td.w2ui-head-last').hide() + records.css({ + top: ((this.columnGroups.length > 0 && this.show.columns ? 1 : 0) + w2utils.getSize(columns, 'height')) +'px', + overflow: 'hidden' + }) + if (records.length > 0) { this.last.scrollTop = 0; this.last.scrollLeft = 0 } // if no scrollbars, always show top + } + if (bodyOverflowX) { + frecords.css('margin-bottom', w2utils.scrollBarSize() + 'px') + scroll1.show() + } else { + frecords.css('margin-bottom', 0) + scroll1.hide() + } + frecords.css({ overflow: 'hidden', top: records.css('top') }) + if (this.show.emptyRecords && !bodyOverflowY) { + let max = Math.floor((records[0]?.clientHeight ?? 0) / this.recordHeight) - 1 + let leftover = 0 + if (records[0]) leftover = records[0].scrollHeight - max * this.recordHeight + if (leftover >= this.recordHeight) { + leftover -= this.recordHeight + max++ + } + if (this.fixedBody) { + for (let di = buffered; di < max; di++) { + addEmptyRow(di, this.recordHeight, this) + } + addEmptyRow(max, leftover, this) + } + } + function addEmptyRow(row, height, grid) { + let html1 = '' + let html2 = '' + let htmlp = '' + html1 += '' + html2 += '' + if (grid.show.lineNumbers) html1 += '' + if (grid.show.selectColumn) html1 += '' + if (grid.show.expandColumn) html1 += '' + html2 += '' + if (grid.reorderRows) html2 += '' + for (let j = 0; j < grid.columns.length; j++) { + let col = grid.columns[j] + if ((col.hidden || j < grid.last.colStart || j > grid.last.colEnd) && !col.frozen) continue + htmlp = '' + if (col.frozen) html1 += htmlp; else html2 += htmlp + } + html1 += ' ' + html2 += ' ' + query(grid.box).find('#grid_'+ grid.name +'_frecords > table').append(html1) + query(grid.box).find('#grid_'+ grid.name +'_records > table').append(html2) + } + let width_box, percent + if (body.length > 0) { + let width_max = parseInt(body[0].clientWidth) + - (bodyOverflowY ? w2utils.scrollBarSize() : 0) + - (this.show.lineNumbers ? lineNumberWidth : 0) + - (this.reorderRows ? 26 : 0) + - (this.show.selectColumn ? 26 : 0) + - (this.show.expandColumn ? 26 : 0) + - 1 // left is 1px due to border width + width_box = width_max + percent = 0 + // gridMinWidth processing + let restart = false + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + if (col.gridMinWidth > 0) { + if (col.gridMinWidth > width_box && col.hidden !== true) { + col.hidden = true + restart = true + } + if (col.gridMinWidth < width_box && col.hidden === true) { + col.hidden = false + restart = true + } + } + } + if (restart === true) { + this.refresh() + return + } + // assign PX column s + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + if (col.hidden) continue + if (String(col.size).substr(String(col.size).length-2).toLowerCase() == 'px') { + width_max -= parseFloat(col.size) + this.columns[i].sizeCalculated = col.size + this.columns[i].sizeType = 'px' + } else { + percent += parseFloat(col.size) + this.columns[i].sizeType = '%' + delete col.sizeCorrected + } + } + // if sum != 100% -- reassign proportionally + if (percent != 100 && percent > 0) { + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + if (col.hidden) continue + if (col.sizeType == '%') { + col.sizeCorrected = Math.round(parseFloat(col.size) * 100 * 100 / percent) / 100 + '%' + } + } + } + // calculate % columns + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + if (col.hidden) continue + if (col.sizeType == '%') { + if (this.columns[i].sizeCorrected != null) { + // make it 1px smaller, so margin of error can be calculated correctly + this.columns[i].sizeCalculated = Math.floor(width_max * parseFloat(col.sizeCorrected) / 100) - 1 + 'px' + } else { + // make it 1px smaller, so margin of error can be calculated correctly + this.columns[i].sizeCalculated = Math.floor(width_max * parseFloat(col.size) / 100) - 1 + 'px' + } + } + } + } + // fix margin of error that is due percentage calculations + let width_cols = 0 + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + if (col.hidden) continue + if (col.min == null) col.min = 20 + if (parseInt(col.sizeCalculated) < parseInt(col.min)) col.sizeCalculated = col.min + 'px' + if (parseInt(col.sizeCalculated) > parseInt(col.max)) col.sizeCalculated = col.max + 'px' + width_cols += parseInt(col.sizeCalculated) + } + let width_diff = parseInt(width_box) - parseInt(width_cols) + if (width_diff > 0 && percent > 0) { + let i = 0 + while (true) { + let col = this.columns[i] + if (col == null) { i = 0; continue } + if (col.hidden || col.sizeType == 'px') { i++; continue } + col.sizeCalculated = (parseInt(col.sizeCalculated) + 1) + 'px' + width_diff-- + if (width_diff === 0) break + i++ + } + } else if (width_diff > 0) { + columns.find(':scope > table > tbody > tr:nth-child(1) td.w2ui-head-last') + .css('width', w2utils.scrollBarSize() + 'px') + .show() + } + // find width of frozen columns + let fwidth = 1 + if (this.show.lineNumbers) fwidth += lineNumberWidth + if (this.show.selectColumn) fwidth += 26 + // if (this.reorderRows) fwidth += 26; + if (this.show.expandColumn) fwidth += 26 + for (let i = 0; i < this.columns.length; i++) { + if (this.columns[i].hidden) continue + if (this.columns[i].frozen) fwidth += parseInt(this.columns[i].sizeCalculated) + } + fcolumns.css('width', fwidth + 'px') + frecords.css('width', fwidth + 'px') + fsummary.css('width', fwidth + 'px') + scroll1.css('width', fwidth + 'px') + columns.css('left', fwidth + 'px') + records.css('left', fwidth + 'px') + summary.css('left', fwidth + 'px') + // resize columns + columns.find(':scope > table > tbody > tr:nth-child(1) td') + .add(fcolumns.find(':scope > table > tbody > tr:nth-child(1) td')) + .each(el => { + // line numbers + if (query(el).hasClass('w2ui-col-number')) { + query(el).css('width', lineNumberWidth + 'px') + } + // records + let ind = query(el).attr('col') + if (ind != null) { + if (ind == 'start') { + let width = 0 + for (let i = 0; i < obj.last.colStart; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue + width += parseInt(obj.columns[i].sizeCalculated) + } + query(el).css('width', width + 'px') + } + if (obj.columns[ind]) query(el).css('width', obj.columns[ind].sizeCalculated) // already has px + } + // last column + if (query(el).hasClass('w2ui-head-last')) { + if (obj.last.colEnd + 1 < obj.columns.length) { + let width = 0 + for (let i = obj.last.colEnd + 1; i < obj.columns.length; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue + width += parseInt(obj.columns[i].sizeCalculated) + } + query(el).css('width', width + 'px') + } else { + query(el).css('width', w2utils.scrollBarSize() + (width_diff > 0 && percent === 0 ? width_diff : 0) + 'px') + } + } + }) + // if there are column groups - hide first row (needed for sizing) + if (columns.find(':scope > table > tbody > tr').length == 3) { + columns.find(':scope > table > tbody > tr:nth-child(1) td') + .add(fcolumns.find(':scope > table > tbody > tr:nth-child(1) td')) + .html('').css({ + 'height' : '0', + 'border' : '0', + 'padding': '0', + 'margin' : '0' + }) + } + // resize records + records.find(':scope > table > tbody > tr:nth-child(1) td') + .add(frecords.find(':scope > table > tbody > tr:nth-child(1) td')) + .each(el => { + // line numbers + if (query(el).hasClass('w2ui-col-number')) { + query(el).css('width', lineNumberWidth + 'px') + } + // records + let ind = query(el).attr('col') + if (ind != null) { + if (ind == 'start') { + let width = 0 + for (let i = 0; i < obj.last.colStart; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue + width += parseInt(obj.columns[i].sizeCalculated) + } + query(el).css('width', width + 'px') + } + if (obj.columns[ind]) query(el).css('width', obj.columns[ind].sizeCalculated) + } + // last column + if (query(el).hasClass('w2ui-grid-data-last') && query(el).parents('.w2ui-grid-frecords').length === 0) { // not in frecords + if (obj.last.colEnd + 1 < obj.columns.length) { + let width = 0 + for (let i = obj.last.colEnd + 1; i < obj.columns.length; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue + width += parseInt(obj.columns[i].sizeCalculated) + } + query(el).css('width', width + 'px') + } else { + query(el).css('width', (width_diff > 0 && percent === 0 ? width_diff : 0) + 'px') + } + } + }) + // resize summary + summary.find(':scope > table > tbody > tr:nth-child(1) td') + .add(fsummary.find(':scope > table > tbody > tr:nth-child(1) td')) + .each(el => { + // line numbers + if (query(el).hasClass('w2ui-col-number')) { + query(el).css('width', lineNumberWidth + 'px') + } + // records + let ind = query(el).attr('col') + if (ind != null) { + if (ind == 'start') { + let width = 0 + for (let i = 0; i < obj.last.colStart; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue + width += parseInt(obj.columns[i].sizeCalculated) + } + query(el).css('width', width + 'px') + } + if (obj.columns[ind]) query(el).css('width', obj.columns[ind].sizeCalculated) + } + // last column + if (query(el).hasClass('w2ui-grid-data-last') && query(el).parents('.w2ui-grid-frecords').length === 0) { // not in frecords + query(el).css('width', w2utils.scrollBarSize() + (width_diff > 0 && percent === 0 ? width_diff : 0) + 'px') + } + }) + this.initResize() + this.refreshRanges() + // apply last scroll if any + if ((this.last.scrollTop || this.last.scrollLeft) && records.length > 0) { + columns.prop('scrollLeft', this.last.scrollLeft) + records.prop('scrollTop', this.last.scrollTop) + records.prop('scrollLeft', this.last.scrollLeft) + } + } + getSearchesHTML() { + let html = ` +
    + ${w2utils.lang('Advanced Search')} + + + +
    + + ` + for (let i = 0; i < this.searches.length; i++) { + let s = this.searches[i] + s.type = String(s.type).toLowerCase() + if (s.hidden) continue + if (s.attr == null) s.attr = '' + if (s.text == null) s.text = '' + if (s.style == null) s.style = '' + if (s.type == null) s.type = 'text' + if (s.label == null && s.caption != null) { + console.log('NOTICE: grid search.caption property is deprecated, please use search.label. Search ->', s) + s.label = s.caption + } + let operator =`` + html += ` + + + ' + + '' + } + html += ` + + +
    ${(w2utils.lang(s.label) || '')}${operator}` + let tmpStyle + switch (s.type) { + case 'text': + case 'alphanumeric': + case 'hex': + case 'color': + case 'list': + case 'combo': + case 'enum': + tmpStyle = 'width: 250px;' + if (['hex', 'color'].indexOf(s.type) != -1) tmpStyle = 'width: 90px;' + html += `` + break + case 'int': + case 'float': + case 'money': + case 'currency': + case 'percent': + case 'date': + case 'time': + case 'datetime': + tmpStyle = 'width: 90px;' + if (s.type == 'datetime') tmpStyle = 'width: 140px;' + html += ` + ` + break + case 'select': + html += `` + break + } + html += s.text + + '
    + + + + +
    ` + return html + } + getOperators(type, opers) { + let operators = this.operators[this.operatorsMap[type]] || [] + if (opers != null && Array.isArray(opers)) { + operators = opers + } + let html = '' + operators.forEach(oper => { + let displayText = oper + let operValue = oper + if (Array.isArray(oper)) { + displayText = oper[1] + operValue = oper[0] + } else if (w2utils.isPlainObject(oper)) { + displayText = oper.text + operValue = oper.oper + } + if (displayText == null) displayText = oper + html += `\n` + }) + return html + } + initOperator(ind) { + let options + let search = this.searches[ind] + let sdata = this.getSearchData(search.field) + let overlay = query(`#w2overlay-${this.name}-search-overlay`) + let $rng = overlay.find(`#grid_${this.name}_range_${ind}`) + let $fld1 = overlay.find(`#grid_${this.name}_field_${ind}`) + let $fld2 = overlay.find(`#grid_${this.name}_field2_${ind}`) + let $oper = overlay.find(`#grid_${this.name}_operator_${ind}`) + let oper = $oper.val() + $fld1.show() + $rng.hide() + // init based on operator value + switch (oper) { + case 'between': + $rng.show() + break + case 'null': + case 'not null': + $fld1.hide() + $fld1.val(oper) // need to insert something for search to activate + $fld1.trigger('change') + break + } + // init based on search type + switch (search.type) { + case 'text': + case 'alphanumeric': + let fld = $fld1[0]._w2field + if (fld) { fld.reset() } + break + case 'int': + case 'float': + case 'hex': + case 'color': + case 'money': + case 'currency': + case 'percent': + case 'date': + case 'time': + case 'datetime': + if (!$fld1[0]._w2field) { + // init fields + new w2field(search.type, { el: $fld1[0], ...search.options }) + new w2field(search.type, { el: $fld2[0], ...search.options }) + setTimeout(() => { // convert to date if it is number + $fld1.trigger('keydown') + $fld2.trigger('keydown') + }, 1) + } + break + case 'list': + case 'combo': + case 'enum': + options = search.options + if (search.type == 'list') options.selected = {} + if (search.type == 'enum') options.selected = [] + if (sdata) options.selected = sdata.value + if (!$fld1[0]._w2field) { + let fld = new w2field(search.type, { el: $fld1[0], ...options }) + if (sdata && sdata.text != null) { + fld.set({ id: sdata.value, text: sdata.text }) + } + } + break + case 'select': + // build options + options = '' + for (let i = 0; i < search.options.items.length; i++) { + let si = search.options.items[i] + if (w2utils.isPlainObject(search.options.items[i])) { + let val = si.id + let txt = si.text + if (val == null && si.value != null) val = si.value + if (txt == null && si.text != null) txt = si.text + if (val == null) val = '' + options += '' + } else { + options += '' + } + } + $fld1.html(options) + break + } + } + initSearches() { + let overlay = query(`#w2overlay-${this.name}-search-overlay`) + // init searches + for (let ind = 0; ind < this.searches.length; ind++) { + let search = this.searches[ind] + let sdata = this.getSearchData(search.field) + search.type = String(search.type).toLowerCase() + if (typeof search.options != 'object') search.options = {} + // operators + let operator = search.operator + let operators = [...this.operators[this.operatorsMap[search.type]]] || [] // need a copy + if (search.operators) operators = search.operators + // normalize + if (w2utils.isPlainObject(operator)) operator = operator.oper + operators.forEach((oper, ind) => { + if (w2utils.isPlainObject(oper)) operators[ind] = oper.oper + }) + if (sdata && sdata.operator) { + operator = sdata.operator + } + // default operator + let def = this.defaultOperator[this.operatorsMap[search.type]] + if (operators.indexOf(operator) == -1) { + operator = def + } + overlay.find(`#grid_${this.name}_operator_${ind}`).val(operator) + this.initOperator(ind) + // populate field value + let $fld1 = overlay.find(`#grid_${this.name}_field_${ind}`) + let $fld2 = overlay.find(`#grid_${this.name}_field2_${ind}`) + if (sdata != null) { + if (!Array.isArray(sdata.value)) { + if (sdata.value != null) $fld1.val(sdata.value).trigger('change') + } else { + if (['in', 'not in'].includes(sdata.operator)) { + $fld1[0]._w2field.set(sdata.value) + } else { + $fld1.val(sdata.value[0]).trigger('change') + $fld2.val(sdata.value[1]).trigger('change') + } + } + } + } + // add on change event + overlay.find('.w2ui-grid-search-advanced *[rel=search]') + .on('keypress', evnt => { + if (evnt.keyCode == 13) { + this.search() + w2tooltip.hide(this.name + '-search-overlay') + } + }) + } + getColumnsHTML() { + let self = this + let html1 = '' + let html2 = '' + if (this.show.columnHeaders) { + if (this.columnGroups.length > 0) { + let tmp1 = getColumns(true) + let tmp2 = getGroups() + let tmp3 = getColumns(false) + html1 = tmp1[0] + tmp2[0] + tmp3[0] + html2 = tmp1[1] + tmp2[1] + tmp3[1] + } else { + let tmp = getColumns(true) + html1 = tmp[0] + html2 = tmp[1] + } + } + return [html1, html2] + function getGroups() { + let html1 = '' + let html2 = '' + let tmpf = '' + // add empty group at the end + let tmp = self.columnGroups.length - 1 + if (self.columnGroups[tmp].text == null && self.columnGroups[tmp].caption != null) { + console.log('NOTICE: grid columnGroup.caption property is deprecated, please use columnGroup.text. Group -> ', self.columnGroups[tmp]) + self.columnGroups[tmp].text = self.columnGroups[tmp].caption + } + if (self.columnGroups[self.columnGroups.length-1].text != '') self.columnGroups.push({ text: '' }) + if (self.show.lineNumbers) { + html1 += '' + + '
     
    ' + + '' + } + if (self.show.selectColumn) { + html1 += '' + + '
     
    ' + + '' + } + if (self.show.expandColumn) { + html1 += '' + + '
     
    ' + + '' + } + let ii = 0 + html2 += `` + if (self.reorderRows) { + html2 += '' + + '
     
    ' + + '' + } + for (let i = 0; i < self.columnGroups.length; i++) { + let colg = self.columnGroups[i] + let col = self.columns[ii] || {} + if (colg.colspan != null) colg.span = colg.colspan + if (colg.span == null || colg.span != parseInt(colg.span)) colg.span = 1 + if (col.text == null && col.caption != null) { + console.log('NOTICE: grid column.caption property is deprecated, please use column.text. Column ->', col) + col.text = col.caption + } + let colspan = 0 + for (let jj = ii; jj < ii + colg.span; jj++) { + if (self.columns[jj] && !self.columns[jj].hidden) { + colspan++ + } + } + if (i == self.columnGroups.length-1) { + colspan = 100 // last column + } + if (colspan <= 0) { + // do nothing here, all columns in the group are hidden. + } else if (colg.main === true) { + let sortStyle = '' + for (let si = 0; si < self.sortData.length; si++) { + if (self.sortData[si].field == col.field) { + if ((self.sortData[si].direction || '').toLowerCase() === 'asc') sortStyle = 'w2ui-sort-up' + if ((self.sortData[si].direction || '').toLowerCase() === 'desc') sortStyle = 'w2ui-sort-down' + } + } + let resizer = '' + if (col.resizable !== false) { + resizer = `
    ` + } + let text = w2utils.lang(typeof col.text == 'function' ? col.text(col) : col.text) + tmpf = ``+ resizer + + `
    ` + + `
    ` + (!text ? ' ' : text) + + '
    '+ + '' + if (col && col.frozen) html1 += tmpf; else html2 += tmpf + } else { + let gText = w2utils.lang(typeof colg.text == 'function' ? colg.text(colg) : colg.text) + tmpf = `` + + `
    ${!gText ? ' ' : gText}
    ` + + '' + if (col && col.frozen) html1 += tmpf; else html2 += tmpf + } + ii += colg.span + } + html1 += '' // need empty column for border-right + html2 += `` + return [html1, html2] + } + function getColumns(main) { + let html1 = '' + let html2 = '' + if (self.show.lineNumbers) { + html1 += '' + + '
    #
    ' + + '' + } + if (self.show.selectColumn) { + html1 += '' + + '
    ' + + ` ' + + '
    ' + + '' + } + if (self.show.expandColumn) { + html1 += '' + + '
     
    ' + + '' + } + let ii = 0 + let id = 0 + let colg + html2 += `` + if (self.reorderRows) { + html2 += ''+ + '
     
    '+ + '' + } + for (let i = 0; i < self.columns.length; i++) { + let col = self.columns[i] + if (col.text == null && col.caption != null) { + console.log('NOTICE: grid column.caption property is deprecated, please use column.text. Column -> ', col) + col.text = col.caption + } + if (col.size == null) col.size = '100%' + if (i == id) { // always true on first iteration + colg = self.columnGroups[ii++] || {} + id = id + colg.span + } + if ((i < self.last.colStart || i > self.last.colEnd) && !col.frozen) + continue + if (col.hidden) + continue + if (colg.main !== true || main) { // grouping of columns + let colCellHTML = self.getColumnCellHTML(i) + if (col && col.frozen) html1 += colCellHTML; else html2 += colCellHTML + } + } + html1 += '
     
    ' + html2 += '
     
    ' + html1 += '' + html2 += '' + return [html1, html2] + } + } + getColumnCellHTML(i) { + let col = this.columns[i] + if (col == null) return '' + // reorder style + let reorderCols = (this.reorderColumns && (!this.columnGroups || !this.columnGroups.length)) ? ' w2ui-col-reorderable ' : '' + // sort style + let sortStyle = '' + for (let si = 0; si < this.sortData.length; si++) { + if (this.sortData[si].field == col.field) { + if ((this.sortData[si].direction || '').toLowerCase() === 'asc') sortStyle = 'w2ui-sort-up' + if ((this.sortData[si].direction || '').toLowerCase() === 'desc') sortStyle = 'w2ui-sort-down' + } + } + // col selected + let tmp = this.last.selection.columns + let selected = false + for (let t in tmp) { + for (let si = 0; si < tmp[t].length; si++) { + if (tmp[t][si] == i) selected = true + } + } + let text = w2utils.lang(typeof col.text == 'function' ? col.text(col) : col.text) + let html = '' + + (col.resizable !== false ? '
    ' : '') + + '
    '+ + '
    '+ + (!text ? ' ' : text) + + '
    '+ + '' + return html + } + columnTooltipShow(ind, event) { + let $el = query(this.box).find('#grid_'+ this.name + '_column_'+ ind) + let item = this.columns[ind] + let pos = this.columnTooltip + w2tooltip.show({ + name: this.name + '-column-tooltip', + anchor: $el.get(0), + html: item.tooltip, + position: pos, + }) + } + columnTooltipHide(ind, event) { + w2tooltip.hide(this.name + '-column-tooltip') + } + getRecordsHTML() { + let buffered = this.records.length + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (this.searchData.length != 0 && !url) buffered = this.last.searchIds.length + // larger number works better with chrome, smaller with FF. + if (buffered > this.vs_start) this.last.show_extra = this.vs_extra; else this.last.show_extra = this.vs_start + let records = query(this.box).find(`#grid_${this.name}_records`) + let limit = Math.floor((records.get(0)?.clientHeight || 0) / this.recordHeight) + this.last.show_extra + 1 + if (!this.fixedBody || limit > buffered) limit = buffered + // always need first record for resizing purposes + let rec_html = this.getRecordHTML(-1, 0) + let html1 = '' + rec_html[0] + let html2 = '
    ' + rec_html[1] + // first empty row with height + html1 += ''+ + ' '+ + '' + html2 += ''+ + ' '+ + '' + for (let i = 0; i < limit; i++) { + rec_html = this.getRecordHTML(i, i+1) + html1 += rec_html[0] + html2 += rec_html[1] + } + let h2 = (buffered - limit) * this.recordHeight + html1 += '' + + ' '+ + ''+ + ''+ + ' '+ + ''+ + '
    ' + html2 += '' + + ' '+ + ''+ + ''+ + ' '+ + ''+ + '' + this.last.range_start = 0 + this.last.range_end = limit + return [html1, html2] + } + getSummaryHTML() { + if (this.summary.length === 0) return + let rec_html = this.getRecordHTML(-1, 0) // need this in summary too for colspan to work properly + let html1 = '' + rec_html[0] + let html2 = '
    ' + rec_html[1] + for (let i = 0; i < this.summary.length; i++) { + rec_html = this.getRecordHTML(i, i+1, true) + html1 += rec_html[0] + html2 += rec_html[1] + } + html1 += '
    ' + html2 += '' + return [html1, html2] + } + scroll(event) { + let obj = this + let url = (typeof this.url != 'object' ? this.url : this.url.get) + let records = query(this.box).find(`#grid_${this.name}_records`) + let frecords = query(this.box).find(`#grid_${this.name}_frecords`) + // sync scroll positions + if (event) { + let sTop = event.target.scrollTop + let sLeft = event.target.scrollLeft + this.last.scrollTop = sTop + this.last.scrollLeft = sLeft + let cols = query(this.box).find(`#grid_${this.name}_columns`)[0] + let summary = query(this.box).find(`#grid_${this.name}_summary`)[0] + if (cols) cols.scrollLeft = sLeft + if (summary) summary.scrollLeft = sLeft + if (frecords[0]) frecords[0].scrollTop = sTop + } + // hide bubble + if (this.last.bubbleEl) { + w2tooltip.hide(this.name + '-bubble') + this.last.bubbleEl = null + } + // column virtual scroll + let colStart = null + let colEnd = null + if (this.disableCVS || this.columnGroups.length > 0) { + // disable virtual scroll + colStart = 0 + colEnd = this.columns.length - 1 + } else { + let sWidth = records.prop('clientWidth') + let cLeft = 0 + for (let i = 0; i < this.columns.length; i++) { + if (this.columns[i].frozen || this.columns[i].hidden) continue + let cSize = parseInt(this.columns[i].sizeCalculated ? this.columns[i].sizeCalculated : this.columns[i].size) + if (cLeft + cSize + 30 > this.last.scrollLeft && colStart == null) colStart = i + if (cLeft + cSize - 30 > this.last.scrollLeft + sWidth && colEnd == null) colEnd = i + cLeft += cSize + } + if (colEnd == null) colEnd = this.columns.length - 1 + } + if (colStart != null) { + if (colStart < 0) colStart = 0 + if (colEnd < 0) colEnd = 0 + if (colStart == colEnd) { + if (colStart > 0) colStart--; else colEnd++ // show at least one column + } + // --------- + if (colStart != this.last.colStart || colEnd != this.last.colEnd) { + let $box = query(this.box) + let deltaStart = Math.abs(colStart - this.last.colStart) + let deltaEnd = Math.abs(colEnd - this.last.colEnd) + // add/remove columns for small jumps + if (deltaStart < 5 && deltaEnd < 5) { + let $cfirst = $box.find(`.w2ui-grid-columns #grid_${this.name}_column_start`) + let $clast = $box.find('.w2ui-grid-columns .w2ui-head-last') + let $rfirst = $box.find(`#grid_${this.name}_records .w2ui-grid-data-spacer`) + let $rlast = $box.find(`#grid_${this.name}_records .w2ui-grid-data-last`) + let $sfirst = $box.find(`#grid_${this.name}_summary .w2ui-grid-data-spacer`) + let $slast = $box.find(`#grid_${this.name}_summary .w2ui-grid-data-last`) + // remove on left + if (colStart > this.last.colStart) { + for (let i = this.last.colStart; i < colStart; i++) { + $box.find('#grid_'+ this.name +'_columns #grid_'+ this.name +'_column_'+ i).remove() // column + $box.find('#grid_'+ this.name +'_records td[col="'+ i +'"]').remove() // record + $box.find('#grid_'+ this.name +'_summary td[col="'+ i +'"]').remove() // summary + } + } + // remove on right + if (colEnd < this.last.colEnd) { + for (let i = this.last.colEnd; i > colEnd; i--) { + $box.find('#grid_'+ this.name +'_columns #grid_'+ this.name +'_column_'+ i).remove() // column + $box.find('#grid_'+ this.name +'_records td[col="'+ i +'"]').remove() // record + $box.find('#grid_'+ this.name +'_summary td[col="'+ i +'"]').remove() // summary + } + } + // add on left + if (colStart < this.last.colStart) { + for (let i = this.last.colStart - 1; i >= colStart; i--) { + if (this.columns[i] && (this.columns[i].frozen || this.columns[i].hidden)) continue + $cfirst.after(this.getColumnCellHTML(i)) // column + // record + $rfirst.each(el => { + let index = query(el).parent().attr('index') + let td = '' // width column + if (index != null) td = this.getCellHTML(parseInt(index), i, false) + query(el).after(td) + }) + // summary + $sfirst.each(el => { + let index = query(el).parent().attr('index') + let td = '' // width column + if (index != null) td = this.getCellHTML(parseInt(index), i, true) + query(el).after(td) + }) + } + } + // add on right + if (colEnd > this.last.colEnd) { + for (let i = this.last.colEnd + 1; i <= colEnd; i++) { + if (this.columns[i] && (this.columns[i].frozen || this.columns[i].hidden)) continue + $clast.before(this.getColumnCellHTML(i)) // column + // record + $rlast.each(el => { + let index = query(el).parent().attr('index') + let td = '' // width column + if (index != null) td = this.getCellHTML(parseInt(index), i, false) + query(el).before(td) + }) + // summary + $slast.each(el => { + let index = query(el).parent().attr('index') || -1 + let td = this.getCellHTML(parseInt(index), i, true) + query(el).before(td) + }) + } + } + this.last.colStart = colStart + this.last.colEnd = colEnd + this.resizeRecords() + } else { + this.last.colStart = colStart + this.last.colEnd = colEnd + // dot not just call this.refresh(); + let colHTML = this.getColumnsHTML() + let recHTML = this.getRecordsHTML() + let sumHTML = this.getSummaryHTML() + let $columns = $box.find(`#grid_${this.name}_columns`) + let $records = $box.find(`#grid_${this.name}_records`) + let $frecords = $box.find(`#grid_${this.name}_frecords`) + let $summary = $box.find(`#grid_${this.name}_summary`) + $columns.find('tbody').html(colHTML[1]) + $frecords.html(recHTML[0]) + $records.prepend(recHTML[1]) + if (sumHTML != null) $summary.html(sumHTML[1]) + // need timeout to clean up (otherwise scroll problem) + setTimeout(() => { + $records.find(':scope > table').filter(':not(table:first-child)').remove() + if ($summary[0]) $summary[0].scrollLeft = this.last.scrollLeft + }, 1) + this.resizeRecords() + } + } + } + // perform virtual scroll + let buffered = this.records.length + if (buffered > this.total && this.total !== -1) buffered = this.total + if (this.searchData.length != 0 && !url) buffered = this.last.searchIds.length + if (buffered === 0 || records.length === 0 || records.prop('clientHeight') === 0) return + if (buffered > this.vs_start) this.last.show_extra = this.vs_extra; else this.last.show_extra = this.vs_start + // update footer + let t1 = Math.round(records.prop('scrollTop') / this.recordHeight + 1) + let t2 = t1 + (Math.round(records.prop('clientHeight') / this.recordHeight) - 1) + if (t1 > buffered) t1 = buffered + if (t2 >= buffered - 1) t2 = buffered + query(this.box).find('#grid_'+ this.name + '_footer .w2ui-footer-right').html( + (this.show.statusRange + ? w2utils.formatNumber(this.offset + t1) + '-' + w2utils.formatNumber(this.offset + t2) + + (this.total != -1 ? ' ' + w2utils.lang('of') + ' ' + w2utils.formatNumber(this.total) : '') + : '') + + (url && this.show.statusBuffered ? ' ('+ w2utils.lang('buffered') + ' '+ w2utils.formatNumber(buffered) + + (this.offset > 0 ? ', skip ' + w2utils.formatNumber(this.offset) : '') + ')' : '') + ) + // only for local data source, else no extra records loaded + if (!url && (!this.fixedBody || (this.total != -1 && this.total <= this.vs_start))) return + // regular processing + let start = Math.floor(records.prop('scrollTop') / this.recordHeight) - this.last.show_extra + let end = start + Math.floor(records.prop('clientHeight') / this.recordHeight) + this.last.show_extra * 2 + 1 + // let div = start - this.last.range_start; + if (start < 1) start = 1 + if (end > this.total && this.total != -1) end = this.total + let tr1 = records.find('#grid_'+ this.name +'_rec_top') + let tr2 = records.find('#grid_'+ this.name +'_rec_bottom') + let tr1f = frecords.find('#grid_'+ this.name +'_frec_top') + let tr2f = frecords.find('#grid_'+ this.name +'_frec_bottom') + // if row is expanded + if (String(tr1.next().prop('id')).indexOf('_expanded_row') != -1) { + tr1.next().remove() + tr1f.next().remove() + } + if (this.total > end && String(tr2.prev().prop('id')).indexOf('_expanded_row') != -1) { + tr2.prev().remove() + tr2f.prev().remove() + } + let first = parseInt(tr1.next().attr('line')) + let last = parseInt(tr2.prev().attr('line')) + let tmp, tmp1, tmp2, rec_start, rec_html + if (first < start || first == 1 || this.last.pull_refresh) { // scroll down + if (end <= last + this.last.show_extra - 2 && end != this.total) return + this.last.pull_refresh = false + // remove from top + while (true) { + tmp1 = frecords.find('#grid_'+ this.name +'_frec_top').next() + tmp2 = records.find('#grid_'+ this.name +'_rec_top').next() + if (tmp2.attr('line') == 'bottom') break + if (parseInt(tmp2.attr('line')) < start) { + tmp1.remove() + tmp2.remove() + } else { + break + } + } + // add at bottom + tmp = records.find('#grid_'+ this.name +'_rec_bottom').prev() + rec_start = tmp.attr('line') + if (rec_start == 'top') rec_start = start + for (let i = parseInt(rec_start) + 1; i <= end; i++) { + if (!this.records[i-1]) continue + tmp2 = this.records[i-1].w2ui + if (tmp2 && !Array.isArray(tmp2.children)) { + tmp2.expanded = false + } + rec_html = this.getRecordHTML(i-1, i) + tr2.before(rec_html[1]) + tr2f.before(rec_html[0]) + } + markSearch() + setTimeout(() => { this.refreshRanges() }, 0) + } else { // scroll up + if (start >= first - this.last.show_extra + 2 && start > 1) return + // remove from bottom + while (true) { + tmp1 = frecords.find('#grid_'+ this.name +'_frec_bottom').prev() + tmp2 = records.find('#grid_'+ this.name +'_rec_bottom').prev() + if (tmp2.attr('line') == 'top') break + if (parseInt(tmp2.attr('line')) > end) { + tmp1.remove() + tmp2.remove() + } else { + break + } + } + // add at top + tmp = records.find('#grid_'+ this.name +'_rec_top').next() + rec_start = tmp.attr('line') + if (rec_start == 'bottom') rec_start = end + for (let i = parseInt(rec_start) - 1; i >= start; i--) { + if (!this.records[i-1]) continue + tmp2 = this.records[i-1].w2ui + if (tmp2 && !Array.isArray(tmp2.children)) { + tmp2.expanded = false + } + rec_html = this.getRecordHTML(i-1, i) + tr1.after(rec_html[1]) + tr1f.after(rec_html[0]) + } + markSearch() + setTimeout(() => { this.refreshRanges() }, 0) + } + // first/last row size + let h1 = (start - 1) * this.recordHeight + let h2 = (buffered - end) * this.recordHeight + if (h2 < 0) h2 = 0 + tr1.css('height', h1 + 'px') + tr1f.css('height', h1 + 'px') + tr2.css('height', h2 + 'px') + tr2f.css('height', h2 + 'px') + this.last.range_start = start + this.last.range_end = end + // load more if needed + let s = Math.floor(records.prop('scrollTop') / this.recordHeight) + let e = s + Math.floor(records.prop('clientHeight') / this.recordHeight) + if (e + 10 > buffered && this.last.pull_more !== true && (buffered < this.total - this.offset || (this.total == -1 && this.last.fetch.hasMore))) { + if (this.autoLoad === true) { + this.last.pull_more = true + this.last.fetch.offset += this.limit + this.request('load') + } + // scroll function + let more = query(this.box).find('#grid_'+ this.name +'_rec_more, #grid_'+ this.name +'_frec_more') + more.show() + .eq(1) // only main table + .off('.load-more') + .on('click.load-more', function() { + // show spinner + query(this).find('td').html('
    ') + // load more + obj.last.pull_more = true + obj.last.fetch.offset += obj.limit + obj.request('load') + }) + .find('td') + .html(obj.autoLoad + ? '
    ' + : '
    '+ w2utils.lang('Load ${count} more...', { count: obj.limit }) + '
    ' + ) + } + function markSearch() { + // mark search + if (!obj.markSearch) return + clearTimeout(obj.last.marker_timer) + obj.last.marker_timer = setTimeout(() => { + // mark all search strings + let search = [] + for (let s = 0; s < obj.searchData.length; s++) { + let sdata = obj.searchData[s] + let fld = obj.getSearch(sdata.field) + if (!fld || fld.hidden) continue + let ind = obj.getColumn(sdata.field, true) + search.push({ field: sdata.field, search: sdata.value, col: ind }) + } + if (search.length > 0) { + search.forEach((item) => { + let el = query(obj.box).find('td[col="'+ item.col +'"]:not(.w2ui-head)') + w2utils.marker(el, item.search) + }) + } + }, 50) + } + } + getRecordHTML(ind, lineNum, summary) { + let tmph = '' + let rec_html1 = '' + let rec_html2 = '' + let sel = this.last.selection + let record + // first record needs for resize purposes + if (ind == -1) { + rec_html1 += '' + rec_html2 += '' + if (this.show.lineNumbers) rec_html1 += '' + if (this.show.selectColumn) rec_html1 += '' + if (this.show.expandColumn) rec_html1 += '' + rec_html2 += '' + if (this.reorderRows) rec_html2 += '' + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + tmph = '' + if (col.frozen && !col.hidden) { + rec_html1 += tmph + } else { + if (col.hidden || i < this.last.colStart || i > this.last.colEnd) continue + rec_html2 += tmph + } + } + rec_html1 += '' + rec_html2 += '' + rec_html1 += '' + rec_html2 += '' + return [rec_html1, rec_html2] + } + // regular record + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (summary !== true) { + if (this.searchData.length > 0 && !url) { + if (ind >= this.last.searchIds.length) return '' + ind = this.last.searchIds[ind] + record = this.records[ind] + } else { + if (ind >= this.records.length) return '' + record = this.records[ind] + } + } else { + if (ind >= this.summary.length) return '' + record = this.summary[ind] + } + if (!record) return '' + if (record.recid == null && this.recid != null) { + let rid = this.parseField(record, this.recid) + if (rid != null) record.recid = rid + } + let isRowSelected = false + if (sel.indexes.indexOf(ind) != -1) isRowSelected = true + let rec_style = (record.w2ui ? record.w2ui.style : '') + if (rec_style == null || typeof rec_style != 'string') rec_style = '' + let rec_class = (record.w2ui ? record.w2ui.class : '') + if (rec_class == null || typeof rec_class != 'string') rec_class = '' + // render TR + rec_html1 += '' + rec_html2 += '' + if (this.show.lineNumbers) { + rec_html1 += ''+ + (summary !== true ? this.getLineHTML(lineNum, record) : '') + + '' + } + if (this.show.selectColumn) { + rec_html1 += + ''+ + (summary !== true && !(record.w2ui && record.w2ui.hideCheckBox === true) ? + '
    '+ + ' '+ + '
    ' + : + '' ) + + '' + } + if (this.show.expandColumn) { + let tmp_img = '' + if (record.w2ui?.expanded === true) tmp_img = '-'; else tmp_img = '+' + if ((record.w2ui?.expanded == 'none' || !Array.isArray(record.w2ui.children) || !record.w2ui.children.length)) tmp_img = '+' + if (record.w2ui?.expanded == 'spinner') tmp_img = '
    ' + rec_html1 += + ''+ + (summary !== true ? `
    ${tmp_img}
    ` : '' ) + + '' + } + // insert empty first column + rec_html2 += '' + if (this.reorderRows) { + rec_html2 += + ''+ + (summary !== true ? '
     
    ' : '' ) + + '' + } + let col_ind = 0 + let col_skip = 0 + while (true) { + let col_span = 1 + let col = this.columns[col_ind] + if (col == null) break + if (col.hidden) { + col_ind++ + if (col_skip > 0) col_skip-- + continue + } + if (col_skip > 0) { + col_ind++ + if (this.columns[col_ind] == null) break + record.w2ui.colspan[this.columns[col_ind-1].field] = 0 // need it for other methods + col_skip-- + continue + } else if (record.w2ui) { + let tmp1 = record.w2ui.colspan + let tmp2 = this.columns[col_ind].field + if (tmp1 && tmp1[tmp2] === 0) { + delete tmp1[tmp2] // if no longer colspan then remove 0 + } + } + // column virtual scroll + if ((col_ind < this.last.colStart || col_ind > this.last.colEnd) && !col.frozen) { + col_ind++ + continue + } + if (record.w2ui) { + if (typeof record.w2ui.colspan == 'object') { + let span = parseInt(record.w2ui.colspan[col.field]) || null + if (span > 1) { + // if there are hidden columns, then no colspan on them + let hcnt = 0 + for (let i = col_ind; i < col_ind + span; i++) { + if (i >= this.columns.length) break + if (this.columns[i].hidden) hcnt++ + } + col_span = span - hcnt + col_skip = span - 1 + } + } + } + let rec_cell = this.getCellHTML(ind, col_ind, summary, col_span) + if (col.frozen) rec_html1 += rec_cell; else rec_html2 += rec_cell + col_ind++ + } + rec_html1 += '' + rec_html2 += '' + rec_html1 += '' + rec_html2 += '' + return [rec_html1, rec_html2] + } + getLineHTML(lineNum) { + return '
    ' + lineNum + '
    ' + } + getCellHTML(ind, col_ind, summary, col_span) { + let obj = this + let col = this.columns[col_ind] + if (col == null) return '' + let record = (summary !== true ? this.records[ind] : this.summary[ind]) + // value, attr, style, className, divAttr + let { value, style, className, attr, divAttr } = this.getCellValue(ind, col_ind, summary, true) + let edit = (ind !== -1 ? this.getCellEditable(ind, col_ind) : '') + let divStyle = 'max-height: '+ parseInt(this.recordHeight) +'px;' + (col.clipboardCopy ? 'margin-right: 20px' : '') + let isChanged = !summary && record?.w2ui?.changes && record.w2ui.changes[col.field] != null + let sel = this.last.selection + let isRowSelected = false + let infoBubble = '' + if (sel.indexes.indexOf(ind) != -1) isRowSelected = true + if (col_span == null) { + if (record?.w2ui?.colspan && record.w2ui.colspan[col.field]) { + col_span = record.w2ui.colspan[col.field] + } else { + col_span = 1 + } + } + // expand icon + if (col_ind === 0 && Array.isArray(record?.w2ui?.children)) { + let level = 0 + let subrec = this.get(record.w2ui.parent_recid, true) + while (true) { + if (subrec != null) { + level++ + let tmp = this.records[subrec].w2ui + if (tmp != null && tmp.parent_recid != null) { + subrec = this.get(tmp.parent_recid, true) + } else { + break + } + } else { + break + } + } + if (record.w2ui.parent_recid) { + for (let i = 0; i < level; i++) { + infoBubble += '' + } + } + let className = record.w2ui.children.length > 0 + ? (record.w2ui.expanded ? 'w2ui-icon-collapse' : 'w2ui-icon-expand') + : 'w2ui-icon-empty' + infoBubble += `` + } + // info bubble + if (col.info === true) col.info = {} + if (col.info != null) { + let infoIcon = 'w2ui-icon-info' + if (typeof col.info.icon == 'function') { + infoIcon = col.info.icon(record, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) + } else if (typeof col.info.icon == 'object') { + infoIcon = col.info.icon[this.parseField(record, col.field)] || '' + } else if (typeof col.info.icon == 'string') { + infoIcon = col.info.icon + } + let infoStyle = col.info.style || '' + if (typeof col.info.style == 'function') { + infoStyle = col.info.style(record, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) + } else if (typeof col.info.style == 'object') { + infoStyle = col.info.style[this.parseField(record, col.field)] || '' + } else if (typeof col.info.style == 'string') { + infoStyle = col.info.style + } + infoBubble += `` + } + let data = value + // if editable checkbox + if (edit && ['checkbox', 'check'].indexOf(edit.type) != -1) { + let changeInd = summary ? -(ind + 1) : ind + divStyle += 'text-align: center;' + data = `` + infoBubble = '' + } + data = `
    ${infoBubble}${String(data)}
    ` + if (data == null) data = '' + // --> cell TD + if (typeof col.render == 'string') { + let tmp = col.render.toLowerCase().split(':') + if (['number', 'int', 'float', 'money', 'currency', 'percent', 'size'].indexOf(tmp[0]) != -1) { + style += 'text-align: right;' + } + } + if (record?.w2ui) { + if (typeof record.w2ui.style == 'object') { + if (typeof record.w2ui.style[col_ind] == 'string') style += record.w2ui.style[col_ind] + ';' + if (typeof record.w2ui.style[col.field] == 'string') style += record.w2ui.style[col.field] + ';' + } + if (typeof record.w2ui.class == 'object') { + if (typeof record.w2ui.class[col_ind] == 'string') className += record.w2ui.class[col_ind] + ' ' + if (typeof record.w2ui.class[col.field] == 'string') className += record.w2ui.class[col.field] + ' ' + } + } + let isCellSelected = false + if (isRowSelected && sel.columns[ind]?.includes(col_ind)) isCellSelected = true + // clipboardCopy + let clipboardIcon + if (col.clipboardCopy){ + clipboardIcon = '' + } + // data + data = ' 1 ? 'colspan="'+ col_span + '"' : '') + + '>' + data + (clipboardIcon && w2utils.stripTags(data) ? clipboardIcon : '') +'' + // summary top row + if (ind === -1 && summary === true) { + data = ' 1 ? 'colspan="'+ col_span + '"' : '') + + '>' + } + return data + function getTitle(cellData){ + let title + if (obj.show.recordTitles) { + if (col.title != null) { + if (typeof col.title == 'function') { + title = col.title.call(obj, record, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) + } + if (typeof col.title == 'string') title = col.title + } else { + title = w2utils.stripTags(String(cellData).replace(/"/g, '\'\'')) + } + } + return (title != null) ? 'title="' + String(title) + '"' : '' + } + } + clipboardCopy(ind, col_ind, summary) { + let rec = summary ? this.summary[ind] : this.records[ind] + let col = this.columns[col_ind] + let txt = (col ? this.parseField(rec, col.field) : '') + if (typeof col.clipboardCopy == 'function') { + txt = col.clipboardCopy(rec, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) + } + query(this.box).find('#grid_' + this.name + '_focus').text(txt).get(0).select() + document.execCommand('copy') + } + showBubble(ind, col_ind, summary) { + let info = this.columns[col_ind].info + if (!info) return + let html = '' + let rec = this.records[ind] + let el = query(this.box).find(`${summary ? '.w2ui-grid-summary' : ''} #grid_${this.name}_data_${ind}_${col_ind} .w2ui-info`) + if (this.last.bubbleEl) { + w2tooltip.hide(this.name + '-bubble') + } + this.last.bubbleEl = el + // if no fields defined - show all + if (info.fields == null) { + info.fields = [] + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + info.fields.push(col.field + (typeof col.render == 'string' ? ':' + col.render : '')) + } + } + let fields = info.fields + if (typeof fields == 'function') { + fields = fields(rec, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) // custom renderer + } + // generate html + if (typeof info.render == 'function') { + html = info.render(rec, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) + } else if (Array.isArray(fields)) { + // display mentioned fields + html = '' + for (let i = 0; i < fields.length; i++) { + let tmp = String(fields[i]).split(':') + if (tmp[0] == '' || tmp[0] == '-' || tmp[0] == '--' || tmp[0] == '---') { + html += '' + continue + } + let col = this.getColumn(tmp[0]) + if (col == null) col = { field: tmp[0], caption: tmp[0] } // if not found in columns + let val = (col ? this.parseField(rec, col.field) : '') + if (tmp.length > 1) { + if (w2utils.formatters[tmp[1]]) { + val = w2utils.formatters[tmp[1]](val, tmp[2] || null, rec) + } else { + console.log('ERROR: w2utils.formatters["'+ tmp[1] + '"] does not exists.') + } + } + if (info.showEmpty !== true && (val == null || val == '')) continue + if (info.maxLength != null && typeof val == 'string' && val.length > info.maxLength) val = val.substr(0, info.maxLength) + '...' + html += '' + } + html += '
    ' + col.text + '' + ((val === 0 ? '0' : val) || '') + '
    ' + } else if (w2utils.isPlainObject(fields)) { + // display some fields + html = '' + for (let caption in fields) { + let fld = fields[caption] + if (fld == '' || fld == '-' || fld == '--' || fld == '---') { + html += '' + continue + } + let tmp = String(fld).split(':') + let col = this.getColumn(tmp[0]) + if (col == null) col = { field: tmp[0], caption: tmp[0] } // if not found in columns + let val = (col ? this.parseField(rec, col.field) : '') + if (tmp.length > 1) { + if (w2utils.formatters[tmp[1]]) { + val = w2utils.formatters[tmp[1]](val, tmp[2] || null, rec) + } else { + console.log('ERROR: w2utils.formatters["'+ tmp[1] + '"] does not exists.') + } + } + if (typeof fld == 'function') { + val = fld(rec, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) + } + if (info.showEmpty !== true && (val == null || val == '')) continue + if (info.maxLength != null && typeof val == 'string' && val.length > info.maxLength) val = val.substr(0, info.maxLength) + '...' + html += '' + } + html += '
    ' + caption + '' + ((val === 0 ? '0' : val) || '') + '
    ' + } + return w2tooltip.show(w2utils.extend({ + name: this.name + '-bubble', + html, + anchor: el.get(0), + position: 'top|bottom', + class: 'w2ui-info-bubble', + style: '', + hideOn: ['doc-click'] + }, info.options ?? {})) + .hide(() => [ + this.last.bubbleEl = null + ]) + } + // return null or the editable object if the given cell is editable + getCellEditable(ind, col_ind) { + let col = this.columns[col_ind] + let rec = this.records[ind] + if (!rec || !col) return null + let edit = (rec.w2ui ? rec.w2ui.editable : null) + if (edit === false) return null + if (edit == null || edit === true) { + edit = (Object.keys(col.editable ?? {}).length > 0 ? col.editable : null) + if (typeof edit === 'function') { + let value = this.getCellValue(ind, col_ind, false) + // same arguments as col.render() + edit = edit.call(this, rec, { self: this, value, index: ind, colIndex: col_ind }) + } + } + return edit + } + getCellValue(ind, col_ind, summary, extra) { + let col = this.columns[col_ind] + let record = (summary !== true ? this.records[ind] : this.summary[ind]) + let value = this.parseField(record, col.field) + let className = '', style = '', attr = '', divAttr = '' + // if change by inline editing + if (record?.w2ui?.changes?.[col.field] != null) { + value = record.w2ui.changes[col.field] + } + // if there is a cell renderer + if (col.render != null && ind !== -1) { + if (typeof col.render == 'function' && record != null) { + let html + try { + html = col.render(record, { self: this, value, index: ind, colIndex: col_ind, summary: !!summary }) + } catch (e) { + throw new Error(`Render function for column "${col.field}" in grid "${this.name}": -- ` + e.message) + } + if (html != null && typeof html == 'object' && typeof html != 'function') { + if (html.id != null && html.text != null) { + // normalized menu kind of return + value = html.text + } else if (typeof html.html == 'string') { + value = (html.html || '').trim() + } else { + value = '' + console.log('ERROR: render function should return a primitive or an object of the following structure.', + { html: '', attr: '', style: '', class: '', divAttr: '' }) + } + attr = html.attr ?? '' + style = html.style ?? '' + className = html.class ?? '' + divAttr = html.divAttr ?? '' + } else { + value = String(html || '').trim() + } + } + // if it is an object + if (typeof col.render == 'object') { + let tmp = col.render[value] + if (tmp != null && tmp !== '') { + value = tmp + } + } + // formatters + if (typeof col.render == 'string') { + let strInd = col.render.toLowerCase().indexOf(':') + let tmp = [] + if (strInd == -1) { + tmp[0] = col.render.toLowerCase() + tmp[1] = '' + } else { + tmp[0] = col.render.toLowerCase().substr(0, strInd) + tmp[1] = col.render.toLowerCase().substr(strInd + 1) + } + // formatters + let func = w2utils.formatters[tmp[0]] + if (col.options && col.options.autoFormat === false) { + func = null + } + value = (typeof func == 'function' ? func(value, tmp[1], record) : '') + } + } + if (value == null) value = '' + return !extra ? value : { value, attr, style, className, divAttr } + } + getFooterHTML() { + return '
    '+ + ' '+ + ' '+ + ' '+ + '
    ' + } + status(msg) { + if (msg != null) { + query(this.box).find(`#grid_${this.name}_footer`).find('.w2ui-footer-left').html(msg) + } else { + // show number of selected + let msgLeft = '' + let sel = this.getSelection() + if (sel.length > 0) { + if (this.show.statusSelection && sel.length > 1) { + msgLeft = String(sel.length).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + w2utils.settings.groupSymbol) + ' ' + w2utils.lang('selected') + } + if (this.show.statusRecordID && sel.length == 1) { + let tmp = sel[0] + if (typeof tmp == 'object') tmp = tmp.recid + ', '+ w2utils.lang('Column') +': '+ tmp.column + msgLeft = w2utils.lang('Record ID') + ': '+ tmp + ' ' + } + } + query(this.box).find('#grid_'+ this.name +'_footer .w2ui-footer-left').html(msgLeft) + } + } + lock(msg, showSpinner) { + let args = Array.from(arguments) + args.unshift(this.box) + setTimeout(() => { + // hide empty msg if any + query(this.box).find('#grid_'+ this.name +'_empty_msg').remove() + w2utils.lock(...args) + }, 10) + } + unlock(speed) { + setTimeout(() => { + // do not unlock if there is a message + if (query(this.box).find('.w2ui-message').hasClass('w2ui-closing')) return + w2utils.unlock(this.box, speed) + }, 25) // needed timer so if server fast, it will not flash + } + stateSave(returnOnly) { + let state = { + columns: [], + show: w2utils.clone(this.show), + last: { + search: this.last.search, + multi : this.last.multi, + logic : this.last.logic, + label : this.last.label, + field : this.last.field, + scrollTop : this.last.scrollTop, + scrollLeft: this.last.scrollLeft + }, + sortData : [], + searchData: [] + } + let prop_val + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + let col_save_obj = {} + // iterate properties to save + Object.keys(this.stateColProps).forEach((prop, idx) => { + if (this.stateColProps[prop]){ + // check if the property is defined on the column + if (col[prop] !== undefined){ + prop_val = col[prop] + } else { + // use fallback or null + prop_val = this.colTemplate[prop] || null + } + col_save_obj[prop] = prop_val + } + }) + state.columns.push(col_save_obj) + } + for (let i = 0; i < this.sortData.length; i++) state.sortData.push(w2utils.clone(this.sortData[i])) + for (let i = 0; i < this.searchData.length; i++) state.searchData.push(w2utils.clone(this.searchData[i])) + // event before + let edata = this.trigger('stateSave', { target: this.name, state: state }) + if (edata.isCancelled === true) { + return + } + // save into local storage + if (returnOnly !== true) { + this.cacheSave('state', state) + } + // event after + edata.finish() + return state + } + stateRestore(newState) { + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (!newState) { + newState = this.cache('state') + } + // event before + let edata = this.trigger('stateRestore', { target: this.name, state: newState }) + if (edata.isCancelled === true) { + return + } + // default behavior + if (w2utils.isPlainObject(newState)) { + w2utils.extend(this.show, newState.show ?? {}) + w2utils.extend(this.last, newState.last ?? {}) + let sTop = this.last.scrollTop + let sLeft = this.last.scrollLeft + for (let c = 0; c < newState.columns?.length; c++) { + let tmp = newState.columns[c] + let col_index = this.getColumn(tmp.field, true) + if (col_index !== null) { + w2utils.extend(this.columns[col_index], tmp) + // restore column order from saved state + if (c !== col_index) this.columns.splice(c, 0, this.columns.splice(col_index, 1)[0]) + } + } + this.sortData.splice(0, this.sortData.length) + for (let c = 0; c < newState.sortData?.length; c++) { + this.sortData.push(newState.sortData[c]) + } + this.searchData.splice(0, this.searchData.length) + for (let c = 0; c < newState.searchData?.length; c++) { + this.searchData.push(newState.searchData[c]) + } + // apply sort and search + setTimeout(() => { + // needs timeout as records need to be populated + // ez 10.09.2014 this --> + if (!url) { + if (this.sortData.length > 0) this.localSort() + if (this.searchData.length > 0) this.localSearch() + } + this.last.scrollTop = sTop + this.last.scrollLeft = sLeft + this.refresh() + }, 1) + console.log(`INFO (w2ui): state restored for "${this.name}"`) + } + // event after + edata.finish() + return true + } + stateReset() { + this.stateRestore(this.last.state) + this.cacheSave('state', null) + } + parseField(obj, field) { + if (this.nestedFields) { + let val = '' + try { // need this to make sure no error in fields + val = obj + let tmp = String(field).split('.') + for (let i = 0; i < tmp.length; i++) { + val = val[tmp[i]] + } + } catch (event) { + val = '' + } + return val + } else { + return obj ? obj[field] : '' + } + } + prepareData() { + let obj = this + // loops thru records and prepares date and time objects + for (let r = 0; r < this.records.length; r++) { + let rec = this.records[r] + prepareRecord(rec) + } + // prepare date and time objects for the 'rec' record and its closed children + function prepareRecord(rec) { + for (let c = 0; c < obj.columns.length; c++) { + let column = obj.columns[c] + if (rec[column.field] == null || typeof column.render != 'string') continue + // number + if (['number', 'int', 'float', 'money', 'currency', 'percent'].indexOf(column.render.split(':')[0]) != -1) { + if (typeof rec[column.field] != 'number') rec[column.field] = parseFloat(rec[column.field]) + } + // date + if (['date', 'age'].indexOf(column.render.split(':')[0]) != -1) { + if (!rec[column.field + '_']) { + let dt = rec[column.field] + if (w2utils.isInt(dt)) dt = parseInt(dt) + rec[column.field + '_'] = new Date(dt) + } + } + // time + if (['time'].indexOf(column.render) != -1) { + if (w2utils.isTime(rec[column.field])) { // if string + let tmp = w2utils.isTime(rec[column.field], true) + let dt = new Date() + dt.setHours(tmp.hours, tmp.minutes, (tmp.seconds ? tmp.seconds : 0), 0) // sets hours, min, sec, mills + if (!rec[column.field + '_']) rec[column.field + '_'] = dt + } else { // if date object + let tmp = rec[column.field] + if (w2utils.isInt(tmp)) tmp = parseInt(tmp) + tmp = (tmp != null ? new Date(tmp) : new Date()) + let dt = new Date() + dt.setHours(tmp.getHours(), tmp.getMinutes(), tmp.getSeconds(), 0) // sets hours, min, sec, mills + if (!rec[column.field + '_']) rec[column.field + '_'] = dt + } + } + } + if (rec.w2ui?.children && rec.w2ui?.expanded !== true) { + // there are closed children, prepare them too. + for (let r = 0; r < rec.w2ui.children.length; r++) { + let subRec = rec.w2ui.children[r] + prepareRecord(subRec) + } + } + } + } + nextCell(index, col_ind, editable) { + let check = col_ind + 1 + if (check >= this.columns.length) { + index = this.nextRow(index) + return index == null ? index : this.nextCell(index, -1, editable) + } + let tmp = this.records[index].w2ui + let col = this.columns[check] + let span = (tmp && tmp.colspan && !isNaN(tmp.colspan[col.field]) ? parseInt(tmp.colspan[col.field]) : 1) + if (col == null) return null + if (col && col.hidden || span === 0) return this.nextCell(index, check, editable) + if (editable) { + let edit = this.getCellEditable(index, check) + if (edit == null || ['checkbox', 'check'].indexOf(edit.type) != -1) { + return this.nextCell(index, check, editable) + } + } + return { index, colIndex: check } + } + prevCell(index, col_ind, editable) { + let check = col_ind - 1 + if (check < 0) { + index = this.prevRow(index) + return index == null ? index : this.prevCell(index, this.columns.length, editable) + } + if (check < 0) return null + let tmp = this.records[index].w2ui + let col = this.columns[check] + let span = (tmp && tmp.colspan && !isNaN(tmp.colspan[col.field]) ? parseInt(tmp.colspan[col.field]) : 1) + if (col == null) return null + if (col && col.hidden || span === 0) return this.prevCell(index, check, editable) + if (editable) { + let edit = this.getCellEditable(index, check) + if (edit == null || ['checkbox', 'check'].indexOf(edit.type) != -1) { + return this.prevCell(index, check, editable) + } + } + return { index, colIndex: check } + } + nextRow(ind, col_ind, numRows) { + let sids = this.last.searchIds + let ret = null + if (numRows == null) numRows = 1 + if (numRows == -1) { + return this.records.length-1 + } + if ((ind + numRows < this.records.length && sids.length === 0) // if there are more records + || (sids.length > 0 && ind < sids[sids.length-numRows])) { + ind += numRows + if (sids.length > 0) while (true) { + if (sids.includes(ind) || ind > this.records.length) break + ind += numRows + } + // colspan + let tmp = this.records[ind].w2ui + let col = this.columns[col_ind] + let span = (tmp && tmp.colspan && col != null && !isNaN(tmp.colspan[col.field]) ? parseInt(tmp.colspan[col.field]) : 1) + if (span === 0) { + ret = this.nextRow(ind, col_ind, numRows) + } else { + ret = ind + } + } + return ret + } + prevRow(ind, col_ind, numRows) { + let sids = this.last.searchIds + let ret = null + if (numRows == null) numRows = 1 + if (numRows == -1) { + return 0 + } + if ((ind - numRows >= 0 && sids.length === 0) // if there are more records + || (sids.length > 0 && ind > sids[0])) { + ind -= numRows + if (sids.length > 0) while (true) { + if (sids.includes(ind) || ind < 0) break + ind -= numRows + } + // colspan + let tmp = this.records[ind].w2ui + let col = this.columns[col_ind] + let span = (tmp && tmp.colspan && col != null && !isNaN(tmp.colspan[col.field]) ? parseInt(tmp.colspan[col.field]) : 1) + if (span === 0) { + ret = this.prevRow(ind, col_ind, numRows) + } else { + ret = ind + } + } + return ret + } + selectionSave() { + this.last.saved_sel = this.getSelection() + return this.last.saved_sel + } + selectionRestore(noRefresh) { + let time = Date.now() + this.last.selection = { indexes: [], columns: {} } + let sel = this.last.selection + let lst = this.last.saved_sel + if (lst) for (let i = 0; i < lst.length; i++) { + if (w2utils.isPlainObject(lst[i])) { + // selectType: cell + let tmp = this.get(lst[i].recid, true) + if (tmp != null) { + if (sel.indexes.indexOf(tmp) == -1) sel.indexes.push(tmp) + if (!sel.columns[tmp]) sel.columns[tmp] = [] + sel.columns[tmp].push(lst[i].column) + } + } else { + // selectType: row + let tmp = this.get(lst[i], true) + if (tmp != null) sel.indexes.push(tmp) + } + } + delete this.last.saved_sel + if (noRefresh !== true) this.refresh() + return Date.now() - time + } + message(options) { + return w2utils.message({ + owner: this, + box : this.box, + after: '.w2ui-grid-header' + }, options) + } + confirm(options) { + return w2utils.confirm({ + owner: this, + box : this.box, + after: '.w2ui-grid-header' + }, options) + } +} + +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2tabs, w2toolbar, w2tooltip, w2field + * + * == TODO == + * - include delta on save + * - tabs below some fields (could already be implemented) + * - form with toolbar & tabs + * - promise for load, save, etc. + * + * == 2.0 changes + * - CSP - fixed inline events + * - removed jQuery dependency + * - better groups support tabs now + * - form.confirm - refactored + * - form.message - refactored + * - observeResize for the box + * - removed msgNotJSON, msgAJAXerror + * - applyFocus -> setFocus + * - getFieldValue(fieldName) = returns { curent, previous, original } + * - setFieldVallue(fieldName, value) + * - getValue(..., original) -- return original if any + * - added .hideErrors() + * - reuqest, save, submit - return promises + * - added prepareParams + * - this.recid = null if no record needs to be pulled + * - remove form.multiplart + * - this.method - for saving only + */ + +class w2form extends w2base { + constructor(options) { + super(options.name) + this.name = null + this.header = '' + this.box = null // HTML element that hold this element + this.url = '' + this.method = null // if defined, it will be http method when saving + this.routeData = {} // data for dynamic routes + this.formURL = '' // url where to get form HTML + this.formHTML = '' // form HTML (might be loaded from the url) + this.page = 0 // current page + this.pageStyle = '' + this.recid = null // if not null, then load record + this.fields = [] + this.actions = {} + this.record = {} + this.original = null + this.dataType = null // only used when not null, otherwise from w2utils.settings.dataType + this.postData = {} + this.httpHeaders = {} + this.toolbar = {} // if not empty, then it is toolbar + this.tabs = {} // if not empty, then it is tabs object + this.style = '' + this.focus = 0 // focus first or other element + this.autosize = true // autosize, if false the container must have a height set + this.nestedFields = true // use field name containing dots as separator to look into object + this.tabindexBase = 0 // this will be added to the auto numbering + this.isGenerated = false + this.last = { + fetchCtrl: null, // last fetch AbortController + fetchOptions: null, // last fetch options + errors: [] + } + this.onRequest = null + this.onLoad = null + this.onValidate = null + this.onSubmit = null + this.onProgress = null + this.onSave = null + this.onChange = null + this.onInput = null + this.onRender = null + this.onRefresh = null + this.onResize = null + this.onDestroy = null + this.onAction = null + this.onToolbar = null + this.onError = null + this.msgRefresh = 'Loading...' + this.msgSaving = 'Saving...' + this.msgServerError = 'Server error' + this.ALL_TYPES = [ 'text', 'textarea', 'email', 'pass', 'password', 'int', 'float', 'money', 'currency', + 'percent', 'hex', 'alphanumeric', 'color', 'date', 'time', 'datetime', 'toggle', 'checkbox', 'radio', + 'check', 'checks', 'list', 'combo', 'enum', 'file', 'select', 'map', 'array', 'div', 'custom', 'html', + 'empty'] + this.LIST_TYPES = ['select', 'radio', 'check', 'checks', 'list', 'combo', 'enum'] + this.W2FIELD_TYPES = ['int', 'float', 'money', 'currency', 'percent', 'hex', 'alphanumeric', 'color', + 'date', 'time', 'datetime', 'list', 'combo', 'enum', 'file'] + // mix in options + w2utils.extend(this, options) + // remember items + let record = options.record + let original = options.original + let fields = options.fields + let toolbar = options.toolbar + let tabs = options.tabs + // extend items + Object.assign(this, { record: {}, original: null, fields: [], tabs: {}, toolbar: {}, handlers: [] }) + // preprocess fields + if (fields) { + let sub =_processFields(fields) + this.fields = sub.fields + if (!tabs && sub.tabs.length > 0) { + tabs = sub.tabs + } + } + // prepare tabs + if (Array.isArray(tabs)) { + w2utils.extend(this.tabs, { tabs: [] }) + for (let t = 0; t < tabs.length; t++) { + let tmp = tabs[t] + if (typeof tmp === 'object') { + this.tabs.tabs.push(tmp) + if (tmp.active === true) { + this.tabs.active = tmp.id + } + } else { + this.tabs.tabs.push({ id: tmp, text: tmp }) + } + } + } else { + w2utils.extend(this.tabs, tabs) + } + w2utils.extend(this.toolbar, toolbar) + for (let p in record) { // it is an object + if (w2utils.isPlainObject(record[p])) { + this.record[p] = w2utils.clone(record[p]) + } else { + this.record[p] = record[p] + } + } + for (let p in original) { // it is an object + if (w2utils.isPlainObject(original[p])) { + this.original[p] = w2utils.clone(original[p]) + } else { + this.original[p] = original[p] + } + } + // generate html if necessary + if (this.formURL !== '') { + fetch(this.formURL) + .then(resp => resp.text()) + .then(text => { + this.formHTML = text + this.isGenerated = true + if (this.box) this.render(this.box) + }) + } else if (!this.formURL && !this.formHTML) { + this.formHTML = this.generateHTML() + this.isGenerated = true + } else if (this.formHTML) { + this.isGenerated = true + } + // render if box specified + if (typeof this.box == 'string') this.box = query(this.box).get(0) + if (this.box) this.render(this.box) + function _processFields(fields) { + let newFields = [] + let tabs = [] + // if it is an object + if (w2utils.isPlainObject(fields)) { + let tmp = fields + fields = [] + Object.keys(tmp).forEach((key) => { + let fld = tmp[key] + if (fld.type == 'group') { + fld.text = key + if (w2utils.isPlainObject(fld.fields)) { + let tmp2 = fld.fields + fld.fields = [] + Object.keys(tmp2).forEach((key2) => { + let fld2 = tmp2[key2] + fld2.field = key2 + fld.fields.push(_process(fld2)) + }) + } + fields.push(fld) + } else if (fld.type == 'tab') { + // add tab + let tab = { id: key, text: key } + if (fld.style) { + tab.style = fld.style + } + tabs.push(tab) + // add page to fields + let sub = _processFields(fld.fields).fields + sub.forEach(fld2 => { + fld2.html = fld2.html || {} + fld2.html.page = tabs.length -1 + _process2(fld, fld2) + }) + fields.push(...sub) + } else { + fld.field = key + fields.push(_process(fld)) + } + }) + function _process(fld) { + let ignore = ['html'] + if (fld.html == null) fld.html = {} + Object.keys(fld).forEach((key => { + if (ignore.indexOf(key) != -1) return + if (['label', 'attr', 'style', 'text', 'span', 'page', 'column', 'anchor', + 'group', 'groupStyle', 'groupTitleStyle', 'groupCollapsible'].indexOf(key) != -1) { + fld.html[key] = fld[key] + delete fld[key] + } + })) + return fld + } + function _process2(fld, fld2) { + let ignore = ['style', 'html'] + Object.keys(fld).forEach((key => { + if (ignore.indexOf(key) != -1) return + if (['span', 'column', 'attr', 'text', 'label'].indexOf(key) != -1) { + if (fld[key] && !fld2.html[key]) { + fld2.html[key] = fld[key] + } + } + })) + } + } + // process groups + fields.forEach(field => { + if (field.type == 'group') { + // group properties + let group = { + group: field.text || '', + groupStyle: field.style || '', + groupTitleStyle: field.titleStyle || '', + groupCollapsible: field.collapsible === true ? true : false, + } + // loop through fields + if (Array.isArray(field.fields)) { + field.fields.forEach(gfield => { + let fld = w2utils.clone(gfield) + if (fld.html == null) fld.html = {} + w2utils.extend(fld.html, group) + Array('span', 'column', 'attr', 'label', 'page').forEach(key => { + if (fld.html[key] == null && field[key] != null) { + fld.html[key] = field[key] + } + }) + if (fld.field == null && fld.name != null) { + console.log('NOTICE: form field.name property is deprecated, please use field.field. Field ->', field) + fld.field = fld.name + } + newFields.push(fld) + }) + } + } else { + let fld = w2utils.clone(field) + if (fld.field == null && fld.name != null) { + console.log('NOTICE: form field.name property is deprecated, please use field.field. Field ->', field) + fld.field = fld.name + } + newFields.push(fld) + } + }) + return { fields: newFields, tabs } + } + } + get(field, returnIndex) { + if (arguments.length === 0) { + let all = [] + for (let f1 = 0; f1 < this.fields.length; f1++) { + if (this.fields[f1].field != null) all.push(this.fields[f1].field) + } + return all + } else { + for (let f2 = 0; f2 < this.fields.length; f2++) { + if (this.fields[f2].field == field) { + if (returnIndex === true) return f2; else return this.fields[f2] + } + } + return null + } + } + set(field, obj) { + for (let f = 0; f < this.fields.length; f++) { + if (this.fields[f].field == field) { + w2utils.extend(this.fields[f] , obj) + this.refresh(field) + return true + } + } + return false + } + getValue(field, original) { + if (this.nestedFields) { + let val = undefined + try { // need this to make sure no error in fields + let rec = original === true ? this.original : this.record + val = String(field).split('.').reduce((rec, i) => { return rec[i] }, rec) + } catch (event) { + } + return val + } else { + return this.record[field] + } + } + setValue(field, value) { + // will not refresh the form! + if (value === '' || value == null + || (Array.isArray(value) && value.length === 0) + || (w2utils.isPlainObject(value) && Object.keys(value).length == 0)) { + value = null + } + if (this.nestedFields) { + try { // need this to make sure no error in fields + let rec = this.record + String(field).split('.').map((fld, i, arr) => { + if (arr.length - 1 !== i) { + if (rec[fld]) rec = rec[fld]; else { rec[fld] = {}; rec = rec[fld] } + } else { + rec[fld] = value + } + }) + return true + } catch (event) { + return false + } + } else { + this.record[field] = value + return true + } + } + getFieldValue(name) { + let field = this.get(name) + if (field == null) return + let el = field.el + let previous = this.getValue(name) + let original = this.getValue(name, true) + // orginary input control + let current = el.value + // should not be set to '', incosistent logic + // if (previous == null) previous = '' + // clean extra chars + if (['int', 'float', 'percent', 'money', 'currency'].includes(field.type)) { + current = field.w2field.clean(current) + } + // radio list + if (['radio'].includes(field.type)) { + let selected = query(el).closest('div').find('input:checked').get(0) + if (selected) { + let item = field.options.items[query(selected).data('index')] + current = item.id + } else { + current = null + } + } + // single checkbox + if (['toggle', 'checkbox'].includes(field.type)) { + current = el.checked + } + // check list + if (['check', 'checks'].indexOf(field.type) !== -1) { + current = [] + let selected = query(el).closest('div').find('input:checked') + if (selected.length > 0) { + selected.each(el => { + let item = field.options.items[query(el).data('index')] + current.push(item.id) + }) + } + if (!Array.isArray(previous)) previous = [] + } + // lists + let selected = el._w2field?.selected // drop downs and other w2field objects + if (['list', 'enum', 'file'].includes(field.type) && selected) { + // TODO: check when w2field is refactored + let nv = selected + let cv = previous + if (Array.isArray(nv)) { + current = [] + for (let i = 0; i < nv.length; i++) current[i] = w2utils.clone(nv[i]) // clone array + } + if (Array.isArray(cv)) { + previous = [] + for (let i = 0; i < cv.length; i++) previous[i] = w2utils.clone(cv[i]) // clone array + } + if (w2utils.isPlainObject(nv)) { + current = w2utils.clone(nv) // clone object + } + if (w2utils.isPlainObject(cv)) { + previous = w2utils.clone(cv) // clone object + } + } + // map, array + if (['map', 'array'].includes(field.type)) { + current = (field.type == 'map' ? {} : []) + field.$el.parent().find('.w2ui-map-field').each(div => { + let key = query(div).find('.w2ui-map.key').val() + let value = query(div).find('.w2ui-map.value').val() + if (field.type == 'map') { + current[key] = value + } else { + current.push(value) + } + }) + } + return { current, previous, original } // current - in input, previous - in form.record, original - before form change + } + setFieldValue(name, value) { + let field = this.get(name) + if (field == null) return + let el = field.el + switch (field.type) { + case 'toggle': + case 'checkbox': + el.checked = value ? true : false + break + case 'radio': { + value = value?.id ?? value + let inputs = query(el).closest('div').find('input') + let items = field.options.items + items.forEach((it, ind) => { + if (it.id === value) { // need exact match so to match empty string and 0 + inputs.filter(`[data-index="${ind}"]`).prop('checked', true) + } + }) + break + } + case 'check': + case 'checks': { + if (!Array.isArray(value)) { + if (value != null) { + value = [value] + } else { + value = [] + } + } + value = value.map(val => val?.id ?? val) // convert if array of objects + let inputs = query(el).closest('div').find('input') + let items = field.options.items + items.forEach((it, ind) => { + inputs.filter(`[data-index="${ind}"]`).prop('checked', value.includes(it.id) ? true : false) + }) + break + } + case 'list': + case 'combo': + let item = value + // find item in options.items, if any + if (item?.id == null && Array.isArray(field.options?.items)) { + field.options.items.forEach(it => { + if (it.id === value) item = it + }) + } + // if item is found in field.options, update it in the this.records + if (item != value) { + this.setValue(field.name, item) + } + if (field.type == 'list') { + field.w2field.selected = item + field.w2field.refresh() + } else { + field.el.value = item?.text ?? value + } + break + case 'enum': + case 'file': { + if (!Array.isArray(value)) { + value = value != null ? [value] : [] + } + let items = [...value] + // find item in options.items, if any + let updated = false + items.forEach((item, ind) => { + if (item?.id == null && Array.isArray(field.options.items)) { + field.options.items.forEach(it => { + if (it.id == item) { + items[ind] = it + updated = true + } + }) + } + }) + if (updated) { + this.setValue(field.name, items) + } + field.w2field.selected = items + field.w2field.refresh() + break + } + case 'map': + case 'array': { + // init map + if (field.type == 'map' && (value == null || !w2utils.isPlainObject(value))) { + this.setValue(field.field, {}) + value = this.getValue(field.field) + } + if (field.type == 'array' && (value == null || !Array.isArray(value))) { + this.setValue(field.field, []) + value = this.getValue(field.field) + } + let container = query(field.el).parent().find('.w2ui-map-container') + field.el.mapRefresh(value, container) + break + } + case 'div': + case 'custom': + query(el).html(value) + break + case 'html': + case 'empty': + break + default: + // regular text fields + el.value = value ?? '' + break + } + } + show() { + let effected = [] + for (let a = 0; a < arguments.length; a++) { + let fld = this.get(arguments[a]) + if (fld && fld.hidden) { + fld.hidden = false + effected.push(fld.field) + } + } + if (effected.length > 0) this.refresh.apply(this, effected) + this.updateEmptyGroups() + return effected + } + hide() { + let effected = [] + for (let a = 0; a < arguments.length; a++) { + let fld = this.get(arguments[a]) + if (fld && !fld.hidden) { + fld.hidden = true + effected.push(fld.field) + } + } + if (effected.length > 0) this.refresh.apply(this, effected) + this.updateEmptyGroups() + return effected + } + enable() { + let effected = [] + for (let a = 0; a < arguments.length; a++) { + let fld = this.get(arguments[a]) + if (fld && fld.disabled) { + fld.disabled = false + effected.push(fld.field) + } + } + if (effected.length > 0) this.refresh.apply(this, effected) + return effected + } + disable() { + let effected = [] + for (let a = 0; a < arguments.length; a++) { + let fld = this.get(arguments[a]) + if (fld && !fld.disabled) { + fld.disabled = true + effected.push(fld.field) + } + } + if (effected.length > 0) this.refresh.apply(this, effected) + return effected + } + updateEmptyGroups() { + // hide empty groups + query(this.box).find('.w2ui-group').each((group) =>{ + if (isHidden(query(group).find('.w2ui-field'))) { + query(group).hide() + } else { + query(group).show() + } + }) + function isHidden($els) { + let flag = true + $els.each((el) => { + if (el.style.display != 'none') flag = false + }) + return flag + } + } + change() { + Array.from(arguments).forEach((field) => { + let tmp = this.get(field) + if (tmp.$el) tmp.$el.change() + }) + } + reload(callBack) { + let url = (typeof this.url !== 'object' ? this.url : this.url.get) + if (url && this.recid != null) { + // this.clear(); + return this.request(callBack) // returns promise + } else { + // this.refresh(); // no need to refresh + if (typeof callBack === 'function') callBack() + return new Promise(resolve => { resolve() }) // resolved promise + } + } + clear() { + if (arguments.length != 0) { + Array.from(arguments).forEach((field) => { + let rec = this.record + String(field).split('.').map((fld, i, arr) => { + if (arr.length - 1 !== i) rec = rec[fld]; else delete rec[fld] + }) + this.refresh(field) + }) + } else { + this.recid = null + this.record = {} + this.original = null + this.refresh() + this.hideErrors() + } + } + error(msg) { + // let the management of the error outside of the form + let edata = this.trigger('error', { + target: this.name, + message: msg, + fetchCtrl: this.last.fetchCtrl, + fetchOptions: this.last.fetchOptions + }) + if (edata.isCancelled === true) return + // need a time out because message might be already up) + setTimeout(() => { this.message(msg) }, 1) + // event after + edata.finish() + } + message(options) { + return w2utils.message({ + owner: this, + box : this.box, + after: '.w2ui-form-header' + }, options) + } + confirm(options) { + return w2utils.confirm({ + owner: this, + box : this.box, + after: '.w2ui-form-header' + }, options) + } + validate(showErrors) { + if (showErrors == null) showErrors = true + // validate before saving + let errors = [] + for (let f = 0; f < this.fields.length; f++) { + let field = this.fields[f] + if (this.getValue(field.field) == null) this.setValue(field.field, '') + if (['int', 'float', 'currency', 'money'].indexOf(field.type) != -1) { + let val = this.getValue(field.field) + let min = field.options.min + let max = field.options.max + if (min != null && val < min) { + errors.push({ field: field, error: w2utils.lang('Should be more than ${min}', { min }) }) + } + if (max != null && val > max) { + errors.push({ field: field, error: w2utils.lang('Should be less than ${max}', { max }) }) + } + } + switch (field.type) { + case 'alphanumeric': + if (this.getValue(field.field) && !w2utils.isAlphaNumeric(this.getValue(field.field))) { + errors.push({ field: field, error: w2utils.lang('Not alpha-numeric') }) + } + break + case 'int': + if (this.getValue(field.field) && !w2utils.isInt(this.getValue(field.field))) { + errors.push({ field: field, error: w2utils.lang('Not an integer') }) + } + break + case 'percent': + case 'float': + if (this.getValue(field.field) && !w2utils.isFloat(this.getValue(field.field))) { + errors.push({ field: field, error: w2utils.lang('Not a float') }) + } + break + case 'currency': + case 'money': + if (this.getValue(field.field) && !w2utils.isMoney(this.getValue(field.field))) { + errors.push({ field: field, error: w2utils.lang('Not in money format') }) + } + break + case 'color': + case 'hex': + if (this.getValue(field.field) && !w2utils.isHex(this.getValue(field.field))) { + errors.push({ field: field, error: w2utils.lang('Not a hex number') }) + } + break + case 'email': + if (this.getValue(field.field) && !w2utils.isEmail(this.getValue(field.field))) { + errors.push({ field: field, error: w2utils.lang('Not a valid email') }) + } + break + case 'checkbox': + // convert true/false + if (this.getValue(field.field) == true) { + this.setValue(field.field, true) + } else { + this.setValue(field.field, false) + } + break + case 'date': + // format date before submit + if (!field.options.format) field.options.format = w2utils.settings.dateFormat + if (this.getValue(field.field) && !w2utils.isDate(this.getValue(field.field), field.options.format)) { + errors.push({ field: field, error: w2utils.lang('Not a valid date') + ': ' + field.options.format }) + } + break + case 'list': + case 'combo': + break + case 'enum': + break + } + // === check required - if field is '0' it should be considered not empty + let val = this.getValue(field.field) + if (field.hidden !== true && field.required + && !['div', 'custom', 'html', 'empty'].includes(field.type) + && (val == null || val === '' || (Array.isArray(val) && val.length === 0) + || (w2utils.isPlainObject(val) && Object.keys(val).length == 0))) { + errors.push({ field: field, error: w2utils.lang('Required field') }) + } + if (field.hidden !== true && field.options?.minLength > 0 + && !['enum', 'list', 'combo'].includes(field.type) // since minLength is used there for other purpose + && (val == null || val.length < field.options.minLength)) { + errors.push({ field: field, error: w2utils.lang('Field should be at least ${count} characters.', + { count: field.options.minLength })}) + } + } + // event before + let edata = this.trigger('validate', { target: this.name, errors: errors }) + if (edata.isCancelled === true) return + // show error + this.last.errors = errors + if (showErrors) this.showErrors() + // event after + edata.finish() + return errors + } + showErrors() { + // TODO: check edge cases + // -- scroll + // -- invisible pages + // -- form refresh + let errors = this.last.errors + if (errors.length <= 0) return + // show errors + this.goto(errors[0].field.page) + query(errors[0].field.$el).parents('.w2ui-field')[0].scrollIntoView({ block: 'nearest', inline: 'nearest' }) + // show errors + // show only for visible controls + errors.forEach(error => { + let opt = w2utils.extend({ + anchorClass: 'w2ui-error', + class: 'w2ui-light', + position: 'right|left', + hideOn: ['input'] + }, error.options) + if (error.field == null) return + let anchor = error.field.el + if (error.field.type === 'radio') { // for radio and checkboxes + anchor = query(error.field.el).closest('div').get(0) + } else if (['enum', 'file'].includes(error.field.type)) { + // TODO: check + // anchor = (error.field.el).data('w2field').helpers.multi + // $(fld).addClass('w2ui-error') + } + w2tooltip.show(w2utils.extend({ + anchor, + name: `${this.name}-${error.field.field}-error`, + html: error.error + }, opt)) + }) + // hide errors on scroll + query(errors[0].field.$el).parents('.w2ui-page') + .off('.hideErrors') + .on('scroll.hideErrors', (evt) => { this.hideErrors() }) + } + hideErrors() { + this.fields.forEach(field => { + w2tooltip.hide(`${this.name}-${field.field}-error`) + }) + } + getChanges() { + // TODO: not working on nested structures + let diff = {} + if (this.original != null && typeof this.original == 'object' && Object.keys(this.record).length !== 0) { + diff = doDiff(this.record, this.original, {}) + } + return diff + function doDiff(record, original, result) { + if (Array.isArray(record) && Array.isArray(original)) { + while (record.length < original.length) { + record.push(null) + } + } + for (let i in record) { + if (record[i] != null && typeof record[i] === 'object') { + result[i] = doDiff(record[i], original[i] || {}, {}) + if (!result[i] || (Object.keys(result[i]).length == 0 && Object.keys(original[i].length == 0))) delete result[i] + } else if (record[i] != original[i] || (record[i] == null && original[i] != null)) { // also catch field clear + result[i] = record[i] + } + } + return Object.keys(result).length != 0 ? result : null + } + } + getCleanRecord(strict) { + let data = w2utils.clone(this.record) + this.fields.forEach((fld) => { + if (['list', 'combo', 'enum'].indexOf(fld.type) != -1) { + let tmp = { nestedFields: true, record: data } + let val = this.getValue.call(tmp, fld.field) + if (w2utils.isPlainObject(val) && val.id != null) { // should be true if val.id === '' + this.setValue.call(tmp, fld.field, val.id) + } + if (Array.isArray(val)) { + val.forEach((item, ind) => { + if (w2utils.isPlainObject(item) && item.id) { + val[ind] = item.id + } + }) + } + } + if (fld.type == 'map') { + let tmp = { nestedFields: true, record: data } + let val = this.getValue.call(tmp, fld.field) + if (val._order) delete val._order + } + if (fld.type == 'file') { + let tmp = { nestedFields: true, record: data } + let val = this.getValue.call(tmp, fld.field) ?? [] + val.forEach(v => { + delete v.file + delete v.modified + }) + this.setValue.call(tmp, fld.field, val) + } + }) + // return only records present in description + if (strict === true) { + Object.keys(data).forEach((key) => { + if (!this.get(key)) delete data[key] + }) + } + return data + } + prepareParams(url, fetchOptions) { + let dataType = this.dataType ?? w2utils.settings.dataType + let postParams = fetchOptions.body + switch (dataType) { + case 'HTTPJSON': + postParams = { request: postParams } + body2params() + break + case 'HTTP': + body2params() + break + case 'RESTFULL': + if (fetchOptions.method == 'POST') { + fetchOptions.headers['Content-Type'] = 'application/json' + } else { + body2params() + } + break + case 'JSON': + if (fetchOptions.method == 'GET') { + postParams = { request: postParams } + body2params() + } else { + fetchOptions.headers['Content-Type'] = 'application/json' + fetchOptions.method = 'POST' + } + break + } + fetchOptions.body = typeof fetchOptions.body == 'string' ? fetchOptions.body : JSON.stringify(fetchOptions.body) + return fetchOptions + function body2params() { + Object.keys(postParams).forEach(key => { + let param = postParams[key] + if (typeof param == 'object') param = JSON.stringify(param) + url.searchParams.append(key, param) + }) + delete fetchOptions.body + } + } + request(postData, callBack) { // if (1) param then it is call back if (2) then postData and callBack + let self = this + let resolve, reject + let responseProm = new Promise((res, rej) => { resolve = res; reject = rej }) + // check for multiple params + if (typeof postData === 'function') { + callBack = postData + postData = null + } + if (postData == null) postData = {} + if (!this.url || (typeof this.url === 'object' && !this.url.get)) return + // build parameters list + let params = {} + // add list params + params.action = 'get' + params.recid = this.recid + params.name = this.name + // append other params + w2utils.extend(params, this.postData) + w2utils.extend(params, postData) + // event before + let edata = this.trigger('request', { target: this.name, url: this.url, httpMethod: 'GET', + postData: params, httpHeaders: this.httpHeaders }) + if (edata.isCancelled === true) return + // default action + this.record = {} + this.original = null + // call server to get data + this.lock(w2utils.lang(this.msgRefresh)) + let url = edata.detail.url + if (typeof url === 'object' && url.get) url = url.get + if (this.last.fetchCtrl) try { this.last.fetchCtrl.abort() } catch (e) {} + // process url with routeData + if (Object.keys(this.routeData).length != 0) { + let info = w2utils.parseRoute(url) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + if (this.routeData[info.keys[k].name] == null) continue + url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]) + } + } + } + url = new URL(url, location) + let fetchOptions = this.prepareParams(url, { + method: edata.detail.httpMethod, + headers: edata.detail.httpHeaders, + body: edata.detail.postData + }) + this.last.fetchCtrl = new AbortController() + fetchOptions.signal = this.last.fetchCtrl.signal + this.last.fetchOptions = fetchOptions + fetch(url, fetchOptions) + .catch(processError) + .then((resp) => { + if (resp?.status != 200) { + // if resp is undefined, it means request was aborted + if (resp) processError(resp) + return + } + resp.json() + .catch(processError) + .then(data => { + // event before + let edata = self.trigger('load', { + target: self.name, + fetchCtrl: this.last.fetchCtrl, + fetchOptions: this.last.fetchOptions, + data + }) + if (edata.isCancelled === true) return + // for backward compatibility + if (data.error == null && data.status === 'error') { + data.error = true + } + // if data.record is not present, then assume that entire response is the record + if (!data.record) { + Object.assign(data, { record: w2utils.clone(data) }) + } + // server response error, not due to network issues + if (data.error === true) { + self.error(w2utils.lang(data.message ?? this.msgServerError)) + } else { + self.record = w2utils.clone(data.record) + } + // event after + self.unlock() + edata.finish() + self.refresh() + self.setFocus() + // call back + if (typeof callBack === 'function') callBack(data) + resolve(data) + }) + }) + // event after + edata.finish() + return responseProm + function processError(response) { + if (response.name === 'AbortError') { + // request was aborted by the form + return + } + self.unlock() + // trigger event + let edata2 = self.trigger('error', { response, fetchCtrl: self.last.fetchCtrl, fetchOptions: self.last.fetchOptions }) + if (edata2.isCancelled === true) return + // default behavior + if (response.status && response.status != 200) { + self.error(response.status + ': ' + response.statusText) + } else { + console.log('ERROR: Server request failed.', response, '. ', + 'Expected Response:', { error: false, record: { field1: 1, field2: 'item' }}, + 'OR:', { error: true, message: 'Error description' }) + self.error(String(response)) + } + // event after + edata2.finish() + reject(response) + } + } + submit(postData, callBack) { + return this.save(postData, callBack) + } + save(postData, callBack) { + let self = this + let resolve, reject + let saveProm = new Promise((res, rej) => { resolve = res; reject = rej }) + // check for multiple params + if (typeof postData === 'function') { + callBack = postData + postData = null + } + // validation + let errors = self.validate(true) + if (errors.length !== 0) return + // submit save + if (postData == null) postData = {} + if (!self.url || (typeof self.url === 'object' && !self.url.save)) { + console.log('ERROR: Form cannot be saved because no url is defined.') + return + } + self.lock(w2utils.lang(self.msgSaving) + ' ') + // build parameters list + let params = {} + // add list params + params.action = 'save' + params.recid = self.recid + params.name = self.name + // append other params + w2utils.extend(params, self.postData) + w2utils.extend(params, postData) + params.record = w2utils.clone(self.record) + // event before + let edata = self.trigger('submit', { target: self.name, url: self.url, httpMethod: this.method ?? 'POST', + postData: params, httpHeaders: self.httpHeaders }) + if (edata.isCancelled === true) return + // default action + let url = edata.detail.url + if (typeof url === 'object' && url.save) url = url.save + if (self.last.fetchCtrl) self.last.fetchCtrl.abort() + // process url with routeData + if (Object.keys(self.routeData).length > 0) { + let info = w2utils.parseRoute(url) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + if (self.routeData[info.keys[k].name] == null) continue + url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), self.routeData[info.keys[k].name]) + } + } + } + url = new URL(url, location) + let fetchOptions = this.prepareParams(url, { + method: edata.detail.httpMethod, + headers: edata.detail.httpHeaders, + body: edata.detail.postData + }) + this.last.fetchCtrl = new AbortController() + fetchOptions.signal = this.last.fetchCtrl.signal + this.last.fetchOptions = fetchOptions + fetch(url, fetchOptions) + .catch(processError) + .then(resp => { + self.unlock() + if (resp?.status != 200) { + processError(resp ?? {}) + return + } + // parse server response + resp.json() + .catch(processError) + .then(data => { + // event before + let edata = self.trigger('save', { + target: self.name, + fetchCtrl: this.last.fetchCtrl, + fetchOptions: this.last.fetchOptions, + data + }) + if (edata.isCancelled === true) return + // server error, not due to network issues + if (data.error === true) { + self.error(w2utils.lang(data.message ?? this.msgServerError)) + } else { + self.original = null + } + // event after + edata.finish() + self.refresh() + // call back + if (typeof callBack === 'function') callBack(data) + resolve(data) + }) + }) + // event after + edata.finish() + return saveProm + function processError(response) { + if (response?.name === 'AbortError') { + // request was aborted by the form + return + } + self.unlock() + // trigger event + let edata2 = self.trigger('error', { response, fetchCtrl: self.last.fetchCtrl, fetchOptions: self.last.fetchOptions }) + if (edata2.isCancelled === true) return + // default behavior + if (response.status && response.status != 200) { + self.error(response.status + ': ' + response.statusText) + } else { + console.log('ERROR: Server request failed.', response, '. ', + 'Expected Response:', { error: false, record: { field1: 1, field2: 'item' }}, + 'OR:', { error: true, message: 'Error description' }) + self.error(String(response)) + } + // event after + edata2.finish() + reject() + } + } + lock(msg, showSpinner) { + let args = Array.from(arguments) + args.unshift(this.box) + w2utils.lock(...args) + } + unlock(speed) { + let box = this.box + w2utils.unlock(box, speed) + } + lockPage(page, msg, spinner) { + let $page = query(this.box).find('.page-' + page) + if ($page.length){ + // page found + w2utils.lock($page, msg, spinner) + return true + } + // page with this id not found! + return false + } + unlockPage(page, speed) { + let $page = query(this.box).find('.page-' + page) + if ($page.length) { + // page found + w2utils.unlock($page, speed) + return true + } + // page with this id not found! + return false + } + goto(page) { + if (this.page === page) return // already on this page + if (page != null) this.page = page + // if it was auto size, resize it + if (query(this.box).data('autoSize') === true) { + query(this.box).get(0).clientHeight = 0 + } + this.refresh() + } + generateHTML() { + let pages = [] // array for each page + let group = '' + let page + let column + let html + let tabindex + let tabindex_str + for (let f = 0; f < this.fields.length; f++) { + html = '' + tabindex = this.tabindexBase + f + 1 + tabindex_str = ' tabindex="'+ tabindex +'"' + let field = this.fields[f] + if (field.html == null) field.html = {} + if (field.options == null) field.options = {} + if (field.html.caption != null && field.html.label == null) { + console.log('NOTICE: form field.html.caption property is deprecated, please use field.html.label. Field ->', field) + field.html.label = field.html.caption + } + if (field.html.label == null) field.html.label = field.field + field.html = w2utils.extend({ label: '', span: 6, attr: '', text: '', style: '', page: 0, column: 0 }, field.html) + if (page == null) page = field.html.page + if (column == null) column = field.html.column + // input control + let input = `` + switch (field.type) { + case 'pass': + case 'password': + input = input.replace('type="text"', 'type="password"') + break + case 'checkbox': { + input = ` + ` + break + } + case 'check': + case 'checks': { + if (field.options.items == null && field.html.items != null) field.options.items = field.html.items + let items = field.options.items + input = '' + // normalized options + if (!Array.isArray(items)) items = [] + if (items.length > 0) { + items = w2utils.normMenu.call(this, items, field) + } + // generate + for (let i = 0; i < items.length; i++) { + input += ` + +
    ` + } + break + } + case 'radio': { + input = '' + // normalized options + if (field.options.items == null && field.html.items != null) field.options.items = field.html.items + let items = field.options.items + if (!Array.isArray(items)) items = [] + if (items.length > 0) { + items = w2utils.normMenu.call(this, items, field) + } + // generate + for (let i = 0; i < items.length; i++) { + input += ` + +
    ` + } + break + } + case 'select': { + input = `' + break + } + case 'textarea': + input = `` + break + case 'toggle': + input = ` +
    ` + break + case 'map': + case 'array': + field.html.key = field.html.key || {} + field.html.value = field.html.value || {} + field.html.tabindex_str = tabindex_str + input = '' + (field.html.text || '') + '' + + ''+ + '
    ' + break + case 'div': + case 'custom': + input = '
    '+ + (field && field.html && field.html.html ? field.html.html : '') + + '
    ' + break + case 'html': + case 'empty': + input = (field && field.html ? (field.html.html || '') + (field.html.text || '') : '') + break + } + if (group !== '') { + if (page != field.html.page || column != field.html.column || (field.html.group && (group != field.html.group))) { + pages[page][column] += '\n
    \n
    ' + group = '' + } + } + if (field.html.group && (group != field.html.group)) { + let collapsible = '' + if (field.html.groupCollapsible) { + collapsible = '' + } + html += '\n
    ' + + '\n
    ' + + collapsible + w2utils.lang(field.html.group) + '
    \n' + + '
    ' + group = field.html.group + } + if (field.html.anchor == null) { + let span = (field.html.span != null ? 'w2ui-span'+ field.html.span : '') + if (field.html.span == -1) span = 'w2ui-span-none' + let label = '' + w2utils.lang(field.type != 'checkbox' ? field.html.label : field.html.text) +'' + if (!field.html.label) label = '' + html += '\n
    '+ + '\n '+ label + + ((field.type === 'empty') ? input : '\n
    '+ input + (field.type != 'array' && field.type != 'map' ? w2utils.lang(field.type != 'checkbox' ? field.html.text : '') : '') + '
    ') + + '\n
    ' + } else { + pages[field.html.page].anchors = pages[field.html.page].anchors || {} + pages[field.html.page].anchors[field.html.anchor] = '
    '+ + ((field.type === 'empty') ? input : '
    '+ w2utils.lang(field.type != 'checkbox' ? field.html.label : field.html.text, true) + input + w2utils.lang(field.type != 'checkbox' ? field.html.text : '') + '
    ') + + '
    ' + } + if (pages[field.html.page] == null) pages[field.html.page] = {} + if (pages[field.html.page][field.html.column] == null) pages[field.html.page][field.html.column] = '' + pages[field.html.page][field.html.column] += html + page = field.html.page + column = field.html.column + } + if (group !== '') pages[page][column] += '\n
    \n
    ' + if (this.tabs.tabs) { + for (let i = 0; i < this.tabs.tabs.length; i++) if (pages[i] == null) pages[i] = [] + } + // buttons if any + let buttons = '' + if (Object.keys(this.actions).length > 0) { + buttons += '\n
    ' + tabindex = this.tabindexBase + this.fields.length + 1 + for (let a in this.actions) { // it is an object + let act = this.actions[a] + let info = { text: '', style: '', 'class': '' } + if (w2utils.isPlainObject(act)) { + if (act.text == null && act.caption != null) { + console.log('NOTICE: form action.caption property is deprecated, please use action.text. Action ->', act) + act.text = act.caption + } + if (act.text) info.text = act.text + if (act.style) info.style = act.style + if (act.class) info.class = act.class + } else { + info.text = a + if (['save', 'update', 'create'].indexOf(a.toLowerCase()) !== -1) info.class = 'w2ui-btn-blue'; else info.class = '' + } + buttons += '\n ' + tabindex++ + } + buttons += '\n
    ' + } + html = '' + for (let p = 0; p < pages.length; p++){ + html += '
    ' + if (!pages[p]) { + console.log(`ERROR: Page ${p} does not exist`) + return false + } + if (pages[p].before) { + html += pages[p].before + } + html += '
    ' + Object.keys(pages[p]).sort().forEach((c, ind) => { + if (c == parseInt(c)) { + html += '
    ' + (pages[p][c] || '') + '\n
    ' + } + }) + html += '\n
    ' + if (pages[p].after) { + html += pages[p].after + } + html += '\n
    ' + // process page anchors + if (pages[p].anchors) { + Object.keys(pages[p].anchors).forEach((key, ind) => { + html = html.replace(key, pages[p].anchors[key]) + }) + } + } + html += buttons + return html + } + toggleGroup(groupName, show) { + let el = query(this.box).find('.w2ui-group-title[data-group="' + w2utils.base64encode(groupName) + '"]') + if (el.length === 0) return + let el_next = query(el.prop('nextElementSibling')) + if (typeof show === 'undefined') { + show = (el_next.css('display') == 'none') + } + if (show) { + el_next.show() + el.find('span').addClass('w2ui-icon-collapse').removeClass('w2ui-icon-expand') + } else { + el_next.hide() + el.find('span').addClass('w2ui-icon-expand').removeClass('w2ui-icon-collapse') + } + } + action(action, event) { + let act = this.actions[action] + let click = act + if (w2utils.isPlainObject(act) && act.onClick) click = act.onClick + // event before + let edata = this.trigger('action', { target: action, action: act, originalEvent: event }) + if (edata.isCancelled === true) return + // default actions + if (typeof click === 'function') click.call(this, event) + // event after + edata.finish() + } + resize() { + let self = this + // event before + let edata = this.trigger('resize', { target: this.name }) + if (edata.isCancelled === true) return + // default behaviour + let header = query(this.box).find(':scope > div .w2ui-form-header') + let toolbar = query(this.box).find(':scope > div .w2ui-form-toolbar') + let tabs = query(this.box).find(':scope > div .w2ui-form-tabs') + let page = query(this.box).find(':scope > div .w2ui-page') + let dpage = query(this.box).find(':scope > div .w2ui-page.page-'+ this.page + ' > div') + let buttons = query(this.box).find(':scope > div .w2ui-buttons') + // if no height, calculate it + let { headerHeight, tbHeight, tabsHeight } = resizeElements() + if (this.autosize) { // we don't need autosize every time + let cHeight = query(this.box).get(0).clientHeight + if (cHeight === 0 || query(this.box).data('autosize') == 'yes') { + query(this.box).css({ + height: headerHeight + tbHeight + tabsHeight + 15 // 15 is extra height + + (page.length > 0 ? w2utils.getSize(dpage, 'height') : 0) + + (buttons.length > 0 ? w2utils.getSize(buttons, 'height') : 0) + + 'px' + }) + query(this.box).data('autosize', 'yes') + } + resizeElements() + } + // event after + edata.finish() + function resizeElements() { + let headerHeight = (self.header !== '' ? w2utils.getSize(header, 'height') : 0) + let tbHeight = (Array.isArray(self.toolbar?.items) && self.toolbar?.items?.length > 0) + ? w2utils.getSize(toolbar, 'height') + : 0 + let tabsHeight = (Array.isArray(self.tabs?.tabs) && self.tabs?.tabs?.length > 0) + ? w2utils.getSize(tabs, 'height') + : 0 + // resize elements + toolbar.css({ top: headerHeight + 'px' }) + tabs.css({ top: headerHeight + tbHeight + 'px' }) + page.css({ + top: headerHeight + tbHeight + tabsHeight + 'px', + bottom: (buttons.length > 0 ? w2utils.getSize(buttons, 'height') : 0) + 'px' + }) + // return some params + return { headerHeight, tbHeight, tabsHeight } + } + } + refresh() { + let time = Date.now() + let self = this + if (!this.box) return + if (!this.isGenerated || !query(this.box).html()) return + // event before + let edata = this.trigger('refresh', { target: this.name, page: this.page, field: arguments[0], fields: arguments }) + if (edata.isCancelled === true) return + let fields = Array.from(this.fields.keys()) + if (arguments.length > 0) { + fields = Array.from(arguments) + .map((fld, ind) => { + if (typeof fld != 'string') console.log('ERROR: Arguments in refresh functions should be field names') + return this.get(fld, true) // get index of field + }) + .filter((fld, ind) => { + if (fld != null) return true; else return false + }) + } else { + // update field.page with page it belongs too + query(this.box).find('input, textarea, select').each(el => { + let name = (query(el).attr('name') != null ? query(el).attr('name') : query(el).attr('id')) + let field = this.get(name) + if (field) { + // find page + let div = query(el).closest('.w2ui-page') + if (div.length > 0) { + for (let i = 0; i < 100; i++) { + if (div.hasClass('page-'+i)) { field.page = i; break } + } + } + } + }) + // default action + query(this.box).find('.w2ui-page').hide() + query(this.box).find('.w2ui-page.page-' + this.page).show() + query(this.box).find('.w2ui-form-header').html(w2utils.lang(this.header)) + // refresh tabs if needed + if (typeof this.tabs === 'object' && Array.isArray(this.tabs.tabs) && this.tabs.tabs.length > 0) { + query(this.box).find('#form_'+ this.name +'_tabs').show() + this.tabs.active = this.tabs.tabs[this.page].id + this.tabs.refresh() + } else { + query(this.box).find('#form_'+ this.name +'_tabs').hide() + } + // refresh tabs if needed + if (typeof this.toolbar === 'object' && Array.isArray(this.toolbar.items) && this.toolbar.items.length > 0) { + query(this.box).find('#form_'+ this.name +'_toolbar').show() + this.toolbar.refresh() + } else { + query(this.box).find('#form_'+ this.name +'_toolbar').hide() + } + } + // refresh values of fields + for (let f = 0; f < fields.length; f++) { + let field = this.fields[fields[f]] + if (field.name == null && field.field != null) field.name = field.field + if (field.field == null && field.name != null) field.field = field.name + field.$el = query(this.box).find(`[name='${String(field.name).replace(/\\/g, '\\\\')}']`) + field.el = field.$el.get(0) + if (field.el) field.el.id = field.name + // TODO: check + if (field.w2field) { + field.w2field.reset() + } + field.$el + .off('.w2form') + .on('change.w2form', function(event) { + let value = self.getFieldValue(field.field) + // clear error class + if (['enum', 'file'].includes(field.type)) { + let helper = field.el._w2field?.helpers?.multi + query(helper).removeClass('w2ui-error') + } + if (this._previous != null) { + value.previous = this._previous + delete this._previous + } + // event before + let edata2 = self.trigger('change', { target: this.name, field: this.name, value, originalEvent: event }) + if (edata2.isCancelled === true) return + // default behavior + self.setValue(this.name, value.current) + // event after + edata2.finish() + }) + .on('input.w2form', function(event) { + // remember original + if (self.original == null) { + if (Object.keys(self.record).length > 0) { + self.original = w2utils.clone(self.record) + } else { + self.original = {} + } + } + let value = self.getFieldValue(field.field) + // save previous for change event + if (this._previous == null) { + this._previous = value.previous + } + // event before + let edata2 = self.trigger('input', { target: self.name, value, originalEvent: event }) + if (edata2.isCancelled === true) return + // default action + self.setValue(this.name, value.current) + // event after + edata2.finish() + }) + // required + if (field.required) { + field.$el.closest('.w2ui-field').addClass('w2ui-required') + } else { + field.$el.closest('.w2ui-field').removeClass('w2ui-required') + } + // disabled + if (field.disabled != null) { + if (field.disabled) { + if (field.$el.data('tabIndex') == null) { + field.$el.data('tabIndex', field.$el.prop('tabIndex')) + } + field.$el + .prop('readOnly', true) + .prop('disabled', true) + .prop('tabIndex', -1) + .closest('.w2ui-field') + .addClass('w2ui-disabled') + } else { + field.$el + .prop('readOnly', false) + .prop('disabled', false) + .prop('tabIndex', field.$el.data('tabIndex') ?? field.$el.prop('tabIndex') ?? 0) + .closest('.w2ui-field') + .removeClass('w2ui-disabled') + } + } + // hidden + let tmp = field.el + if (!tmp) tmp = query(this.box).find('#' + field.field) + if (field.hidden) { + query(tmp).closest('.w2ui-field').hide() + } else { + query(tmp).closest('.w2ui-field').show() + } + } + // attach actions on buttons + query(this.box).find('button, input[type=button]').each(el => { + query(el).off('click').on('click', function(event) { + let action = this.value + if (this.id) action = this.id + if (this.name) action = this.name + self.action(action, event) + }) + }) + // init controls with record + for (let f = 0; f < fields.length; f++) { + let field = this.fields[fields[f]] + if (!field.el) continue + if (!field.$el.hasClass('w2ui-input')) field.$el.addClass('w2ui-input') + field.type = String(field.type).toLowerCase() + if (!field.options) field.options = {} + // list type + if (this.LIST_TYPES.includes(field.type)) { + let items = field.options.items + if (items == null) field.options.items = [] + field.options.items = w2utils.normMenu.call(this, items, field) + } + // HTML select + if (field.type == 'select') { + // generate options + let items = field.options.items + let options = '' + items.forEach(item => { + options += `` + }) + field.$el.html(options) + } + // w2fields + if (this.W2FIELD_TYPES.includes(field.type)) { + field.w2field = field.w2field + ?? new w2field(w2utils.extend({}, field.options, { type: field.type })) + field.w2field.render(field.el) + } + // map and arrays + if (['map', 'array'].includes(field.type)) { + // need closure + (function (obj, field) { + let keepFocus + field.el.mapAdd = function(field, div, cnt) { + let attr = (field.disabled ? ' readOnly ' : '') + (field.html.tabindex_str || '') + let html = ` +
    + ${field.type == 'map' + ? ` + ${field.html.key.text || ''} + ` + : '' + } + + ${field.html.value.text || ''} +
    ` + div.append(html) + } + field.el.mapRefresh = function(map, div) { + // generate options + let keys, $k, $v + if (field.type == 'map') { + if (!w2utils.isPlainObject(map)) map = {} + if (map._order == null) map._order = Object.keys(map) + keys = map._order + } + if (field.type == 'array') { + if (!Array.isArray(map)) map = [] + keys = map.map((item, ind) => { return ind }) + } + // delete extra fields (including empty one) + let all = div.find('.w2ui-map-field') + for (let i = all.length-1; i >= keys.length; i--) { + div.find(`div[data-index='${i}']`).remove() + } + for (let ind = 0; ind < keys.length; ind++) { + let key = keys[ind] + let fld = div.find(`div[data-index='${ind}']`) + // add if does not exists + if (fld.length == 0) { + field.el.mapAdd(field, div, ind) + fld = div.find(`div[data-index='${ind}']`) + } + fld.attr('data-key', key) + $k = fld.find('.w2ui-map.key') + $v = fld.find('.w2ui-map.value') + let val = map[key] + if (field.type == 'array') { + let tmp = map.filter((it) => { return it.key == key ? true : false}) + if (tmp.length > 0) val = tmp[0].value + } + $k.val(key) + $v.val(val) + if (field.disabled === true || field.disabled === false) { + $k.prop('readOnly', field.disabled ? true : false) + $v.prop('readOnly', field.disabled ? true : false) + } + } + let cnt = keys.length + let curr = div.find(`div[data-index='${cnt}']`) + // if not disabled - add next if needed + if (curr.length === 0 && (!$k || $k.val() != '' || $v.val() != '') + && !($k && ($k.prop('readOnly') === true || $k.prop('disabled') === true)) + ) { + field.el.mapAdd(field, div, cnt) + } + if (field.disabled === true || field.disabled === false) { + curr.find('.key').prop('readOnly', field.disabled ? true : false) + curr.find('.value').prop('readOnly', field.disabled ? true : false) + } + // attach events + let container = query(field.el).get(0)?.nextSibling // should be div + query(container).find('input.w2ui-map') + .off('.mapChange') + .on('keyup.mapChange', function(event) { + let $div = query(event.target).closest('.w2ui-map-field') + let next = $div.get(0).nextElementSibling + let prev = $div.get(0).previousElementSibling + if (event.keyCode == 13) { + let el = keepFocus ?? next + if (el instanceof HTMLElement) { + let inp = query(el).find('input') + if (inp.length > 0) { + inp.get(0).focus() + } + } + keepFocus = undefined + } + let className = query(event.target).hasClass('key') ? 'key' : 'value' + if (event.keyCode == 38 && prev) { // up key + query(prev).find(`input.${className}`).get(0).select() + event.preventDefault() + } + if (event.keyCode == 40 && next) { // down key + query(next).find(`input.${className}`).get(0).select() + event.preventDefault() + } + }) + .on('keydown.mapChange', function(event) { + if (event.keyCode == 38 || event.keyCode == 40) { + event.preventDefault() + } + }) + .on('input.mapChange', function(event) { + let fld = query(event.target).closest('div') + let cnt = fld.data('index') + let next = fld.get(0).nextElementSibling + // if last one, add new empty + if (fld.find('input').val() != '' && !next) { + field.el.mapAdd(field, div, parseInt(cnt) + 1) + } else if (fld.find('input').val() == '' && next) { + let isEmpty = true + query(next).find('input').each(el => { + if (el.value != '') isEmpty = false + }) + if (isEmpty) { + query(next).remove() + } + } + }) + .on('change.mapChange', function(event) { + // remember original + if (self.original == null) { + if (Object.keys(self.record).length > 0) { + self.original = w2utils.clone(self.record) + } else { + self.original = {} + } + } + // event before + let { current, previous, original } = self.getFieldValue(field.field) + let $cnt = query(event.target).closest('.w2ui-map-container') + if (field.type == 'map') current._order = [] + $cnt.find('.w2ui-map.key').each(el => { current._order.push(el.value) }) + let edata = self.trigger('change', { target: field.field, field: field.field, originalEvent: event, + value: { current, previous, original } + }) + if (edata.isCancelled === true) { + return + } + // delete empty + if (field.type == 'map') { + current._order = current._order.filter(k => k !== '') + delete current[''] + } + if (field.type == 'array') { + current = current.filter(k => k !== '') + } + if (query(event.target).parent().find('input').val() == '') { + keepFocus = event.target + } + self.setValue(field.field, current) + field.el.mapRefresh(current, div) + // event after + edata.finish() + }) + } + })(this, field) + } + // set value to HTML input field + this.setFieldValue(field.field, this.getValue(field.name)) + } + // event after + edata.finish() + this.resize() + return Date.now() - time + } + render(box) { + let time = Date.now() + let self = this + if (typeof box == 'string') box = query(box).get(0) + // event before + let edata = this.trigger('render', { target: this.name, box: box ?? this.box }) + if (edata.isCancelled === true) return + // default action + if (box != null) { + // clean previous box + if (query(this.box).find('#form_'+ this.name +'_form').length > 0) { + query(this.box).removeAttr('name') + .removeClass('w2ui-reset w2ui-form') + .html('') + } + this.box = box + } + if (!this.isGenerated && !this.formHTML) return + if (!this.box) return + // render form + let html = '
    ' + + (this.header !== '' ? '
    ' + w2utils.lang(this.header) + '
    ' : '') + + ' ' + + ' ' + + this.formHTML + + '
    ' + query(this.box).attr('name', this.name) + .addClass('w2ui-reset w2ui-form') + .html(html) + if (query(this.box).length > 0) query(this.box)[0].style.cssText += this.style + w2utils.bindEvents(query(this.box).find('.w2ui-eaction'), this) + // init toolbar regardless it is defined or not + if (typeof this.toolbar.render !== 'function') { + this.toolbar = new w2toolbar(w2utils.extend({}, this.toolbar, { name: this.name +'_toolbar', owner: this })) + this.toolbar.on('click', function(event) { + let edata = self.trigger('toolbar', { target: event.target, originalEvent: event }) + if (edata.isCancelled === true) return + // no default action + edata.finish() + }) + } + if (typeof this.toolbar === 'object' && typeof this.toolbar.render === 'function') { + this.toolbar.render(query(this.box).find('#form_'+ this.name +'_toolbar')[0]) + } + // init tabs regardless it is defined or not + if (typeof this.tabs.render !== 'function') { + this.tabs = new w2tabs(w2utils.extend({}, this.tabs, { name: this.name +'_tabs', owner: this, active: this.tabs.active })) + this.tabs.on('click', function(event) { + self.goto(this.get(event.target, true)) + }) + } + if (typeof this.tabs === 'object' && typeof this.tabs.render === 'function') { + this.tabs.render(query(this.box).find('#form_'+ this.name +'_tabs')[0]) + if (this.tabs.active) this.tabs.click(this.tabs.active) + } + // event after + edata.finish() + // after render actions + this.resize() + let url = (typeof this.url !== 'object' ? this.url : this.url.get) + if (url && this.recid != null) { + this.request().catch(error => this.refresh()) // even if there was error, still need refresh + } else { + this.refresh() + } + // observe div resize + this.last.observeResize = new ResizeObserver(() => { this.resize() }) + this.last.observeResize.observe(this.box) + // focus on load + if (this.focus != -1) { + let setCount = 0 + let setFocus = () => { + if (query(self.box).find('input, select, textarea').length > 0) { + self.setFocus() + } else { + setCount++ + if (setCount < 20) setTimeout(setFocus, 50) // 1 sec max + } + } + setFocus() + } + return Date.now() - time + } + setFocus(focus) { + if (typeof focus === 'undefined'){ + // no argument - use form's focus property + focus = this.focus + } + let $input + // focus field by index + if (w2utils.isInt(focus)){ + if (focus < 0) { + return + } + let inputs = query(this.box) + .find('div:not(.w2ui-field-helper) > input, select, textarea, div > label:nth-child(1) > [type=radio]') + .filter(':not(.file-input)') + // find visible (offsetParent == null for any element is not visible) + while (inputs[focus].offsetParent == null && inputs.length >= focus) { + focus++ + } + if (inputs[focus]) { + $input = query(inputs[focus]) + } + } else if (typeof focus === 'string') { + // focus field by name + $input = query(this.box).find(`[name='${focus}']`) + } + if ($input.length > 0){ + $input.get(0).focus() + } + return $input + } + destroy() { + // event before + let edata = this.trigger('destroy', { target: this.name }) + if (edata.isCancelled === true) return + // clean up + if (typeof this.toolbar === 'object' && this.toolbar.destroy) this.toolbar.destroy() + if (typeof this.tabs === 'object' && this.tabs.destroy) this.tabs.destroy() + if (query(this.box).find('#form_'+ this.name +'_tabs').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-form') + .html('') + } + this.last.observeResize?.disconnect() + delete w2ui[this.name] + // event after + edata.finish() + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2tooltip, w2color, w2menu, w2date + * + * == TODO == + * - upload (regular files) + * - BUG with prefix/postfix and arrows (test in different contexts) + * - multiple date selection + * - month selection, year selections + * - MultiSelect - Allow Copy/Paste for single and multi values + * - add routeData to list/enum + * - ENUM, LIST: should have same as grid (limit, offset, search, sort) + * - ENUM, LIST: should support wild chars + * - add selection of predefined times (used for appointments) + * - options.items - can be an array + * - options.msgNoItems - can be a function + * - REMOTE fields + * + * == 2.0 changes + * - removed jQuery dependency + * - enum options.autoAdd + * - [numeric, date] - options.autoCorrect to enforce range and validity + * - silent only left for files, removed form the rest + * - remote source response items => records or just an array + * - deprecated "success" field for remote source response + * - CSP - fixed inline events + * - remove clear, use reset instead + * - options.msgSearch + * - options.msgNoItems + */ + +class w2field extends w2base { + constructor(type, options) { + super() + // sanitization + if (typeof type == 'string' && options == null) { + options = { type: type } + } + if (typeof type == 'object' && options == null) { + options = w2utils.clone(type) + } + if (typeof type == 'string' && typeof options == 'object') { + options.type = type + } + options.type = String(options.type).toLowerCase() + this.el = options.el ?? null + this.selected = null + this.helpers = {} // object or helper elements + this.type = options.type ?? 'text' + this.options = w2utils.clone(options) + this.onSearch = options.onSearch ?? null + this.onRequest = options.onRequest ?? null + this.onLoad = options.onLoad ?? null + this.onError = options.onError ?? null + this.onClick = options.onClick ?? null + this.onAdd = options.onAdd ?? null + this.onNew = options.onNew ?? null + this.onRemove = options.onRemove ?? null + this.onMouseEnter= options.onMouseEnter ?? null + this.onMouseLeave= options.onMouseLeave ?? null + this.onScroll = options.onScroll ?? null + this.tmp = {} // temp object + // clean up some options + delete this.options.type + delete this.options.onSearch + delete this.options.onRequest + delete this.options.onLoad + delete this.options.onError + delete this.options.onClick + delete this.options.onMouseEnter + delete this.options.onMouseLeave + delete this.options.onScroll + if (this.el) { + this.render(this.el) + } + } + render(el) { + if (!(el instanceof HTMLElement)) { + console.log('ERROR: Cannot init w2field on empty subject') + return + } + if (el._w2field) { + el._w2field.reset() + } else { + el._w2field = this + } + this.el = el + this.init() + } + init() { + let options = this.options + let defaults + // only for INPUT or TEXTAREA + if (!['INPUT', 'TEXTAREA'].includes(this.el.tagName.toUpperCase())) { + console.log('ERROR: w2field could only be applied to INPUT or TEXTAREA.', this.el) + return + } + switch (this.type) { + case 'text': + case 'int': + case 'float': + case 'money': + case 'currency': + case 'percent': + case 'alphanumeric': + case 'bin': + case 'hex': + defaults = { + min: null, + max: null, + step: 1, + autoFormat: true, + autoCorrect: true, + currencyPrefix: w2utils.settings.currencyPrefix, + currencySuffix: w2utils.settings.currencySuffix, + currencyPrecision: w2utils.settings.currencyPrecision, + decimalSymbol: w2utils.settings.decimalSymbol, + groupSymbol: w2utils.settings.groupSymbol, + arrow: false, + keyboard: true, + precision: null, + prefix: '', + suffix: '' + } + this.options = w2utils.extend({}, defaults, options) + options = this.options // since object is re-created, need to re-assign + options.numberRE = new RegExp('['+ options.groupSymbol + ']', 'g') + options.moneyRE = new RegExp('['+ options.currencyPrefix + options.currencySuffix + options.groupSymbol +']', 'g') + options.percentRE = new RegExp('['+ options.groupSymbol + '%]', 'g') + // no keyboard support needed + if (['text', 'alphanumeric', 'hex', 'bin'].includes(this.type)) { + options.arrow = false + options.keyboard = false + } + break + case 'color': + defaults = { + prefix : '#', + suffix : `
     
    `, + arrow : false, + advanced : null, // open advanced by default + transparent : true + } + this.options = w2utils.extend({}, defaults, options) + options = this.options // since object is re-created, need to re-assign + break + case 'date': + defaults = { + format : w2utils.settings.dateFormat, // date format + keyboard : true, + autoCorrect : true, + start : null, + end : null, + blockDates : [], // array of blocked dates + blockWeekdays : [], // blocked weekdays 0 - sunday, 1 - monday, etc + colored : {}, // ex: { '3/13/2022': 'bg-color|text-color' } + btnNow : true + } + this.options = w2utils.extend({ type: 'date' }, defaults, options) + options = this.options // since object is re-created, need to re-assign + if (query(this.el).attr('placeholder') == null) { + query(this.el).attr('placeholder', options.format) + } + break + case 'time': + defaults = { + format : w2utils.settings.timeFormat, + keyboard : true, + autoCorrect : true, + start : null, + end : null, + btnNow : true, + noMinutes : false + } + this.options = w2utils.extend({ type: 'time' }, defaults, options) + options = this.options // since object is re-created, need to re-assign + if (query(this.el).attr('placeholder') == null) { + query(this.el).attr('placeholder', options.format) + } + break + case 'datetime': + defaults = { + format : w2utils.settings.dateFormat + '|' + w2utils.settings.timeFormat, + keyboard : true, + autoCorrect : true, + start : null, + end : null, + startTime : null, + endTime : null, + blockDates : [], // array of blocked dates + blockWeekdays : [], // blocked weekdays 0 - sunday, 1 - monday, etc + colored : {}, // ex: { '3/13/2022': 'bg-color|text-color' } + btnNow : true, + noMinutes : false + } + this.options = w2utils.extend({ type: 'datetime' }, defaults, options) + options = this.options // since object is re-created, need to re-assign + if (query(this.el).attr('placeholder') == null) { + query(this.el).attr('placeholder', options.placeholder || options.format) + } + break + case 'list': + case 'combo': + defaults = { + items : [], + selected : {}, + url : null, // url to pull data from // TODO: implement + recId : null, // map retrieved data from url to id, can be string or function + recText : null, // map retrieved data from url to text, can be string or function + method : null, // default httpMethod + interval : 350, // number of ms to wait before sending server call on search + postData : {}, + minLength : 1, // min number of chars when trigger search + cacheMax : 250, + maxDropHeight : 350, // max height for drop down menu + maxDropWidth : null, // if null then auto set + minDropWidth : null, // if null then auto set + match : 'begins', // ['contains', 'is', 'begins', 'ends'] + icon : null, + iconStyle : '', + align : 'both', // same width as control + altRows : true, // alternate row color + renderDrop : null, // render function for drop down item + compare : null, // compare function for filtering + filter : true, // weather to filter at all + hideSelected : false, // hide selected item from drop down + prefix : '', + suffix : '', + msgNoItems : 'No matches', + msgSearch : 'Type to search...', + openOnFocus : false, // if to show overlay onclick or when typing + markSearch : false, + onSearch : null, // when search needs to be performed + onRequest : null, // when request is submitted + onLoad : null, // when data is received + onError : null // when data fails to load due to server error or other failure modes + } + if (typeof options.items == 'function') { + options._items_fun = options.items + } + // need to be first + options.items = w2utils.normMenu.call(this, options.items) + if (this.type === 'list') { + // defaults.search = (options.items && options.items.length >= 10 ? true : false); + query(this.el).addClass('w2ui-select') + // if simple value - look it up + if (!w2utils.isPlainObject(options.selected) && Array.isArray(options.items)) { + options.items.forEach(item => { + if (item && item.id === options.selected) { + options.selected = w2utils.clone(item) + } + }) + } + } + options = w2utils.extend({}, defaults, options) + this.options = options + if (!w2utils.isPlainObject(options.selected)) options.selected = {} + this.selected = options.selected + query(this.el) + .attr('autocapitalize', 'off') + .attr('autocomplete', 'off') + .attr('autocorrect', 'off') + .attr('spellcheck', 'false') + if (options.selected.text != null) { + query(this.el).val(options.selected.text) + } + break + case 'enum': + defaults = { + items : [], // id, text, tooltip, icon + selected : [], + max : 0, // max number of selected items, 0 - unlimited + url : null, // not implemented + recId : null, // map retrieved data from url to id, can be string or function + recText : null, // map retrieved data from url to text, can be string or function + interval : 350, // number of ms to wait before sending server call on search + method : null, // default httpMethod + postData : {}, + minLength : 1, // min number of chars when trigger search + cacheMax : 250, + maxItemWidth : 250, // max width for a single item + maxDropHeight : 350, // max height for drop down menu + maxDropWidth : null, // if null then auto set + match : 'contains', // ['contains', 'is', 'begins', 'ends'] + align : '', // align drop down related to search field + altRows : true, // alternate row color + openOnFocus : false, // if to show overlay onclick or when typing + markSearch : false, + renderDrop : null, // render function for drop down item + renderItem : null, // render selected item + compare : null, // compare function for filtering + filter : true, // alias for compare + hideSelected : true, // hide selected item from drop down + style : '', // style for container div + msgNoItems : 'No matches', + msgSearch : 'Type to search...', + onSearch : null, // when search needs to be performed + onRequest : null, // when request is submitted + onLoad : null, // when data is received + onError : null, // when data fails to load due to server error or other failure modes + onClick : null, // when an item is clicked + onAdd : null, // when an item is added + onNew : null, // when new item should be added + onRemove : null, // when an item is removed + onMouseEnter : null, // when an item is mouse over + onMouseLeave : null, // when an item is mouse out + onScroll : null // when div with selected items is scrolled + } + options = w2utils.extend({}, defaults, options, { suffix: '' }) + if (typeof options.items == 'function') { + options._items_fun = options.items + } + options.items = w2utils.normMenu.call(this, options.items) + options.selected = w2utils.normMenu.call(this, options.selected) + this.options = options + if (!Array.isArray(options.selected)) options.selected = [] + this.selected = options.selected + break + case 'file': + defaults = { + selected : [], + max : 0, + maxSize : 0, // max size of all files, 0 - unlimited + maxFileSize : 0, // max size of a single file, 0 -unlimited + maxItemWidth : 250, // max width for a single item + maxDropHeight : 350, // max height for drop down menu + maxDropWidth : null, // if null then auto set + readContent : true, // if true, it will readAsDataURL content of the file + silent : true, + align : 'both', // same width as control + altRows : true, // alternate row color + renderItem : null, // render selected item + style : '', // style for container div + onClick : null, // when an item is clicked + onAdd : null, // when an item is added + onRemove : null, // when an item is removed + onMouseEnter : null, // when an item is mouse over + onMouseLeave : null // when an item is mouse out + } + options = w2utils.extend({}, defaults, options) + this.options = options + if (!Array.isArray(options.selected)) options.selected = [] + this.selected = options.selected + if (query(this.el).attr('placeholder') == null) { + query(this.el).attr('placeholder', w2utils.lang('Attach files by dragging and dropping or Click to Select')) + } + break + } + // attach events + query(this.el) + .css('box-sizing', 'border-box') + .addClass('w2field w2ui-input') + .off('.w2field') + .on('change.w2field', (event) => { this.change(event) }) + .on('click.w2field', (event) => { this.click(event) }) + .on('focus.w2field', (event) => { this.focus(event) }) + .on('blur.w2field', (event) => { if (this.type !== 'list') this.blur(event) }) + .on('keydown.w2field', (event) => { this.keyDown(event) }) + .on('keyup.w2field', (event) => { this.keyUp(event) }) + // suffix and prefix need to be after styles + this.addPrefix() // only will add if needed + this.addSuffix() // only will add if needed + this.addSearch() + this.addMultiSearch() + // this.refresh() // do not call refresh, on change will trigger refresh (for list at list) + // format initial value + this.change(new Event('change')) + } + get() { + let ret + if (['list', 'enum', 'file'].indexOf(this.type) !== -1) { + ret = this.selected + } else { + ret = query(this.el).val() + } + return ret + } + set(val, append) { + if (['list', 'enum', 'file'].indexOf(this.type) !== -1) { + if (this.type !== 'list' && append) { + if (!Array.isArray(this.selected)) this.selected = [] + this.selected.push(val) + // update selected array in overlay + let overlay = w2menu.get(this.el.id + '_menu') + if (overlay) overlay.options.selected = this.selected + query(this.el).trigger('input').trigger('change') + } else { + if (val == null) val = [] + let it = (this.type === 'enum' && !Array.isArray(val) ? [val] : val) + this.selected = it + query(this.el).trigger('input').trigger('change') + } + this.refresh() + } else { + query(this.el).val(val) + } + } + setIndex(ind, append) { + if (['list', 'enum'].indexOf(this.type) !== -1) { + let items = this.options.items + if (items && items[ind]) { + if (this.type == 'list') { + this.selected = items[ind] + } + if (this.type == 'enum') { + if (!append) this.selected = [] + this.selected.push(items[ind]) + } + let overlay = w2menu.get(this.el.id + '_menu') + if (overlay) overlay.options.selected = this.selected + query(this.el).trigger('input').trigger('change') + this.refresh() + return true + } + } + return false + } + refresh() { + let options = this.options + let time = Date.now() + let styles = getComputedStyle(this.el) + // enum + if (this.type == 'list') { + query(this.el).parent().css('white-space', 'nowrap') // needs this for arrow always to appear on the right side + // hide focus and show text + if (this.helpers.prefix) this.helpers.prefix.hide() + if (!this.helpers.search) return + // if empty show no icon + if (this.selected == null && options.icon) { + options.prefix = ` + + ` + this.addPrefix() + } else { + options.prefix = '' + this.addPrefix() + } + // focus helper + let focus = query(this.helpers.search_focus) + let icon = query(focus[0].previousElementSibling) + focus.css({ outline: 'none' }) + if (focus.val() === '') { + focus.css('opacity', 0) + icon.css('opacity', 0) + if (this.selected?.id) { + let text = this.selected.text + let ind = this.findItemIndex(options.items, this.selected.id) + if (text != null) { + query(this.el) + .val(w2utils.lang(text)) + .data({ + selected: text, + selectedIndex: ind[0] + }) + } + } else { + this.el.value = '' + query(this.el).removeData('selected selectedIndex') + } + } else { + focus.css('opacity', 1) + icon.css('opacity', 1) + query(this.el).val('') + setTimeout(() => { + if (this.helpers.prefix) this.helpers.prefix.hide() + if (options.icon) { + focus.css('margin-left', '17px') + query(this.helpers.search).find('.w2ui-icon-search') + .addClass('show-search') + } else { + focus.css('margin-left', '0px') + query(this.helpers.search).find('.w2ui-icon-search') + .removeClass('show-search') + } + }, 1) + } + // if readonly or disabled + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) { + setTimeout(() => { + if (this.helpers.prefix) query(this.helpers.prefix).css('opacity', '0.6') + if (this.helpers.suffix) query(this.helpers.suffix).css('opacity', '0.6') + }, 1) + } else { + setTimeout(() => { + if (this.helpers.prefix) query(this.helpers.prefix).css('opacity', '1') + if (this.helpers.suffix) query(this.helpers.suffix).css('opacity', '1') + }, 1) + } + } + let div = this.helpers.multi + if (['enum', 'file'].includes(this.type) && div) { + let html = '' + if (Array.isArray(this.selected)) { + this.selected.forEach((it, ind) => { + if (it == null) return + html += ` +
    + ${ + typeof options.renderItem === 'function' + ? options.renderItem(it, ind, `
      
    `) + : ` + ${it.icon ? `` : ''} +
      
    + ${(this.type === 'enum' ? it.text : it.name) ?? it.id ?? it } + ${it.size ? ` - ${w2utils.formatSize(it.size)}` : ''} + ` + } +
    ` + }) + } + let ul = div.find('.w2ui-multi-items') + if (options.style) { + div.attr('style', div.attr('style') + ';' + options.style) + } + query(this.el).css('z-index', '-1') + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) { + setTimeout(() => { + div[0].scrollTop = 0 // scroll to the top + div.addClass('w2ui-readonly') + .find('.li-item').css('opacity', '0.9') + .parent().find('.li-search').hide() + .find('input').prop('readOnly', true) + .closest('.w2ui-multi-items') + .find('.w2ui-list-remove').hide() + }, 1) + } else { + setTimeout(() => { + div.removeClass('w2ui-readonly') + .find('.li-item').css('opacity', '1') + .parent().find('.li-search').show() + .find('input').prop('readOnly', false) + .closest('.w2ui-multi-items') + .find('.w2ui-list-remove').show() + }, 1) + } + // clean + if (this.selected?.length > 0) { + query(this.el).attr('placeholder', '') + } + div.find('.w2ui-enum-placeholder').remove() + ul.find('.li-item').remove() + // add new list + if (html !== '') { + ul.prepend(html) + } else if (query(this.el).attr('placeholder') != null && div.find('input').val() === '') { + let style = w2utils.stripSpaces(` + padding-top: ${styles['padding-top']}; + padding-left: ${styles['padding-left']}; + box-sizing: ${styles['box-sizing']}; + line-height: ${styles['line-height']}; + font-size: ${styles['font-size']}; + font-family: ${styles['font-family']}; + `) + div.prepend(`
    ${query(this.el).attr('placeholder')}
    `) + } + // ITEMS events + div.off('.w2item') + .on('scroll.w2item', (event) => { + let edata = this.trigger('scroll', { target: this.el, originalEvent: event }) + if (edata.isCancelled === true) return + // hide tooltip if any + w2tooltip.hide(this.el.id + '_preview') + // event after + edata.finish() + }) + .find('.li-item') + .on('click.w2item', (event) => { + let target = query(event.target).closest('.li-item') + let index = target.attr('index') + let item = this.selected[index] + if (query(target).hasClass('li-search')) return + event.stopPropagation() + let edata + // default behavior + if (query(event.target).hasClass('w2ui-list-remove')) { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + // trigger event + edata = this.trigger('remove', { target: this.el, originalEvent: event, item }) + if (edata.isCancelled === true) return + // default behavior + this.selected.splice(index, 1) + query(this.el).trigger('input').trigger('change') + query(event.target).remove() + } else { + // trigger event + edata = this.trigger('click', { target: this.el, originalEvent: event.originalEvent, item }) + if (edata.isCancelled === true) return + // if file - show image preview + let preview = item.tooltip + if (this.type === 'file') { + if ((/image/i).test(item.type)) { // image + preview = ` +
    + +
    ` + } + preview += ` +
    +
    ${w2utils.lang('Name')}:
    +
    ${item.name}
    +
    ${w2utils.lang('Size')}:
    +
    ${w2utils.formatSize(item.size)}
    +
    ${w2utils.lang('Type')}:
    +
    ${item.type}
    +
    ${w2utils.lang('Modified')}:
    +
    ${w2utils.date(item.modified)}
    +
    ` + } + if (preview) { + let name = this.el.id + '_preview' + w2tooltip.show({ + name, + anchor: target.get(0), + html: preview, + hideOn: ['doc-click'], + class: '' + }) + .show((event) => { + let $img = query(`#w2overlay-${name} img`) + $img.on('load', function (event) { + let w = this.clientWidth + let h = this.clientHeight + if (w < 300 & h < 300) return + if (w >= h && w > 300) query(this).css('width', '300px') + if (w < h && h > 300) query(this).css('height', '300px') + }) + .on('error', function (event) { + this.style.display = 'none' + }) + }) + } + edata.finish() + } + }) + .on('mouseenter.w2item', (event) => { + let target = query(event.target).closest('.li-item') + if (query(target).hasClass('li-search')) return + let item = this.selected[query(event.target).attr('index')] + // trigger event + let edata = this.trigger('mouseEnter', { target: this.el, originalEvent: event, item }) + if (edata.isCancelled === true) return + // event after + edata.finish() + }) + .on('mouseleave.w2item', (event) => { + let target = query(event.target).closest('.li-item') + if (query(target).hasClass('li-search')) return + let item = this.selected[query(event.target).attr('index')] + // trigger event + let edata = this.trigger('mouseLeave', { target: this.el, originalEvent: event, item }) + if (edata.isCancelled === true) return + // event after + edata.finish() + }) + // update size for enum, hide for file + if (this.type === 'enum') { + let search = this.helpers.multi.find('input') + search.css({ width: '15px' }) + } else { + this.helpers.multi.find('.li-search').hide() + } + this.resize() + } + return Date.now() - time + } + // resizing width of list, enum, file controls + resize() { + let width = this.el.clientWidth + // let height = this.el.clientHeight + // if (this.tmp.current_width == width && height > 0) return + let styles = getComputedStyle(this.el) + let focus = this.helpers.search + let multi = this.helpers.multi + let suffix = this.helpers.suffix + let prefix = this.helpers.prefix + // resize helpers + if (focus) { + query(focus).css('width', width) + } + if (multi) { + query(multi).css('width', width - parseInt(styles['margin-left'], 10) - parseInt(styles['margin-right'], 10)) + } + if (suffix) { + this.addSuffix() + } + if (prefix) { + this.addPrefix() + } + // enum or file + let div = this.helpers.multi + if (['enum', 'file'].includes(this.type) && div) { + // adjust height + query(this.el).css('height', 'auto') + let cntHeight = query(div).find(':scope div.w2ui-multi-items').get(0).clientHeight + 5 + if (cntHeight < 20) cntHeight = 20 + // max height + if (cntHeight > this.tmp['max-height']) { + cntHeight = this.tmp['max-height'] + } + // min height + if (cntHeight < this.tmp['min-height']) { + cntHeight = this.tmp['min-height'] + } + let inpHeight = w2utils.getSize(this.el, 'height') - 2 + if (inpHeight > cntHeight) cntHeight = inpHeight + query(div).css({ + 'height': cntHeight + 'px', + overflow: (cntHeight == this.tmp['max-height'] ? 'auto' : 'hidden') + }) + query(div).css('height', cntHeight + 'px') + query(this.el).css({ 'height': cntHeight + 'px' }) + } + // remember width + this.tmp.current_width = width + } + reset() { + // restore paddings + if (this.tmp != null) { + query(this.el).css('height', 'auto') + Array('padding-left', 'padding-right', 'background-color', 'border-color').forEach(prop => { + if (this.tmp && this.tmp['old-'+ prop] != null) { + query(this.el).css(prop, this.tmp['old-' + prop]) + delete this.tmp['old-' + prop] + } + }) + // remove resize watcher + clearInterval(this.tmp.sizeTimer) + } + // remove events and (data) + query(this.el) + .val(this.clean(query(this.el).val())) + .removeClass('w2field') + .removeData('selected selectedIndex') + .off('.w2field') // remove only events added by w2field + // remove helpers + Object.keys(this.helpers).forEach(key => { + query(this.helpers[key]).remove() + }) + this.helpers = {} + } + clean(val) { + // issue #499 + if (typeof val === 'number'){ + return val + } + let options = this.options + val = String(val).trim() + // clean + if (['int', 'float', 'money', 'currency', 'percent'].includes(this.type)) { + if (typeof val === 'string') { + if (options.autoFormat) { + if (['money', 'currency'].includes(this.type)) { + val = String(val).replace(options.moneyRE, '') + } + if (this.type === 'percent') { + val = String(val).replace(options.percentRE, '') + } + if (['int', 'float'].includes(this.type)) { + val = String(val).replace(options.numberRE, '') + } + } + val = val.replace(/\s+/g, '') + .replace(new RegExp(options.groupSymbol, 'g'), '') + .replace(options.decimalSymbol, '.') + } + if (val !== '' && w2utils.isFloat(val)) val = Number(val); else val = '' + } + return val + } + format(val) { + let options = this.options + // auto format numbers or money + if (options.autoFormat && val !== '') { + switch (this.type) { + case 'money': + case 'currency': + val = w2utils.formatNumber(val, options.currencyPrecision, true) + if (val !== '') val = options.currencyPrefix + val + options.currencySuffix + break + case 'percent': + val = w2utils.formatNumber(val, options.precision, true) + if (val !== '') val += '%' + break + case 'float': + val = w2utils.formatNumber(val, options.precision, true) + break + case 'int': + val = w2utils.formatNumber(val, 0, true) + break + } + // if default group symbol does not match - replase it + let group = parseInt(1000).toLocaleString(w2utils.settings.locale, { useGrouping: true }).slice(1, 2) + if (group !== this.options.groupSymbol) { + val = val.replaceAll(group, this.options.groupSymbol) + } + } + return val + } + change(event) { + // numeric + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(this.type) !== -1) { + // check max/min + let val = query(this.el).val() + let new_val = this.format(this.clean(query(this.el).val())) + // if was modified + if (val !== '' && val != new_val) { + query(this.el).val(new_val) + // cancel event + event.stopPropagation() + event.preventDefault() + return false + } + } + // color + if (this.type === 'color') { + let color = query(this.el).val() + if (color.substr(0, 3).toLowerCase() !== 'rgb') { + color = '#' + color + let len = query(this.el).val().length + if (len !== 8 && len !== 6 && len !== 3) color = '' + } + let next = query(this.el).get(0).nextElementSibling + query(next).find('div').css('background-color', color) + if (query(this.el).hasClass('has-focus')) { + this.updateOverlay() + } + } + // list, enum + if (['list', 'enum', 'file'].indexOf(this.type) !== -1) { + this.refresh() + } + // date, time + if (['date', 'time', 'datetime'].indexOf(this.type) !== -1) { + // convert linux timestamps + let tmp = parseInt(this.el.value) + if (w2utils.isInt(this.el.value) && tmp > 3000) { + if (this.type === 'time') tmp = w2utils.formatTime(new Date(tmp), this.options.format) + if (this.type === 'date') tmp = w2utils.formatDate(new Date(tmp), this.options.format) + if (this.type === 'datetime') tmp = w2utils.formatDateTime(new Date(tmp), this.options.format) + query(this.el).val(tmp).trigger('input').trigger('change') + } + } + } + click(event) { + // lists + if (['list', 'combo', 'enum'].includes(this.type)) { + if (!query(this.el).hasClass('has-focus')) { + this.focus(event) + } + if (this.type == 'combo') { + this.updateOverlay() + } + // since list has separate search input, in order to keep the overlay open, need to stop + if (this.type == 'list') { + this.updateOverlay() + event.stopPropagation() + } + } + // other fields with drops + if (['date', 'time', 'datetime', 'color'].includes(this.type)) { + this.updateOverlay() + } + } + focus(event) { + if (this.type == 'list' && document.activeElement == this.el) { + this.helpers.search_focus.focus() + return + } + // color, date, time + if (['color', 'date', 'time', 'datetime'].indexOf(this.type) !== -1) { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + this.updateOverlay() + } + // menu + if (['list', 'combo', 'enum'].indexOf(this.type) !== -1) { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) { + // still add focus + query(this.el).addClass('has-focus') + return + } + // regenerate items + if (typeof this.options._items_fun == 'function') { + this.options.items = w2utils.normMenu.call(this, this.options._items_fun) + } + if (this.helpers.search) { + let search = this.helpers.search_focus + search.value = '' + search.select() + } + if (this.type == 'enum') { + // file control in particular need to receive focus after file select + let search = query(this.el.previousElementSibling).find('.li-search input').get(0) + if (document.activeElement !== search) { + search.focus() + } + } + this.resize() + // update overlay if needed + if (event.showMenu !== false && (this.options.openOnFocus !== false || query(this.el).hasClass('has-focus'))) { + setTimeout(() => { this.updateOverlay() }, 100) // execute at the end of event loop + } + } + if (this.type == 'file') { + let prev = query(this.el).get(0).previousElementSibling + query(prev).addClass('has-focus') + } + query(this.el).addClass('has-focus') + } + blur(event) { + let val = query(this.el).val().trim() + query(this.el).removeClass('has-focus') + if (['int', 'float', 'money', 'currency', 'percent'].includes(this.type)) { + if (val !== '') { + let newVal = val + let error = '' + if (!this.isStrValid(val)) { // validity is also checked in blur + newVal = '' + } else { + let rVal = this.clean(val) + if (this.options.min != null && rVal < this.options.min) { + newVal = this.options.min + error = `Should be >= ${this.options.min}` + } + if (this.options.max != null && rVal > this.options.max) { + newVal = this.options.max + error = `Should be <= ${this.options.max}` + } + } + if (this.options.autoCorrect) { + query(this.el).val(newVal).trigger('input').trigger('change') + if (error) { + w2tooltip.show({ + name: this.el.id + '_error', + anchor: this.el, + html: error + }) + setTimeout(() => { w2tooltip.hide(this.el.id + '_error') }, 3000) + } + } + } + } + // date or time + if (['date', 'time', 'datetime'].includes(this.type) && this.options.autoCorrect) { + if (val !== '') { + let check = this.type == 'date' ? w2utils.isDate : + (this.type == 'time' ? w2utils.isTime : w2utils.isDateTime) + if (!w2date.inRange(this.el.value, this.options) + || !check.bind(w2utils)(this.el.value, this.options.format)) { + // if not in range or wrong value - clear it + query(this.el).val('').trigger('input').trigger('change') + } + } + } + // clear search input + if (this.type === 'enum') { + query(this.helpers.multi).find('input').val('').css('width', '15px') + } + if (this.type == 'file') { + let prev = this.el.previousElementSibling + query(prev).removeClass('has-focus') + } + if (this.type === 'list') { + this.el.value = this.selected?.text ?? '' + } + } + keyDown(event, extra) { + let options = this.options + let key = event.keyCode || (extra && extra.keyCode) + let cancel = false + let val, inc, daymil, dt, newValue, newDT + // ignore wrong pressed key + if (['int', 'float', 'money', 'currency', 'percent', 'hex', 'bin', 'color', 'alphanumeric'].includes(this.type)) { + if (!event.metaKey && !event.ctrlKey && !event.altKey) { + if (!this.isStrValid(event.key ?? '1', true) && // valid & is not arrows, dot, comma, etc keys + ![9, 8, 13, 27, 37, 38, 39, 40, 46].includes(event.keyCode)) { + event.preventDefault() + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true + return false + } + } + } + // numeric + if (['int', 'float', 'money', 'currency', 'percent'].includes(this.type)) { + if (!options.keyboard || query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + val = parseFloat(query(this.el).val().replace(options.moneyRE, '')) || 0 + inc = options.step + if (event.ctrlKey || event.metaKey) inc = options.step * 10 + switch (key) { + case 38: // up + if (event.shiftKey) break // no action if shift key is pressed + newValue = (val + inc <= options.max || options.max == null ? Number((val + inc).toFixed(12)) : options.max) + query(this.el).val(newValue).trigger('input').trigger('change') + cancel = true + break + case 40: // down + if (event.shiftKey) break // no action if shift key is pressed + newValue = (val - inc >= options.min || options.min == null ? Number((val - inc).toFixed(12)) : options.min) + query(this.el).val(newValue).trigger('input').trigger('change') + cancel = true + break + } + if (cancel) { + event.preventDefault() + this.moveCaret2end() + } + } + // date/datetime + if (['date', 'datetime'].includes(this.type)) { + if (!options.keyboard || query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + let is = (this.type == 'date' ? w2utils.isDate : w2utils.isDateTime).bind(w2utils) + let format = (this.type == 'date' ? w2utils.formatDate : w2utils.formatDateTime).bind(w2utils) + daymil = 24*60*60*1000 + inc = 1 + if (event.ctrlKey || event.metaKey) inc = 10 // by month + dt = is(query(this.el).val(), options.format, true) + if (!dt) { dt = new Date(); daymil = 0 } + switch (key) { + case 38: // up + if (event.shiftKey) break // no action if shift key is pressed + if (inc == 10) { + dt.setMonth(dt.getMonth() + 1) + } else { + dt.setTime(dt.getTime() + daymil) + } + newDT = format(dt.getTime(), options.format) + query(this.el).val(newDT).trigger('input').trigger('change') + cancel = true + break + case 40: // down + if (event.shiftKey) break // no action if shift key is pressed + if (inc == 10) { + dt.setMonth(dt.getMonth() - 1) + } else { + dt.setTime(dt.getTime() - daymil) + } + newDT = format(dt.getTime(), options.format) + query(this.el).val(newDT).trigger('input').trigger('change') + cancel = true + break + } + if (cancel) { + event.preventDefault() + this.moveCaret2end() + this.updateOverlay() + } + } + // time + if (this.type === 'time') { + if (!options.keyboard || query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + inc = (event.ctrlKey || event.metaKey ? 60 : 1) + val = query(this.el).val() + let time = w2date.str2min(val) || w2date.str2min((new Date()).getHours() + ':' + ((new Date()).getMinutes() - 1)) + switch (key) { + case 38: // up + if (event.shiftKey) break // no action if shift key is pressed + time += inc + cancel = true + break + case 40: // down + if (event.shiftKey) break // no action if shift key is pressed + time -= inc + cancel = true + break + } + if (cancel) { + event.preventDefault() + query(this.el).val(w2date.min2str(time)).trigger('input').trigger('change') + this.moveCaret2end() + } + } + // list/enum + if (['list', 'enum'].includes(this.type)) { + switch (key) { + case 8: // delete + case 46: // backspace + if (this.type == 'list') { + let search = query(this.helpers.search_focus) + if (search.val() == '') { + this.selected = null + w2menu.hide(this.el.id + '_menu') + query(this.el).val('').trigger('input').trigger('change') + } + } else { + let search = query(this.helpers.multi).find('input') + if (search.val() == '') { + w2menu.hide(this.el.id + '_menu') + this.selected.pop() + // update selected array in overlay + let overlay = w2menu.get(this.el.id + '_menu') + if (overlay) overlay.options.selected = this.selected + this.refresh() + } + } + break + case 9: // tab key + case 16: // shift key (when shift+tab) + break + case 27: // escape + w2menu.hide(this.el.id + '_menu') + this.refresh() + break + default: { + // let overlay = w2menu.get(this.el.id + '_menu') + // if (!overlay && !overlay?.displayed) { + // this.updateOverlay() + // } + } + } + } + } + keyUp(event) { + if (this.type == 'list') { + let search = query(this.helpers.search_focus) + if (search.val() !== '') { + query(this.el).attr('placeholder', '') + } else { + query(this.el).attr('placeholder', this.tmp.pholder) + } + if (event.keyCode == 13) { + setTimeout(() => { + search.val('') + w2menu.hide(this.el.id + '_menu') + this.refresh() + }, 1) + } else { + // tab, shift+tab, esc, delete, backspace + if ([8, 9, 16, 27, 46].includes(event.keyCode)) { + w2menu.hide(this.el.id + '_menu') + } else { + this.updateOverlay() + } + } + this.refresh() + } + if (this.type == 'combo') { + this.updateOverlay() + } + if (this.type == 'enum') { + let search = this.helpers.multi.find('input') + let styles = getComputedStyle(search.get(0)) + let width = w2utils.getStrWidth(search.val(), + `font-family: ${styles['font-family']}; font-size: ${styles['font-size']};`) + search.css({ width: (width + 15) + 'px' }) + this.resize() + } + } + findItemIndex(items, id, parents) { + let inds = [] + if (!parents) parents = [] + items.forEach((item, ind) => { + if (item.id === id) { + inds = parents.concat([ind]) + this.options.index = [ind] + } + if (inds.length == 0 && item.items && item.items.length > 0) { + parents.push(ind) + inds = this.findItemIndex(item.items, id, parents) + parents.pop() + } + }) + return inds + } + updateOverlay(indexOnly) { + let options = this.options + let params + // color + if (this.type === 'color') { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + w2color.show(w2utils.extend({ + name: this.el.id + '_color', + anchor: this.el, + transparent: options.transparent, + advanced: options.advanced, + color: this.el.value, + liveUpdate: true + }, this.options)) + .select(event => { + let color = event.detail.color + query(this.el).val(color).trigger('input').trigger('change') + }) + .liveUpdate(event => { + let color = event.detail.color + query(this.helpers.suffix).find(':scope > div').css('background-color', '#' + color) + }) + } + // list + if (['list', 'combo', 'enum'].includes(this.type)) { + let el = this.el + let input = this.el + if (this.type === 'enum') { + el = this.helpers.multi.get(0) + input = query(el).find('input').get(0) + } + if (this.type === 'list') { + let sel = this.selected + if (w2utils.isPlainObject(sel) && Object.keys(sel).length > 0) { + let ind = this.findItemIndex(options.items, sel.id) + if (ind.length > 0) { + options.index = ind + } + } + input = this.helpers.search_focus + } + if (query(this.el).hasClass('has-focus') && !this.el.readOnly && !this.el.disabled) { + let msgNoItems = w2utils.lang(options.msgNoItems) + if (options.url != null && String(query(input).val()).length < options.minLength && this.tmp.emptySet !== true) { + msgNoItems = w2utils.lang('${count} letters or more...', { count: options.minLength }) + } + if (options.url != null && query(input).val() === '' && this.tmp.emptySet !== true) { + msgNoItems = w2utils.lang(options.msgSearch) + } + // TODO: remote url + // if (options.url == null && options.items.length === 0) msgNoItems = w2utils.lang('Empty list') + // if (options.msgNoItems != null) { + // let eventData = { + // search: query(input).val(), + // options: w2utils.clone(options) + // } + // if (options.url) { + // eventData.remote = { + // url: options.url, + // empty: this.tmp.emptySet ? true : false, + // error: this.tmp.lastError, + // minLength: options.minLength + // } + // } + // msgNoItems = (typeof options.msgNoItems === 'function' + // ? options.msgNoItems(eventData) + // : options.msgNoItems) + // } + // if (this.tmp.lastError) { + // msgNoItems = this.tmp.lastError + // } + // if (msgNoItems) { + // msgNoItems = '
    ' + msgNoItems + '
    ' + // } + params = w2utils.extend({}, options, { + name: this.el.id + '_menu', + anchor: input, + selected: this.selected, + search: false, + render: options.renderDrop, + anchorClass: '', + offsetY: 5, + maxHeight: options.maxDropHeight, // TODO: check + maxWidth: options.maxDropWidth, // TODO: check + minWidth: options.minDropWidth, // TODO: check + msgNoItems: msgNoItems, + }) + this.tmp.overlay = w2menu.show(params) + .select(event => { + if (['list', 'combo'].includes(this.type)) { + this.selected = event.detail.item + query(input).val('') + query(this.el).val(this.selected.text).trigger('input').trigger('change') + this.focus({ showMenu: false }) + } else { + let selected = this.selected + let newItem = event.detail?.item + if (newItem) { + // trigger event + let edata = this.trigger('add', { target: this.el, item: newItem, originalEvent: event }) + if (edata.isCancelled === true) return + // default behavior + if (selected.length >= options.max && options.max > 0) selected.pop() + delete newItem.hidden + selected.push(newItem) + query(this.el).trigger('input').trigger('change') + query(this.helpers.multi).find('input').val('') + // updaet selected array in overlays + let overlay = w2menu.get(this.el.id + '_menu') + if (overlay) overlay.options.selected = this.selected + // event after + edata.finish() + } + } + }) + } + } + // date + if (['date', 'time', 'datetime'].includes(this.type)) { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + w2date.show(w2utils.extend({ + name: this.el.id + '_date', + anchor: this.el, + value: this.el.value, + }, this.options)) + .select(event => { + let date = event.detail.date + if (date != null) { + query(this.el).val(date).trigger('input').trigger('change') + } + }) + } + } + /* + * INTERNAL FUNCTIONS + */ + isStrValid(ch, loose) { + let isValid = true + switch (this.type) { + case 'int': + if (loose && ['-', this.options.groupSymbol].includes(ch)) { + isValid = true + } else { + isValid = w2utils.isInt(ch.replace(this.options.numberRE, '')) + } + break + case 'percent': + ch = ch.replace(/%/g, '') + case 'float': + if (loose && ['-', '', this.options.decimalSymbol, this.options.groupSymbol].includes(ch)) { + isValid = true + } else { + isValid = w2utils.isFloat(ch.replace(this.options.numberRE, '')) + } + break + case 'money': + case 'currency': + if (loose && ['-', this.options.decimalSymbol, this.options.groupSymbol, this.options.currencyPrefix, + this.options.currencySuffix].includes(ch)) { + isValid = true + } else { + isValid = w2utils.isFloat(ch.replace(this.options.moneyRE, '')) + } + break + case 'bin': + isValid = w2utils.isBin(ch) + break + case 'color': + case 'hex': + isValid = w2utils.isHex(ch) + break + case 'alphanumeric': + isValid = w2utils.isAlphaNumeric(ch) + break + } + return isValid + } + addPrefix() { + if (!this.options.prefix) { + return + } + let helper + let styles = getComputedStyle(this.el) + if (this.tmp['old-padding-left'] == null) { + this.tmp['old-padding-left'] = styles['padding-left'] + } + // remove if already displayed + if (this.helpers.prefix) query(this.helpers.prefix).remove() + query(this.el).before(`
    ${this.options.prefix}
    `) + helper = query(this.el).get(0).previousElementSibling + query(helper) + .css({ + 'color' : styles.color, + 'font-family' : styles['font-family'], + 'font-size' : styles['font-size'], + 'height' : this.el.clientHeight + 'px', + 'padding-top' : styles['padding-top'], + 'padding-bottom' : styles['padding-bottom'], + 'padding-left' : this.tmp['old-padding-left'], + 'padding-right' : 0, + 'margin-top' : (parseInt(styles['margin-top'], 10) + 2) + 'px', + 'margin-bottom' : (parseInt(styles['margin-bottom'], 10) + 1) + 'px', + 'margin-left' : styles['margin-left'], + 'margin-right' : 0, + 'z-index' : 1, + }) + // only if visible + query(this.el).css('padding-left', helper.clientWidth + 'px !important') + // remember helper + this.helpers.prefix = helper + } + addSuffix() { + if (!this.options.suffix && !this.options.arrow) { + return + } + let helper + let self = this + let styles = getComputedStyle(this.el) + if (this.tmp['old-padding-right'] == null) { + this.tmp['old-padding-right'] = styles['padding-right'] + } + let pr = parseInt(styles['padding-right'] || 0) + if (this.options.arrow) { + // remove if already displayed + if (this.helpers.arrow) query(this.helpers.arrow).remove() + // add fresh + query(this.el).after( + '
     '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    ') + helper = query(this.el).get(0).nextElementSibling + query(helper).css({ + 'color' : styles.color, + 'font-family' : styles['font-family'], + 'font-size' : styles['font-size'], + 'height' : this.el.clientHeight + 'px', + 'padding' : 0, + 'margin-top' : (parseInt(styles['margin-top'], 10) + 1) + 'px', + 'margin-bottom' : 0, + 'border-left' : '1px solid silver', + 'width' : '16px', + 'transform' : 'translateX(-100%)' + }) + .on('mousedown', function(event) { + if (query(event.target).hasClass('arrow-up')) { + self.keyDown(event, { keyCode: 38 }) + } + if (query(event.target).hasClass('arrow-down')) { + self.keyDown(event, { keyCode: 40 }) + } + }) + pr += helper.clientWidth // width of the control + query(this.el).css('padding-right', pr + 'px !important') + this.helpers.arrow = helper + } + if (this.options.suffix !== '') { + // remove if already displayed + if (this.helpers.suffix) query(this.helpers.suffix).remove() + // add fresh + query(this.el).after(`
    ${this.options.suffix}
    `) + helper = query(this.el).get(0).nextElementSibling + query(helper) + .css({ + 'color' : styles.color, + 'font-family' : styles['font-family'], + 'font-size' : styles['font-size'], + 'height' : this.el.clientHeight + 'px', + 'padding-top' : styles['padding-top'], + 'padding-bottom' : styles['padding-bottom'], + 'padding-left' : 0, + 'padding-right' : styles['padding-right'], + 'margin-top' : (parseInt(styles['margin-top'], 10) + 2) + 'px', + 'margin-bottom' : (parseInt(styles['margin-bottom'], 10) + 1) + 'px', + 'transform' : 'translateX(-100%)' + }) + query(this.el).css('padding-right', helper.clientWidth + 'px !important') + this.helpers.suffix = helper + } + } + // Only used for list + addSearch() { + if (this.type !== 'list') return + // clean up & init + if (this.helpers.search) query(this.helpers.search).remove() + // remember original tabindex + let tabIndex = parseInt(query(this.el).attr('tabIndex')) + if (!isNaN(tabIndex) && tabIndex !== -1) this.tmp['old-tabIndex'] = tabIndex + if (this.tmp['old-tabIndex']) tabIndex = this.tmp['old-tabIndex'] + if (tabIndex == null || isNaN(tabIndex)) tabIndex = 0 + // if there is id, add to search with "_search" + let searchId = '' + if (query(this.el).attr('id') != null) { + searchId = 'id="' + query(this.el).attr('id') + '_search"' + } + // build helper + let html = ` +
    + + +
    ` + query(this.el).attr('tabindex', -1).before(html) + let helper = query(this.el).get(0).previousElementSibling + this.helpers.search = helper + this.helpers.search_focus = query(helper).find('input').get(0) + let styles = getComputedStyle(this.el) + query(helper).css({ + width : this.el.clientWidth + 'px', + 'margin-top' : styles['margin-top'], + 'margin-left' : styles['margin-left'], + 'margin-bottom' : styles['margin-bottom'], + 'margin-right' : styles['margin-right'] + }) + .find('input') + .css({ + cursor : 'default', + width : '100%', + opacity : 1, + padding : styles.padding, + margin : styles.margin, + border : '1px solid transparent', + 'background-color' : 'transparent' + }) + // INPUT events + query(helper).find('input') + .off('.helper') + .on('focus.helper', event => { + query(event.target).val('') + this.tmp.pholder = query(this.el).attr('placeholder') ?? '' + this.focus(event) + event.stopPropagation() + }) + .on('blur.helper', event => { + query(event.target).val('') + if (this.tmp.pholder != null) query(this.el).attr('placeholder', this.tmp.pholder) + this.blur(event) + event.stopPropagation() + }) + .on('keydown.helper', event => { this.keyDown(event) }) + .on('keyup.helper', event => { this.keyUp(event) }) + // MAIN div + query(helper).on('click', event => { + query(event.target).find('input').focus() + }) + } + // Used in enum/file + addMultiSearch() { + if (!['enum', 'file'].includes(this.type)) { + return + } + // clean up & init + query(this.helpers.multi).remove() + // build helper + let html = '' + let styles = getComputedStyle(this.el) + let margin = w2utils.stripSpaces(` + margin-top: 0px; + margin-bottom: 0px; + margin-left: ${styles['margin-left']}; + margin-right: ${styles['margin-right']}; + width: ${(w2utils.getSize(this.el, 'width') - parseInt(styles['margin-left'], 10) + - parseInt(styles['margin-right'], 10))}px; + `) + if (this.tmp['min-height'] == null) { + let min = this.tmp['min-height'] = parseInt((styles['min-height'] != 'none' ? styles['min-height'] : 0) || 0) + let current = parseInt(styles.height) + this.tmp['min-height'] = Math.max(min, current) + } + if (this.tmp['max-height'] == null && styles['max-height'] != 'none') { + this.tmp['max-height'] = parseInt(styles['max-height']) + } + // if there is id, add to search with "_search" + let searchId = '' + if (query(this.el).attr('id') != null) { + searchId = `id="${query(this.el).attr('id')}_search"` + } + // remember original tabindex + let tabIndex = parseInt(query(this.el).attr('tabIndex')) + if (!isNaN(tabIndex) && tabIndex !== -1) this.tmp['old-tabIndex'] = tabIndex + if (this.tmp['old-tabIndex']) tabIndex = this.tmp['old-tabIndex'] + if (tabIndex == null || isNaN(tabIndex)) tabIndex = 0 + if (this.type === 'enum') { + html = ` +
    +
    + +
    +
    ` + } + if (this.type === 'file') { + html = ` +
    +
    + +
    +
    + +
    +
    ` + } + // old bg and border + this.tmp['old-background-color'] = styles['background-color'] + this.tmp['old-border-color'] = styles['border-color'] + query(this.el) + .before(html) + .css({ + 'border-color': 'transparent', + 'background-color': 'transparent' + }) + let div = query(this.el.previousElementSibling) + this.helpers.multi = div + query(this.el).attr('tabindex', -1) + // click anywhere on the field + div.on('click', event => { this.focus(event) }) + // search field + div.find('input:not(.file-input)') + .on('click', event => { this.click(event) }) + .on('focus', event => { this.focus(event) }) + .on('blur', event => { this.blur(event) }) + .on('keydown', event => { this.keyDown(event) }) + .on('keyup', event => { this.keyUp(event) }) + // file input + if (this.type === 'file') { + div.find('input.file-input') + .off('.drag') + .on('click.drag', (event) => { + event.stopPropagation() + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + this.focus(event) + }) + .on('dragenter.drag', (event) => { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + div.addClass('w2ui-file-dragover') + }) + .on('dragleave.drag', (event) => { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + div.removeClass('w2ui-file-dragover') + }) + .on('drop.drag', (event) => { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + div.removeClass('w2ui-file-dragover') + let files = Array.from(event.dataTransfer.files) + files.forEach(file => { this.addFile(file) }) + this.focus(event) + // cancel to stop browser behaviour + event.preventDefault() + event.stopPropagation() + }) + .on('dragover.drag', (event) => { + // cancel to stop browser behaviour + event.preventDefault() + event.stopPropagation() + }) + .on('change.drag', (event) => { + if (typeof event.target.files !== 'undefined') { + Array.from(event.target.files).forEach(file => { this.addFile(file) }) + } + this.focus(event) + }) + } + this.refresh() + } + addFile(file) { + let options = this.options + let selected = this.selected + let newItem = { + name : file.name, + type : file.type, + modified : file.lastModifiedDate, + size : file.size, + content : null, + file : file + } + let size = 0 + let cnt = 0 + let errors = [] + if (Array.isArray(selected)) { + selected.forEach(item => { + if (item.name == file.name && item.size == file.size) { + errors.push(w2utils.lang('The file "${name}" (${size}) is already added.', { + name: file.name, size: w2utils.formatSize(file.size) })) + } + size += item.size + cnt++ + }) + } + if (options.maxFileSize !== 0 && newItem.size > options.maxFileSize) { + errors.push(w2utils.lang('Maximum file size is ${size}', { size: w2utils.formatSize(options.maxFileSize) })) + } + if (options.maxSize !== 0 && size + newItem.size > options.maxSize) { + errors.push(w2utils.lang('Maximum total size is ${size}', { size: w2utils.formatSize(options.maxSize) })) + } + if (options.max !== 0 && cnt >= options.max) { + errors.push(w2utils.lang('Maximum number of files is ${count}', { count: options.max })) + } + // trigger event + let edata = this.trigger('add', { target: this.el, file: newItem, total: cnt, totalSize: size, errors }) + if (edata.isCancelled === true) return + // if errors and not silent + if (options.silent !== true && errors.length > 0) { + w2tooltip.show({ + anchor: this.el, + html: 'Errors: ' + errors.join('
    ') + }) + console.log('ERRORS (while adding files): ', errors) + return + } + // check params + selected.push(newItem) + // read file as base64 + if (typeof FileReader !== 'undefined' && options.readContent === true) { + let reader = new FileReader() + let self = this + // need a closure + reader.onload = (function onload() { + return function closure(event) { + let fl = event.target.result + let ind = fl.indexOf(',') + newItem.content = fl.substr(ind + 1) + self.refresh() + query(self.el).trigger('input').trigger('change') + // event after + edata.finish() + } + })() + reader.readAsDataURL(file) + } else { + this.refresh() + query(this.el).trigger('input').trigger('change') + edata.finish() + } + } + // move cursror to end + moveCaret2end() { + setTimeout(() => { + this.el.setSelectionRange(this.el.value.length, this.el.value.length) + }, 0) + } +} +export { + w2ui, w2utils, query, w2locale, w2event, w2base, + w2popup, w2alert, w2confirm, w2prompt, Dialog, + w2tooltip, w2menu, w2color, w2date, Tooltip, + w2toolbar, w2sidebar, w2tabs, w2layout, w2grid, w2form, w2field +} \ No newline at end of file diff --git a/lib/w2ui/w2ui.es6.min.js b/lib/w2ui/w2ui.es6.min.js new file mode 100644 index 0000000..015b66d --- /dev/null +++ b/lib/w2ui/w2ui.es6.min.js @@ -0,0 +1,486 @@ +/* w2ui 2.0.x (nightly) (1/16/2023, 8:50:38 AM) (c) http://w2ui.com, vitmalina@gmail.com */ +class w2event{constructor(e,t){Object.assign(this,{type:t.type??null,detail:t,owner:e,target:t.target??null,phase:t.phase??"before",object:t.object??null,execute:null,isStopped:!1,isCancelled:!1,onComplete:null,listeners:[]}),delete t.type,delete t.target,delete t.object,this.complete=new Promise((e,t)=>{this._resolve=e,this._reject=t}),this.complete.catch(()=>{})}finish(e){e&&w2utils.extend(this.detail,e),this.phase="after",this.owner.trigger.call(this.owner,this)}done(e){this.listeners.push(e)}preventDefault(){this._reject(),this.isCancelled=!0}stopPropagation(){this.isStopped=!0}}class w2base{constructor(e){if(this.activeEvents=[],this.listeners=[],void 0!==e){if(!w2utils.checkName(e))return;w2ui[e]=this}this.debug=!1}on(e,r){return(e="string"==typeof e?e.split(/[,\s]+/):[e]).forEach(e=>{var t,i,s,l="string"==typeof e?e:e.type+":"+e.execute+"."+e.scope;"string"==typeof e&&([i,t]=e.split("."),[i,s]=i.replace(":complete",":after").replace(":done",":after").split(":"),e={type:i,execute:s??"before",scope:t}),(e=w2utils.extend({type:null,execute:"before",onComplete:null},e)).type?r?(Array.isArray(this.listeners)||(this.listeners=[]),this.listeners.push({name:l,edata:e,handler:r}),this.debug&&console.log("w2base: add event",{name:l,edata:e,handler:r})):console.log("ERROR: You must specify event handler function when calling .on() method of "+this.name):console.log("ERROR: You must specify event type when calling .on() method of "+this.name)}),this}off(e,r){return(e="string"==typeof e?e.split(/[,\s]+/):[e]).forEach(i=>{var e,t,s,l="string"==typeof i?i:i.type+":"+i.execute+"."+i.scope;if("string"==typeof i&&([t,e]=i.split("."),[t,s]=t.replace(":complete",":after").replace(":done",":after").split(":"),i={type:t||"*",execute:s||"",scope:e||""}),(i=w2utils.extend({type:null,execute:null,onComplete:null},i)).type||i.scope){r=r||null;let t=0;this.listeners=this.listeners.filter(e=>"*"!==i.type&&i.type!==e.edata.type||""!==i.execute&&i.execute!==e.edata.execute||""!==i.scope&&i.scope!==e.edata.scope||null!=i.handler&&i.handler!==e.edata.handler||(t++,!1)),this.debug&&console.log(`w2base: remove event (${t})`,{name:l,edata:i,handler:r})}else console.log("ERROR: You must specify event type when calling .off() method of "+this.name)}),this}trigger(e,i){if(1==arguments.length?i=e:(i.type=e,i.target=i.target??this),w2utils.isPlainObject(i)&&"after"==i.phase){if(!(i=this.activeEvents.find(e=>e.type==i.type&&e.target==i.target)))return void console.log(`ERROR: Cannot find even handler for "${i.type}" on "${i.target}".`);console.log("NOTICE: This syntax \"edata.trigger({ phase: 'after' })\" is outdated. Use edata.finish() instead.")}else i instanceof w2event||(i=new w2event(this,i),this.activeEvents.push(i));let s,t,l;Array.isArray(this.listeners)||(this.listeners=[]),this.debug&&console.log(`w2base: trigger "${i.type}:${i.phase}"`,i);for(let e=this.listeners.length-1;0<=e;e--){let t=this.listeners[e];if(!(null==t||t.edata.type!==i.type&&"*"!==t.edata.type||t.edata.target!==i.target&&null!=t.edata.target||t.edata.execute!==i.phase&&"*"!==t.edata.execute&&"*"!==t.edata.phase)&&(Object.keys(t.edata).forEach(e=>{null==i[e]&&null!=t.edata[e]&&(i[e]=t.edata[e])}),s=[],l=new RegExp(/\((.*?)\)/).exec(String(t.handler).split("=>")[0]),2===(s=l?l[1].split(/\s*,\s*/):s).length?(t.handler.call(this,i.target,i),this.debug&&console.log(" - call (old)",t.handler)):(t.handler.call(this,i),this.debug&&console.log(" - call",t.handler)),!0===i.isStopped||!0===i.stop))return i}e="on"+i.type.substr(0,1).toUpperCase()+i.type.substr(1);if(!("before"===i.phase&&"function"==typeof this[e]&&(t=this[e],s=[],l=new RegExp(/\((.*?)\)/).exec(String(t).split("=>")[0]),2===(s=l?l[1].split(/\s*,\s*/):s).length?(t.call(this,i.target,i),this.debug&&console.log(" - call: on[Event] (old)",t)):(t.call(this,i),this.debug&&console.log(" - call: on[Event]",t)),!0===i.isStopped||!0===i.stop)||null!=i.object&&"before"===i.phase&&"function"==typeof i.object[e]&&(t=i.object[e],s=[],l=new RegExp(/\((.*?)\)/).exec(String(t).split("=>")[0]),2===(s=l?l[1].split(/\s*,\s*/):s).length?(t.call(this,i.target,i),this.debug&&console.log(" - call: edata.object (old)",t)):(t.call(this,i),this.debug&&console.log(" - call: edata.object",t)),!0===i.isStopped||!0===i.stop)||"after"!==i.phase)){"function"==typeof i.onComplete&&i.onComplete.call(this,i);for(let e=0;e{this[t]=e})}static _fragment(e){let i=document.createElement("template");return i.innerHTML=e,i.content.childNodes.forEach(e=>{var t=Query._scriptConvert(e);t!=e&&i.content.replaceChild(t,e)}),i.content}static _scriptConvert(e){let t=e=>{var t=e.ownerDocument.createElement("script"),i=(t.text=e.text,e.attributes);for(let e=0;e{e.parentNode.replaceChild(t(e),e)}),e}static _fixProp(e){var t={cellpadding:"cellPadding",cellspacing:"cellSpacing",class:"className",colspan:"colSpan",contenteditable:"contentEditable",for:"htmlFor",frameborder:"frameBorder",maxlength:"maxLength",readonly:"readOnly",rowspan:"rowSpan",tabindex:"tabIndex",usemap:"useMap"};return t[e]||e}_insert(l,i){let r=[],a=this.length;if(!(a<1)){let e=this;if("string"==typeof i)this.each(e=>{var t=Query._fragment(i);r.push(...t.childNodes),e[l](t)});else if(i instanceof Query){let s=1==a;i.each(i=>{this.each(e=>{var t=s?i:i.cloneNode(!0);r.push(t),e[l](t),Query._scriptConvert(t)})}),s||i.remove()}else{if(!(i instanceof Node))throw new Error(`Incorrect argument for "${l}(html)". It expects one string argument.`);this.each(e=>{var t=1===a?i:Query._fragment(i.outerHTML);r.push(...1===a?[i]:t.childNodes),e[l](t)}),1{e=Array.from(e.querySelectorAll(t));0{(e===t||"string"==typeof t&&e.matches&&e.matches(t)||"function"==typeof t&&t(e))&&i.push(e)}),new Query(i,this.context,this)}next(){let t=[];return this.each(e=>{e=e.nextElementSibling;e&&t.push(e)}),new Query(t,this.context,this)}prev(){let t=[];return this.each(e=>{e=e.previousElementSibling;e&&t.push(e)}),new Query(t,this.context,this)}shadow(e){let t=[];this.each(e=>{e.shadowRoot&&t.push(e.shadowRoot)});var i=new Query(t,this.context,this);return e?i.find(e):i}closest(t){let i=[];return this.each(e=>{e=e.closest(t);e&&i.push(e)}),new Query(i,this.context,this)}host(t){let i=[],s=e=>e.parentNode?s(e.parentNode):e,l=e=>{e=s(e);i.push(e.host||e),e.host&&t&&l(e.host)};return this.each(e=>{l(e)}),new Query(i,this.context,this)}parent(e){return this.parents(e,!0)}parents(e,t){let i=[],s=e=>{if(-1==i.indexOf(e)&&i.push(e),!t&&e.parentNode)return s(e.parentNode)};this.each(e=>{e.parentNode&&s(e.parentNode)});var l=new Query(i,this.context,this);return e?l.filter(e):l}add(e){e=e instanceof Query?e.nodes:Array.isArray(e)?e:[e];return new Query(this.nodes.concat(e),this.context,this)}each(i){return this.nodes.forEach((e,t)=>{i(e,t,this)}),this}append(e){return this._insert("append",e)}prepend(e){return this._insert("prepend",e)}after(e){return this._insert("after",e)}before(e){return this._insert("before",e)}replace(e){return this._insert("replaceWith",e)}remove(){return this.each(e=>{e.remove()}),this}css(e,t){let s=e;var i,l=arguments.length;return 0===l||1===l&&"string"==typeof e?this[0]?(l=this[0].style,"string"==typeof e?(i=l.getPropertyPriority(e),l.getPropertyValue(e)+(i?"!"+i:"")):Object.fromEntries(this[0].style.cssText.split(";").filter(e=>!!e).map(e=>e.split(":").map(e=>e.trim())))):void 0:("object"!=typeof e&&((s={})[e]=t),this.each((i,e)=>{Object.keys(s).forEach(e=>{var t=String(s[e]).toLowerCase().includes("!important")?"important":"";i.style.setProperty(e,String(s[e]).replace(/\!important/i,""),t)})}),this)}addClass(e){return this.toggleClass(e,!0),this}removeClass(e){return this.toggleClass(e,!1),this}toggleClass(t,s){return"string"==typeof t&&(t=t.split(/[,\s]+/)),this.each(i=>{let e=t;(e=null==e&&!1===s?Array.from(i.classList):e).forEach(t=>{if(""!==t){let e=null!=s?s?"add":"remove":"toggle";i.classList[e](t)}})}),this}hasClass(e){if(null==(e="string"==typeof e?e.split(/[,\s]+/):e)&&0{i=i||e.every(e=>Array.from(t.classList??[]).includes(e))}),i}on(e,s,l){"function"==typeof s&&(l=s,s=void 0);let r;return s?.delegate&&(r=s.delegate,delete s.delegate),(e=e.split(/[,\s]+/)).forEach(e=>{let[t,i]=String(e).toLowerCase().split(".");if(r){let i=l;l=e=>{var t=query(e.target).parents(r);0{this._save(e,"events",[{event:t,scope:i,callback:l,options:s}]),e.addEventListener(t,l,s)})}),this}off(e,t,r){return"function"==typeof t&&(r=t,t=void 0),(e=(e??"").split(/[,\s]+/)).forEach(e=>{let[s,l]=String(e).toLowerCase().split(".");this.each(t=>{if(Array.isArray(t._mQuery?.events))for(let e=t._mQuery.events.length-1;0<=e;e--){var i=t._mQuery.events[e];null==l||""===l?i.event!=s&&""!==s||i.callback!=r&&null!=r||(t.removeEventListener(i.event,i.callback,i.options),t._mQuery.events.splice(e,1)):i.event!=s&&""!==s||i.scope!=l||(t.removeEventListener(i.event,i.callback,i.options),t._mQuery.events.splice(e,1))}})}),this}trigger(e,t){let i;return i=e instanceof Event||e instanceof CustomEvent?e:new(["click","dblclick","mousedown","mouseup","mousemove"].includes(e)?MouseEvent:["keydown","keyup","keypress"].includes(e)?KeyboardEvent:Event)(e,t),this.each(e=>{e.dispatchEvent(i)}),this}attr(t,i){if(void 0===i&&"string"==typeof t)return this[0]?this[0].getAttribute(t):void 0;{let e={};return"object"==typeof t?e=t:e[t]=i,this.each(i=>{Object.entries(e).forEach(([e,t])=>{i.setAttribute(e,t)})}),this}}removeAttr(){return this.each(t=>{Array.from(arguments).forEach(e=>{t.removeAttribute(e)})}),this}prop(t,i){if(void 0===i&&"string"==typeof t)return this[0]?this[0][t]:void 0;{let e={};return"object"==typeof t?e=t:e[t]=i,this.each(i=>{Object.entries(e).forEach(([e,t])=>{e=Query._fixProp(e);i[e]=t,"innerHTML"==e&&Query._scriptConvert(i)})}),this}}removeProp(){return this.each(t=>{Array.from(arguments).forEach(e=>{delete t[Query._fixProp(e)]})}),this}data(i,t){if(i instanceof Object)Object.entries(i).forEach(e=>{this.data(e[0],e[1])});else{if(i&&-1!=i.indexOf("-")&&console.error(`Key "${i}" contains "-" (dash). Dashes are not allowed in property names. Use camelCase instead.`),!(arguments.length<2))return this.each(e=>{null!=t?e.dataset[i]=t instanceof Object?JSON.stringify(t):t:delete e.dataset[i]}),this;if(this[0]){let t=Object.assign({},this[0].dataset);return Object.keys(t).forEach(e=>{if(t[e].startsWith("[")||t[e].startsWith("{"))try{t[e]=JSON.parse(t[e])}catch(e){}}),i?t[i]:t}}}removeData(e){return"string"==typeof e&&(e=e.split(/[,\s]+/)),this.each(t=>{e.forEach(e=>{delete t.dataset[e]})}),this}show(){return this.toggle(!0)}hide(){return this.toggle(!1)}toggle(l){return this.each(e=>{var t=e.style.display,i=getComputedStyle(e).display,s="none"==t||"none"==i;!s||null!=l&&!0!==l||(e.style.display=e._mQuery?.prevDisplay??(t==i&&"none"!=i?"":"block"),this._save(e,"prevDisplay",null)),s||null!=l&&!1!==l||("none"!=i&&this._save(e,"prevDisplay",i),e.style.setProperty("display","none"))})}empty(){return this.html("")}html(e){return this.prop("innerHTML",e)}text(e){return this.prop("textContent",e)}val(e){return this.prop("value",e)}change(){return this.trigger("change")}click(){return this.trigger("click")}}let query=function(e,t){if("function"!=typeof e)return new Query(e,t);"complete"==document.readyState?e():window.addEventListener("load",e)},w2ui=(query.html=e=>{e=Query._fragment(e);return query(e.children,e)},query.version=Query.version,{});class Utils{constructor(){this.version="2.0.x",this.tmp={},this.settings=this.extend({},{dataType:"HTTPJSON",dateStartYear:1950,dateEndYear:2030,macButtonOrder:!1,warnNoPhrase:!1},w2locale,{phrases:null}),this.i18nCompare=Intl.Collator().compare,this.hasLocalStorage=function(){var e="w2ui_test";try{return localStorage.setItem(e,e),localStorage.removeItem(e),!0}catch(e){return!1}}(),this.isMac=/Mac/i.test(navigator.platform),this.isMobile=/(iphone|ipod|ipad|mobile|android)/i.test(navigator.userAgent),this.isIOS=/(iphone|ipod|ipad)/i.test(navigator.platform),this.isAndroid=/(android)/i.test(navigator.userAgent),this.isSafari=/^((?!chrome|android).)*safari/i.test(navigator.userAgent),this.formatters={number(e,t){return 20'+w2utils.formatDate(i,t)+""},datetime(e,t){if(""===t&&(t=w2utils.settings.datetimeFormat),null==e||0===e||""===e)return"";let i=w2utils.isDateTime(e,t,!0);return''+w2utils.formatDateTime(i,t)+""},time(e,t){if(""===t&&(t=w2utils.settings.timeFormat),null==e||0===e||""===e)return"";let i=w2utils.isDateTime(e,t="h24"===(t="h12"===t?"hh:mi pm":t)?"h24:mi":t,!0);return''+w2utils.formatTime(e,t)+""},timestamp(e,t){if(""===t&&(t=w2utils.settings.datetimeFormat),null==e||0===e||""===e)return"";let i=w2utils.isDateTime(e,t,!0);return(i=!1===i?w2utils.isDate(e,t,!0):i).toString?i.toString():""},gmt(e,t){if(""===t&&(t=w2utils.settings.datetimeFormat),null==e||0===e||""===e)return"";let i=w2utils.isDateTime(e,t,!0);return(i=!1===i?w2utils.isDate(e,t,!0):i).toUTCString?i.toUTCString():""},age(e,t){if(null==e||0===e||""===e)return"";let i=w2utils.isDateTime(e,null,!0);return''+w2utils.age(e)+(t?" "+t:"")+""},interval(e,t){return null==e||0===e||""===e?"":w2utils.interval(e)+(t?" "+t:"")},toggle(e,t){return e?"Yes":""},password(t,e){let i="";for(let e=0;ei||!this.isInt(e[0])||2'+(r=l==e?this.lang("Yesterday"):r)+""}formatSize(e){var t;return this.isFloat(e)&&""!==e?0===(e=parseFloat(e))?0:(t=parseInt(Math.floor(Math.log(e)/Math.log(1024))),(Math.floor(e/Math.pow(1024,t)*10)/10).toFixed(0===t?0:1)+" "+(["Bt","KB","MB","GB","TB","PB","EB","ZB"][t]||"??")):""}formatNumber(e,t,i){return null==e||""===e||"object"==typeof e?"":(i={minimumFractionDigits:parseInt(t),maximumFractionDigits:parseInt(t),useGrouping:!!i},(null==t||t<0)&&(i.minimumFractionDigits=0,i.maximumFractionDigits=20),parseFloat(e).toLocaleString(this.settings.locale,i))}formatDate(e,t){if(t=t||this.settings.dateFormat,""===e||null==e||"object"==typeof e&&!e.getMonth)return"";let i=new Date(e);var s,l;return this.isInt(e)&&(i=new Date(Number(e))),"Invalid Date"===String(i)?"":(e=i.getFullYear(),s=i.getMonth(),l=i.getDate(),t.toLowerCase().replace("month",this.settings.fullmonths[s]).replace("mon",this.settings.shortmonths[s]).replace(/yyyy/g,("000"+e).slice(-4)).replace(/yyy/g,("000"+e).slice(-4)).replace(/yy/g,("0"+e).slice(-2)).replace(/(^|[^a-z$])y/g,"$1"+e).replace(/mm/g,("0"+(s+1)).slice(-2)).replace(/dd/g,("0"+l).slice(-2)).replace(/th/g,1==l?"st":"th").replace(/th/g,2==l?"nd":"th").replace(/th/g,3==l?"rd":"th").replace(/(^|[^a-z$])m/g,"$1"+(s+1)).replace(/(^|[^a-z$])d/g,"$1"+l))}formatTime(e,t){if(t=t||this.settings.timeFormat,""===e||null==e||"object"==typeof e&&!e.getMonth)return"";let i=new Date(e);if(this.isInt(e)&&(i=new Date(Number(e))),this.isTime(e)&&(e=this.isTime(e,!0),(i=new Date).setHours(e.hours),i.setMinutes(e.minutes)),"Invalid Date"===String(i))return"";let s="am",l=i.getHours();e=i.getHours();let r=i.getMinutes(),a=i.getSeconds();return r<10&&(r="0"+r),a<10&&(a="0"+a),-1===t.indexOf("am")&&-1===t.indexOf("pm")||(12<=l&&(s="pm"),12{i[t]=this.stripSpaces(e)}):(i=this.extend({},i),Object.keys(i).forEach(e=>{i[e]=this.stripSpaces(i[e])}))}return i}stripTags(i){if(null!=i)switch(typeof i){case"number":break;case"string":i=String(i).replace(/<(?:[^>=]|='[^']*'|="[^"]*"|=[^'"][^\s>]*)*>/gi,"");break;case"object":Array.isArray(i)?(i=this.extend([],i)).forEach((e,t)=>{i[t]=this.stripTags(e)}):(i=this.extend({},i),Object.keys(i).forEach(e=>{i[e]=this.stripTags(i[e])}))}return i}encodeTags(i){if(null!=i)switch(typeof i){case"number":break;case"string":i=String(i).replace(/&/g,"&").replace(/>/g,">").replace(/{i[t]=this.encodeTags(e)}):(i=this.extend({},i),Object.keys(i).forEach(e=>{i[e]=this.encodeTags(i[e])}))}return i}decodeTags(i){if(null!=i)switch(typeof i){case"number":break;case"string":i=String(i).replace(/>/g,">").replace(/</g,"<").replace(/"/g,'"').replace(/&/g,"&");break;case"object":Array.isArray(i)?(i=this.extend([],i)).forEach((e,t)=>{i[t]=this.decodeTags(e)}):(i=this.extend({},i),Object.keys(i).forEach(e=>{i[e]=this.decodeTags(i[e])}))}return i}escapeId(e){return""===e||null==e?"":(e+"").replace(/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,(e,t)=>t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e)}unescapeId(e){return""===e||null==e?"":e.replace(/\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\([^\r\n\f])/g,(e,t)=>{e="0x"+e.slice(1)-65536;return t||(e<0?String.fromCharCode(65536+e):String.fromCharCode(e>>10|55296,1023&e|56320))})}base64encode(e){return btoa(e)}base64decode(e){return atob(e)}async sha256(e){e=(new TextEncoder).encode(e);return crypto.subtle.digest("SHA-256",e).then(e=>{return Array.from(new Uint8Array(e)).map(e=>e.toString(16).padStart(2,"0")).join("")})}transition(r,a,n,o){return new Promise((e,t)=>{var i=getComputedStyle(r);let s=parseInt(i.width),l=parseInt(i.height);if(r&&a){switch(r.parentNode.style.cssText+="perspective: 900px; overflow: hidden;",r.style.cssText+="; position: absolute; z-index: 1019; backface-visibility: hidden",a.style.cssText+="; position: absolute; z-index: 1020; backface-visibility: hidden",n){case"slide-left":r.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0)",a.style.cssText+="overflow: hidden; transform: translate3d("+s+"px, 0, 0)",query(a).show(),setTimeout(()=>{a.style.cssText+="transition: 0.5s; transform: translate3d(0, 0, 0)",r.style.cssText+="transition: 0.5s; transform: translate3d(-"+s+"px, 0, 0)"},1);break;case"slide-right":r.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0)",a.style.cssText+="overflow: hidden; transform: translate3d(-"+s+"px, 0, 0)",query(a).show(),setTimeout(()=>{a.style.cssText+="transition: 0.5s; transform: translate3d(0px, 0, 0)",r.style.cssText+="transition: 0.5s; transform: translate3d("+s+"px, 0, 0)"},1);break;case"slide-down":r.style.cssText+="overflow: hidden; z-index: 1; transform: translate3d(0, 0, 0)",a.style.cssText+="overflow: hidden; z-index: 0; transform: translate3d(0, 0, 0)",query(a).show(),setTimeout(()=>{a.style.cssText+="transition: 0.5s; transform: translate3d(0, 0, 0)",r.style.cssText+="transition: 0.5s; transform: translate3d(0, "+l+"px, 0)"},1);break;case"slide-up":r.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0)",a.style.cssText+="overflow: hidden; transform: translate3d(0, "+l+"px, 0)",query(a).show(),setTimeout(()=>{a.style.cssText+="transition: 0.5s; transform: translate3d(0, 0, 0)",r.style.cssText+="transition: 0.5s; transform: translate3d(0, 0, 0)"},1);break;case"flip-left":r.style.cssText+="overflow: hidden; transform: rotateY(0deg)",a.style.cssText+="overflow: hidden; transform: rotateY(-180deg)",query(a).show(),setTimeout(()=>{a.style.cssText+="transition: 0.5s; transform: rotateY(0deg)",r.style.cssText+="transition: 0.5s; transform: rotateY(180deg)"},1);break;case"flip-right":r.style.cssText+="overflow: hidden; transform: rotateY(0deg)",a.style.cssText+="overflow: hidden; transform: rotateY(180deg)",query(a).show(),setTimeout(()=>{a.style.cssText+="transition: 0.5s; transform: rotateY(0deg)",r.style.cssText+="transition: 0.5s; transform: rotateY(-180deg)"},1);break;case"flip-down":r.style.cssText+="overflow: hidden; transform: rotateX(0deg)",a.style.cssText+="overflow: hidden; transform: rotateX(180deg)",query(a).show(),setTimeout(()=>{a.style.cssText+="transition: 0.5s; transform: rotateX(0deg)",r.style.cssText+="transition: 0.5s; transform: rotateX(-180deg)"},1);break;case"flip-up":r.style.cssText+="overflow: hidden; transform: rotateX(0deg)",a.style.cssText+="overflow: hidden; transform: rotateX(-180deg)",query(a).show(),setTimeout(()=>{a.style.cssText+="transition: 0.5s; transform: rotateX(0deg)",r.style.cssText+="transition: 0.5s; transform: rotateX(180deg)"},1);break;case"pop-in":r.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0)",a.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0); transform: scale(.8); opacity: 0;",query(a).show(),setTimeout(()=>{a.style.cssText+="transition: 0.5s; transform: scale(1); opacity: 1;",r.style.cssText+="transition: 0.5s;"},1);break;case"pop-out":r.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0); transform: scale(1); opacity: 1;",a.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0); opacity: 0;",query(a).show(),setTimeout(()=>{a.style.cssText+="transition: 0.5s; opacity: 1;",r.style.cssText+="transition: 0.5s; transform: scale(1.7); opacity: 0;"},1);break;default:r.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0)",a.style.cssText+="overflow: hidden; translate3d(0, 0, 0); opacity: 0;",query(a).show(),setTimeout(()=>{a.style.cssText+="transition: 0.5s; opacity: 1;",r.style.cssText+="transition: 0.5s"},1)}setTimeout(()=>{"slide-down"===n&&(query(r).css("z-index","1019"),query(a).css("z-index","1020")),a&&query(a).css({opacity:"1"}).css({transition:"",transform:""}),r&&query(r).css({opacity:"1"}).css({transition:"",transform:""}),"function"==typeof o&&o(),e()},500)}else console.log("ERROR: Cannot do transition when one of the divs is null")})}lock(l,r={}){if(null!=l){"string"==typeof r&&(r={msg:r}),arguments[2]&&(r.spinner=arguments[2]),r=this.extend({spinner:!1},r),l?.[0]instanceof Node&&(l=Array.isArray(l)?l:l.get()),r.msg||0===r.msg||(r.msg=""),this.unlock(l);var a=query(l).get(0);let e=a.scrollWidth,t=a.scrollHeight,i=("BODY"==a.tagName&&(e
    `+'
    '),query(l).find(".w2ui-lock"));a=query(l).find(".w2ui-lock-msg"),l=(r.msg||a.css({"background-color":"transparent","background-image":"none",border:"0px","box-shadow":"none"}),!0===r.spinner&&(r.msg=`
    `+r.msg),r.msg?a.html(r.msg).css("display","block"):a.remove(),null!=r.opacity&&i.css("opacity",r.opacity),i.css({display:"block"}),r.bgColor&&i.css({"background-color":r.bgColor}),getComputedStyle(i.get(0)));let s=l.opacity??.15;i.on("mousedown",function(){"function"==typeof r.onClick?r.onClick():i.css({transition:".2s",opacity:1.5*s})}).on("mouseup",function(){"function"!=typeof r.onClick&&i.css({transition:".2s",opacity:s})}).on("mousewheel",function(e){e&&(e.stopPropagation(),e.preventDefault())})}}unlock(e,t){var i;null!=e&&(clearTimeout(e._prevUnlock),e?.[0]instanceof Node&&(e=Array.isArray(e)?e:e.get()),this.isInt(t)&&0{query(e).find(".w2ui-lock").remove()},t)):query(e).find(".w2ui-lock").remove(),query(e).find(".w2ui-lock-msg").remove())}message(r,s){let e,t,l;var i=()=>{var e=query(r?.box).find(".w2ui-message");0!=e.length&&"function"==typeof(s=e.get(0)._msg_options||{})?.close&&s.close()};let a=e=>{var t,i=e.box._msg_prevFocus;query(r.box).find(".w2ui-message").length<=1?r.owner?r.owner.unlock(r.param,150):this.unlock(r.box,150):query(r.box).find(`#w2ui-message-${r.owner?.name}-`+(e.msgIndex-1)).css("z-index",1500),i?0<(t=query(i).closest(".w2ui-message")).length?t.get(0)._msg_options.setFocus(i):i.focus():"function"==typeof r.owner?.focus&&r.owner.focus(),query(e.box).remove(),0===e.msgIndex&&(c.css("z-index",e.tmp.zIndex),query(r.box).css("overflow",e.tmp.overflow)),e.trigger&&l.finish()};if("object"!=typeof(s="string"!=typeof s&&"number"!=typeof s?s:{width:String(s).length<300?350:550,height:String(s).length<300?170:250,text:String(s)}))return void i();null!=s.text&&(s.body=`
    ${s.text}
    `),null==s.width&&(s.width=350),null==s.height&&(s.height=170),null==s.hideOn&&(s.hideOn=["esc"]),null==s.on&&(h=s,s=new w2base,w2utils.extend(s,h)),s.on("open",e=>{w2utils.bindEvents(query(s.box).find(".w2ui-eaction"),s),query(e.detail.box).find("button, input, textarea, [name=hidden-first]").off(".message").on("keydown.message",function(e){27==e.keyCode&&s.hideOn.includes("esc")&&(s.cancelAction?s.action(s.cancelAction):s.close())}),setTimeout(()=>s.setFocus(s.focus),300)}),s.off(".prom");let n={self:s,action(e){return s.on("action.prom",e),n},close(e){return s.on("close.prom",e),n},open(e){return s.on("open.prom",e),n},then(e){return s.on("open:after.prom",e),n}},o=(null==s.actions&&null==s.buttons&&null==s.html&&(s.actions={Ok(e){e.detail.self.close()}}),s.off(".buttons"),null!=s.actions&&(s.buttons="",Object.keys(s.actions).forEach(e=>{var t=s.actions[e];let i=e;"function"==typeof t&&(s.buttons+=``),"object"==typeof t&&(s.buttons+=``,i=Array.isArray(s.actions)?t.text:e),"string"==typeof t&&(s.buttons+=``,i=t),"string"==typeof i&&(i=i[0].toLowerCase()+i.substr(1).replace(/\s+/g,"")),n[i]=function(t){return s.on("action.buttons",e=>{e.detail.action[0].toLowerCase()+e.detail.action.substr(1).replace(/\s+/g,"")==i&&t(e)}),n}})),Array("html","body","buttons").forEach(e=>{s[e]=String(s[e]??"").trim()}),""===s.body&&""===s.buttons||(s.html=` +
    ${s.body||""}
    +
    ${s.buttons||""}
    + `),getComputedStyle(query(r.box).get(0)));var h=parseFloat(o.width),d=parseFloat(o.height);let u=0,c=(0h&&(s.width=h-10),s.height>d-u&&(s.height=d-10-u),s.originalWidth=s.width,s.originalHeight=s.height,parseInt(s.width)<0&&(s.width=h+s.width),parseInt(s.width)<10&&(s.width=10),parseInt(s.height)<0&&(s.height=d+s.height-u),parseInt(s.height)<10&&(s.height=10),s.originalHeight<0&&(s.height=d+s.originalHeight-u),s.originalWidth<0&&(s.width=h+2*s.originalWidth),query(r.box).find(r.after));return s.tmp||(s.tmp={zIndex:c.css("z-index"),overflow:o.overflow}),""===s.html&&""===s.body&&""===s.buttons?i():(s.msgIndex=query(r.box).find(".w2ui-message").length,0===s.msgIndex&&"function"==typeof this.lock&&(query(r.box).css("overflow","hidden"),r.owner?r.owner.lock(r.param):this.lock(r.box)),query(r.box).find(".w2ui-message").css("z-index",1390),c.css("z-index",1501),d=` +
    + + ${s.html} + +
    `,0{!0===(l=s.trigger("open",{target:this.name,box:s.box,self:s})).isCancelled?(query(r.box).find(`#w2ui-message-${r.owner?.name}-`+s.msgIndex).remove(),0===s.msgIndex&&(c.css("z-index",s.tmp.zIndex),query(r.box).css("overflow",s.tmp.overflow))):query(s.box).css({transition:"0.3s",transform:"translateY(0px)"})},0),t=setTimeout(()=>{query(r.box).find(`#w2ui-message-${r.owner?.name}-`+s.msgIndex).removeClass("animating").css({transition:"0s"}),l.finish()},300)),s.action=(e,t)=>{let i=s.actions[e];i instanceof Object&&i.onClick&&(i=i.onClick);e=s.trigger("action",{target:this.name,action:e,self:s,originalEvent:t,value:s.input?s.input.value:null});!0!==e.isCancelled&&("function"==typeof i&&i(e),e.finish())},s.close=()=>{!0!==(l=s.trigger("close",{target:"self",box:s.box,self:s})).isCancelled&&(clearTimeout(t),query(s.box).hasClass("animating")?(clearTimeout(e),a(s)):(query(s.box).addClass("w2ui-closing animating").css({transition:"0.15s",transform:"translateY(-"+s.height+"px)"}),0!==s.msgIndex&&query(r.box).find(`#w2ui-message-${r.owner?.name}-`+(s.msgIndex-1)).css("z-index",1499),e=setTimeout(()=>{a(s)},150)))},s.setFocus=e=>{var t=query(r.box).find(".w2ui-message").length-1;let s=query(r.box).find(`#w2ui-message-${r.owner?.name}-`+t),l="input, button, select, textarea, [contentEditable], .w2ui-input";(null!=e?isNaN(e)?s.find(l).filter(e).get(0):s.find(l).get(e):s.find("[name=hidden-first]").get(0))?.focus(),query(r.box).find(".w2ui-message").find(l+",[name=hidden-first],[name=hidden-last]").off(".keep-focus"),query(s).find(l+",[name=hidden-first],[name=hidden-last]").on("blur.keep-focus",function(e){setTimeout(()=>{var e=document.activeElement,t=0{if("object"==typeof i&&(i=(s=i).text),(s=s||{}).where=s.where??document.body,s.timeout=s.timeout??15e3,"function"==typeof this.tmp.notify_resolve&&(this.tmp.notify_resolve(),query(this.tmp.notify_where).find("#w2ui-notify").remove()),this.tmp.notify_resolve=t,this.tmp.notify_where=s.where,clearTimeout(this.tmp.notify_timer),i){if("object"==typeof s.actions){let t={};Object.keys(s.actions).forEach(e=>{t[e]=`${e}`}),i=this.execTemplate(i,t)}var e=` +
    +
    + ${i} + +
    +
    `;query(s.where).append(e),query(s.where).find("#w2ui-notify").find(".w2ui-notify-close").on("click",e=>{query(s.where).find("#w2ui-notify").remove(),t()}),s.actions&&query(s.where).find("#w2ui-notify .w2ui-notify-link").on("click",e=>{e=query(e.target).attr("value");s.actions[e](),query(s.where).find("#w2ui-notify").remove(),t()}),0{query(s.where).find("#w2ui-notify").remove(),t()},s.timeout))}})}confirm(e,t){w2utils.normButtons(t="string"==typeof t?{text:t}:t,{yes:"Yes",no:"No"});e=w2utils.message(e,t);return e&&e.action(e=>{e.detail.self.close()}),e}normButtons(i,s){i.actions=i.actions??{};var e=Object.keys(s);return e.forEach(t=>{var e=i["btn_"+t];e&&(s[t]={text:w2utils.lang(e.text??""),class:e.class??"",style:e.style??"",attrs:e.attrs??""},delete i["btn_"+t]),Array("text","class","style","attrs").forEach(e=>{i[t+"_"+e]&&("string"==typeof s[t]&&(s[t]={text:s[t]}),s[t][e]=i[t+"_"+e],delete i[t+"_"+e])})}),e.includes("yes")&&e.includes("no")&&(w2utils.settings.macButtonOrder?w2utils.extend(i.actions,{no:s.no,yes:s.yes}):w2utils.extend(i.actions,{yes:s.yes,no:s.no})),e.includes("ok")&&e.includes("cancel")&&(w2utils.settings.macButtonOrder?w2utils.extend(i.actions,{cancel:s.cancel,ok:s.ok}):w2utils.extend(i.actions,{ok:s.ok,cancel:s.cancel})),i}getSize(e,t){let i=0;if(0<(e=query(e)).length){e=e[0];var s=getComputedStyle(e);switch(t){case"width":i=parseFloat(s.width),"auto"===s.width&&(i=0);break;case"height":i=parseFloat(s.height),"auto"===s.height&&(i=0);break;default:i=parseFloat(s[t]??0)||0}}return i}getStrWidth(e,t){query("body").append(` +
    + ${this.encodeTags(e)} +
    `);t=query("#_tmp_width")[0].clientWidth;return query("#_tmp_width").remove(),t}execTemplate(e,i){return"string"==typeof e&&i&&"object"==typeof i?e.replace(/\${([^}]+)?}/g,function(e,t){return i[t]||t}):e}marker(e,s,l={onlyFirst:!1,wholeWord:!1}){Array.isArray(s)||(s=null!=s&&""!==s?[s]:[]);let r=l.wholeWord;query(e).each(t=>{for(var e=t,i=/\((.|\n|\r)*)\<\/span\>/gi;-1!==e.innerHTML.indexOf('{e=(e="string"!=typeof e?String(e):e).replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&").replace(/&/g,"&").replace(//g,"<");e=new RegExp((r?"\\b":"")+e+(r?"\\b":"")+"(?!([^<]+)?>)","i"+(l.onlyFirst?"":"g"));t.innerHTML=t.innerHTML.replace(e,e=>''+e+"")})})}lang(e,t){if(!e||null==this.settings.phrases||"string"!=typeof e||"<=>=".includes(e))return this.execTemplate(e,t);let i=this.settings.phrases[e];return null==i?(i=e,this.settings.warnNoPhrase&&(this.settings.missing||(this.settings.missing={}),this.settings.missing[e]="---",this.settings.phrases[e]="---",console.log(`Missing translation for "%c${e}%c", see %c w2utils.settings.phrases %c with value "---"`,"color: orange","","color: #999",""))):"---"!==i||this.settings.warnNoPhrase||(i=e),"---"===i&&(i=`---`),this.execTemplate(i,t)}locale(l,i,r){return new Promise((s,t)=>{if(Array.isArray(l)){this.settings.phrases={};let i=[],t={};l.forEach((e,t)=>{5===e.length&&(e="locale/"+e.toLowerCase()+".json",l[t]=e),i.push(this.locale(e,!0,!1))}),void Promise.allSettled(i).then(e=>{e.forEach(e=>{e.value&&(t[e.value.file]=e.value.data)}),l.forEach(e=>{this.settings=this.extend({},this.settings,t[e])}),s()})}else(l=l||"en-us")instanceof Object?this.settings=this.extend({},this.settings,w2locale,l):(5===l.length&&(l="locale/"+l.toLowerCase()+".json"),fetch(l,{method:"GET"}).then(e=>e.json()).then(e=>{!0!==r&&(this.settings=i?this.extend({},this.settings,e):this.extend({},this.settings,w2locale,{phrases:{}},e)),s({file:l,data:e})}).catch(e=>{console.log("ERROR: Cannot load locale "+l),t(e)}))})}scrollBarSize(){return this.tmp.scrollBarSize||(query("body").append(` +
    +
    1
    +
    + `),this.tmp.scrollBarSize=100-query("#_scrollbar_width > div")[0].clientWidth,query("#_scrollbar_width").remove()),this.tmp.scrollBarSize}checkName(e){return null==e?(console.log('ERROR: Property "name" is required but not supplied.'),!1):null!=w2ui[e]?(console.log(`ERROR: Object named "${e}" is already registered as w2ui.${e}.`),!1):!!this.isAlphaNumeric(e)||(console.log('ERROR: Property "name" has to be alpha-numeric (a-z, 0-9, dash and underscore).'),!1)}checkUniqueId(t,i,s,l){Array.isArray(i)||(i=[i]);let r=!0;return i.forEach(e=>{e.id===t&&(console.log(`ERROR: The item id="${t}" is not unique within the ${s} "${l}".`,i),r=!1)}),r}encodeParams(t,i=""){let s="";return Object.keys(t).forEach(e=>{""!=s&&(s+="&"),"object"==typeof t[e]?s+=this.encodeParams(t[e],i+e+(i?"]":"")+"["):s+=""+i+e+(i?"]":"")+"="+t[e]}),s}parseRoute(e){let a=[];e=e.replace(/\/\(/g,"(?:/").replace(/\+/g,"__plus__").replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g,(e,t,i,s,l,r)=>(a.push({name:s,optional:!!r}),t=t||"",(r?"":t)+"(?:"+(r?t:"")+(i||"")+(l||(i?"([^/.]+?)":"([^/]+?)"))+")"+(r||""))).replace(/([\/.])/g,"\\$1").replace(/__plus__/g,"(.+)").replace(/\*/g,"(.*)");return{path:new RegExp("^"+e+"$","i"),keys:a}}getCursorPosition(e){if(null==e)return null;let t=0;var i,s=e.ownerDocument||e.document,l=s.defaultView||s.parentWindow;let r;return["INPUT","TEXTAREA"].includes(e.tagName)?t=e.selectionStart:l.getSelection?0<(r=l.getSelection()).rangeCount&&((i=(l=r.getRangeAt(0)).cloneRange()).selectNodeContents(e),i.setEnd(l.endContainer,l.endOffset),t=i.toString().length):(r=s.selection)&&"Control"!==r.type&&(l=r.createRange(),(i=s.body.createTextRange()).moveToElementText(e),i.setEndPoint("EndToEnd",l),t=i.text.length),t}setCursorPosition(s,l,t){if(null!=s){var r=document.createRange();let i,e=window.getSelection();if(["INPUT","TEXTAREA"].includes(s.tagName))s.setSelectionRange(l,t??l);else{for(let t=0;t").replace(/&/g,"&").replace(/"/g,'"').replace(/ /g," "):e).length){(i=(i=s.childNodes[t]).childNodes&&0i.length&&(l=i.length),r.setStart(i,l),t?r.setEnd(i,t):r.collapse(!0),e.removeAllRanges(),e.addRange(r))}}}parseColor(e){if("string"!=typeof e)return null;let t={};if(3===(e="#"===(e=e.trim().toUpperCase())[0]?e.substr(1):e).length)t={r:parseInt(e[0]+e[0],16),g:parseInt(e[1]+e[1],16),b:parseInt(e[2]+e[2],16),a:1};else if(6===e.length)t={r:parseInt(e.substr(0,2),16),g:parseInt(e.substr(2,2),16),b:parseInt(e.substr(4,2),16),a:1};else if(8===e.length)t={r:parseInt(e.substr(0,2),16),g:parseInt(e.substr(2,2),16),b:parseInt(e.substr(4,2),16),a:Math.round(parseInt(e.substr(6,2),16)/255*100)/100};else if(4{s[t]=this.clone(e,i)}):this.isPlainObject(e)?(s={},Object.assign(s,e),i.exclude&&i.exclude.forEach(e=>{delete s[e]}),Object.keys(s).forEach(e=>{s[e]=this.clone(s[e],i),void 0===s[e]&&delete s[e]})):e instanceof Function&&!i.functions||e instanceof Node&&!i.elements||e instanceof Event&&!i.events||(s=e),s}extend(i,s){if(Array.isArray(i)){if(!Array.isArray(s))throw new Error("Arrays can be extended with arrays only");i.splice(0,i.length),s.forEach(e=>{i.push(this.clone(e))})}else{if(i instanceof Node||i instanceof Event)throw new Error("HTML elmenents and events cannot be extended");if(i&&"object"==typeof i&&null!=s){if("object"!=typeof s)throw new Error("Object can be extended with other objects only.");Object.keys(s).forEach(e=>{var t;null!=i[e]&&"object"==typeof i[e]&&null!=s[e]&&"object"==typeof s[e]?(t=this.clone(s[e]),i[e]instanceof Node||i[e]instanceof Event?i[e]=t:(Array.isArray(i[e])&&this.isPlainObject(t)&&(i[e]={}),this.extend(i[e],t))):i[e]=this.clone(s[e])})}else if(null!=s)throw new Error("Object is not extendable, only {} or [] can be extended.")}if(2{"string"==typeof e||"number"==typeof e?i[t]={id:e,text:String(e)}:null!=e?(null!=e.caption&&null==e.text&&(e.text=e.caption),null!=e.text&&null==e.id&&(e.id=e.text),null==e.text&&null!=e.id&&(e.text=e.id)):i[t]={id:null,text:"null"}}),i):"function"==typeof i?(e=i.call(this,i,e),w2utils.normMenu.call(this,e)):"object"==typeof i?Object.keys(i).map(e=>({id:e,text:i[e]})):void 0}bindEvents(e,r){0!=e.length&&(e?.[0]instanceof Node&&(e=Array.isArray(e)?e:e.get()),query(e).each(s=>{let l=query(s).data();Object.keys(l).forEach(i=>{if(-1!=["click","dblclick","mouseenter","mouseleave","mouseover","mouseout","mousedown","mousemove","mouseup","contextmenu","focus","focusin","focusout","blur","input","change","keydown","keyup","keypress"].indexOf(String(i).toLowerCase())){let e=l[i],t=(e="string"==typeof e?e.split("|").map(e=>{"null"===(e="undefined"===(e="false"===(e="true"===e?!0:e)?!1:e)?void 0:e)&&(e=null);var t=["'",'"',"`"];return e="string"==typeof(e=parseFloat(e)==e?parseFloat(e):e)&&t.includes(e[0])&&t.includes(e[e.length-1])?e.substring(1,e.length-1):e}):e)[0];e=e.slice(1),query(s).off(i+".w2utils-bind").on(i+".w2utils-bind",function(i){switch(t){case"alert":alert(e[0]);break;case"stop":i.stopPropagation();break;case"prevent":i.preventDefault();break;case"stopPrevent":return i.stopPropagation(),i.preventDefault(),!1;default:if(null==r[t])throw new Error(`Cannot dispatch event as the method "${t}" does not exist.`);r[t].apply(r,e.map((e,t)=>{switch(String(e).toLowerCase()){case"event":return i;case"this":return this;default:return e}}))}})}})}))}debounce(t,i=250){let s;return(...e)=>{clearTimeout(s),s=setTimeout(()=>{t(...e)},i)}}}var w2utils=new Utils;class Dialog extends w2base{constructor(){super(),this.defaults={title:"",text:"",body:"",buttons:"",width:450,height:250,focus:null,actions:null,style:"",speed:.3,modal:!1,maximized:!1,keyboard:!0,showClose:!0,showMax:!1,transition:null,openMaximized:!1,moved:!1},this.name="popup",this.status="closed",this.onOpen=null,this.onClose=null,this.onMax=null,this.onMin=null,this.onToggle=null,this.onKeydown=null,this.onAction=null,this.onMove=null,this.tmp={},this.handleResize=e=>{this.options.moved||this.center(void 0,void 0,!0)}}open(s){let l=this;"closing"!=this.status&&!query("#w2ui-popup").hasClass("animating")||this.close(!0);var e=this.options;null!=(s=["string","number"].includes(typeof s)?w2utils.extend({title:"Notification",body:`
    ${s}
    `,actions:{Ok(){l.close()}},cancelAction:"ok"},arguments[1]??{}):s).text&&(s.body=`
    ${s.text}
    `),s=Object.assign({},this.defaults,e,{title:"",body:""},s,{maximized:!1}),this.options=s,0===query("#w2ui-popup").length&&(this.off("*"),Object.keys(this).forEach(e=>{e.startsWith("on")&&"on"!=e&&(this[e]=null)})),Object.keys(s).forEach(e=>{e.startsWith("on")&&"on"!=e&&s[e]&&(this[e]=s[e])}),s.width=parseInt(s.width),s.height=parseInt(s.height);let r,t,i;var{top:a,left:n}=this.center();let o={self:this,action(e){return l.on("action.prom",e),o},close(e){return l.on("close.prom",e),o},then(e){return l.on("open:after.prom",e),o}};if(null==s.actions||s.buttons||(s.buttons="",Object.keys(s.actions).forEach(e=>{var t=s.actions[e];let i=e;"function"==typeof t&&(s.buttons+=``),"object"==typeof t&&(s.buttons+=``,i=Array.isArray(s.actions)?t.text:e),"string"==typeof t&&(s.buttons+=``,i=t),"string"==typeof i&&(i=i[0].toLowerCase()+i.substr(1).replace(/\s+/g,"")),o[i]=function(t){return l.on("action.buttons",e=>{e.detail.action[0].toLowerCase()+e.detail.action.substr(1).replace(/\s+/g,"")==i&&t(e)}),o}})),0===query("#w2ui-popup").length){if(!0===(r=this.trigger("open",{target:"popup",present:!1})).isCancelled)return;this.status="opening",w2utils.lock(document.body,{opacity:.3,onClick:s.modal?null:()=>{this.close()}});let e="";s.showClose&&(e+=`
    + +
    `),s.showMax&&(e+=`
    + +
    `);n=` + left: ${n}px; + top: ${a}px; + width: ${parseInt(s.width)}px; + height: ${parseInt(s.height)}px; + transition: ${s.speed}s + `;t=`
    `,query("body").append(t),query("#w2ui-popup")[0]._w2popup={self:this,created:new Promise(e=>{this._promCreated=e}),opened:new Promise(e=>{this._promOpened=e}),closing:new Promise(e=>{this._promClosing=e}),closed:new Promise(e=>{this._promClosed=e})},n=`${s.title?"":"top: 0px !important;"} `+(s.buttons?"":"bottom: 0px !important;"),t=` + +
    ${e}
    +
    +
    +
    +
    +
    +
    + + `,query("#w2ui-popup").html(t),s.title&&query("#w2ui-popup .w2ui-popup-title").append(w2utils.lang(s.title)),s.buttons&&query("#w2ui-popup .w2ui-popup-buttons").append(s.buttons),s.body&&query("#w2ui-popup .w2ui-popup-body").append(s.body),setTimeout(()=>{query("#w2ui-popup").css("transition",s.speed+"s").removeClass("w2ui-anim-open"),w2utils.bindEvents("#w2ui-popup .w2ui-eaction",this),query("#w2ui-popup").find(".w2ui-popup-body").show(),this._promCreated()},1),clearTimeout(this._timer),this._timer=setTimeout(()=>{this.status="open",l.setFocus(s.focus),r.finish(),this._promOpened(),query("#w2ui-popup").removeClass("animating")},1e3*s.speed)}else{if(!0===(r=this.trigger("open",{target:"popup",present:!0})).isCancelled)return;this.status="opening",null!=e&&(e.maximized||e.width==s.width&&e.height==s.height||this.resize(s.width,s.height),s.prevSize=s.width+"px:"+s.height+"px",s.maximized=e.maximized);a=query("#w2ui-popup .w2ui-box").get(0).cloneNode(!0);query(a).removeClass("w2ui-box").addClass("w2ui-box-temp").find(".w2ui-popup-body").empty().append(s.body),query("#w2ui-popup .w2ui-box").after(a),s.buttons?(query("#w2ui-popup .w2ui-popup-buttons").show().html("").append(s.buttons),query("#w2ui-popup .w2ui-popup-body").removeClass("w2ui-popup-no-buttons"),query("#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp").css("bottom","")):(query("#w2ui-popup .w2ui-popup-buttons").hide().html(""),query("#w2ui-popup .w2ui-popup-body").addClass("w2ui-popup-no-buttons"),query("#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp").css("bottom","0px")),s.title?(query("#w2ui-popup .w2ui-popup-title").show().html((s.showClose?`
    + +
    `:"")+(s.showMax?`
    + +
    `:"")).append(s.title),query("#w2ui-popup .w2ui-popup-body").removeClass("w2ui-popup-no-title"),query("#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp").css("top","")):(query("#w2ui-popup .w2ui-popup-title").hide().html(""),query("#w2ui-popup .w2ui-popup-body").addClass("w2ui-popup-no-title"),query("#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp").css("top","0px"));let t=query("#w2ui-popup .w2ui-box")[0],i=query("#w2ui-popup .w2ui-box-temp")[0];query("#w2ui-popup").addClass("animating"),w2utils.transition(t,i,s.transition,()=>{query(t).remove(),query(i).removeClass("w2ui-box-temp").addClass("w2ui-box");var e=query(i).find(".w2ui-popup-body");1==e.length&&(e[0].style.cssText=s.style,e.show()),l.setFocus(s.focus),query("#w2ui-popup").removeClass("animating")}),this.status="open",r.finish(),w2utils.bindEvents("#w2ui-popup .w2ui-eaction",this),query("#w2ui-popup").find(".w2ui-popup-body").show()}return s.openMaximized&&this.max(),s._last_focus=document.activeElement,s.keyboard&&query(document.body).on("keydown",e=>{this.keydown(e)}),query(window).on("resize",this.handleResize),i={resizing:!1,mvMove:function(e){1==i.resizing&&(e=e||window.event,i.div_x=e.screenX-i.x,i.div_y=e.screenY-i.y,!0!==(e=l.trigger("move",{target:"popup",div_x:i.div_x,div_y:i.div_y,originalEvent:e})).isCancelled)&&(query("#w2ui-popup").css({transition:"none",transform:"translate3d("+i.div_x+"px, "+i.div_y+"px, 0px)"}),l.options.moved=!0,e.finish())},mvStop:function(e){1!=i.resizing||(e=e||window.event,l.status="open",i.div_x=e.screenX-i.x,i.div_y=e.screenY-i.y,query("#w2ui-popup").css({left:i.pos_x+i.div_x+"px",top:i.pos_y+i.div_y+"px"}).css({transition:"none",transform:"translate3d(0px, 0px, 0px)"}),i.resizing=!1,query(document.body).off(".w2ui-popup"),i.isLocked)||l.unlock()}},query("#w2ui-popup .w2ui-popup-title").on("mousedown",function(e){var t;l.options.maximized||(e=(e=e)||window.event,l.status="moving",t=query("#w2ui-popup").get(0).getBoundingClientRect(),Object.assign(i,{resizing:!0,isLocked:1==query("#w2ui-popup > .w2ui-lock").length,x:e.screenX,y:e.screenY,pos_x:t.x,pos_y:t.y}),i.isLocked||l.lock({opacity:0}),query(document.body).on("mousemove.w2ui-popup",i.mvMove).on("mouseup.w2ui-popup",i.mvStop),e.stopPropagation?e.stopPropagation():e.cancelBubble=!0,e.preventDefault&&e.preventDefault())}),o}load(s){return new Promise((i,e)=>{if(null==(s="string"==typeof s?{url:s}:s).url)console.log("ERROR: The url is not defined."),e("The url is not defined");else{this.status="loading";let[e,t]=String(s.url).split("#");e&&fetch(e).then(e=>e.text()).then(e=>{i(this.template(e,t,s))})}})}template(t,e,i={}){let s;try{s=query(t)}catch(e){s=query.html(t)}return e&&(s=s.filter("#"+e)),Object.assign(i,{width:parseInt(query(s).css("width")),height:parseInt(query(s).css("height")),title:query(s).find("[rel=title]").html(),body:query(s).find("[rel=body]").html(),buttons:query(s).find("[rel=buttons]").html(),style:query(s).find("[rel=body]").get(0).style.cssText}),this.open(i)}action(e,t){let i=this.options.actions[e];i instanceof Object&&i.onClick&&(i=i.onClick);e=this.trigger("action",{action:e,target:"popup",self:this,originalEvent:t,value:this.input?this.input.value:null});!0!==e.isCancelled&&("function"==typeof i&&i.call(this,t),e.finish())}keydown(e){var t;this.options&&!this.options.keyboard||!0!==(t=this.trigger("keydown",{target:"popup",originalEvent:e})).isCancelled&&(27===e.keyCode&&(e.preventDefault(),0==query("#w2ui-popup .w2ui-message").length)&&(this.options.cancelAction?this.action(this.options.cancelAction):this.close()),t.finish())}close(e){let t=this.trigger("close",{target:"popup"});var i;!0!==t.isCancelled&&(i=()=>{query("#w2ui-popup").remove(),this.options._last_focus&&0{e.finish()},1e3*this.options.speed+50))}max(){if(!0!==this.options.maximized){let e=this.trigger("max",{target:"popup"});var t;!0!==e.isCancelled&&(this.status="resizing",t=query("#w2ui-popup").get(0).getBoundingClientRect(),this.options.prevSize=t.width+":"+t.height,this.resize(1e4,1e4,()=>{this.status="open",this.options.maximized=!0,e.finish()}))}}min(){if(!0===this.options.maximized){var t=this.options.prevSize.split(":");let e=this.trigger("min",{target:"popup"});!0!==e.isCancelled&&(this.status="resizing",this.options.maximized=!1,this.resize(parseInt(t[0]),parseInt(t[1]),()=>{this.status="open",this.options.prevSize=null,e.finish()}))}}clear(){query("#w2ui-popup .w2ui-popup-title").html(""),query("#w2ui-popup .w2ui-popup-body").html(""),query("#w2ui-popup .w2ui-popup-buttons").html("")}reset(){this.open(this.defaults)}message(e){return w2utils.message({owner:this,box:query("#w2ui-popup").get(0),after:".w2ui-popup-title"},e)}confirm(e){return w2utils.confirm({owner:this,box:query("#w2ui-popup"),after:".w2ui-popup-title"},e)}setFocus(e){let s=query("#w2ui-popup"),l="input, button, select, textarea, [contentEditable], .w2ui-input";null!=e?(isNaN(e)?s.find(l).filter(e).get(0):s.find(l).get(e))?.focus():(e=s.find("[name=hidden-first]").get(0))&&e.focus(),query(s).find(l+",[name=hidden-first],[name=hidden-last]").off(".keep-focus").on("blur.keep-focus",function(e){setTimeout(()=>{var e=document.activeElement,t=0{s.resizeMessages()},10);setTimeout(()=>{clearInterval(n),s.resizeMessages(),"function"==typeof i&&i()},1e3*this.options.speed+50)}resizeMessages(){query("#w2ui-popup .w2ui-message").each(e=>{var t=e._msg_options,i=query("#w2ui-popup"),s=(parseInt(t.width)<10&&(t.width=10),parseInt(t.height)<10&&(t.height=10),i[0].getBoundingClientRect()),i=parseInt(i.find(".w2ui-popup-title")[0].clientHeight),l=parseInt(s.width),s=parseInt(s.height);t.width=t.originalWidth,t.width>l-10&&(t.width=l-10),t.height=t.originalHeight,t.height>s-i-5&&(t.height=s-i-5),t.originalHeight<0&&(t.height=s+t.originalHeight-i),t.originalWidth<0&&(t.width=l+2*t.originalWidth),query(e).css({left:(l-t.width)/2+"px",width:t.width+"px",height:t.height+"px"})})}}function w2alert(e,t,i){let s;t={title:w2utils.lang(t??"Notification"),body:`
    ${e}
    `,showClose:!1,actions:["Ok"],cancelAction:"ok"};return(s=0{"function"==typeof e.detail.self?.close&&e.detail.self.close(),"function"==typeof i&&i()}),s}function w2confirm(e,t,i){let s,l=e;return(l=["string","number"].includes(typeof l)?{msg:l}:l).msg&&(l.body=`
    ${l.msg}
    `,delete l.msg),w2utils.extend(l,{title:w2utils.lang(t??"Confirmation"),showClose:!1,modal:!0,cancelAction:"no"}),w2utils.normButtons(l,{yes:"Yes",no:"No"}),(s=0{"function"==typeof e.detail.self?.close&&e.detail.self.close(),"function"==typeof i&&i(e.detail.action)}),s}function w2prompt(e,t,i){let s,l=e;return(l=["string","number"].includes(typeof l)?{label:l}:l).label&&(l.focus=0,l.body=l.textarea?`
    +
    ${l.label}
    + +
    `:`
    + + +
    `),w2utils.extend(l,{title:w2utils.lang(t??"Notification"),showClose:!1,modal:!0,cancelAction:"cancel"}),w2utils.normButtons(l,{ok:"Ok",cancel:"Cancel"}),(s=0{e=e.detail.box||query("#w2ui-popup .w2ui-popup-body").get(0);w2utils.bindEvents(query(e).find("#w2prompt"),{keydown(e){27==e.keyCode&&e.stopPropagation()},change(e){var t=s.self.trigger("change",{target:"prompt",originalEvent:e});!0!==t.isCancelled&&(13==e.keyCode&&e.ctrlKey&&s.self.action("Ok",e),27==e.keyCode&&s.self.action("Cancel",e),t.finish())}}),query(e).find(".w2ui-eaction").trigger("keyup")}).on("action:after.prompt",e=>{"function"==typeof e.detail.self?.close&&e.detail.self.close(),"function"==typeof i&&i(e.detail.action)}),s}let w2popup=new Dialog;class Tooltip{static active={};constructor(){this.defaults={name:null,html:"",style:"",class:"",position:"top|bottom",align:"",anchor:null,anchorClass:"",anchorStyle:"",autoShow:!1,autoShowOn:null,autoHideOn:null,arrowSize:8,margin:0,margin:1,screenMargin:2,autoResize:!0,offsetX:0,offsetY:0,maxWidth:null,maxHeight:null,watchScroll:null,watchResize:null,hideOn:null,onThen:null,onShow:null,onHide:null,onUpdate:null,onMove:null}}static observeRemove=new MutationObserver(e=>{let t=0;Object.keys(Tooltip.active).forEach(e=>{e=Tooltip.active[e];e.displayed&&(e.anchor&&e.anchor.isConnected?t++:e.hide())}),0===t&&Tooltip.observeRemove.disconnect()});trigger(e,t){var i;if(2==arguments.length&&(i=e,(e=t).type=i),e.overlay)return e.overlay.trigger(e);console.log("ERROR: cannot find overlay where to trigger events")}get(e){return 0==arguments.length?Object.keys(Tooltip.active):!0===e?Tooltip.active:Tooltip.active[e.replace(/[\s\.#]/g,"_")]}attach(t,s){let l,r,a=this;if(0!=arguments.length){1==arguments.length&&t.anchor?t=(l=t).anchor:2===arguments.length&&"string"==typeof s?s=(l={anchor:t,html:s}).html:2===arguments.length&&null!=s&&"object"==typeof s&&(s=(l=s).html),l=w2utils.extend({},this.defaults,l||{}),!(s=!s&&l.text?l.text:s)&&l.html&&(s=l.html),delete l.anchor;let e=l.name||t.id;t!=document&&t!=document.body||(t=document.body,e="context-menu"),e||(e="noname-"+Object.keys(Tooltip.active).length,console.log("NOTICE: name property is not defined for tooltip, could lead to too many instances")),e=e.replace(/[\s\.#]/g,"_"),Tooltip.active[e]?((r=Tooltip.active[e]).prevOptions=r.options,r.options=l,r.anchor=t,r.prevOptions.html==r.options.html&&r.prevOptions.class==r.options.class&&r.prevOptions.style==r.options.style||(r.needsUpdate=!0),l=r.options):(r=new w2base,Object.assign(r,{id:"w2overlay-"+e,name:e,options:l,anchor:t,displayed:!1,tmp:{observeResize:new ResizeObserver(()=>{this.resize(r.name)})},hide(){a.hide(e)}}),Tooltip.active[e]=r),Object.keys(r.options).forEach(e=>{var t=r.options[e];e.startsWith("on")&&"function"==typeof t&&(r[e]=t,delete r.options[e])}),!0===l.autoShow&&(l.autoShowOn=l.autoShowOn??"mouseenter",l.autoHideOn=l.autoHideOn??"mouseleave",l.autoShow=!1),l.autoShowOn&&(s="autoShow-"+r.name,query(t).off("."+s).on(l.autoShowOn+"."+s,e=>{a.show(r.name),e.stopPropagation()}),delete l.autoShowOn),l.autoHideOn&&(s="autoHide-"+r.name,query(t).off("."+s).on(l.autoHideOn+"."+s,e=>{a.hide(r.name),e.stopPropagation()}),delete l.autoHideOn),r.off(".attach");let i={overlay:r,then:t=>(r.on("show:after.attach",e=>{t(e)}),i),show:t=>(r.on("show.attach",e=>{t(e)}),i),hide:t=>(r.on("hide.attach",e=>{t(e)}),i),update:t=>(r.on("update.attach",e=>{t(e)}),i),move:t=>(r.on("move.attach",e=>{t(e)}),i)};return i}}update(e,t){var i=Tooltip.active[e];i?(i.needsUpdate=!0,i.options.html=t,this.show(e)):console.log(`Tooltip "${e}" is not displayed. Cannot update it.`)}show(i){if(i instanceof HTMLElement||i instanceof Object){let e=i,t=(i instanceof HTMLElement&&((e=arguments[1]||{}).anchor=i),this.attach(e));return query(t.overlay.anchor).off(".autoShow-"+t.overlay.name).off(".autoHide-"+t.overlay.name),setTimeout(()=>{this.show(t.overlay.name)},1),t}let t,r=this,a=Tooltip.active[i.replace(/[\s\.#]/g,"_")];if(a){let l=a.options;if(!a||a.displayed&&!a.needsUpdate)this.resize(a?.name);else{var s=l.position.split("|"),s=["top","bottom"].includes(s[0]);let e="both"==l.align&&s?"":"white-space: nowrap;";if(l.maxWidth&&w2utils.getStrWidth(l.html,"")>l.maxWidth&&(e="width: "+l.maxWidth+"px; white-space: inherit; overflow: auto;"),e+=" max-height: "+(l.maxHeight||window.innerHeight-40)+"px;",""!==l.html&&null!=l.html){if(a.box){if(!0===(t=this.trigger("update",{target:i,overlay:a})).isCancelled)return void(a.prevOptions&&(a.options=a.prevOptions,delete a.prevOptions));query(a.box).find(".w2ui-overlay-body").attr("style",(l.style||"")+"; "+e).removeClass().addClass("w2ui-overlay-body "+l.class).html(l.html)}else{if(!0===(t=this.trigger("show",{target:i,overlay:a})).isCancelled)return;query("body").append(``),a.box=query("#"+w2utils.escapeId(a.id))[0],a.displayed=!0;s=query(a.anchor).data("tooltipName")??[];s.push(i),query(a.anchor).data("tooltipName",s),w2utils.bindEvents(a.box,{}),a.tmp.originalCSS="",0{r.hide(a.name)},i=query(a.anchor),s="tooltip-"+a.name;query("body").off("."+s),l.hideOn.includes("doc-click")&&(["INPUT","TEXTAREA"].includes(a.anchor.tagName)&&i.off(`.${s}-doc`).on(`click.${s}-doc`,e=>{e.stopPropagation()}),query("body").on("click."+s,t));l.hideOn.includes("focus-change")&&query("body").on("focusin."+s,e=>{document.activeElement!=a.anchor&&r.hide(a.name)});["INPUT","TEXTAREA"].includes(a.anchor.tagName)&&(i.off("."+s),l.hideOn.forEach(e=>{-1==["doc-click","focus-change"].indexOf(e)&&i.on(e+"."+s,{once:!0},t)}))}{var n=document.body;let e="tooltip-"+a.name,t=n;"BODY"==n.tagName&&(t=n.ownerDocument);query(t).off("."+e).on("scroll."+e,e=>{Object.assign(a.tmp,{scrollLeft:n.scrollLeft,scrollTop:n.scrollTop}),r.resize(a.name)})}return query(a.box).show(),a.tmp.observeResize.observe(a.box),Tooltip.observeRemove.observe(document.body,{subtree:!0,childList:!0}),query(a.box).css("opacity",1).find(".w2ui-overlay-body").html(l.html),setTimeout(()=>{query(a.box).css({"pointer-events":"auto"}).data("ready","yes")},100),delete a.needsUpdate,a.box.overlay=a,t&&t.finish(),{overlay:a}}r.hide(i)}}}hide(e){let i;if(0==arguments.length)Object.keys(Tooltip.active).forEach(e=>{this.hide(e)});else if(e instanceof HTMLElement)(query(e).data("tooltipName")??[]).forEach(e=>{this.hide(e)});else if("string"==typeof e&&(e=e.replace(/[\s\.#]/g,"_"),i=Tooltip.active[e]),i&&i.box){delete Tooltip.active[e];e=this.trigger("hide",{target:e,overlay:i});if(!0!==e.isCancelled){var s="tooltip-"+i.name;i.tmp.observeResize?.disconnect(),i.options.watchScroll&&query(i.options.watchScroll).off(".w2scroll-"+i.name);let t=0;Object.keys(Tooltip.active).forEach(e=>{Tooltip.active[e].displayed&&t++}),0==t&&Tooltip.observeRemove.disconnect(),query("body").off("."+s),query(document).off("."+s),i.box.remove(),i.box=null,i.displayed=!1;var l=query(i.anchor).data("tooltipName")??[];-1!=l.indexOf(i.name)&&l.splice(l.indexOf(i.name),1),0==l.length?query(i.anchor).removeData("tooltipName"):query(i.anchor).data("tooltipName",l),i.anchor.style.cssText=i.tmp.originalCSS,query(i.anchor).off("."+s).removeClass(i.options.anchorClass),e.finish()}}}resize(i){if(0==arguments.length)Object.keys(Tooltip.active).forEach(e=>{e=Tooltip.active[e];e.displayed&&this.resize(e.name)});else{var s=Tooltip.active[i.replace(/[\s\.#]/g,"_")];let t=this.getPosition(s.name);var l=t.left+"x"+t.top;let e;s.tmp.lastPos!=l&&(e=this.trigger("move",{target:i,overlay:s,pos:t})),query(s.box).css({left:t.left+"px",top:t.top+"px"}).then(e=>{null!=t.width&&e.css("width",t.width+"px").find(".w2ui-overlay-body").css("width","100%"),null!=t.height&&e.css("height",t.height+"px").find(".w2ui-overlay-body").css("height","100%")}).find(".w2ui-overlay-body").removeClass("w2ui-arrow-right w2ui-arrow-left w2ui-arrow-top w2ui-arrow-bottom").addClass(t.arrow.class).closest(".w2ui-overlay").find("style").text(t.arrow.style),s.tmp.lastPos!=l&&e&&(s.tmp.lastPos=l,e.finish())}}getPosition(e){let g=Tooltip.active[e.replace(/[\s\.#]/g,"_")];if(g&&g.box){let t=g.options;(g.tmp.resizedY||g.tmp.resizedX)&&query(g.box).css({width:"",height:"",scroll:"auto"});var e=w2utils.scrollBarSize(),y=!(document.body.scrollWidth==document.body.clientWidth),w=!(document.body.scrollHeight==document.body.clientHeight);let i={width:window.innerWidth-(w?e:0),height:window.innerHeight-(y?e:0)};var b,v=("auto"==t.position?"top|bottom|right|left":t.position).split("|");let s=["top","bottom"].includes(v[0]),l=g.box.getBoundingClientRect(),r=g.anchor.getBoundingClientRect(),a=(g.anchor==document.body&&({x,y:_,width:q,height:C}=t.originalEvent,r={left:x-2,top:_-4,width:q,height:C,arrow:"none"}),t.arrowSize),n=("none"==r.arrow&&(a=0),{top:r.top,bottom:i.height-(r.top+r.height)-+(y?e:0),left:r.left,right:i.width-(r.left+r.width)+(w?e:0)});l.width<22&&(l.width=22),l.height<14&&(l.height=14);let o,h,d,u,c="",p={offset:0,class:"",style:`#${g.id} { --tip-size: ${a}px; }`},f={left:0,top:0},m={posX:"",x:0,posY:"",y:0};v.forEach(e=>{["top","bottom"].includes(e)&&(!c&&l.height+a/1.893m.y)&&Object.assign(m,{posY:e,y:n[e]}),["left","right"].includes(e)&&(!c&&l.width+a/1.893m.x)&&Object.assign(m,{posX:e,x:n[e]})}),c=c||(s?m.posY:m.posX),t.autoResize&&(["top","bottom"].includes(c)&&(l.height>n[c]?(u=n[c],g.tmp.resizedY=!0):g.tmp.resizedY=!1),["left","right"].includes(c))&&(l.width>n[c]?(d=n[c],g.tmp.resizedX=!0):g.tmp.resizedX=!1);var x=c;switch(p.class=r.arrow||"w2ui-arrow-"+x,x){case"top":o=r.left+(r.width-(d??l.width))/2,h=r.top-(u??l.height)-a/1.5+1;break;case"bottom":o=r.left+(r.width-(d??l.width))/2,h=r.top+r.height+a/1.25+1;break;case"left":o=r.left-(d??l.width)-a/1.2-1,h=r.top+(r.height-(u??l.height))/2;break;case"right":o=r.left+r.width+a/1.2+1,h=r.top+(r.height-(u??l.height))/2}if(s)"left"==t.align&&(f.left=r.left-o,o=r.left),"right"==t.align&&(f.left=r.left+r.width-(d??l.width)-o,o=r.left+r.width-(d??l.width)),["top","bottom"].includes(c)&&t.align.startsWith("both")&&(b=t.align.split(":")[1]??50,r.width>=b)&&(o=r.left,d=r.width),"top"==t.align&&(f.top=r.top-h,h=r.top),"bottom"==t.align&&(f.top=r.top+r.height-(u??l.height)-h,h=r.top+r.height-(u??l.height)),["left","right"].includes(c)&&t.align.startsWith("both")&&(b=t.align.split(":")[1]??50,r.height>=b)&&(h=r.top,u=r.height);{let e;(["left","right"].includes(t.align)&&r.width<(d??l.width)||["top","bottom"].includes(t.align)&&r.height<(u??l.height))&&(e=!0);var _="right"==c?a:t.screenMargin,q="bottom"==c?a:t.screenMargin,C=i.width-(d??l.width)-("left"==c?a:t.screenMargin),y=i.height-(u??l.height)-("top"==c?a:t.screenMargin)+3;(["top","bottom"].includes(c)||t.autoResize)&&(o<_&&(e=!0,f.left-=o,o=_),o>C)&&(e=!0,f.left-=o-C,o+=C-o);(["left","right"].includes(c)||t.autoResize)&&(hy)&&(e=!0,f.top-=h-y,h+=y-h);e&&(_=s?"left":"top",C=s?"width":"height",p.offset=-f[_],q=l[C]/2-a,Math.abs(p.offset)>q+a&&(p.class=""),Math.abs(p.offset)>q&&(p.offset=p.offset<0?-q:q),p.style=w2utils.stripSpaces(`#${g.id} .w2ui-overlay-body:after, + #${g.id} .w2ui-overlay-body:before { + --tip-size: ${a}px; + margin-${_}: ${p.offset}px; + }`))}w="top"==c?-t.margin:"bottom"==c?t.margin:0,e="left"==c?-t.margin:"right"==c?t.margin:0;return h=Math.floor(100*(h+parseFloat(t.offsetY)+parseFloat(w)))/100,{left:o=Math.floor(100*(o+parseFloat(t.offsetX)+parseFloat(e)))/100,top:h,arrow:p,adjust:f,width:d,height:u,pos:c}}}}class ColorTooltip extends Tooltip{constructor(){super(),this.palette=[["000000","333333","555555","777777","888888","999999","AAAAAA","CCCCCC","DDDDDD","EEEEEE","F7F7F7","FFFFFF"],["FF011B","FF9838","FFC300","FFFD59","86FF14","14FF7A","2EFFFC","2693FF","006CE7","9B24F4","FF21F5","FF0099"],["FFEAEA","FCEFE1","FCF4DC","FFFECF","EBFFD9","D9FFE9","E0FFFF","E8F4FF","ECF4FC","EAE6F4","FFF5FE","FCF0F7"],["F4CCCC","FCE5CD","FFF1C2","FFFDA1","D5FCB1","B5F7D0","BFFFFF","D6ECFF","CFE2F3","D9D1E9","FFE3FD","FFD9F0"],["EA9899","F9CB9C","FFE48C","F7F56F","B9F77E","84F0B1","83F7F7","B5DAFF","9FC5E8","B4A7D6","FAB9F6","FFADDE"],["E06666","F6B26B","DEB737","E0DE51","8FDB48","52D189","4EDEDB","76ACE3","6FA8DC","8E7CC3","E07EDA","F26DBD"],["CC0814","E69138","AB8816","B5B20E","6BAB30","27A85F","1BA8A6","3C81C7","3D85C6","674EA7","A14F9D","BF4990"],["99050C","B45F17","80650E","737103","395E14","10783D","13615E","094785","0A5394","351C75","780172","782C5A"]],this.defaults=w2utils.extend({},this.defaults,{advanced:!1,transparent:!0,position:"top|bottom",class:"w2ui-white",color:"",liveUpdate:!0,arrowSize:12,autoResize:!1,anchorClass:"w2ui-focus",autoShowOn:"focus",hideOn:["doc-click","focus-change"],onSelect:null,onLiveUpdate:null})}attach(e,t){let i;1==arguments.length&&e.anchor?e=(i=e).anchor:2===arguments.length&&null!=t&&"object"==typeof t&&((i=t).anchor=e);t=i.hideOn;i=w2utils.extend({},this.defaults,i||{}),t&&(i.hideOn=t),i.style+="; padding: 0;",i.transparent&&"333333"==this.palette[0][1]&&(this.palette[0].splice(1,1),this.palette[0].push("")),i.transparent||"333333"==this.palette[0][1]||(this.palette[0].splice(1,0,"333333"),this.palette[0].pop()),i.color&&(i.color=String(i.color).toUpperCase()),"string"==typeof i.color&&"#"===i.color.substr(0,1)&&(i.color=i.color.substr(1)),this.index=[-1,-1];let s=super.attach(i),l=s.overlay;return l.options.html=this.getColorHTML(l.name,i),l.on("show.attach",e=>{var e=e.detail.overlay,t=e.anchor,i=e.options;["INPUT","TEXTAREA"].includes(t.tagName)&&!i.color&&t.value&&(e.tmp.initColor=t.value),delete e.newColor}),l.on("show:after.attach",e=>{var t;s.overlay?.box&&(t=query(s.overlay.box).find(".w2ui-eaction"),w2utils.bindEvents(t,this),this.initControls(s.overlay))}),l.on("update:after.attach",e=>{var t;s.overlay?.box&&(t=query(s.overlay.box).find(".w2ui-eaction"),w2utils.bindEvents(t,this),this.initControls(s.overlay))}),l.on("hide.attach",e=>{var e=e.detail.overlay,t=e.anchor,i=e.newColor??e.options.color??"",t=(["INPUT","TEXTAREA"].includes(t.tagName)&&t.value!=i&&(t.value=i),this.trigger("select",{color:i,target:e.name,overlay:e}));!0!==t.isCancelled&&t.finish()}),s.liveUpdate=t=>(l.on("liveUpdate.attach",e=>{t(e)}),s),s.select=t=>(l.on("select.attach",e=>{t(e)}),s),s}select(e,t){let i;this.index=[-1,-1],"string"!=typeof t&&(i=t.target,this.index=query(i).attr("index").split(":"),t=query(i).closest(".w2ui-overlay").attr("name"));var s=this.get(t),t=this.trigger("liveUpdate",{color:e,target:t,overlay:s,param:arguments[1]});!0!==t.isCancelled&&(["INPUT","TEXTAREA"].includes(s.anchor.tagName)&&s.options.liveUpdate&&query(s.anchor).val(e),s.newColor=e,query(s.box).find(".w2ui-selected").removeClass("w2ui-selected"),i&&query(i).addClass("w2ui-selected"),t.finish())}nextColor(e){var t=this.palette;switch(e){case"up":this.index[0]--;break;case"down":this.index[0]++;break;case"right":this.index[1]++;break;case"left":this.index[1]--}return this.index[0]<0&&(this.index[0]=0),this.index[0]>t.length-2&&(this.index[0]=t.length-2),this.index[1]<0&&(this.index[1]=0),this.index[1]>t[0].length-1&&(this.index[1]=t[0].length-1),t[this.index[0]][this.index[1]]}tabClick(e,t){"string"!=typeof t&&(t=query(t.target).closest(".w2ui-overlay").attr("name"));var t=this.get(t),i=query(t.box).find(`.w2ui-color-tab:nth-child(${e})`);query(t.box).find(".w2ui-color-tab").removeClass("w2ui-selected"),query(i).addClass("w2ui-selected"),query(t.box).find(".w2ui-tab-content").hide().closest(".w2ui-colors").find(".tab-"+e).show()}getColorHTML(s,l){let r=` +
    +
    `;for(let i=0;i';for(let t=0;t  +
    `}r+="
    ",i<2&&(r+='
    ')}return r=(r=(r+="")+` + `)+` +
    +
    +
    +
    + ${"string"==typeof l.html?l.html:""} +
    +
    `}initControls(n){let a,o=this;var e=n.options;let h=w2utils.parseColor(e.color||n.tmp.initColor),d=(null==h&&(h={r:140,g:150,b:160,a:1}),w2utils.rgb2hsv(h));!0===e.advanced&&this.tabClick(2,n.name),u(d,!0,!0),query(n.box).find("input").off(".w2color").on("change.w2color",e=>{e=query(e.target);let t=parseFloat(e.val());var i=parseFloat(e.attr("max")),i=(isNaN(t)&&(t=0,e.val(0)),1i&&(e.val(i),t=i),t<0&&(e.val(0),t=0),e.attr("name")),e={};-1!==["r","g","b","a"].indexOf(i)?(h[i]=t,d=w2utils.rgb2hsv(h)):-1!==["h","s","v"].indexOf(i)&&(e[i]=t),u(e,!0)}),query(n.box).find(".color-original").off(".w2color").on("click.w2color",e=>{e=w2utils.parseColor(query(e.target).css("background-color"));null!=e&&(h=e,u(d=w2utils.rgb2hsv(h),!0))});e=`${w2utils.isIOS?"touchstart":"mousedown"}.w2color`;let s=`${w2utils.isIOS?"touchend":"mouseup"}.w2color`,l=`${w2utils.isIOS?"touchmove":"mousemove"}.w2color`;function u(e,t,i){null!=e.h&&(d.h=e.h),null!=e.s&&(d.s=e.s),null!=e.v&&(d.v=e.v),null!=e.a&&(h.a=e.a,d.a=e.a);let s="rgba("+(h=w2utils.hsv2rgb(d)).r+","+h.g+","+h.b+","+h.a+")",l=[Number(h.r).toString(16).toUpperCase(),Number(h.g).toString(16).toUpperCase(),Number(h.b).toString(16).toUpperCase(),Math.round(255*Number(h.a)).toString(16).toUpperCase()];var r,a;l.forEach((e,t)=>{1===e.length&&(l[t]="0"+e)}),s=l[0]+l[1]+l[2]+l[3],1===h.a&&(s=l[0]+l[1]+l[2]),query(n.box).find(".color-preview").css("background-color","#"+s),query(n.box).find("input").each(e=>{e.name&&(null!=h[e.name]&&(e.value=h[e.name]),null!=d[e.name]&&(e.value=d[e.name]),"a"===e.name)&&(e.value=h.a)}),i?(e=n.tmp?.initColor||s,query(n.box).find(".color-original").css("background-color","#"+e),query(n.box).find(".w2ui-colors .w2ui-selected").removeClass("w2ui-selected"),query(n.box).find(`.w2ui-colors [name="${e}"]`).addClass("w2ui-selected"),8==s.length&&o.tabClick(2,n.name)):o.select(s,n.name),t&&(i=query(n.box).find(".palette .value1"),e=query(n.box).find(".rainbow .value2"),t=query(n.box).find(".alpha .value2"),r=parseInt(i[0].clientWidth)/2,a=parseInt(e[0].clientWidth)/2,i.css({left:150*d.s/100-r+"px",top:125*(100-d.v)/100-r+"px"}),e.css("left",d.h/2.4-a+"px"),t.css("left",150*h.a-a+"px"),c())}function c(){var e=w2utils.hsv2rgb(d.h,100,100),e=`${e.r},${e.g},`+e.b;query(n.box).find(".palette").css("background-image",`linear-gradient(90deg, rgba(${e},0) 0%, rgba(${e},1) 100%)`)}function r(e){query("body").off(".w2color")}function p(e){var t=a.el,i=e.pageX-a.x,e=e.pageY-a.y;let s=a.left+i,l=a.top+e;var i=parseInt(t.prop("clientWidth"))/2,e=(s<-i&&(s=-i),l<-i&&(l=-i),s>a.width-i&&(s=a.width-i),l>a.height-i&&(l=a.height-i),t.hasClass("move-x")&&t.css({left:s+"px"}),t.hasClass("move-y")&&t.css({top:l+"px"}),query(t.get(0).parentNode).attr("name")),r=parseInt(t.css("left"))+i,t=parseInt(t.css("top"))+i;"palette"===e&&u({s:Math.round(r/a.width*100),v:Math.round(100-t/a.height*100)}),"rainbow"===e&&(u({h:Math.round(2.4*r)}),c()),"alpha"===e&&u({a:parseFloat(Number(r/150).toFixed(2))})}query(n.box).find(".palette, .rainbow, .alpha").off(".w2color").on(e+".w2color",function(e){var t=query(this).find(".value1, .value2"),i=parseInt(t.prop("clientWidth"))/2;t.hasClass("move-x")&&t.css({left:e.offsetX-i+"px"});t.hasClass("move-y")&&t.css({top:e.offsetY-i+"px"});a={el:t,x:e.pageX,y:e.pageY,width:t.prop("parentNode").clientWidth,height:t.prop("parentNode").clientHeight,left:parseInt(t.css("left")),top:parseInt(t.css("top"))},p(e),query("body").off(".w2color").on(l,p).on(s,r)})}}class MenuTooltip extends Tooltip{constructor(){super(),this.defaults=w2utils.extend({},this.defaults,{type:"normal",items:[],index:null,render:null,spinner:!1,msgNoItems:w2utils.lang("No items found"),topHTML:"",menuStyle:"",filter:!1,markSearch:!1,match:"contains",search:!1,altRows:!1,arrowSize:10,align:"left",position:"bottom|top",class:"w2ui-white",anchorClass:"w2ui-focus",autoShowOn:"focus",hideOn:["doc-click","focus-change","select"],onSelect:null,onSubMenu:null,onRemove:null})}attach(e,t){let i;1==arguments.length&&e.anchor?e=(i=e).anchor:2===arguments.length&&null!=t&&"object"==typeof t&&((i=t).anchor=e);t=i.hideOn;i=w2utils.extend({},this.defaults,i||{}),t&&(i.hideOn=t),i.style+="; padding: 0;",null==i.items&&(i.items=[]),i.html=this.getMenuHTML(i);let s=super.attach(i),l=s.overlay;return l.on("show:after.attach, update:after.attach",e=>{if(s.overlay?.box){let e="";l.selected=null,l.options.items=w2utils.normMenu(l.options.items),["INPUT","TEXTAREA"].includes(l.anchor.tagName)&&(e=l.anchor.value,l.selected=l.anchor.dataset.selectedIndex);var t=query(s.overlay.box).find(".w2ui-eaction"),t=(w2utils.bindEvents(t,this),this.applyFilter(l.name,null,e));l.tmp.searchCount=t,l.tmp.search=e,this.refreshSearch(l.name),this.initControls(s.overlay),this.refreshIndex(l.name)}}),l.on("hide:after.attach",e=>{w2tooltip.hide(l.name+"-tooltip")}),s.select=t=>(l.on("select.attach",e=>{t(e)}),s),s.remove=t=>(l.on("remove.attach",e=>{t(e)}),s),s.subMenu=t=>(l.on("subMenu.attach",e=>{t(e)}),s),s}update(e,t){var i,s=Tooltip.active[e];s?((i=s.options).items!=t&&(i.items=t),t=this.getMenuHTML(i),i.html!=t&&(i.html=t,s.needsUpdate=!0,this.show(e))):console.log(`Tooltip "${e}" is not displayed. Cannot update it.`)}initControls(i){query(i.box).find(".w2ui-menu:not(.w2ui-sub-menu)").off(".w2menu").on("mouseDown.w2menu",{delegate:".w2ui-menu-item"},e=>{var t=e.delegate.dataset;this.menuDown(i,e,t.index,t.parents)}).on((w2utils.isIOS?"touchStart":"click")+".w2menu",{delegate:".w2ui-menu-item"},e=>{var t=e.delegate.dataset;this.menuClick(i,e,parseInt(t.index),t.parents)}).find(".w2ui-menu-item").off(".w2menu").on("mouseEnter.w2menu",e=>{var t=e.target.dataset,t=i.options.items[t.index]?.tooltip;t&&w2tooltip.show({name:i.name+"-tooltip",anchor:e.target,html:t,position:"right|left",hideOn:["doc-click"]})}).on("mouseLeave.w2menu",e=>{w2tooltip.hide(i.name+"-tooltip")}),["INPUT","TEXTAREA"].includes(i.anchor.tagName)&&query(i.anchor).off(".w2menu").on("input.w2menu",e=>{}).on("keyup.w2menu",e=>{e._searchType="filter",this.keyUp(i,e)}),i.options.search&&query(i.box).find("#menu-search").off(".w2menu").on("keyup.w2menu",e=>{e._searchType="search",this.keyUp(i,e)})}getCurrent(e,t){var e=Tooltip.active[e.replace(/[\s\.#]/g,"_")],i=e.options;let s=(t||(e.selected??"")).split("-");var t=s.length-1,e=s[t],l=s.slice(0,s.length-1).join("-"),e=w2utils.isInt(e)?parseInt(e):0;let r=i.items;return s.forEach((e,t)=>{t +
    +
    + ${w2utils.lang("Loading...")} +
    + `;u=u||[],null==e&&(e=h.items),Array.isArray(e)||(e=[]);let c=0,t=null,i="",p=(!d&&h.search&&(i+=` + `,e.forEach(e=>e.hidden=!1)),!d&&h.topHTML&&(i+=`
    ${h.topHTML}
    `),` + ${i} +
    + `);return e.forEach((r,a)=>{t=r.icon;var n=(0`),s=``),"break"!==r.type&&null!=i&&""!==i&&"--"!=String(i).substr(0,2)){var o=["w2ui-menu-item"];1==h.altRows&&o.push(c%2==0?"w2ui-even":"w2ui-odd");let e=1,t=(""===s&&e++,null==r.count&&null==r.hotkey&&!0!==r.remove&&null==r.items&&e++,null==r.tooltip&&null!=r.hint&&(r.tooltip=r.hint),"");if(!0===r.remove)t='x';else if(null!=r.items){let e=[];"function"==typeof r.items?e=r.items(r):Array.isArray(r.items)&&(e=r.items),t="",l=` +
    + ${this.getMenuHTML(h,e,!0,u.concat(a))} +
    `}else null!=r.count&&(t+=""+r.count+""),null!=r.hotkey&&(t+=''+r.hotkey+"");!0===r.disabled&&o.push("w2ui-disabled"),!0===r._noSearchInside&&o.push("w2ui-no-search-inside"),""!==l&&(o.push("has-sub-menu"),r.expanded?o.push("expanded"):o.push("collapsed")),p+=` +
    +
    + ${s} + + +
    + `+l,c++}else{o=(i??"").replace(/^-+/g,"");p+=` +
    +
    + ${o?`
    ${o}
    `:""} +
    `}}e[a]=r}),0===c&&h.msgNoItems&&(p+=` +
    + ${w2utils.lang(h.msgNoItems)} +
    `),p+="
    "}refreshIndex(e){var t,i,e=Tooltip.active[e.replace(/[\s\.#]/g,"_")];e&&(e.displayed||this.show(e.name),t=query(e.box).find(".w2ui-overlay-body").get(0),i=query(e.box).find(".w2ui-menu-search, .w2ui-menu-top").get(0),query(e.box).find(".w2ui-menu-item.w2ui-selected").removeClass("w2ui-selected"),e=query(e.box).find(`.w2ui-menu-item[index="${e.selected}"]`).addClass("w2ui-selected").get(0))&&(e.offsetTop+e.clientHeight>t.clientHeight+t.scrollTop&&e.scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),e.offsetTop{var t;this.getCurrent(i,e.getAttribute("index")).item.hidden?query(e).hide():((t=s.tmp?.search)&&s.options.markSearch&&w2utils.marker(e,t,{onlyFirst:"begins"==s.options.match}),query(e).show())}),query(s.box).find(".w2ui-sub-menu").each(e=>{var t=query(e).find(".w2ui-menu-item").get().some(e=>"none"!=e.style.display);this.getCurrent(i,e.dataset.parent).item.expanded&&(t?query(e).parent().show():query(e).parent().hide())}),0!=s.tmp.searchCount&&0!=s.options?.items?.length||(0==query(s.box).find(".w2ui-no-items").length&&query(s.box).find(".w2ui-menu:not(.w2ui-sub-menu)").append(` +
    + ${w2utils.lang(s.options.msgNoItems)} +
    `),query(s.box).find(".w2ui-no-items").show()))}applyFilter(r,e,a){let n=0;var t=Tooltip.active[r.replace(/[\s\.#]/g,"_")];let o=t.options;if(!1!==o.filter){null==e&&(e=t.options.items),null==a&&(a=["INPUT","TEXTAREA"].includes(t.anchor.tagName)?t.anchor.value:"");let l=[];return o.selected&&(Array.isArray(o.selected)?l=o.selected.map(e=>e?.id??e):o.selected?.id&&(l=[o.selected.id])),e.forEach(e=>{let t="",i="";-1!==["is","begins","begins with"].indexOf(o.match)&&(t="^"),-1!==["is","ends","ends with"].indexOf(o.match)&&(i="$");try{new RegExp(t+a+i,"i").test(e.text)||"..."===e.text?e.hidden=!1:e.hidden=!0}catch(e){}var s;o.hideSelected&&l.includes(e.id)&&(e.hidden=!0),Array.isArray(e.items)&&0{e.hidden||e.disabled||e?.text?.startsWith("--")||(l.push(s.concat([t]).join("-")),Array.isArray(e.items)&&0{l=l[e].items}),l[i]);if(!n.disabled){let l=(i,s)=>{i.forEach((e,t)=>{e.id!=n.id&&(e.group===n.group&&e.checked&&(a.find(`.w2ui-menu-item[index="${(s?s+"-":"")+t}"] .w2ui-icon`).removeClass("w2ui-icon-check").addClass("w2ui-icon-empty"),i[t].checked=!1),Array.isArray(e.items))&&l(e.items,t)})};"check"!==e.type&&"radio"!==e.type||!1===n.group||query(t.target).hasClass("remove")||query(t.target).closest(".w2ui-menu-item").hasClass("has-sub-menu")||(n.checked="radio"==e.type||!n.checked,n.checked?("radio"===e.type&&query(t.target).closest(".w2ui-menu").find(".w2ui-icon").removeClass("w2ui-icon-check").addClass("w2ui-icon-empty"),"check"===e.type&&null!=n.group&&l(e.items),r.removeClass("w2ui-icon-empty").addClass("w2ui-icon-check")):"check"===e.type&&r.removeClass("w2ui-icon-check").addClass("w2ui-icon-empty")),query(t.target).hasClass("remove")||(a.find(".w2ui-menu-item").removeClass("w2ui-selected"),query(t.delegate).addClass("w2ui-selected"))}}menuClick(t,i,s,l){var r=t.options;let a=r.items;var n=query(i.delegate).closest(".w2ui-menu-item");let o=!r.hideOn.includes("select");(i.shiftKey||i.metaKey||i.ctrlKey)&&(o=!0),"string"==typeof l&&""!==l?l.split("-").forEach(e=>{a=a[e].items}):l=null;var h=(a="function"==typeof a?a({overlay:t,index:s,parentIndex:l,event:i}):a)[s];if(!h.disabled||query(i.target).hasClass("remove")){let e;if(query(i.target).hasClass("remove")){if(!0===(e=this.trigger("remove",{originalEvent:i,target:t.name,overlay:t,item:h,index:s,parentIndex:l,el:n[0]})).isCancelled)return;o=!r.hideOn.includes("item-remove"),n.remove()}else if(n.hasClass("has-sub-menu")){if(!0===(e=this.trigger("subMenu",{originalEvent:i,target:t.name,overlay:t,item:h,index:s,parentIndex:l,el:n[0]})).isCancelled)return;o=!0,n.hasClass("expanded")?(h.expanded=!1,n.removeClass("expanded").addClass("collapsed"),query(n.get(0).nextElementSibling).hide()):(h.expanded=!0,n.addClass("expanded").removeClass("collapsed"),query(n.get(0).nextElementSibling).show()),t.selected=parseInt(n.attr("index"))}else{r=this.findChecked(r.items);if(t.selected=parseInt(n.attr("index")),!0===(e=this.trigger("select",{originalEvent:i,target:t.name,overlay:t,item:h,index:s,parentIndex:l,selected:r,keepOpen:o,el:n[0]})).isCancelled)return;null!=h.keepOpen&&(o=h.keepOpen),["INPUT","TEXTAREA"].includes(t.anchor.tagName)&&(t.anchor.dataset.selected=h.id,t.anchor.dataset.selectedIndex=t.selected)}o||this.hide(t.name),e.finish()}}findChecked(e){let t=[];return e.forEach(e=>{e.checked&&t.push(e),Array.isArray(e.items)&&(t=t.concat(this.findChecked(e.items)))}),t}keyUp(s,l){var e,r=s.options,t=l.target.value;let a=!0,n=!1;switch(l.keyCode){case 8:""!==t||s.displayed||(a=!1);break;case 13:if(!s.displayed||!s.selected)return;var{index:i,parents:o}=this.getCurrent(s.name);l.delegate=query(s.box).find(".w2ui-selected").get(0),this.menuClick(s,l,parseInt(i),o),a=!1;break;case 27:a=!1,s.displayed?this.hide(s.name):(i=s.anchor,["INPUT","TEXTAREA"].includes(i.tagName)&&(i.value="",delete i.dataset.selected,delete i.dataset.selectedIndex));break;case 37:{if(!s.displayed)return;let{item:e,index:t,parents:i}=this.getCurrent(s.name);i&&(e=r.items[i],t=parseInt(i),i="",n=!0),Array.isArray(e?.items)&&0{var e=e.detail.overlay,t=e.anchor,i=e.options;["INPUT","TEXTAREA"].includes(t.tagName)&&!i.value&&t.value&&(e.tmp.initValue=t.value),delete e.newValue,delete e.newDate}),l.on("show:after.attach",e=>{s.overlay?.box&&this.initControls(s.overlay)}),l.on("update:after.attach",e=>{s.overlay?.box&&this.initControls(s.overlay)}),l.on("hide.attach",e=>{var e=e.detail.overlay,t=e.anchor;null!=e.newValue&&(e.newDate&&(e.newValue=e.newDate+" "+e.newValue),["INPUT","TEXTAREA"].includes(t.tagName)&&t.value!=e.newValue&&(t.value=e.newValue),!0!==(t=this.trigger("select",{date:e.newValue,target:e.name,overlay:e})).isCancelled)&&t.finish()}),s.select=t=>(l.on("select.attach",e=>{t(e)}),s),s}initControls(l){let r=l.options,t=e=>{let{month:t,year:i}=l.tmp;12<(t+=e)&&(t=1,i++),t<1&&(t=12,i--);e=this.getMonthHTML(r,t,i);Object.assign(l.tmp,e),query(l.box).find(".w2ui-overlay-body").html(e.html),this.initControls(l)},i=(e,t)=>{query(e.target).parent().find(".w2ui-jump-month, .w2ui-jump-year").removeClass("w2ui-selected"),query(e.target).addClass("w2ui-selected");e=new Date;let{jumpMonth:i,jumpYear:s}=l.tmp;(i=t&&(null==s&&(s=e.getFullYear()),null==i)?e.getMonth()+1:i)&&s&&(t=this.getMonthHTML(r,i,s),Object.assign(l.tmp,t),query(l.box).find(".w2ui-overlay-body").html(t.html),l.tmp.jump=!1,this.initControls(l))};query(l.box).find(".w2ui-cal-title").off(".calendar").on("click.calendar",e=>{var t,i;Object.assign(l.tmp,{jumpYear:null,jumpMonth:null}),l.tmp.jump?({month:t,year:i}=l.tmp,t=this.getMonthHTML(r,t,i),query(l.box).find(".w2ui-overlay-body").html(t.html),l.tmp.jump=!1):(query(l.box).find(".w2ui-overlay-body .w2ui-cal-days").replace(this.getYearHTML()),(i=query(l.box).find(`[name="${l.tmp.year}"]`).get(0))&&i.scrollIntoView(!0),l.tmp.jump=!0),this.initControls(l),e.stopPropagation()}).find(".w2ui-cal-previous").off(".calendar").on("click.calendar",e=>{t(-1),e.stopPropagation()}).parent().find(".w2ui-cal-next").off(".calendar").on("click.calendar",e=>{t(1),e.stopPropagation()}),query(l.box).find(".w2ui-cal-now").off(".calendar").on("click.calendar",e=>{"datetime"==r.type?l.newDate?l.newValue=w2utils.formatTime(new Date,r.format.split("|")[1]):l.newValue=w2utils.formatDateTime(new Date,r.format):"date"==r.type?l.newValue=w2utils.formatDate(new Date,r.format):"time"==r.type&&(l.newValue=w2utils.formatTime(new Date,r.format)),this.hide(l.name)}),query(l.box).off(".calendar").on("click.calendar",{delegate:".w2ui-day.w2ui-date"},e=>{"datetime"==r.type?(l.newDate=query(e.target).attr("date"),query(l.box).find(".w2ui-overlay-body").html(this.getHourHTML(l.options).html),this.initControls(l)):(l.newValue=query(e.target).attr("date"),this.hide(l.name))}).on("click.calendar",{delegate:".w2ui-jump-month"},e=>{l.tmp.jumpMonth=parseInt(query(e.target).attr("name")),i(e)}).on("dblclick.calendar",{delegate:".w2ui-jump-month"},e=>{l.tmp.jumpMonth=parseInt(query(e.target).attr("name")),i(e,!0)}).on("click.calendar",{delegate:".w2ui-jump-year"},e=>{l.tmp.jumpYear=parseInt(query(e.target).attr("name")),i(e)}).on("dblclick.calendar",{delegate:".w2ui-jump-year"},e=>{l.tmp.jumpYear=parseInt(query(e.target).attr("name")),i(e,!0)}).on("click.calendar",{delegate:".w2ui-time.hour"},e=>{var e=query(e.target).attr("hour");let t=this.str2min(r.value)%60;l.tmp.initValue&&!r.value&&(t=this.str2min(l.tmp.initValue)%60),r.noMinutes?(l.newValue=this.min2str(60*e,r.format),this.hide(l.name)):(l.newValue=e+":"+t,e=this.getMinHTML(e,r).html,query(l.box).find(".w2ui-overlay-body").html(e),this.initControls(l))}).on("click.calendar",{delegate:".w2ui-time.min"},e=>{e=60*Math.floor(this.str2min(l.newValue)/60)+parseInt(query(e.target).attr("min"));l.newValue=this.min2str(e,r.format),this.hide(l.name)})}getMonthHTML(l,r,e){var t=w2utils.settings.fulldays.slice(),i=w2utils.settings.shortdays.slice();"M"!==w2utils.settings.weekStarts&&(t.unshift(t.pop()),i.unshift(i.pop()));let s=new Date;var t="datetime"===l.type?w2utils.isDateTime(l.value,l.format,!0):w2utils.isDate(l.value,l.format,!0),a=w2utils.formatDate(t);null!=r&&null!=e||(e=(t||s).getFullYear(),r=t?t.getMonth()+1:s.getMonth()+1),12${i[e]}`}let c=` +
    +
    +
    +
    +
    +
    +
    + ${w2utils.settings.fullmonths[r-1]}, ${e} + +
    +
    + ${o} + `,p=new Date(e+`/${r}/1`);t=(p=new Date(p.getTime()+432e5)).getDay();"M"==w2utils.settings.weekStarts&&n--,0 + ${g} +
    `,p=new Date(p.getTime()+864e5)}return c+="",l.btnNow&&(t=w2utils.lang("Today"+("datetime"==l.type?" & Now":"")),c+=`
    ${t}
    `),{html:c,month:r,year:e}}getYearHTML(){let t="",i="";for(let e=0;e${w2utils.settings.shortmonths[e]}`;for(let e=w2utils.settings.dateStartYear;e<=w2utils.settings.dateEndYear;e++)i+=`
    ${e}
    `;return`
    +
    ${t}
    +
    ${i}
    +
    `}getHourHTML(l){(l=l??{}).format||(l.format=w2utils.settings.timeFormat);var r=-1${e}
    `}return{html:`
    +
    ${w2utils.lang("Select Hour")}
    +
    +
    ${n[0]}
    +
    ${n[1]}
    +
    ${n[2]}
    +
    + ${l.btnNow?`
    ${w2utils.lang("Now")}
    `:""} +
    `}}getMinHTML(i,s){null==i&&(i=0),(s=s??{}).format||(s.format=w2utils.settings.timeFormat);var l=-1${n}
    `}return{html:`
    +
    ${w2utils.lang("Select Minute")}
    +
    +
    ${a[0]}
    +
    ${a[1]}
    +
    ${a[2]}
    +
    + ${s.btnNow?`
    ${w2utils.lang("Now")}
    `:""} +
    `}}inRange(i,s,e){let l=!1;if("date"===s.type){var r=w2utils.isDate(i,s.format,!0);if(r){if(s.start||s.end){var a="string"==typeof s.start?s.start:query(s.start).val(),n="string"==typeof s.end?s.end:query(s.end).val();let e=w2utils.isDate(a,s.format,!0),t=w2utils.isDate(n,s.format,!0);a=new Date(r);e=e||a,t=t||a,a>=e&&a<=t&&(l=!0)}else l=!0;Array.isArray(s.blockDates)&&s.blockDates.includes(i)&&(l=!1),Array.isArray(s.blockWeekdays)&&s.blockWeekdays.includes(r.getDay())&&(l=!1)}}else if("time"===s.type)if(s.start||s.end){n=this.str2min(i);let e=this.str2min(s.start),t=this.str2min(s.end);e=e||n,t=t||n,n>=e&&n<=t&&(l=!0)}else l=!0;else"datetime"===s.type&&(a=w2utils.isDateTime(i,s.format,!0))&&(r=s.format.split("|").map(e=>e.trim()),e?(n=w2utils.formatDate(a,r[0]),i=w2utils.extend({},s,{type:"date",format:r[0]}),this.inRange(n,i)&&(l=!0)):(e=w2utils.formatTime(a,r[1]),n={type:"time",format:r[1],start:s.startTime,end:s.endTime},this.inRange(e,n)&&(l=!0)));return l}str2min(e){var t;return"string"!=typeof e||2!==(t=e.split(":")).length?null:(t[0]=parseInt(t[0]),t[1]=parseInt(t[1]),-1!==e.indexOf("pm")&&12!==t[0]&&(t[0]+=12),e.includes("am")&&12==t[0]&&(t[0]=0),60*t[0]+t[1])}min2str(e,t){let i="";1440<=e&&(e%=1440),e<0&&(e=1440+e);var s=Math.floor(e/60),e=(e%60<10?"0":"")+e%60;return t=t||w2utils.settings.timeFormat,i=-1!==t.indexOf("h24")?s+":"+e:(s<=12?s:s-12)+":"+e+" "+(12<=s?"pm":"am")}}let w2tooltip=new Tooltip,w2menu=new MenuTooltip,w2color=new ColorTooltip,w2date=new DateTooltip;class w2toolbar extends w2base{constructor(e){super(e.name),this.box=null,this.name=null,this.routeData={},this.items=[],this.right="",this.tooltip="top|left",this.onClick=null,this.onMouseDown=null,this.onMouseUp=null,this.onMouseEnter=null,this.onMouseLeave=null,this.onRender=null,this.onRefresh=null,this.onResize=null,this.onDestroy=null,this.item_template={id:null,type:"button",text:null,html:"",tooltip:null,count:null,hidden:!1,disabled:!1,checked:!1,icon:null,route:null,arrow:null,style:null,group:null,items:null,selected:null,color:null,overlay:{anchorClass:""},onClick:null,onRefresh:null},this.last={badge:{}};var t=e.items;delete e.items,Object.assign(this,e),Array.isArray(t)&&this.add(t,!0),e.items=t,"string"==typeof this.box&&(this.box=query(this.box).get(0)),this.box&&this.render(this.box)}add(e,t){this.insert(null,e,t)}insert(r,e,a){(e=Array.isArray(e)?e:[e]).forEach((e,t,i)=>{"string"==typeof e&&(e=i[t]={id:e,text:e});var l,s=["button","check","radio","drop","menu","menu-radio","menu-check","color","text-color","html","break","spacer","new-line"];if(s.includes(String(e.type)))if(null!=e.id||["break","spacer","new-line"].includes(e.type)){if(null==e.type)console.log('ERROR: The parameter "type" is required but not supplied.',e);else if(w2utils.checkUniqueId(e.id,this.items,"toolbar",this.name)){let s=w2utils.extend({},this.item_template,e);"menu-check"==s.type?(Array.isArray(s.selected)||(s.selected=[]),Array.isArray(s.items)&&s.items.forEach(e=>{(e="string"==typeof e?i[t]={id:e,text:e}:e).checked&&!s.selected.includes(e.id)&&s.selected.push(e.id),!e.checked&&s.selected.includes(e.id)&&(e.checked=!0),null==e.checked&&(e.checked=!1)})):"menu-radio"==s.type&&Array.isArray(s.items)&&s.items.forEach((e,t,i)=>{(e="string"==typeof e?i[t]={id:e,text:e}:e).checked&&null==s.selected?s.selected=e.id:e.checked=!1,e.checked||s.selected!=e.id||(e.checked=!0),null==e.checked&&(e.checked=!1)}),null==r?this.items.push(s):(l=this.get(r,!0),this.items=this.items.slice(0,l).concat([s],this.items.slice(l))),s.line=s.line??1,!0!==a&&this.refresh(s.id)}}else console.log('ERROR: The parameter "id" is required but not supplied.',e);else console.log('ERROR: The parameter "type" should be one of the following:',s,`, but ${e.type} is supplied.`,e)}),!0!==a&&this.resize()}remove(){let i=0;return Array.from(arguments).forEach(e=>{var t=this.get(e);t&&-1==String(e).indexOf(":")&&(i++,query(this.box).find("#tb_"+this.name+"_item_"+w2utils.escapeId(t.id)).remove(),null!=(e=this.get(t.id,!0)))&&this.items.splice(e,1)}),this.resize(),i}set(e,t){var i=this.get(e);return null!=i&&(Object.assign(i,t),this.refresh(String(e).split(":")[0]),!0)}get(e,i){if(0===arguments.length){var t=[];for(let e=0;e span`);0{var t=this.get(e);t&&(t.hidden=!1,i.push(String(e).split(":")[0]))}),setTimeout(()=>{i.forEach(e=>{this.refresh(e),this.resize()})},15),i}hide(){let i=[];return Array.from(arguments).forEach(e=>{var t=this.get(e);t&&(t.hidden=!0,i.push(String(e).split(":")[0]))}),setTimeout(()=>{i.forEach(e=>{this.refresh(e),this.tooltipHide(e),this.resize()})},15),i}enable(){let i=[];return Array.from(arguments).forEach(e=>{var t=this.get(e);t&&(t.disabled=!1,i.push(String(e).split(":")[0]))}),setTimeout(()=>{i.forEach(e=>{this.refresh(e)})},15),i}disable(){let i=[];return Array.from(arguments).forEach(e=>{var t=this.get(e);t&&(t.disabled=!0,i.push(String(e).split(":")[0]))}),setTimeout(()=>{i.forEach(e=>{this.refresh(e),this.tooltipHide(e)})},15),i}check(){let i=[];return Array.from(arguments).forEach(e=>{var t=this.get(e);t&&-1==String(e).indexOf(":")&&(t.checked=!0,i.push(String(e).split(":")[0]))}),setTimeout(()=>{i.forEach(e=>{this.refresh(e)})},15),i}uncheck(){let i=[];return Array.from(arguments).forEach(e=>{var t=this.get(e);t&&-1==String(e).indexOf(":")&&(["menu","menu-radio","menu-check","drop","color","text-color"].includes(t.type)&&t.checked&&w2tooltip.hide(this.name+"-drop"),t.checked=!1,i.push(String(e).split(":")[0]))}),setTimeout(()=>{i.forEach(e=>{this.refresh(e)})},15),i}click(e,t){var i=String(e).split(":");let l=this.get(i[0]),r=l&&l.items?w2utils.normMenu.call(this,l.items,l):[];if(1{var t=(e,t)=>{let i=this;return function(){i.set(e,{checked:!1})}},i=query(this.box).find("#tb_"+this.name+"_item_"+w2utils.escapeId(l.id));if(w2utils.isPlainObject(l.overlay)||(l.overlay={}),"drop"==l.type&&w2tooltip.show(w2utils.extend({html:l.html,class:"w2ui-white",hideOn:["doc-click"]},l.overlay,{anchor:i[0],name:this.name+"-drop",data:{item:l,btn:s}})).hide(t(l.id,s)),["menu","menu-radio","menu-check"].includes(l.type)){let e="normal";"menu-radio"==l.type&&(e="radio",r.forEach(e=>{l.selected==e.id?e.checked=!0:e.checked=!1})),"menu-check"==l.type&&(e="check",r.forEach(e=>{Array.isArray(l.selected)&&l.selected.includes(e.id)?e.checked=!0:e.checked=!1})),w2menu.show(w2utils.extend({items:r},l.overlay,{type:e,name:this.name+"-drop",anchor:i[0],data:{item:l,btn:s}})).hide(t(l.id,s)).remove(e=>{this.menuClick({name:this.name,remove:!0,item:l,subItem:e.detail.item,originalEvent:e})}).select(e=>{this.menuClick({name:this.name,item:l,subItem:e.detail.item,originalEvent:e})})}["color","text-color"].includes(l.type)&&w2color.show(w2utils.extend({color:l.color},l.overlay,{anchor:i[0],name:this.name+"-drop",data:{item:l,btn:s}})).hide(t(l.id,s)).select(e=>{null!=e.detail.color&&this.colorClick({name:this.name,item:l,color:e.detail.color})})},0)}if(["check","menu","menu-radio","menu-check","drop","color","text-color"].includes(l.type)&&(l.checked=!l.checked,l.checked?query(this.box).find(s).addClass("checked"):query(this.box).find(s).removeClass("checked")),l.route){let t=String("/"+l.route).replace(/\/{2,}/g,"/");var n=w2utils.parseRoute(t);if(0{window.location.hash=t},1)}this.tooltipShow(e),i.finish()}}}scroll(n,o,h){return new Promise((e,t)=>{var i=query(this.box).find(`.w2ui-tb-line:nth-child(${o}) .w2ui-scroll-wrapper`),s=i.get(0).scrollLeft,l=i.find(".w2ui-tb-right").get(0),r=i.parent().get(0).getBoundingClientRect().width,a=s+parseInt(l.offsetLeft)+parseInt(l.clientWidth);switch(n){case"left":(scroll=s-r+50)<=0&&(scroll=0),i.get(0).scrollTo({top:0,left:scroll,behavior:h?"atuo":"smooth"});break;case"right":(scroll=s+r-50)>=a-r&&(scroll=a-r),i.get(0).scrollTo({top:0,left:scroll,behavior:h?"atuo":"smooth"})}setTimeout(()=>{this.resize(),e()},h?0:500)})}render(e){var s=Date.now(),l=("string"==typeof e&&(e=query(e).get(0)),this.trigger("render",{target:this.name,box:e??this.box}));if(!0!==l.isCancelled&&(null!=e&&(0 ",r),null!=r.hint&&console.log("NOTICE: toolbar item.hint property is deprecated, please use item.tooltip. Item -> ",r),0!==e&&"new-line"!=r.type||(i++,t+=` +
    +
    +
    ${this.right[i-1]??""}
    +
    +
    +
    +
    + `),r.line=i)}return query(this.box).attr("name",this.name).addClass("w2ui-reset w2ui-toolbar").html(t),0{this.resize()}),this.last.observeResize.observe(this.box),this.refresh(),this.resize(),l.finish(),Date.now()-s}}refresh(t){var i=Date.now(),l=this.trigger("refresh",{target:null!=t?t:this.name,item:this.get(t)});if(!0!==l.isCancelled){let e;if(null==t)for(let e=0;e{i[e].anchor==s.get(0)&&(i[e].anchor=t)})}if(["menu","menu-radio","menu-check"].includes(r.type)&&r.checked){let t=Array.isArray(r.selected)?r.selected:[r.selected];r.items.forEach(e=>{t.includes(e.id)?e.checked=!0:e.checked=!1}),w2menu.update(this.name+"-drop",r.items)}return"function"==typeof r.onRefresh&&e.finish(),l.finish(),Date.now()-i}}}}resize(){var e=Date.now(),t=this.trigger("resize",{target:this.name});if(!0!==t.isCancelled)return query(this.box).find(".w2ui-tb-line").each(e=>{var e=query(e),t=(e.find(".w2ui-scroll-left, .w2ui-scroll-right").hide(),e.find(".w2ui-scroll-wrapper").get(0)),i=e.find(".w2ui-tb-right"),s=e.get(0).getBoundingClientRect().width,i=0e.id==t)}),""),s="function"==typeof i.text?i.text.call(this,i):i.text;i.icon&&(t=i.icon,"function"==typeof i.icon&&(t=i.icon.call(this,i)),t=`
    ${t="<"!==String(t).slice(0,1)?``:t}
    `);var l=["w2ui-tb-button"];switch(i.checked&&l.push("checked"),i.disabled&&l.push("disabled"),i.hidden&&l.push("hidden"),t||l.push("no-icon"),i.type){case"color":case"text-color":"string"==typeof i.color&&("#"==i.color.slice(0,1)&&(i.color=i.color.slice(1)),[3,6,8].includes(i.color.length))&&(i.color="#"+i.color),"color"==i.type&&(s=` + `+(i.text?`
    ${w2utils.lang(i.text)}
    `:"")),"text-color"==i.type&&(s=''+(i.text?w2utils.lang(i.text):"Aa")+"");case"menu":case"menu-check":case"menu-radio":case"button":case"check":case"radio":case"drop":var r=!0===i.arrow||!1!==i.arrow&&["menu","menu-radio","menu-check","drop","color","text-color"].includes(i.type);e=` +
    + ${t} + ${""!=s?`
    + ${w2utils.lang(s)} + ${null!=i.count?w2utils.stripSpaces(` + ${i.count} + `):""} + ${r?'':""} +
    `:""} +
    + `;break;case"break":e=`
    +   +
    `;break;case"spacer":e=`
    +
    `;break;case"html":e=`
    + ${"function"==typeof i.html?i.html.call(this,i):i.html} +
    `}return e}tooltipShow(t){if(null!=this.tooltip){var i=query(this.box).find("#tb_"+this.name+"_item_"+w2utils.escapeId(t)).get(0),t=this.get(t),s=this.tooltip;let e=t.tooltip;"function"==typeof e&&(e=e.call(this,t)),["menu","menu-radio","menu-check","drop","color","text-color"].includes(t.type)&&1==t.checked||w2tooltip.show({anchor:i,name:this.name+"-tooltip",html:e,position:s})}}tooltipHide(e){null!=this.tooltip&&w2tooltip.hide(this.name+"-tooltip")}menuClick(t){if(t.item&&!t.item.disabled){var i=this.trigger(!0!==t.remove?"click":"remove",{target:t.item.id+":"+t.subItem.id,item:t.item,subItem:t.subItem,originalEvent:t.originalEvent});if(!0!==i.isCancelled){let l=t.subItem,r=this.get(t.item.id),e=r.items;if("function"==typeof e&&(e=r.items()),"menu"==r.type&&(r.selected=l.id),"menu-radio"==r.type&&(r.selected=l.id,Array.isArray(e)&&e.forEach(e=>{!0===e.checked&&delete e.checked,Array.isArray(e.items)&&e.items.forEach(e=>{!0===e.checked&&delete e.checked})}),l.checked=!0),"menu-check"==r.type)if(Array.isArray(r.selected)||(r.selected=[]),null==l.group){var a=r.selected.indexOf(l.id);-1==a?(r.selected.push(l.id),l.checked=!0):(r.selected.splice(a,1),l.checked=!1)}else if(!1!==l.group){let i=[];a=r.selected.indexOf(l.id);let s=e=>{e.forEach(e=>{var t;e.group===l.group&&-1!=(t=r.selected.indexOf(e.id))&&(e.id!=l.id&&i.push(e.id),r.selected.splice(t,1)),Array.isArray(e.items)&&s(e.items)})};s(e),-1==a&&(r.selected.push(l.id),l.checked=!0)}if("string"==typeof l.route){let t=""!==l.route?String("/"+l.route).replace(/\/{2,}/g,"/"):"";var s=w2utils.parseRoute(t);if(0{window.location.hash=t},1)}this.refresh(t.item.id),i.finish()}}}colorClick(e){var t;e.item&&!e.item.disabled&&!0!==(t=this.trigger("click",{target:e.item.id,item:e.item,color:e.color,final:e.final,originalEvent:e.originalEvent})).isCancelled&&(e.item.color=e.color,this.refresh(e.item.id),t.finish())}mouseAction(e,t,i,s){var l=this.get(s),e=this.trigger("mouse"+i,{target:s,item:l,object:l,originalEvent:e});if(!0!==e.isCancelled&&!l.disabled&&!l.hidden){switch(i){case"Enter":query(t).addClass("over"),this.tooltipShow(s);break;case"Leave":query(t).removeClass("over down"),this.tooltipHide(s);break;case"Down":query(t).addClass("down");break;case"Up":query(t).removeClass("down")}e.finish()}}}class w2sidebar extends w2base{constructor(e){super(e.name),this.name=null,this.box=null,this.sidebar=null,this.parent=null,this.nodes=[],this.menu=[],this.routeData={},this.selected=null,this.icon=null,this.style="",this.topHTML="",this.bottomHTML="",this.flatButton=!1,this.keyboard=!0,this.flat=!1,this.hasFocus=!1,this.levelPadding=12,this.skipRefresh=!1,this.tabIndex=null,this.handle={size:0,style:"",html:"",tooltip:""},this.onClick=null,this.onDblClick=null,this.onMouseEnter=null,this.onMouseLeave=null,this.onContextMenu=null,this.onMenuClick=null,this.onExpand=null,this.onCollapse=null,this.onKeydown=null,this.onRender=null,this.onRefresh=null,this.onResize=null,this.onDestroy=null,this.onFocus=null,this.onBlur=null,this.onFlat=null,this.node_template={id:null,text:"",order:null,count:null,icon:null,nodes:[],style:"",route:null,selected:!1,expanded:!1,hidden:!1,disabled:!1,group:!1,groupShowHide:!0,collapsible:!1,plus:!1,onClick:null,onDblClick:null,onContextMenu:null,onExpand:null,onCollapse:null,parent:null,sidebar:null},this.last={badge:{}};var t=e.nodes;delete e.nodes,Object.assign(this,e),Array.isArray(t)&&this.add(t),e.nodes=t,"string"==typeof this.box&&(this.box=query(this.box).get(0)),this.box&&this.render(this.box)}add(e,t){return 1==arguments.length&&(t=arguments[0],e=this),"string"==typeof e&&(e=this.get(e)),this.insert(e=null!=e&&""!=e?e:this,null,t)}insert(t,i,s){let l,r,a,n,o;if(2==arguments.length&&"string"==typeof t)if(s=arguments[1],null!=(i=arguments[0])){if(null==(r=this.get(i)))return null!=(s=Array.isArray(s)?s:[s])[0].caption&&null==s[0].text&&(console.log("NOTICE: sidebar node.caption property is deprecated, please use node.text. Node -> ",s[0]),s[0].text=s[0].caption),l=s[0].text,console.log('ERROR: Cannot insert node "'+l+'" because cannot find node "'+i+'" to insert before.'),null;t=this.get(i).parent}else t=this;null!=(t="string"==typeof t?this.get(t):t)&&""!=t||(t=this),Array.isArray(s)||(s=[s]);for(let e=0;e{null!=(i=this.get(e))&&(null!=this.selected&&this.selected===i.id&&(this.selected=null),null!=(e=this.get(i.parent,e,!0)))&&(i.parent.nodes[e].selected&&i.sidebar.unselect(i.id),i.parent.nodes.splice(e,1),i.parent.collapsible=0{var e=i.nodes&&0{e.nodes&&0{t.call(this,e),e.nodes&&0{-1===e.text.toLowerCase().indexOf(i)?e.hidden=!0:(t++,function e(t){t.parent&&(t.parent.hidden=!1,e(t.parent))}(e),e.hidden=!1)}),this.refresh(),t}show(){let t=[];return Array.from(arguments).forEach(e=>{e=this.get(e);null!=e&&!1!==e.hidden&&(e.hidden=!1,t.push(e.id))}),0{e=this.get(e);null!=e&&!0!==e.hidden&&(e.hidden=!0,t.push(e.id))}),0{e=this.get(e);null!=e&&!1!==e.disabled&&(e.disabled=!1,t.push(e.id))}),0{e=this.get(e);null!=e&&!0!==e.disabled&&(e.disabled=!0,e.selected&&this.unselect(e.id),t.push(e.id))}),0{t.refresh(e)},0),!0):void 0)}expand(e){var t=this.get(e),i=this.trigger("expand",{target:e,object:t});if(!0!==i.isCancelled)return query(this.box).find("#node_"+w2utils.escapeId(e)+"_sub").show(),query(this.box).find("#node_"+w2utils.escapeId(e)+" .w2ui-collapsed").removeClass("w2ui-collapsed").addClass("w2ui-expanded"),t.expanded=!0,i.finish(),this.refresh(e),!0}collapseAll(t){if(null==(t="string"==typeof(t=null==t?this:t)?this.get(t):t).nodes)return!1;for(let e=0;e{var t=query(e).attr("id").replace("node_",""),t=a.get(t);null!=t&&(t.selected=!1),query(e).removeClass("w2ui-selected").find(".w2ui-icon").removeClass("w2ui-icon-selected")});let t=query(a.box).find("#node_"+w2utils.escapeId(l)),s=query(a.box).find("#node_"+w2utils.escapeId(a.selected));t.addClass("w2ui-selected").find(".w2ui-icon").addClass("w2ui-icon-selected"),setTimeout(()=>{var e=a.trigger("click",{target:l,originalEvent:r,node:n,object:n});if(!0===e.isCancelled)t.removeClass("w2ui-selected").find(".w2ui-icon").removeClass("w2ui-icon-selected"),s.addClass("w2ui-selected").find(".w2ui-icon").addClass("w2ui-icon-selected");else{if(null!=s&&(s.selected=!1),a.get(l).selected=!0,a.selected=l,"string"==typeof n.route){let t=""!==n.route?String("/"+n.route).replace(/\/{2,}/g,"/"):"";var i=w2utils.parseRoute(t);if(0{window.location.hash=t},1)}e.finish()}},1)}}focus(e){let t=this;e=this.trigger("focus",{target:this.name,originalEvent:e});if(!0===e.isCancelled)return!1;this.hasFocus=!0,query(this.box).find(".w2ui-sidebar-body").addClass("w2ui-focus"),setTimeout(()=>{var e=query(t.box).find("#sidebar_"+t.name+"_focus").get(0);document.activeElement!=e&&e.focus()},10),e.finish()}blur(e){e=this.trigger("blur",{target:this.name,originalEvent:e});if(!0===e.isCancelled)return!1;this.hasFocus=!1,query(this.box).find(".w2ui-sidebar-body").removeClass("w2ui-focus"),e.finish()}keydown(e){let a=this,t=a.get(a.selected);var i;function s(e,t){null==e||e.hidden||e.disabled||e.group||(a.click(e.id,t),a.inView(e.id))||a.scrollIntoView(e.id)}function l(e,t){for(e=t(e);null!=e&&(e.hidden||e.disabled)&&!e.group;)e=t(e);return e}function r(e){if(null==e)return null;var t=e.parent,e=a.get(e.id,!0);let i=0t.clientHeight+t.scrollTop)}scrollIntoView(i,s){return new Promise((e,t)=>{null==i&&(i=this.selected),null!=this.get(i)&&(query(this.box).find("#node_"+w2utils.escapeId(i)).get(0).scrollIntoView({block:"center",inline:"center",behavior:s?"atuo":"smooth"}),setTimeout(()=>{this.resize(),e()},s?0:500))})}dblClick(e,t){var i=this.get(e),t=this.trigger("dblClick",{target:e,originalEvent:t,object:i});!0!==t.isCancelled&&(this.toggle(e),t.finish())}contextMenu(t,i){var e=this.get(t),s=(t!=this.selected&&this.click(t),this.trigger("contextMenu",{target:t,originalEvent:i,object:e,allowOnDisabled:!1}));!0===s.isCancelled||e.disabled&&!s.allowOnDisabled||(0{this.menuClick(t,parseInt(e.detail.index),i)}),i.preventDefault&&i.preventDefault(),s.finish())}menuClick(e,t,i){e=this.trigger("menuClick",{target:e,originalEvent:i,menuIndex:t,menuItem:this.menu[t]});!0!==e.isCancelled&&e.finish()}goFlat(){var e=this.trigger("flat",{goFlat:!this.flat});!0!==e.isCancelled&&(this.flat=!this.flat,this.refresh(),e.finish())}render(e){var i=Date.now();let s=this;"string"==typeof e&&(e=query(e).get(0));var l=this.trigger("render",{target:this.name,box:e??this.box});if(!0!==l.isCancelled&&(null!=e&&(0 +
    + +
    +
    + `);e=query(this.box).get(0).getBoundingClientRect();query(this.box).find(":scope > div").css({width:e.width+"px",height:e.height+"px"}),query(this.box).get(0).style.cssText+=this.style;let t;return query(this.box).find("#sidebar_"+this.name+"_focus").on("focus",function(e){clearTimeout(t),s.hasFocus||s.focus(e)}).on("blur",function(e){t=setTimeout(()=>{s.hasFocus&&s.blur(e)},100)}).on("keydown",function(e){9!=e.keyCode&&w2ui[s.name].keydown.call(w2ui[s.name],e)}),query(this.box).off("mousedown").on("mousedown",function(t){setTimeout(()=>{var e;-1==["INPUT","TEXTAREA","SELECT"].indexOf(t.target.tagName.toUpperCase())&&(e=query(s.box).find("#sidebar_"+s.name+"_focus"),document.activeElement!=e.get(0))&&e.get(0).focus()},1)}),this.last.observeResize=new ResizeObserver(()=>{this.resize()}),this.last.observeResize.observe(this.box),l.finish(),this.refresh(),Date.now()-i}}update(e,t){var i,s,e=this.get(e);let l;return e&&(i=query(this.box).find("#node_"+w2utils.escapeId(e.id)),e.group?(t.text&&(e.text=t.text,i.find(".w2ui-group-text").replace("function"==typeof e.text?e.text.call(this,e):''+e.text+""),delete t.text),t.class&&(e.class=t.class,l=i.data("level"),i.get(0).className="w2ui-node-group w2ui-level-"+l+(e.class?" "+e.class:""),delete t.class),t.style&&(e.style=t.style,i.get(0).nextElementSibling.style=e.style+";"+(!e.hidden&&e.expanded?"":"display: none;"),delete t.style)):(t.icon&&0<(s=i.find(".w2ui-node-image > span")).length&&(e.icon=t.icon,s[0].className="function"==typeof e.icon?e.icon.call(this,e):e.icon,delete t.icon),t.count&&(e.count=t.count,i.find(".w2ui-node-count").html(e.count),0`),null!=l||""===this.topHTML&&""===e||(query(this.box).find(".w2ui-sidebar-top").html(this.topHTML+e),query(this.box).find(".w2ui-sidebar-body").css("top",query(this.box).find(".w2ui-sidebar-top").get(0)?.clientHeight+"px"),query(this.box).find(".w2ui-flat").off("click").on("click",e=>{this.goFlat()})),null!=l&&""!==this.bottomHTML&&(query(this.box).find(".w2ui-sidebar-bottom").html(this.bottomHTML),query(this.box).find(".w2ui-sidebar-body").css("bottom",query(this.box).find(".w2ui-sidebar-bottom").get(0)?.clientHeight+"px")),query(this.box).find(":scope > div").removeClass("w2ui-sidebar-flat").addClass(this.flat?"w2ui-sidebar-flat":"").css({width:query(this.box).get(0)?.clientWidth+"px",height:query(this.box).get(0)?.clientHeight+"px"}),0'),query(this.box).find(o).remove(),query(this.box).find(i).remove(),query(this.box).find("#sidebar_"+this.name+"_tmp").before(s),query(this.box).find("#sidebar_"+this.name+"_tmp").remove());var l=query(this.box).find(":scope > div").get(0),d={top:l?.scrollTop,left:l?.scrollLeft};query(this.box).find(i).html("");for(let e=0;e ",t),t.text=t.caption),Array.isArray(t.nodes)&&0${e}
    `),i=` +
    + ${t.groupShowHide&&t.collapsible?`${!t.hidden&&t.expanded?w2utils.lang("Hide"):w2utils.lang("Show")}`:""} ${e} +
    +
    +
    `,h.flat&&(i=` +
     
    +
    `)}else{t.selected&&!t.disabled&&(h.selected=t.id),l="",s&&(l=` +
    + +
    `);let e="";var a=null!=t.count?`
    + ${t.count} +
    `:"",n=(!0===t.collapsible&&(e=`
    `),w2utils.lang("function"==typeof t.text?t.text.call(h,t):t.text)),o=["w2ui-node","w2ui-level-"+r,"w2ui-eaction"];t.selected&&o.push("w2ui-selected"),t.disabled&&o.push("w2ui-disabled"),t.class&&o.push(t.class),i=` +
    + ${h.handle.html?`
    + ${"function"==typeof h.handle.html?h.handle.html.call(h,t):h.handle.html} +
    `:""} +
    + ${e} ${l} ${a} +
    ${n}
    +
    +
    +
    `,h.flat&&(i=` +
    +
    ${l}
    +
    +
    `)}return i}}}}mouseAction(e,t,i,s,l){var r=this.get(i),a=w2utils.lang("function"==typeof r.text?r.text.call(this,r):r.text)+(r.count||0===r.count?' - '+r.count+"":""),e=this.trigger("mouse"+e,{target:i,node:r,tooltip:a,originalEvent:s});"tooltip"==l&&this.tooltip(t,a,i),"handle"==l&&this.handleTooltip(t,i),e.finish()}tooltip(e,t,i){e=query(e).find(".w2ui-node-data");""!==t?w2tooltip.show({anchor:e.get(0),name:this.name+"_tooltip",html:t,position:"right|left"}):w2tooltip.hide(this.name+"_tooltip")}handleTooltip(e,t){let i=this.handle.tooltip;""!==(i="function"==typeof i?i(t):i)&&null!=t?w2tooltip.show({anchor:e,name:this.name+"_tooltip",html:i,position:"top|bottom"}):w2tooltip.hide(this.name+"_tooltip")}showPlus(e,t){query(e).find("span:nth-child(1)").css("color",t)}resize(){var e,t=Date.now(),i=this.trigger("resize",{target:this.name});if(!0!==i.isCancelled)return e=query(this.box).get(0).getBoundingClientRect(),query(this.box).css("overflow","hidden"),query(this.box).find(":scope > div").css({width:e.width+"px",height:e.height+"px"}),i.finish(),Date.now()-t}destroy(){var e=this.trigger("destroy",{target:this.name});!0!==e.isCancelled&&(0{var t,i;null==e.id?console.log(`ERROR: The parameter "id" is required but not supplied. (obj: ${this.name})`):w2utils.checkUniqueId(e.id,this.tabs,"tabs",this.name)&&(e=Object.assign({},this.tab_template,e),null==s?(this.tabs.push(e),l.push(this.animateInsert(null,e))):(t=this.get(s,!0),i=this.tabs[t].id,this.tabs.splice(t,0,e),l.push(this.animateInsert(i,e))))}),Promise.all(l)}remove(){let t=0;return Array.from(arguments).forEach(e=>{e=this.get(e);e&&(t++,this.tabs.splice(this.get(e.id,!0),1),query(this.box).find(`#tabs_${this.name}_tab_`+w2utils.escapeId(e.id)).remove())}),this.resize(),t}select(e){return this.active!=e&&null!=this.get(e)&&(this.active=e,this.refresh(),!0)}set(e,t){var i=this.get(e,!0);return null!=i&&(w2utils.extend(this.tabs[i],t),this.refresh(e),!0)}get(t,i){if(0===arguments.length){var s=[];for(let e=0;e{e=this.get(e);e&&!1!==e.hidden&&(e.hidden=!1,t.push(e.id))}),setTimeout(()=>{t.forEach(e=>{this.refresh(e),this.resize()})},15),t}hide(){let t=[];return Array.from(arguments).forEach(e=>{e=this.get(e);e&&!0!==e.hidden&&(e.hidden=!0,t.push(e.id))}),setTimeout(()=>{t.forEach(e=>{this.refresh(e),this.resize()})},15),t}enable(){let t=[];return Array.from(arguments).forEach(e=>{e=this.get(e);e&&!1!==e.disabled&&(e.disabled=!1,t.push(e.id))}),setTimeout(()=>{t.forEach(e=>{this.refresh(e)})},15),t}disable(){let t=[];return Array.from(arguments).forEach(e=>{e=this.get(e);e&&!0!==e.disabled&&(e.disabled=!0,t.push(e.id))}),setTimeout(()=>{t.forEach(e=>{this.refresh(e)})},15),t}dragMove(i){if(this.last.reordering){let s=this;var l=this.last.moving,r=this.tabs[l.index],a=h(l.index,1),n=h(l.index,-1),r=query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(r.id));if(0t)return a=this.tabs.indexOf(a),this.tabs.splice(l.index,0,this.tabs.splice(a,1)[0]),l.$tab.before(o.get(0)),l.$tab.css("opacity",0),void Object.assign(this.last.moving,{index:a,divX:-e,x:i.pageX+e,left:l.left+l.divX+e})}if(l.divX<0&&n){o=query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(n.id));let e=parseInt(r.get(0).clientWidth),t=parseInt(o.get(0).clientWidth);e=et&&(a=this.tabs.indexOf(n),this.tabs.splice(l.index,0,this.tabs.splice(a,1)[0]),o.before(l.$tab),l.$tab.css("opacity",0),Object.assign(l,{index:a,divX:e,x:i.pageX-e,left:l.left+l.divX-e}))}function h(e,t){e+=t;let i=s.tabs[e];return i=i&&i.hidden?h(e,t):i}}}mouseAction(e,t,i){var s=this.get(t),l=this.trigger("mouse"+e,{target:t,tab:s,object:s,originalEvent:i});if(!0!==l.isCancelled&&!s.disabled&&!s.hidden){switch(e){case"Enter":this.tooltipShow(t);break;case"Leave":this.tooltipHide(t);break;case"Down":this.initReorder(t,i)}l.finish()}}tooltipShow(t){var i=this.get(t),t=query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(t)).get(0);if(null!=this.tooltip&&!i.disabled&&!this.last.reordering){var s=this.tooltip;let e=i.tooltip;"function"==typeof e&&(e=e.call(this,i)),w2tooltip.show({anchor:t,name:this.name+"_tooltip",html:e,position:s})}}tooltipHide(e){null!=this.tooltip&&w2tooltip.hide(this.name+"_tooltip")}getTabHTML(e){e=this.get(e,!0),e=this.tabs[e];if(null==e)return!1;null==e.text&&null!=e.caption&&(e.text=e.caption),null==e.tooltip&&null!=e.hint&&(e.tooltip=e.hint),null!=e.caption&&console.log("NOTICE: tabs tab.caption property is deprecated, please use tab.text. Tab -> ",e),null!=e.hint&&console.log("NOTICE: tabs tab.hint property is deprecated, please use tab.tooltip. Tab -> ",e);let t=e.text,i=(null==(t="function"==typeof t?t.call(this,e):t)&&(t=""),""),s="";return e.hidden&&(s+="display: none;"),e.disabled&&(s+="opacity: 0.2;"),e.closable&&!e.disabled&&(i=`
    +
    `),` +
    + ${w2utils.lang(t)+i} +
    `}refresh(e){var t=Date.now(),i=("up"==this.flow?query(this.box).addClass("w2ui-tabs-up"):query(this.box).removeClass("w2ui-tabs-up"),this.trigger("refresh",{target:null!=e?e:this.name,object:this.get(e)}));if(!0!==i.isCancelled){if(null==e)for(let e=0;e +
    +
    ${this.right}
    +
    +
    +
    `,query(this.box).attr("name",this.name).addClass("w2ui-reset w2ui-tabs").html(e),0{this.resize()}),this.last.observeResize.observe(this.box),i.finish(),this.refresh(),this.resize(),Date.now()-t)}initReorder(e,a){if(this.reorder){let t=this,i=query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(e)),s=this.get(e,!0),l=query(i.get(0).cloneNode(!0)),r;l.attr("id","#tabs_"+this.name+"_tab_ghost"),this.last.moving={index:s,indexFrom:s,$tab:i,$ghost:l,divX:0,left:i.get(0).getBoundingClientRect().left,parentX:query(this.box).get(0).getBoundingClientRect().left,x:a.pageX,opacity:i.css("opacity")},query(document).off(".w2uiTabReorder").on("mousemove.w2uiTabReorder",function(e){if(!t.last.reordering){if(!0===(r=t.trigger("reorder",{target:t.tabs[s].id,indexFrom:s,tab:t.tabs[s]})).isCancelled)return;w2tooltip.hide(this.name+"_tooltip"),t.last.reordering=!0,l.addClass("moving"),l.css({"pointer-events":"none",position:"absolute",left:i.get(0).getBoundingClientRect().left}),i.css("opacity",0),query(t.box).find(".w2ui-scroll-wrapper").append(l.get(0)),query(t.box).find(".w2ui-tab-close").hide()}t.last.moving.divX=e.pageX-t.last.moving.x,l.css("left",t.last.moving.left-t.last.moving.parentX+t.last.moving.divX+"px"),t.dragMove(e)}).on("mouseup.w2uiTabReorder",function(){query(document).off(".w2uiTabReorder"),l.css({transition:"0.1s",left:t.last.moving.$tab.get(0).getBoundingClientRect().left-t.last.moving.parentX}),query(t.box).find(".w2ui-tab-close").show(),setTimeout(()=>{l.remove(),i.css({opacity:t.last.moving.opacity}),t.last.reordering&&r.finish({indexTo:t.last.moving.index}),t.last.reordering=!1},100)})}}scroll(n,o){return new Promise((e,t)=>{var i=query(this.box).find(".w2ui-scroll-wrapper"),s=i.get(0).scrollLeft,l=i.find(".w2ui-tabs-right").get(0),r=i.parent().get(0).getBoundingClientRect().width,a=s+parseInt(l.offsetLeft)+parseInt(l.clientWidth);switch(n){case"left":{let e=s-r+50;e<=0&&(e=0),i.get(0).scrollTo({top:0,left:e,behavior:o?"atuo":"smooth"});break}case"right":{let e=s+r-50;e>=a-r&&(e=a-r),i.get(0).scrollTo({top:0,left:e,behavior:o?"atuo":"smooth"});break}}setTimeout(()=>{this.resize(),e()},o?0:350)})}scrollIntoView(i,s){return new Promise((e,t)=>{null==i&&(i=this.active),null!=this.get(i)&&(query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(i)).get(0).scrollIntoView({block:"start",inline:"center",behavior:s?"atuo":"smooth"}),setTimeout(()=>{this.resize(),e()},s?0:500))})}resize(){var e=Date.now();if(null!=this.box){var t,i,s,l,r=this.trigger("resize",{target:this.name});if(!0!==r.isCancelled)return(t=query(this.box)).find(".w2ui-scroll-left, .w2ui-scroll-right").hide(),i=t.find(".w2ui-scroll-wrapper").get(0),l=t.find(".w2ui-tabs-right"),(s=t.get(0).getBoundingClientRect().width)<(l=0{window.location.hash=t},1)}e.finish()}}clickClose(e,t){var i=this.get(e);if(null==i||i.disabled)return!1;let s=this.trigger("close",{target:e,object:i,tab:i,originalEvent:t});!0!==s.isCancelled&&(this.animateClose(e).then(()=>{this.remove(e),s.finish(),this.refresh()}),t)&&t.stopPropagation()}animateClose(r){return new Promise((e,t)=>{var i=query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(r)),s=parseInt(i.get(0).clientWidth||0);let l=i.replace(`
    `);setTimeout(()=>{l.css({width:"0px"})},1),setTimeout(()=>{l.remove(),this.resize(),e()},500)})}animateInsert(t,r){return new Promise((i,e)=>{let s=query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(t)),l=query.html(this.getTabHTML(r.id));if(0==s.length)(s=query(this.box).find("#tabs_tabs_right")).before(l),this.resize();else{l.css({opacity:0}),query(this.box).find("#tabs_tabs_right").before(l.get(0));let e=query(this.box).find("#"+l.attr("id")).get(0).clientWidth??0,t=query.html('
    ');s.before(t),l.hide(),t.before(l[0]),setTimeout(()=>{t.css({width:e+"px"})},1),setTimeout(()=>{t.remove(),l.css({opacity:1}).show(),this.refresh(r.id),this.resize(),i()},500)}})}}let w2panels=["top","left","main","preview","right","bottom"];class w2layout extends w2base{constructor(e){super(e.name),this.box=null,this.name=null,this.panels=[],this.last={},this.padding=1,this.resizer=4,this.style="",this.onShow=null,this.onHide=null,this.onResizing=null,this.onResizerClick=null,this.onRender=null,this.onRefresh=null,this.onChange=null,this.onResize=null,this.onDestroy=null,this.panel_template={type:null,title:"",size:100,minSize:20,maxSize:!1,hidden:!1,resizable:!1,overflow:"auto",style:"",html:"",tabs:null,toolbar:null,width:null,height:null,show:{toolbar:!1,tabs:!1},removed:null,onRefresh:null,onShow:null,onHide:null},Object.assign(this,e),Array.isArray(this.panels)||(this.panels=[]),this.panels.forEach((e,t)=>{var i,s,l;this.panels[t]=w2utils.extend({},this.panel_template,e),(w2utils.isPlainObject(e.tabs)||Array.isArray(e.tabs))&&function(e,t,i){var s=e.get(t);null!=s&&null==i&&(i=s.tabs);if(null==s||null==i)return;Array.isArray(i)&&(i={tabs:i});var l=e.name+"_"+t+"_tabs";w2ui[l]&&w2ui[l].destroy();s.tabs=new w2tabs(w2utils.extend({},i,{owner:e,name:e.name+"_"+t+"_tabs"})),s.show.tabs=!0}(this,e.type),(w2utils.isPlainObject(e.toolbar)||Array.isArray(e.toolbar))&&(t=this,e=e.type,i=void 0,null!=(s=t.get(e))&&null==i&&(i=s.toolbar),null!=s)&&null!=i&&(Array.isArray(i)&&(i={items:i}),l=t.name+"_"+e+"_toolbar",w2ui[l]&&w2ui[l].destroy(),s.toolbar=new w2toolbar(w2utils.extend({},i,{owner:t,name:t.name+"_"+e+"_toolbar"})),s.show.toolbar=!0)}),w2panels.forEach(e=>{null==this.get(e)&&this.panels.push(w2utils.extend({},this.panel_template,{type:e,hidden:"main"!==e,size:50}))}),"string"==typeof this.box&&(this.box=query(this.box).get(0)),this.box&&this.render(this.box)}html(l,r,a){let n=this.get(l);var e={panel:l,html:n.html,error:!1,cancelled:!1,removed(e){"function"==typeof e&&(n.removed=e)}};if("function"==typeof n.removed&&(n.removed({panel:l,html:n.html,html_new:r,transition:a||"none"}),n.removed=null),"css"==l)query(this.box).find("#layout_"+this.name+"_panel_css").html(""),e.status=!0;else if(null==n)console.log("ERROR: incorrect panel name. Panel name can be main, left, right, top, bottom, preview or css"),e.error=!0;else if(null!=r){var t=this.trigger("change",{target:l,panel:n,html_new:r,transition:a});if(!0===t.isCancelled)e.cancelled=!0;else{let i="#layout_"+this.name+"_panel_"+n.type;var o=query(this.box).find(i+"> .w2ui-panel-content");let s=0;if(0 .w2ui-panel-content"),t=(e.after('
    '),query(this.box).find(i+"> .w2ui-panel-content.new-panel"));e.css("top",s),t.css("top",s),"object"==typeof r?(r.box=t[0],r.render()):t.hide().html(r),w2utils.transition(e[0],t[0],a,()=>{e.remove(),t.removeClass("new-panel"),t.css("overflow",n.overflow),query(query(this.box).find(i+"> .w2ui-panel-content").get(1)).remove(),query(this.box).removeClass("animating"),this.refresh(l)})}else this.refresh(l);t.finish()}}return e}message(e,t){var i=this.get(e);let s=query(this.box).find("#layout_"+this.name+"_panel_"+i.type),l=s.css("overflow");s.css("overflow","hidden");i=w2utils.message({owner:this,box:s.get(0),after:".w2ui-panel-title",param:e},t);return i&&i.self.on("close:after",()=>{s.css("overflow",l)}),i}confirm(e,t){var i=this.get(e);let s=query(this.box).find("#layout_"+this.name+"_panel_"+i.type),l=s.css("overflow");s.css("overflow","hidden");i=w2utils.confirm({owner:this,box:s.get(0),after:".w2ui-panel-title",param:e},t);return i&&i.self.on("close:after",()=>{s.css("overflow",l)}),i}load(i,s,l){return new Promise((t,e)=>{"css"!=i&&null==this.get(i)||null==s?e():fetch(s).then(e=>e.text()).then(e=>{this.resize(),t(this.html(i,e,l))})})}sizeTo(e,t,i){return null!=this.get(e)&&(query(this.box).find(":scope > div > .w2ui-panel").css("transition",!0!==i?".2s":"0s"),setTimeout(()=>{this.set(e,{size:t})},1),setTimeout(()=>{query(this.box).find(":scope > div > .w2ui-panel").css("transition","0s"),this.resize()},300),!0)}show(e,t){let i=this.trigger("show",{target:e,thisect:this.get(e),immediate:t});var s;if(!0!==i.isCancelled)return null!=(s=this.get(e))&&(!(s.hidden=!1)===t?(query(this.box).find("#layout_"+this.name+"_panel_"+e).css({opacity:"1"}),i.finish(),this.resize()):(query(this.box).addClass("animating"),query(this.box).find("#layout_"+this.name+"_panel_"+e).css({opacity:"0"}),query(this.box).find(":scope > div > .w2ui-panel").css("transition",".2s"),setTimeout(()=>{this.resize()},1),setTimeout(()=>{query(this.box).find("#layout_"+this.name+"_panel_"+e).css({opacity:"1"})},250),setTimeout(()=>{query(this.box).find(":scope > div > .w2ui-panel").css("transition","0s"),query(this.box).removeClass("animating"),i.finish(),this.resize()},300)),!0)}hide(e,t){let i=this.trigger("hide",{target:e,object:this.get(e),immediate:t});var s;if(!0!==i.isCancelled)return null!=(s=this.get(e))&&((s.hidden=!0)===t?(query(this.box).find("#layout_"+this.name+"_panel_"+e).css({opacity:"0"}),i.finish(),this.resize()):(query(this.box).addClass("animating"),query(this.box).find(":scope > div > .w2ui-panel").css("transition",".2s"),query(this.box).find("#layout_"+this.name+"_panel_"+e).css({opacity:"0"}),setTimeout(()=>{this.resize()},1),setTimeout(()=>{query(this.box).find(":scope > div > .w2ui-panel").css("transition","0s"),query(this.box).removeClass("animating"),i.finish(),this.resize()},300)),!0)}toggle(e,t){var i=this.get(e);return null!=i&&(i.hidden?this.show(e,t):this.hide(e,t))}set(e,t){var i=this.get(e,!0);return null!=i&&(w2utils.extend(this.panels[i],t),null==t.html&&null==t.resizable||this.refresh(e),this.resize(),!0)}get(t,i){for(let e=0;e .w2ui-panel-content");return 1!=e.length?null:e[0]}hideToolbar(e){var t=this.get(e);t&&(t.show.toolbar=!1,query(this.box).find("#layout_"+this.name+"_panel_"+e+"> .w2ui-panel-toolbar").hide(),this.resize())}showToolbar(e){var t=this.get(e);t&&(t.show.toolbar=!0,query(this.box).find("#layout_"+this.name+"_panel_"+e+"> .w2ui-panel-toolbar").show(),this.resize())}toggleToolbar(e){var t=this.get(e);t&&(t.show.toolbar?this.hideToolbar(e):this.showToolbar(e))}assignToolbar(e,t){"string"==typeof t&&null!=w2ui[t]&&(t=w2ui[t]);var i=this.get(e),s=(i.toolbar=t,query(this.box).find(e+"> .w2ui-panel-toolbar"));null!=i.toolbar?(0===s.find("[name="+i.toolbar.name+"]").length?i.toolbar.render(s.get(0)):null!=i.toolbar&&i.toolbar.refresh(),(t.owner=this).showToolbar(e),this.refresh(e)):(s.html(""),this.hideToolbar(e))}hideTabs(e){var t=this.get(e);t&&(t.show.tabs=!1,query(this.box).find("#layout_"+this.name+"_panel_"+e+"> .w2ui-panel-tabs").hide(),this.resize())}showTabs(e){var t=this.get(e);t&&(t.show.tabs=!0,query(this.box).find("#layout_"+this.name+"_panel_"+e+"> .w2ui-panel-tabs").show(),this.resize())}toggleTabs(e){var t=this.get(e);t&&(t.show.tabs?this.hideTabs(e):this.showTabs(e))}render(e){var t=Date.now();let o=this;"string"==typeof e&&(e=query(e).get(0));var i=this.trigger("render",{target:this.name,box:e??this.box});if(!0!==i.isCancelled){if(null!=e&&(0"),0
    ';query(this.box).find(":scope > div").append(s)}return query(this.box).find(":scope > div").append('
    '),this.refresh(),this.last.observeResize=new ResizeObserver(()=>{this.resize()}),this.last.observeResize.observe(this.box),i.finish(),setTimeout(()=>{o.last.events={resizeStart:l,mouseMove:a,mouseUp:r},this.resize()},0),Date.now()-t}function l(e,t){o.box&&(t=t||window.event,query(document).off("mousemove",o.last.events.mouseMove).on("mousemove",o.last.events.mouseMove),query(document).off("mouseup",o.last.events.mouseUp).on("mouseup",o.last.events.mouseUp),o.last.resize={type:e,x:t.screenX,y:t.screenY,diff_x:0,diff_y:0,value:0},w2panels.forEach(e=>{var t=query(o.el(e)).find(".w2ui-lock");0{var t=query(o.el(e)).find(".w2ui-lock");"yes"==t.data("locked")?t.removeData("locked"):o.unlock(e)}),0!==o.last.diff_x||0!==o.last.resize.diff_y){var s=o.get("top"),l=o.get("bottom"),r=o.get(o.last.resize.type),i=w2utils.getSize(query(o.box),"width"),a=w2utils.getSize(query(o.box),"height"),n=String(r.size);let e,t;switch(o.last.resize.type){case"top":e=parseInt(r.sizeCalculated)+o.last.resize.diff_y,t=0;break;case"bottom":e=parseInt(r.sizeCalculated)-o.last.resize.diff_y,t=0;break;case"preview":e=parseInt(r.sizeCalculated)-o.last.resize.diff_y,t=(s&&!s.hidden?s.sizeCalculated:0)+(l&&!l.hidden?l.sizeCalculated:0);break;case"left":e=parseInt(r.sizeCalculated)+o.last.resize.diff_x,t=0;break;case"right":e=parseInt(r.sizeCalculated)-o.last.resize.diff_x,t=0}"%"==n.substr(n.length-1)?r.size=Math.floor(100*e/("left"==r.type||"right"==r.type?i:a-t)*100)/100+"%":"-"==String(r.size).substr(0,1)?r.size=parseInt(r.size)-r.sizeCalculated+e:r.size=e,o.resize()}query(o.box).find("#layout_"+o.name+"_resizer_"+o.last.resize.type).removeClass("active"),delete o.last.resize}}function a(i){if(o.box&&(i=i||window.event,null!=o.last.resize)){var s=o.get(o.last.resize.type),l=o.last.resize,r=o.trigger("resizing",{target:o.name,object:s,originalEvent:i,panel:l?l.type:"all",diff_x:l?l.diff_x:0,diff_y:l?l.diff_y:0});if(!0!==r.isCancelled){var a=query(o.box).find("#layout_"+o.name+"_resizer_"+l.type);let e=i.screenX-l.x,t=i.screenY-l.y;var n=o.get("main");switch(a.hasClass("active")||a.addClass("active"),l.type){case"left":s.minSize-e>s.width&&(e=s.minSize-s.width),s.maxSize&&s.width+e>s.maxSize&&(e=s.maxSize-s.width),n.minSize+e>n.width&&(e=n.width-n.minSize);break;case"right":s.minSize+e>s.width&&(e=s.width-s.minSize),s.maxSize&&s.width-e>s.maxSize&&(e=s.width-s.maxSize),n.minSize-e>n.width&&(e=n.minSize-n.width);break;case"top":s.minSize-t>s.height&&(t=s.minSize-s.height),s.maxSize&&s.height+t>s.maxSize&&(t=s.maxSize-s.height),n.minSize+t>n.height&&(t=n.height-n.minSize);break;case"preview":case"bottom":s.minSize+t>s.height&&(t=s.height-s.minSize),s.maxSize&&s.height-t>s.maxSize&&(t=s.height-s.maxSize),n.minSize-t>n.height&&(t=n.minSize-n.height)}switch(l.diff_x=e,l.diff_y=t,l.type){case"top":case"preview":case"bottom":(l.diff_x=0) .w2ui-panel-content")[0],setTimeout(()=>{0 .w2ui-panel-content").length&&(query(l.box).find(t+"> .w2ui-panel-content").removeClass().removeAttr("name").addClass("w2ui-panel-content").css("overflow",e.overflow)[0].style.cssText+=";"+e.style),e.html&&"function"==typeof e.html.render&&e.html.render()},1)):0 .w2ui-panel-content").length&&(query(l.box).find(t+"> .w2ui-panel-content").removeClass().removeAttr("name").addClass("w2ui-panel-content").html(e.html).css("overflow",e.overflow)[0].style.cssText+=";"+e.style);let i=query(l.box).find(t+"> .w2ui-panel-tabs");e.show.tabs?0===i.find("[name="+e.tabs.name+"]").length&&null!=e.tabs?e.tabs.render(i.get(0)):e.tabs.refresh():i.html("").removeClass("w2ui-tabs").hide(),i=query(l.box).find(t+"> .w2ui-panel-toolbar"),e.show.toolbar?0===i.find("[name="+e.toolbar.name+"]").length&&null!=e.toolbar?e.toolbar.render(i.get(0)):e.toolbar.refresh():i.html("").removeClass("w2ui-toolbar").hide(),i=query(l.box).find(t+"> .w2ui-panel-title"),e.title?i.html(e.title).show():i.html("").hide()}else{if(0===query(l.box).find("#layout_"+l.name+"_panel_main").length)return void l.render();l.resize();for(let e=0;ethis.padding?this.resizer:this.padding,query(this.box).find("#layout_"+this.name+"_resizer_top").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px",cursor:"ns-resize"}).off("mousedown").on("mousedown",function(e){e.preventDefault();var t=i.trigger("resizerClick",{target:"top",originalEvent:e});if(!0!==t.isCancelled)return w2ui[i.name].last.events.resizeStart("top",e),t.finish(),!1}))):(query(this.box).find("#layout_"+this.name+"_panel_top").hide(),query(this.box).find("#layout_"+this.name+"_resizer_top").hide()),null!=c&&!0!==c.hidden?(e=0,t=0+(b?f.sizeCalculated+this.padding:0),s=c.sizeCalculated,l=h-(b?f.sizeCalculated+this.padding:0)-(v?m.sizeCalculated+this.padding:0),query(this.box).find("#layout_"+this.name+"_panel_left").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px"}),c.width=s,c.height=l,c.resizable&&(e=c.sizeCalculated-(0===this.padding?this.resizer:0),s=this.resizer>this.padding?this.resizer:this.padding,query(this.box).find("#layout_"+this.name+"_resizer_left").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px",cursor:"ew-resize"}).off("mousedown").on("mousedown",function(e){e.preventDefault();var t=i.trigger("resizerClick",{target:"left",originalEvent:e});if(!0!==t.isCancelled)return w2ui[i.name].last.events.resizeStart("left",e),t.finish(),!1}))):(query(this.box).find("#layout_"+this.name+"_panel_left").hide(),query(this.box).find("#layout_"+this.name+"_resizer_left").hide()),null!=p&&!0!==p.hidden?(e=o-p.sizeCalculated,t=0+(b?f.sizeCalculated+this.padding:0),s=p.sizeCalculated,l=h-(b?f.sizeCalculated+this.padding:0)-(v?m.sizeCalculated+this.padding:0),query(this.box).find("#layout_"+this.name+"_panel_right").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px"}),p.width=s,p.height=l,p.resizable&&(e-=this.padding,s=this.resizer>this.padding?this.resizer:this.padding,query(this.box).find("#layout_"+this.name+"_resizer_right").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px",cursor:"ew-resize"}).off("mousedown").on("mousedown",function(e){e.preventDefault();var t=i.trigger("resizerClick",{target:"right",originalEvent:e});if(!0!==t.isCancelled)return w2ui[i.name].last.events.resizeStart("right",e),t.finish(),!1}))):(query(this.box).find("#layout_"+this.name+"_panel_right").hide(),query(this.box).find("#layout_"+this.name+"_resizer_right").hide()),null!=m&&!0!==m.hidden?(e=0,t=h-m.sizeCalculated,s=o,l=m.sizeCalculated,query(this.box).find("#layout_"+this.name+"_panel_bottom").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px"}),m.width=s,m.height=l,m.resizable&&(t-=0===this.padding?0:this.padding,l=this.resizer>this.padding?this.resizer:this.padding,query(this.box).find("#layout_"+this.name+"_resizer_bottom").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px",cursor:"ns-resize"}).off("mousedown").on("mousedown",function(e){e.preventDefault();var t=i.trigger("resizerClick",{target:"bottom",originalEvent:e});if(!0!==t.isCancelled)return w2ui[i.name].last.events.resizeStart("bottom",e),t.finish(),!1}))):(query(this.box).find("#layout_"+this.name+"_panel_bottom").hide(),query(this.box).find("#layout_"+this.name+"_resizer_bottom").hide()),e=0+(y?c.sizeCalculated+this.padding:0),t=0+(b?f.sizeCalculated+this.padding:0),s=o-(y?c.sizeCalculated+this.padding:0)-(w?p.sizeCalculated+this.padding:0),l=h-(b?f.sizeCalculated+this.padding:0)-(v?m.sizeCalculated+this.padding:0)-(g?u.sizeCalculated+this.padding:0),query(this.box).find("#layout_"+this.name+"_panel_main").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px"}),d.width=s,d.height=l,null!=u&&!0!==u.hidden?(e=0+(y?c.sizeCalculated+this.padding:0),t=h-(v?m.sizeCalculated+this.padding:0)-u.sizeCalculated,s=o-(y?c.sizeCalculated+this.padding:0)-(w?p.sizeCalculated+this.padding:0),l=u.sizeCalculated,query(this.box).find("#layout_"+this.name+"_panel_preview").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px"}),u.width=s,u.height=l,u.resizable&&(t-=0===this.padding?0:this.padding,l=this.resizer>this.padding?this.resizer:this.padding,query(this.box).find("#layout_"+this.name+"_resizer_preview").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px",cursor:"ns-resize"}).off("mousedown").on("mousedown",function(e){e.preventDefault();var t=i.trigger("resizerClick",{target:"preview",originalEvent:e});if(!0!==t.isCancelled)return w2ui[i.name].last.events.resizeStart("preview",e),t.finish(),!1}))):(query(this.box).find("#layout_"+this.name+"_panel_preview").hide(),query(this.box).find("#layout_"+this.name+"_resizer_preview").hide());for(let t=0;t .w2ui-panel-";let e=0;q&&(q.title&&(_=query(this.box).find(C+"title").css({top:e+"px",display:"block"}),e+=w2utils.getSize(_,"height")),q.show.tabs&&(_=query(this.box).find(C+"tabs").css({top:e+"px",display:"block"}),e+=w2utils.getSize(_,"height")),q.show.toolbar)&&(q=query(this.box).find(C+"toolbar").css({top:e+"px",display:"block"}),e+=w2utils.getSize(q,"height")),query(this.box).find(C+"content").css({display:"block"}).css({top:e+"px"})}return n.finish(),Date.now()-r}}destroy(){var e=this.trigger("destroy",{target:this.name});if(!0!==e.isCancelled)return null!=w2ui[this.name]&&(0'},add:{type:"button",id:"w2ui-add",text:"Add New",tooltip:"Add new record",icon:"w2ui-icon-plus"},edit:{type:"button",id:"w2ui-edit",text:"Edit",tooltip:"Edit selected record",icon:"w2ui-icon-pencil",batch:1,disabled:!0},delete:{type:"button",id:"w2ui-delete",text:"Delete",tooltip:"Delete selected records",icon:"w2ui-icon-cross",batch:!0,disabled:!0},save:{type:"button",id:"w2ui-save",text:"Save",tooltip:"Save changed records",icon:"w2ui-icon-check"}},this.operators={text:["is","begins","contains","ends"],number:["=","between",">","<",">=","<="],date:["is",{oper:"less",text:"before"},{oper:"more",text:"since"},"between"],list:["is"],hex:["is","between"],color:["is","begins","contains","ends"],enum:["in","not in"]},this.defaultOperator={text:"begins",number:"=",date:"is",list:"is",enum:"in",hex:"begins",color:"begins"},this.operatorsMap={text:"text",int:"number",float:"number",money:"number",currency:"number",percent:"number",hex:"hex",alphanumeric:"text",color:"color",date:"date",time:"date",datetime:"date",list:"list",combo:"text",enum:"enum",file:"enum",select:"list",radio:"list",checkbox:"list",toggle:"list"},this.onAdd=null,this.onEdit=null,this.onRequest=null,this.onLoad=null,this.onDelete=null,this.onSave=null,this.onSelect=null,this.onClick=null,this.onDblClick=null,this.onContextMenu=null,this.onContextMenuClick=null,this.onColumnClick=null,this.onColumnDblClick=null,this.onColumnResize=null,this.onColumnAutoResize=null,this.onSort=null,this.onSearch=null,this.onSearchOpen=null,this.onChange=null,this.onRestore=null,this.onExpand=null,this.onCollapse=null,this.onError=null,this.onKeydown=null,this.onToolbar=null,this.onColumnOnOff=null,this.onCopy=null,this.onPaste=null,this.onSelectionExtend=null,this.onEditField=null,this.onRender=null,this.onRefresh=null,this.onReload=null,this.onResize=null,this.onDestroy=null,this.onStateSave=null,this.onStateRestore=null,this.onFocus=null,this.onBlur=null,this.onReorderRow=null,this.onSearchSave=null,this.onSearchRemove=null,this.onSearchSelect=null,this.onColumnSelect=null,this.onColumnDragStart=null,this.onColumnDragEnd=null,this.onResizerDblClick=null,this.onMouseEnter=null,this.onMouseLeave=null,w2utils.extend(this,e),Array.isArray(this.records)){let i=[];this.records.forEach((e,t)=>{null!=e[this.recid]&&(e.recid=e[this.recid]),null==e.recid&&console.log("ERROR: Cannot add records without recid. (obj: "+this.name+")"),!0===e.w2ui?.summary&&(this.summary.push(e),i.push(t))}),i.sort();for(let e=i.length-1;0<=e;e--)this.records.splice(i[e],1)}Array.isArray(this.columns)&&this.columns.forEach((i,e)=>{i=w2utils.extend({},this.colTemplate,i);e=(this.columns[e]=i).searchable;if(null!=e&&!1!==e&&null==this.getSearch(i.field))if(w2utils.isPlainObject(e))this.addSearch(w2utils.extend({field:i.field,label:i.text,type:"text"},e));else{let e=i.searchable,t="";!0===i.searchable&&(e="text",t='size="20"'),this.addSearch({field:i.field,label:i.text,type:e,attr:t})}}),Array.isArray(this.defaultSearches)&&this.defaultSearches.forEach((e,t)=>{e.id="default-"+t,e.icon??="w2ui-icon-search"});e=this.cache("searches");Array.isArray(e)&&e.forEach(e=>{this.savedSearches.push({id:e.id??"none",text:e.text??"none",icon:"w2ui-icon-search",remove:!0,logic:e.logic??"AND",data:e.data??[]})}),"string"==typeof this.box&&(this.box=query(this.box).get(0)),this.box&&this.render(this.box)}add(t,i){Array.isArray(t)||(t=[t]);let s=0;for(let e=0;ethis.records.length&&(n=this.records.length);for(let i=a;i{this.columns.forEach(i=>{if(i.field==e){let t=w2utils.clone(s);Object.keys(t).forEach(e=>{"function"==typeof t[e]&&(t[e]=t[e](i)),i[e]!=t[e]&&l++}),w2utils.extend(i,t)}})}),0{if(!(e.w2ui&&null!=e.w2ui.parent_recid||t.w2ui&&null!=t.w2ui.parent_recid))return o(e,t);var i=a(e),s=a(t);for(let e=0;es.length?1:i.length{this.status(w2utils.lang("Sorting took ${count} seconds",{count:e/1e3}))},10),e;function a(e){var t;return e.w2ui&&null!=e.w2ui.parent_recid?e.w2ui._path||((t=n.get(e.w2ui.parent_recid))?a(t).concat(e):(console.log("ERROR: no parent record: "+e.w2ui.parent_recid),[e])):[e]}function o(s,l){if(s===l)return 0;for(let i=0;it.constructor.name?s:-s;e&&"object"==typeof e&&(e=e.valueOf()),t&&"object"==typeof t&&(t=t.valueOf());var r={}.toString;switch(e&&"object"==typeof e&&e.toString!=r&&(e=String(e)),t&&"object"==typeof t&&t.toString!=r&&(t=String(t)),"string"==typeof e&&(e=e.toLowerCase().trim()),"string"==typeof t&&(t=t.toLowerCase().trim()),l){case"natural":l=w2utils.naturalCompare;break;case"i18n":l=w2utils.i18nCompare}return"function"==typeof l?l(e,t)*s:t=parseFloat(n)&&parseFloat(c.parseField(l,s.field))<=parseFloat(o)&&r++:"date"==s.type?(h=c.parseField(l,s.field+"_")instanceof Date?c.parseField(l,s.field+"_"):c.parseField(l,s.field),a=w2utils.isDate(h,w2utils.settings.dateFormat,!0),n=w2utils.isDate(n,w2utils.settings.dateFormat,!0),null!=(o=w2utils.isDate(o,w2utils.settings.dateFormat,!0))&&(o=new Date(o.getTime()+864e5)),a>=n&&a=n&&a=n)&&a=":d=!0;case">":case"more":-1!=["int","float","money","currency","percent"].indexOf(s.type)?(a=parseFloat(c.parseField(l,s.field)),n=parseFloat(i.value),(a>n||d&&a===n)&&r++):"date"==s.type?(h=c.parseField(l,s.field+"_")instanceof Date?c.parseField(l,s.field+"_"):c.parseField(l,s.field),a=w2utils.isDate(h,w2utils.settings.dateFormat,!0),n=w2utils.isDate(n,w2utils.settings.dateFormat,!0),(a>n||d&&a===n)&&r++):"time"==s.type?(h=c.parseField(l,s.field+"_")instanceof Date?c.parseField(l,s.field+"_"):c.parseField(l,s.field),a=w2utils.formatTime(h,"hh24:mi"),n=w2utils.formatTime(n,"hh24:mi"),(a>n||d&&a===n)&&r++):"datetime"==s.type&&(h=c.parseField(l,s.field+"_")instanceof Date?c.parseField(l,s.field+"_"):c.parseField(l,s.field),a=w2utils.formatDateTime(h,"yyyy-mm-dd|hh24:mm:ss"),n=w2utils.formatDateTime(w2utils.isDateTime(n,w2utils.settings.datetimeFormat,!0),"yyyy-mm-dd|hh24:mm:ss"),a.length==n.length)&&(a>n||d&&a===n)&&r++;break;case"in":h=i.value,-1===(h=i.svalue?i.svalue:h).indexOf(w2utils.isFloat(t)?parseFloat(t):t)&&-1===h.indexOf(a)||r++;break;case"not in":h=i.value,-1===(h=i.svalue?i.svalue:h).indexOf(w2utils.isFloat(t)?parseFloat(t):t)&&-1===h.indexOf(a)&&r++;break;case"begins":case"begins with":0===a.indexOf(n)&&r++;break;case"contains":0<=a.indexOf(n)&&r++;break;case"null":null==c.parseField(l,s.field)&&r++;break;case"not null":null!=c.parseField(l,s.field)&&r++;break;case"ends":case"ends with":let e=a.lastIndexOf(n);-1!==e&&e==a.length-n.length&&r++}}}if("OR"==c.last.logic&&0!==r||"AND"==c.last.logic&&r==c.searchData.length)return!0;if(l.w2ui?.children&&!0!==l.w2ui?.expanded)for(let t=0;tthis.records.length&&(i=this.records.length-s),0{this.status(w2utils.lang("Search took ${count} seconds",{count:e/1e3}))},10),e}}getRangeData(e,i){var s=this.get(e[0].recid,!0),l=this.get(e[1].recid,!0),r=e[0].column,a=e[1].column,n=[];if(r==a)for(let e=s;e<=l;e++){var t=this.records[e],o=t[this.columns[r].field]||null;n.push(!0!==i?o:{data:o,column:r,index:e,record:t})}else if(s==l){var h=this.records[s];for(let e=r;e<=a;e++){var d=h[this.columns[e].field]||null;n.push(!0!==i?d:{data:d,column:e,index:s,record:h})}}else for(let t=s;t<=l;t++){var u=this.records[t];n.push([]);for(let e=r;e<=a;e++){var c=u[this.columns[e].field];!0!==i?n[n.length-1].push(c):n[n.length-1].push({data:c,column:e,index:t,record:u})}}return n}addRange(s){let e=0,l,r;if("row"!=this.selectType){Array.isArray(s)||(s=[s]);for(let i=0;ithis.last.colStart&&(e=query(this.box).find("#grid_"+this.name+"_rec_"+w2utils.escapeId(u.recid)+' td[col="start"]')),u.columnthis.last.colEnd&&(t=query(this.box).find("#grid_"+this.name+"_rec_"+w2utils.escapeId(c.recid)+' td[col="end"]'),l='"end"');var p=parseInt(query(this.box).find("#grid_"+this.name+"_rec_top").next().attr("index")),f=parseInt(query(this.box).find("#grid_"+this.name+"_rec_bottom").prev().attr("index")),m=parseInt(query(this.box).find("#grid_"+this.name+"_frec_top").next().attr("index")),g=parseInt(query(this.box).find("#grid_"+this.name+"_frec_bottom").prev().attr("index"));0===e.length&&u.indexp&&(e=query(this.box).find("#grid_"+this.name+"_rec_top").next().find('td[col="'+u.column+'"]')),0===t.length&&c.index>f&&u.indexm&&(i=query(this.box).find("#grid_"+this.name+"_frec_top").next().find('td[col="'+u.column+'"]')),0===s.length&&c.index>g&&u.index'+("selection"==d.name?'
    ':"")+""),a=query(this.box).find("#grid_"+this.name+"_f"+d.name)):(a.attr("style",d.style),a.find(".w2ui-selection-resizer").show()),0===s.length&&(0===(s=query(this.box).find("#grid_"+this.name+"_frec_"+w2utils.escapeId(c.recid)+" td:last-child")).length&&(s=query(this.box).find("#grid_"+this.name+"_frec_bottom td:first-child")),a.css("border-right","0px"),a.find(".w2ui-selection-resizer").hide()),null!=u.recid)&&null!=c.recid&&0'+("selection"==d.name?'
    ':"")+""),a=query(this.box).find("#grid_"+this.name+"_"+d.name)):a.attr("style",d.style),0===e.length&&0===(e=query(this.box).find("#grid_"+this.name+"_rec_"+w2utils.escapeId(u.recid)+" td:first-child")).length&&(e=query(this.box).find("#grid_"+this.name+"_rec_top td:first-child")),0!==s.length&&a.css("border-left","0px"),null!=u.recid)&&null!=c.recid&&0{e=this.trigger("resizerDblClick",{target:this.name,originalEvent:e});!0!==e.isCancelled&&e.finish()});let n={target:this.name,originalRange:null,newRange:null};return Date.now()-e;function i(s){var l=r.last.move;if(l&&"expand"==l.type){l.divX=s.screenX-l.x,l.divY=s.screenY-l.y;let e,t,i=s.target;"TD"!=i.tagName.toUpperCase()&&(i=query(i).closest("td")[0]),null!=(t=null!=query(i).attr("col")?parseInt(query(i).attr("col")):t)&&(i=query(i).closest("tr")[0],e=r.records[query(i).attr("index")].recid,l.newRange[1].recid!=e||l.newRange[1].column!=t)&&(s=w2utils.clone(l.newRange),l.newRange=[{recid:l.recid,column:l.column},{recid:e,column:t}],n.detail&&(n.detail.newRange=w2utils.clone(l.newRange),n.detail.originalRange=w2utils.clone(l.originalRange)),!0===(n=r.trigger("selectionExtend",n)).isCancelled?(l.newRange=s,n.detail.newRange=s):(r.removeRange("grid-selection-expand"),r.addRange({name:"grid-selection-expand",range:l.newRange,style:"background-color: rgba(100,100,100,0.1); border: 2px dotted rgba(100,100,100,0.5);"})))}}function s(e){r.removeRange("grid-selection-expand"),delete r.last.move,query("body").off(".w2ui-"+r.name),n.finish&&n.finish()}}}select(){if(0===arguments.length)return 0;let s=0;var l=this.last.selection;this.multiSelect||this.selectNone(!0);let t=Array.from(arguments);Array.isArray(t[0])&&(t=t[0]);var e={target:this.name},e=(1==t.length?(e.multiple=!1,w2utils.isPlainObject(t[0])?e.clicked={recid:t[0].recid,column:t[0].column}:e.recid=t[0]):(e.multiple=!0,e.clicked={recids:t}),this.trigger("select",e));if(!0===e.isCancelled)return 0;if("row"==this.selectType)for(let e=0;e=this.last.range_start&&r+1<=this.last.range_end)&&(e=query(this.box).find("#grid_"+this.name+"_frec_"+w2utils.escapeId(i)),t=query(this.box).find("#grid_"+this.name+"_rec_"+w2utils.escapeId(i))),"row"==this.selectType&&-1==l.indexes.indexOf(r)&&(l.indexes.push(r),e&&t&&(e.addClass("w2ui-selected").find(".w2ui-col-number").addClass("w2ui-row-selected"),t.addClass("w2ui-selected").find(".w2ui-col-number").addClass("w2ui-row-selected"),e.find(".w2ui-grid-select-check").prop("checked",!0)),s++)}}else{var a={};for(let e=0;e=this.last.range_start&&u+1<=this.last.range_end&&(t=query(this.box).find("#grid_"+this.name+"_rec_"+w2utils.escapeId(h)),i=query(this.box).find("#grid_"+this.name+"_frec_"+w2utils.escapeId(h)));var c=l.columns[u]||[];-1==l.indexes.indexOf(u)&&l.indexes.push(u);for(let e=0;ee-t);for(let e=0;ee-t);var f=0 td[col="${h}"]`).removeClass("w2ui-selected w2ui-inactive"),query(this.box).find(`#grid_${this.name}_frec_${w2utils.escapeId(r)} > td[col="${h}"]`).removeClass("w2ui-selected w2ui-inactive");let t=!1,i=!1;var d=this.getSelection();for(let e=0;e{i(t,""),Array.isArray(t.items)&&t.items.forEach(e=>{i(e,t.id+":")})}),this.show.toolbarSave&&(0{this.initSearches(),this.last.search_opened=!0;let t=query(`#w2overlay-${this.name}-search-overlay`);t.data("gridName",this.name).off(".grid-search").on("click.grid-search",()=>{t.find("input, select").each(e=>{e=query(e).data("tooltipName");e&&e.forEach(e=>{w2tooltip.hide(e)})})}),w2utils.bindEvents(t.find("select, input, button"),this);var i=query(`#w2overlay-${this.name}-search-overlay *[rel=search]`);0{t.removeClass("checked"),this.last.search_opened=!1})}}}searchClose(){w2tooltip.hide(this.name+"-search-overlay")}searchFieldTooltip(e,t,i){var e=this.searches[e],s=this.searchData[t];let l=s.operator,r=("less"==(l="more"==l&&"date"==s.type?"since":l)&&"date"==s.type&&(l="before"),""),a=s.value;Array.isArray(s.value)?(s.value.forEach(e=>{r+=`${e.text||e}`}),"date"==s.type&&(r="",s.value.forEach(e=>{r+=`${w2utils.formatDate(e)}`}))):"date"==s.type&&(a=w2utils.formatDateTime(a)),w2tooltip.hide(this.name+"-search-props"),w2tooltip.show({name:this.name+"-search-props",anchor:i,class:"w2ui-white",hideOn:"doc-click",html:` +
    + ${e.label} + ${w2utils.lang(l)} + ${Array.isArray(s.value)?""+r:`${a}`} +
    + +
    +
    `}).then(e=>{query(e.detail.overlay.box).find("#remove").on("click",()=>{this.searchData.splice(""+t,1),this.reload(),this.localSearch(),w2tooltip.hide(this.name+"-search-props")})})}searchSuggest(e,t,i){clearTimeout(this.last.kbd_timer),clearTimeout(this.last.overlay_timer),this.searchShowFields(!0),this.searchClose(),!0===t?w2tooltip.hide(this.name+"-search-suggest"):0${t}`:t}}).select(e=>{var t=this.trigger("searchSelect",{target:this.name,index:e.detail.index,item:e.detail.item});!0===t.isCancelled?e.preventDefault():(e.detail.overlay.hide(),this.last.logic=e.detail.item.logic||"AND",this.last.search="",this.last.label="[Multiple Fields]",this.searchData=w2utils.clone(e.detail.item.data),this.searchSelected=w2utils.clone(e.detail.item,{exclude:["icon","remove"]}),this.reload(),t.finish())}).remove(e=>{let i=e.detail.item,s=this.trigger("searchRemove",{target:this.name,index:e.detail.index,item:i});!0===s.isCancelled?e.preventDefault():(e.detail.overlay.hide(),this.confirm(w2utils.lang('Do you want to delete search "${item}"?',{item:i.text})).yes(e=>{var t=this.savedSearches.findIndex(e=>e.id==i.id);-1!==t&&this.savedSearches.splice(t,1),this.cacheSave("searches",this.savedSearches.map(e=>w2utils.clone(e,{exclude:["remove","icon"]}))),e.detail.self.close(),s.finish()}).no(e=>{e.detail.self.close()}))})):this.last.overlay_timer=setTimeout(()=>{this.searchSuggest(!0)},100))}searchSave(){let e="",t=(this.searchSelected&&(e=this.searchSelected.text),this.savedSearches.findIndex(e=>e.id==this.searchSelected?.id)),s=this.trigger("searchSave",{target:this.name,saveLocalStorage:!0});!0!==s.isCancelled&&this.message({width:350,height:150,body:``,buttons:` + + + `}).open(async i=>{query(i.detail.box).find("input, button").eq(0).val(e),await i.complete,query(i.detail.box).find("#grid-search-cancel").on("click",()=>{this.message()}),query(i.detail.box).find("#grid-search-save").on("click",()=>{var e=query(i.detail.box).find(".w2ui-message .search-name").val();this.searchSelected&&-1!=t?Object.assign(this.savedSearches[t],{id:e,text:e,logic:this.last.logic,data:w2utils.clone(this.searchData)}):this.savedSearches.push({id:e,text:e,icon:"w2ui-icon-search",remove:!0,logic:this.last.logic,data:this.searchData}),this.cacheSave("searches",this.savedSearches.map(e=>w2utils.clone(e,{exclude:["remove","icon"]}))),this.message(),(this.searchSelected?(this.searchSelected.text=e,query(this.box).find(`#grid_${this.name}_search_name .name-text`)):(this.searchSelected={text:e,logic:this.last.logic,data:w2utils.clone(this.searchData)},query(i.detail.box).find(`#grid_${this.name}_search_all`).val(" ").prop("readOnly",!0),query(i.detail.box).find(`#grid_${this.name}_search_name`).show().find(".name-text"))).html(e),s.finish({name:e})}),query(i.detail.box).find("input, button").off(".message").on("keydown.message",e=>{var t=String(query(i.detail.box).find(".w2ui-message-body input").val()).trim();13==e.keyCode&&""!=t&&query(i.detail.box).find("#grid-search-save").trigger("click"),27==e.keyCode&&this.message()}).eq(0).on("input.message",e=>{var t=query(i.detail.box).closest(".w2ui-message").find("#grid-search-save");""===String(query(i.detail.box).val()).trim()?t.prop("disabled",!0):t.prop("disabled",!1)}).get(0).focus()})}cache(e){if(w2utils.hasLocalStorage&&this.useLocalStorage)try{var t=JSON.parse(localStorage.w2ui||"{}");return t[this.stateId||this.name]??={},t[this.stateId||this.name][e]}catch(e){}return null}cacheSave(e,t){if(w2utils.hasLocalStorage&&this.useLocalStorage)try{var i=JSON.parse(localStorage.w2ui||"{}");return i[this.stateId||this.name]??={},i[this.stateId||this.name][e]=t,localStorage.w2ui=JSON.stringify(i),!0}catch(e){delete localStorage.w2ui}return!1}searchReset(e){var t=[];let i=!1;for(let e=0;e=this.searches.length?(this.last.field="",this.last.label=""):(this.last.field=this.searches[e].field,this.last.label=this.searches[e].label)}this.last.multi=!1,this.last.fetch.offset=0,this.last.scrollTop=0,this.last.scrollLeft=0,this.last.selection.indexes=[],this.last.selection.columns={},this.searchClose();l=l.val("").get(0);l?._w2field&&l._w2field.reset(),e||this.reload(),s.finish()}}searchShowFields(e){if(!0===e)w2tooltip.hide(this.name+"-search-fields");else{var l=[];for(let s=-1;s",e),e.label=e.caption),l.push({id:e.field,text:w2utils.lang(e.label),search:e,tooltip:i,disabled:t,checked:e.field==this.last.field})}w2menu.show({type:"radio",name:this.name+"-search-fields",anchor:query(this.box).find("#grid_"+this.name+"_search_name").parent().find(".w2ui-search-down").get(0),items:l,align:"none",hideOn:["doc-click","select"]}).select(e=>{this.searchInitInput(e.detail.item.search.field)})}}searchInitInput(e,t){let i;var s=query(this.box).find("#grid_"+this.name+"_search_all");if("all"==e)i={field:"all",label:w2utils.lang("All Fields")};else if(null==(i=this.getSearch(e)))return;""!=this.last.search?(this.last.label=i.label,this.search(i.field,this.last.search)):(this.last.field=i.field,this.last.label=i.label),s.attr("placeholder",w2utils.lang("Search")+" "+w2utils.lang(i.label||i.caption||i.field,!0))}clear(e){this.total=0,this.records=[],this.summary=[],this.last.fetch.offset=0,this.last.idCache={},this.last.selection={indexes:[],columns:{}},this.reset(!0),e||this.refresh()}reset(e){this.last.scrollTop=0,this.last.scrollLeft=0,this.last.range_start=null,this.last.range_end=null,query(this.box).find(`#grid_${this.name}_records`).prop("scrollTop",0),e||this.refresh()}skip(e,t){this.url?.get??this.url?(this.offset=parseInt(e),this.offset>this.total&&(this.offset=this.total-this.limit),(this.offset<0||!w2utils.isInt(this.offset))&&(this.offset=0),this.clear(!0),this.reload(t)):console.log("ERROR: grid.skip() can only be called when you have remote data source.")}load(e,t){return null==e?(console.log('ERROR: You need to provide url argument when calling .load() method of "'+this.name+'" object.'),new Promise((e,t)=>{t()})):(this.clear(!0),this.request("load",{},e,t))}reload(e){let t=this;var i=this.url?.get??this.url;return t.selectionSave(),i?this.load(i,()=>{t.selectionRestore(),"function"==typeof e&&e()}):(this.reset(!0),this.localSearch(),this.selectionRestore(),"function"==typeof e&&e({status:"success"}),new Promise(e=>{e()}))}prepareParams(i,e){var t=this.dataType??w2utils.settings.dataType;let s=e.body;switch(t){case"HTTPJSON":s={request:s},["PUT","DELETE"].includes(e.method)&&(e.method="POST"),l();break;case"HTTP":["PUT","DELETE"].includes(e.method)&&(e.method="POST"),l();break;case"RESTFULL":["PUT","DELETE"].includes(e.method)?e.headers["Content-Type"]="application/json":l();break;case"JSON":"GET"==e.method?(s={request:s},l()):(e.headers["Content-Type"]="application/json",e.method="POST")}return e.body="string"==typeof e.body?e.body:JSON.stringify(e.body),e;function l(){Object.keys(s).forEach(e=>{let t=s[e];"object"==typeof t&&(t=JSON.stringify(t)),i.searchParams.append(e,t)}),delete e.body}}request(i,e,t,s){let l=this,r,a;var n=new Promise((e,t)=>{r=e,a=t});if(null==e&&(e={}),!(t=t||this.url))return new Promise((e,t)=>{t()});w2utils.isInt(this.offset)||(this.offset=0),w2utils.isInt(this.last.fetch.offset)||(this.last.fetch.offset=0);let o;var h={limit:this.limit,offset:parseInt(this.offset)+parseInt(this.last.fetch.offset),searchLogic:this.last.logic,search:this.searchData.map(e=>{e=w2utils.clone(e);return this.searchMap&&this.searchMap[e.field]&&(e.field=this.searchMap[e.field]),e}),sort:this.sortData.map(e=>{e=w2utils.clone(e);return this.sortMap&&this.sortMap[e.field]&&(e.field=this.sortMap[e.field]),e})};if(0===this.searchData.length&&(delete h.search,delete h.searchLogic),0===this.sortData.length&&delete h.sort,w2utils.extend(h,this.postData),w2utils.extend(h,e),"delete"!=i&&"save"!=i||(delete h.limit,delete h.offset,"delete"==(h.action=i)&&(h[this.recid||"recid"]=this.getSelection())),"load"==i){if(!0===(o=this.trigger("request",{target:this.name,url:t,postData:h,httpMethod:"GET",httpHeaders:this.httpHeaders})).isCancelled)return new Promise((e,t)=>{t()})}else o={detail:{url:t,postData:h,httpMethod:"save"==i?"PUT":"DELETE",httpHeaders:this.httpHeaders}};if(0===this.last.fetch.offset&&this.lock(w2utils.lang(this.msgRefresh),!0),this.last.fetch.controller)try{this.last.fetch.controller.abort()}catch(e){}switch(t=o.detail.url,i){case"save":t?.save&&(t=t.save);break;case"delete":t?.remove&&(t=t.remove);break;default:t=t?.get??t}if(0{null!=e&&(200!=e?.status?u(e??{}):(l.unlock(),e.json().catch(u).then(e=>{this.requestComplete(e,i,s,r,a)})))}),"load"==i&&o.finish(),n;function u(e){var t;"AbortError"!==e?.name&&(l.unlock(),!0!==(t=l.trigger("error",{response:e,lastFetch:l.last.fetch})).isCancelled)&&(e.status&&200!=e.status?l.error(e.status+": "+e.statusText):(console.log("ERROR: Server communication failed.","\n EXPECTED:",{total:5,records:[{recid:1,field:"value"}]},"\n OR:",{error:!0,message:"error message"}),l.requestComplete({error:!0,message:w2utils.lang(this.msgHTTPError),response:e},i,s,r,a)),t.finish())}}requestComplete(e,t,i,s,l){let r=e.error??!1,a=(null==e.error&&"error"===e.status&&(r=!0),this.last.fetch.response=(Date.now()-this.last.fetch.start)/1e3,setTimeout(()=>{this.show.statusResponse&&this.status(w2utils.lang("Server Response ${count} seconds",{count:this.last.fetch.response}))},10),this.last.pull_more=!1,this.last.pull_refresh=!0,"load");"save"==this.last.fetch.action&&(a="save"),"delete"==this.last.fetch.action&&(a="delete");var n=this.trigger(a,{target:this.name,error:r,data:e,lastFetch:this.last.fetch});if(!0===n.isCancelled)l();else{if(r)this.error(w2utils.lang(e.message??this.msgServerError)),l(e);else if("function"==typeof this.parser?"object"!=typeof(e=this.parser(e))&&console.log("ERROR: Your parser did not return proper object"):null==e?e={error:!0,message:w2utils.lang(this.msgNotJSON)}:Array.isArray(e)&&(e={error:r,records:e,total:e.length}),"load"==t){if(null==e.total&&(e.total=-1),null==e.records&&(e.records=[]),e.records.length==this.limit?(l=this.records.length+e.records.length,this.last.fetch.hasMore=l!=this.total):(this.last.fetch.hasMore=!1,this.total=this.offset+this.last.fetch.offset+e.records.length),this.last.fetch.hasMore||query(this.box).find("#grid_"+this.name+"_rec_more, #grid_"+this.name+"_frec_more").hide(),0===this.last.fetch.offset)this.records=[],this.summary=[];else if(-1!=e.total&&parseInt(e.total)!=parseInt(this.total)){let e=this;return this.message(w2utils.lang(this.msgNeedReload)).ok(()=>{delete e.last.fetch.offset,e.reload()}),new Promise(e=>{e()})}w2utils.isInt(e.total)&&(this.total=parseInt(e.total)),e.records&&e.records.forEach(e=>{this.recid&&(e.recid=this.parseField(e,this.recid)),null==e.recid&&(e.recid="recid-"+this.records.length),(!0===e.w2ui?.summary?this.summary:this.records).push(e)}),e.summary&&(this.summary=[],e.summary.forEach(e=>{this.recid&&(e.recid=this.parseField(e,this.recid)),null==e.recid&&(e.recid="recid-"+this.summary.length),this.summary.push(e)}))}else if("delete"==t)return this.reset(),this.reload();(this.url?.get??this.url)||(this.localSort(),this.localSearch()),this.total=parseInt(this.total),0===this.last.fetch.offset?this.refresh():(this.scroll(),this.resize()),"function"==typeof i&&i(e),s(e),n.finish(),this.last.fetch.loaded=!0}}error(e){var t=this.trigger("error",{target:this.name,message:e});!0!==t.isCancelled&&(this.message(e),t.finish())}getChanges(t){var i=[];void 0===t&&(t=this.records);for(let e=0;e{e.error||this.mergeChanges(),s.finish(),"function"==typeof t&&t(e)}):(this.mergeChanges(),s.finish()))}editField(d,u,c,p){let f=this;if(!0===this.last.inEditMode)p&&13==p.keyCode?({index:m,column:g,value:y}=this.last._edit,this.editChange({type:"custom",value:y},m,g,p),this.editDone(m,g,p)):0<(y=query(this.box).find("div.w2ui-edit-box .w2ui-input")).length&&("DIV"==y.get(0).tagName?(y.text(y.text()+c),w2utils.setCursorPosition(y.get(0),y.text().length)):(y.val(y.val()+c),w2utils.setCursorPosition(y.get(0),y.val().length)));else{let o=this.get(d,!0),h=this.getCellEditable(o,u);if(h&&!["checkbox","check"].includes(h.type)){let a=this.records[o],n=this.columns[u];var m=!0===n.frozen?"_f":"_";if(-1!=["list","enum","file"].indexOf(h.type))console.log('ERROR: input types "list", "enum" and "file" are not supported in inline editing.');else{var g=this.trigger("editField",{target:this.name,recid:d,column:u,value:c,index:o,originalEvent:p});if(!0!==g.isCancelled){c=g.detail.value,this.last.inEditMode=!0,this.last.editColumn=u,this.last._edit={value:c,index:o,column:u,recid:d},this.selectNone(!0),this.select({recid:d,column:u});var y=query(this.box).find("#grid_"+this.name+m+"rec_"+w2utils.escapeId(d));let e=y.find('[col="'+u+'"] > div'),t=(this.last._edit.tr=y,this.last._edit.div=e,query(this.box).find("div.w2ui-edit-box").remove(),"row"!=this.selectType&&(query(this.box).find("#grid_"+this.name+m+"selection").attr("id","grid_"+this.name+"_editable").removeClass("w2ui-selection").addClass("w2ui-edit-box").prepend('
    ').find(".w2ui-selection-resizer").remove(),e=query(this.box).find("#grid_"+this.name+"_editable > div:first-child")),h.attr=h.attr??"",h.text=h.text??"",h.style=h.style??"",h.items=h.items??[],null!=a.w2ui?.changes?.[n.field]?w2utils.stripTags(a.w2ui.changes[n.field]):w2utils.stripTags(f.parseField(a,n.field))),i="object"!=typeof(t=null==t?"":t)?t:"",s=(null!=g.detail.prevValue&&(i=g.detail.prevValue),null!=c&&(t=c),null!=n.style?n.style+";":"");"string"==typeof n.render&&["number","int","float","money","percent","size"].includes(n.render.split(":")[0])&&(s+="text-align: right;"),0 div').get(0)),m=`font-family: ${p["font-family"]}; font-size: ${p["font-size"]};`;function w(e){try{var t=getComputedStyle(e),i="DIV"==e.tagName.toUpperCase()?e.innerText:e.value,s=query(f.box).find("#grid_"+f.name+"_editable").get(0),l=`font-family: ${t["font-family"]}; font-size: ${t["font-size"]}; white-space: no-wrap;`,r=w2utils.getStrWidth(i,l);r+20>s.clientWidth&&query(s).css("width",r+20+"px")}catch(e){}}"div"===h.type?(e.addClass("w2ui-editable").html(w2utils.stripSpaces(`
    +
    `+h.text)),(l=e.find("div.w2ui-input").get(0)).innerText="object"!=typeof t?t:"",null!=c?w2utils.setCursorPosition(l,l.innerText.length):w2utils.setCursorPosition(l,0,l.innerText.length)):(e.addClass("w2ui-editable").html(w2utils.stripSpaces(``+h.text)),l=e.find("input").get(0),"number"==h.type&&(t=w2utils.formatNumber(t)),"date"==h.type&&(t=w2utils.formatDate(w2utils.isDate(t,h.format,!0)||new Date,h.format)),l.value="object"!=typeof t?t:"",y=e=>{var t=this.last._edit?.escKey;let i=!1;var s=query(l).data("tooltipName");s&&null!=w2tooltip.get(s[0])?.selected&&(i=!0),!this.last.inEditMode||t||!r.includes(h.type)||e.detail.overlay.anchor?.id!=this.last._edit.input?.id&&"list"!=h.type||(this.editChange(),this.editDone(void 0,void 0,{keyCode:i?13:0}))},new w2field(w2utils.extend({},h,{el:l,selected:t,onSelect:y,onHide:y})),null==c&&l&&l.select()),Object.assign(this.last._edit,{input:l,edit:h}),query(l).off(".w2ui-editable").on("blur.w2ui-editable",e=>{var t,i;this.last.inEditMode&&(t=this.last._edit.edit.type,i=query(l).data("tooltipName"),r.includes(t)&&i||(this.editChange(l,o,u,e),this.editDone()))}).on("mousedown.w2ui-editable",e=>{e.stopPropagation()}).on("click.w2ui-editable",e=>{w.call(l,e)}).on("paste.w2ui-editable",e=>{e.preventDefault();e=e.clipboardData.getData("text/plain");document.execCommand("insertHTML",!1,e)}).on("keyup.w2ui-editable",e=>{w.call(l,e)}).on("keydown.w2ui-editable",i=>{switch(i.keyCode){case 8:"list"!=h.type||l._w2field||i.preventDefault();break;case 9:case 13:i.preventDefault();break;case 27:var e=query(l).data("tooltipName");e&&0{switch(i.keyCode){case 9:var e=i.shiftKey?f.prevCell(o,u,!0):f.nextCell(o,u,!0);null!=e&&(t=f.records[e.index].recid,this.editChange(l,o,u,i),this.editDone(o,u,i),"row"!=f.selectType?(f.selectNone(!0),f.select({recid:t,column:e.colIndex})):f.editField(t,e.colIndex,null,i),i.preventDefault)&&i.preventDefault();break;case 13:{let e=!1;var t=query(l).data("tooltipName");t&&null!=w2tooltip.get(t[0]).selected&&(e=!0),t&&e||(this.editChange(l,o,u,i),this.editDone(o,u,i));break}case 27:{this.last._edit.escKey=!1;let e=f.parseField(a,n.field);null!=a.w2ui?.changes?.[n.field]&&(e=a.w2ui.changes[n.field]),null!=l._prevValue&&(e=l._prevValue),"DIV"==l.tagName?l.innerText=null!=e?e:"":l.value=null!=e?e:"",this.editDone(o,u,i),setTimeout(()=>{f.select({recid:d,column:u})},1);break}}w(l)},1)}),l&&(l._prevValue=i),setTimeout(()=>{this.last.inEditMode&&l&&(l.focus(),clearTimeout(this.last.kbd_timer),(l.resize=w)(l))},50),g.finish({input:l})}}}}}editChange(e,t,i,s){e=e??this.last._edit.input,t=t??this.last._edit.index,i=i??this.last._edit.column,s=s??{};var l=(t<0?this.summary:this.records)[t=t<0?-t-1:t],r=this.columns[i];let a="DIV"==e?.tagName?e.innerText:e.value;var n=e._w2field,o=(n&&("list"==n.type&&(a=n.selected),0!==Object.keys(a).length&&null!=a||(a=""),w2utils.isPlainObject(a)||(a=n.clean(a))),"checkbox"==e.type&&(!1===l.w2ui?.editable&&(e.checked=!e.checked),a=e.checked),this.parseField(l,r.field)),h=l.w2ui?.changes&&l.w2ui.changes.hasOwnProperty(r.field)?l.w2ui.changes[r.field]:o;let d={target:this.name,input:e,recid:l.recid,index:t,column:i,originalEvent:s,value:{new:a,previous:h,original:o}},u=(null!=s.target?._prevValue&&(d.value.previous=s.target._prevValue),0);for(;u<20;){if(u++,"object"!=typeof(a=d.value.new)&&String(o)!=String(a)||"object"==typeof a&&a&&a.id!=o&&("object"!=typeof o||null==o||a.id!=o.id)){if(!0!==(d=this.trigger("change",d)).isCancelled){if(a!==d.detail.value.new)continue;(""!==d.detail.value.new&&null!=d.detail.value.new||""!==h&&null!=h)&&(l.w2ui=l.w2ui??{},l.w2ui.changes=l.w2ui.changes??{},l.w2ui.changes[r.field]=d.detail.value.new),d.finish()}}else if(!0!==(d=this.trigger("restore",d)).isCancelled){if(a!==d.detail.value.new)continue;l.w2ui?.changes&&(delete l.w2ui.changes[r.field],0===Object.keys(l.w2ui.changes).length)&&delete l.w2ui.changes,d.finish()}break}}editDone(t,i,s){if(t=t??this.last._edit.index,i=i??this.last._edit.column,s=s??{},this.advanceOnEdit&&13==s.keyCode){let e=s.shiftKey?this.prevRow(t,i,1):this.nextRow(t,i,1);null==e&&(e=t),setTimeout(()=>{"row"!=this.selectType?(this.selectNone(!0),this.select({recid:this.records[e].recid,column:i})):this.editField(this.records[e].recid,i,null,s)},1)}var e=t<0,l=query(this.last._edit.tr).find('[col="'+i+'"]'),r=this.records[t],a=this.columns[i];this.last.inEditMode=!1,this.last._edit=null,e||(null!=r.w2ui?.changes?.[a.field]?l.addClass("w2ui-changed"):l.removeClass("w2ui-changed"),l.replace(this.getCellHTML(t,i,e))),query(this.box).find("div.w2ui-edit-box").remove(),this.updateToolbar(),setTimeout(()=>{var e=query(this.box).find(`#grid_${this.name}_focus`).get(0);document.activeElement===e||this.last.inEditMode||e.focus()},10)}delete(e){var t=this.trigger("delete",{target:this.name,force:e});if(e&&this.message(),!0!==t.isCancelled){e=t.detail.force;var i=this.getSelection();if(0!==i.length)if(""==this.msgDelete||e){if("object"!=typeof this.url?this.url:this.url.remove)this.request("delete");else if("object"!=typeof i[0])this.selectNone(),this.remove.apply(this,i);else{for(let e=0;e{e.detail.self.close(),this.delete(!0)}).no(e=>{e.detail.self.close()})}}click(l,r){var a=Date.now();let n=null;if(!(1==this.last.cancelClick||r&&r.altKey))if("object"==typeof l&&null!==l&&(n=l.column,l=l.recid),null==r&&(r={}),a-parseInt(this.last.click_time)<350&&this.last.click_recid==l&&"click"==r.type)this.dblClick(l,r);else{this.last.bubbleEl&&(this.last.bubbleEl=null),this.last.click_time=a;a=this.last.click_recid;if(this.last.click_recid=l,null==n&&r.target){let e=r.target;"TD"!=e.tagName&&(e=query(e).closest("td")[0]),null!=query(e).attr("col")&&(n=parseInt(query(e).attr("col")))}var o=this.trigger("click",{target:this.name,recid:l,column:n,originalEvent:r});if(!0!==o.isCancelled){var h=this.getSelection(),d=(query(this.box).find("#grid_"+this.name+"_check_all").prop("checked",!1),this.get(l,!0)),u=[];this.last.sel_ind=d,this.last.sel_col=n,this.last.sel_recid=l,this.last.sel_type="click";let e,i,t,s;if(r.shiftKey&&0h[0].column?(t=h[0].column,n):(t=n,h[0].column);for(let e=t;e<=s;e++)u.push(e)}else e=this.get(a,!0),i=this.get(l,!0);var c=[],p=(e>i&&(a=e,e=i,i=a),this.url?.get?this.url.get:this.url);for(let t=e;t<=i;t++)if(!(0=this.records.length?this.selectNone():this.selectAll())}else if(t.altKey&&(l=this.getColumn(s))&&l.sortable&&this.sort(s,null,!(!t||!t.ctrlKey&&!t.metaKey)),"line-number"==e.detail.field)this.getSelection().length>=this.records.length?this.selectNone():this.selectAll();else{t.shiftKey||t.metaKey||t.ctrlKey||this.selectNone(!0);var l=this.getSelection(),s=this.getColumn(e.detail.field,!0),i=[],r=[];if(0!=l.length&&t.shiftKey){let t=s,i=l[0].column;t>i&&(t=l[0].column,i=s);for(let e=t;e<=i;e++)r.push(e)}else r.push(s);if(!0!==(e=this.trigger("columnSelect",{target:this.name,columns:r})).isCancelled){for(let e=0;e{var e=query(this.box).find(`#grid_${this.name}_focus`).get(0);e&&document.activeElement!=e&&e.focus()},10),e.finish()}blur(e){e=this.trigger("blur",{target:this.name,originalEvent:e});if(!0===e.isCancelled)return!1;this.hasFocus=!1,query(this.box).addClass("w2ui-inactive").find(".w2ui-selected").addClass("w2ui-inactive"),query(this.box).find(".w2ui-selection").addClass("w2ui-inactive"),e.finish()}keydown(c){let p=this,f="object"!=typeof this.url?this.url:this.url.get;if(!0===p.keyboard){var m=p.trigger("keydown",{target:p.name,originalEvent:c});if(!0!==m.isCancelled)if(0t&&p.last.sel_ind!=l?p.unselect(p.records[l].recid):p.select(p.records[t].recid);else if(p.last.sel_ind>t&&p.last.sel_ind!=l){t=l;var i=[];for(let e=0;e{var e=query(p.box).find("#grid_"+p.name+"_focus"),t=e.val();e.val(""),p.editField(a,n[0],t,c)},1)),d&&c.preventDefault&&c.preventDefault(),m.finish()}}}scrollIntoView(e,s,t,i){let l=this.records.length;if(0!==(l=0==this.searchData.length||this.url?l:this.last.searchIds.length)){if(null==e){var r=this.getSelection();if(0===r.length)return;w2utils.isPlainObject(r[0])?(e=r[0].index,s=r[0].column):e=this.get(r[0],!0)}var r=query(this.box).find(`#grid_${this.name}_records`),a=r[0].clientWidth,n=r[0].clientHeight,o=r[0].scrollTop,h=r[0].scrollLeft,d=this.last.searchIds.length;if(0{clearTimeout(this.last.kbd_timer),this.contextMenuClick(i,e)}),clearTimeout(this.last.kbd_timer)),e.preventDefault(),t.finish())}}contextMenuClick(e,t){e=this.trigger("contextMenuClick",{target:this.name,recid:e,originalEvent:t.detail.originalEvent,menuEvent:t,menuIndex:t.detail.index,menuItem:t.detail.item});!0!==e.isCancelled&&e.finish()}toggle(e){var t=this.get(e);if(null!=t)return t.w2ui=t.w2ui??{},!0===t.w2ui.expanded?this.collapse(e):this.expand(e)}expand(e,t){var i=this.get(e,!0);let s=this.records[i];s.w2ui=s.w2ui??{};var l=w2utils.escapeId(e),r=s.w2ui.children;let a;if(Array.isArray(r)){if(!0===s.w2ui.expanded||0===r.length)return!1;if(!0===(a=this.trigger("expand",{target:this.name,recid:e})).isCancelled)return!1;s.w2ui.expanded=!0,r.forEach(e=>{e.w2ui=e.w2ui??{},e.w2ui.parent_recid=s.recid,null==e.w2ui.children&&(e.w2ui.children=[])}),this.records.splice.apply(this.records,[i+1,0].concat(r)),-1!==this.total&&(this.total+=r.length),("object"!=typeof this.url?this.url:this.url.get)||(this.localSort(!0,!0),0 + +
    + + + `),query(this.box).find("#grid_"+this.name+"_frec_"+l).after(` + ${this.show.lineNumbers?'':""} + +
    + + `),!0===(a=this.trigger("expand",{target:this.name,recid:e,box_id:"grid_"+this.name+"_rec_"+e+"_expanded",fbox_id:"grid_"+this.name+"_frec_"+e+"_expanded"})).isCancelled)return query(this.box).find("#grid_"+this.name+"_rec_"+l+"_expanded_row").remove(),query(this.box).find("#grid_"+this.name+"_frec_"+l+"_expanded_row").remove(),!1;i=query(this.box).find("#grid_"+this.name+"_rec_"+e+"_expanded"),r=query(this.box).find("#grid_"+this.name+"_frec_"+e+"_expanded"),t=i.find(":scope div:first-child")[0]?.clientHeight??50;i[0].clientHeight{query(this.box).find("#grid_"+this.name+"_rec_"+e+"_expanded_row").remove(),query(this.box).find("#grid_"+this.name+"_frec_"+e+"_expanded_row").remove(),l.w2ui.expanded=!1,a.finish(),this.resizeRecords()},300)}return!0}sort(i,e,s){var t=this.trigger("sort",{target:this.name,field:i,direction:e,multiField:s});if(!0!==t.isCancelled){if(null!=i){let t=this.sortData.length;for(let e=0;ei&&(i=s[e].column),-1==r.indexOf(s[e].index)&&r.push(s[e].index);r.sort((e,t)=>e-t);for(let e=0;e!!e);e.classList.forEach(e=>{t.includes(e)||i.push(e)}),e.classList.remove(...i),e.classList.add(...o)}}if(u.columns[t].style&&u.columns[t].style!=e.style.cssText&&(e.style.cssText=u.columns[t].style??""),null!=s.w2ui.class){if("string"==typeof s.w2ui.class){let t=["w2ui-odd","w2ui-even","w2ui-record"],i=[];a=s.w2ui.class.split(" ").filter(e=>!!e);l&&r&&(l.classList.forEach(e=>{t.includes(e)||i.push(e)}),l.classList.remove(...i),l.classList.add(...a),r.classList.remove(...i),r.classList.add(...a))}if(w2utils.isPlainObject(s.w2ui.class)&&"string"==typeof s.w2ui.class[n.field]){let t=["w2ui-grid-data"],i=[];h=s.w2ui.class[n.field].split(" ").filter(e=>!!e);e.classList.forEach(e=>{t.includes(e)||i.push(e)}),e.classList.remove(...i),e.classList.add(...h)}}null!=s.w2ui.style&&(l&&r&&"string"==typeof s.w2ui.style&&l.style.cssText!==s.w2ui.style&&(l.style.cssText="height: "+u.recordHeight+"px;"+s.w2ui.style,l.setAttribute("custom_style",s.w2ui.style),r.style.cssText="height: "+u.recordHeight+"px;"+s.w2ui.style,r.setAttribute("custom_style",s.w2ui.style)),w2utils.isPlainObject(s.w2ui.style))&&"string"==typeof s.w2ui.style[n.field]&&e.style.cssText!==s.w2ui.style[n.field]&&(e.style.cssText=s.w2ui.style[n.field])}}}}refreshCell(e,t){var i=this.get(e,!0),t=this.getColumn(t,!0),e=!this.records[i]||this.records[i].recid!=e,s=query(this.box).find(`${e?".w2ui-grid-summary ":""}#grid_${this.name}_data_${i}_`+t);return 0!=s.length&&(s.replace(this.getCellHTML(i,t,e)),!0)}refreshRow(t,i=null){let s=query(this.box).find("#grid_"+this.name+"_frec_"+w2utils.escapeId(t)),l=query(this.box).find("#grid_"+this.name+"_rec_"+w2utils.escapeId(t));if(0{var t=[];for(let e=0;e{var t=query(this.box).find('td[col="'+e.col+'"]:not(.w2ui-head)');w2utils.marker(t,e.search)})},50),this.updateToolbar(),t.finish(),this.resize(),this.addRange("selection"),setTimeout(()=>{this.resize(),this.scroll()},1),this.reorderColumns&&!this.last.columnDrag?this.last.columnDrag=this.initColumnDrag():!this.reorderColumns&&this.last.columnDrag&&this.last.columnDrag.remove(),Date.now()-e}}}refreshSearch(){if(this.multiSearch&&0`);let r=` + +
    `;this.searchData.forEach((i,e)=>{var t=this.getSearch(i.field,!0),s=this.searches[t];let l;if(l=Array.isArray(i.value)?`${i.value.length}`:": "+i.value,s&&"date"==s.type)if("between"==i.operator){let e=i.value[0],t=i.value[1];Number(e)===e&&(e=w2utils.formatDate(e)),Number(t)===t&&(t=w2utils.formatDate(t)),l=`: ${e} - `+t}else{let e=i.value,t=(Number(e)==e&&(e=w2utils.formatDate(e)),i.operator);"more:"==(t="less"==(t="more"==t?"since":t)?"before":t).substr(0,5)&&(t="since"),l=`: ${t} `+e}r+=` + ${s?s.label:""} + ${l} + + `}),r+=` + ${this.show.searchSave?`
    + + `:""} + + `,query(this.box).find(`#grid_${this.name}_searches`).html(r),query(this.box).find(`#grid_${this.name}_search_logic`).html(w2utils.lang("AND"==this.last.logic?"All":"Any"))}else query(this.box).find(".w2ui-grid-toolbar").css("height",this.last.toolbar_height+"px").find(".w2ui-grid-searches").remove();this.searchSelected?(query(this.box).find(`#grid_${this.name}_search_all`).val(" ").prop("readOnly",!0),query(this.box).find(`#grid_${this.name}_search_name`).show().find(".name-text").html(this.searchSelected.text)):(query(this.box).find(`#grid_${this.name}_search_all`).prop("readOnly",!1),query(this.box).find(`#grid_${this.name}_search_name`).hide().find(".name-text").html("")),w2utils.bindEvents(query(this.box).find(`#grid_${this.name}_searches .w2ui-action, #grid_${this.name}_searches button`),this)}refreshBody(){this.scroll();var e=this.getRecordsHTML(),t=this.getColumnsHTML(),e='
    '+e[0]+'
    '+e[1]+'
    '+t[0]+'
    '+t[1]+"
    "+``;let l=query(this.box).find(`#grid_${this.name}_body`,this.box).html(e);t=query(this.box).find(`#grid_${this.name}_records`,this.box),e=query(this.box).find(`#grid_${this.name}_frecords`,this.box);"row"==this.selectType&&(t.on("mouseover mouseout",{delegate:"tr"},e=>{var t=query(e.delegate).attr("recid");query(this.box).find(`#grid_${this.name}_frec_`+w2utils.escapeId(t)).toggleClass("w2ui-record-hover","mouseover"==e.type)}),e.on("mouseover mouseout",{delegate:"tr"},e=>{var t=query(e.delegate).attr("recid");query(this.box).find(`#grid_${this.name}_rec_`+w2utils.escapeId(t)).toggleClass("w2ui-record-hover","mouseover"==e.type)})),w2utils.isIOS?t.append(e).on("click",{delegate:"tr"},e=>{var t=query(e.delegate).attr("recid");this.dblClick(t,e)}):t.add(e).on("click",{delegate:"tr"},e=>{var t=query(e.delegate).attr("recid");"-none-"!=t&&this.click(t,e)}).on("contextmenu",{delegate:"tr"},e=>{var t=query(e.delegate).attr("recid"),i=query(e.target).closest("td"),i=parseInt(i.attr("col")??-1);this.showContextMenu(t,i,e)}).on("mouseover",{delegate:"tr"},e=>{this.last.rec_out=!1;let t=query(e.delegate).attr("index"),i=query(e.delegate).attr("recid");t!==this.last.rec_over&&(this.last.rec_over=t,setTimeout(()=>{delete this.last.rec_out,this.trigger("mouseEnter",{target:this.name,originalEvent:e,index:t,recid:i}).finish()}))}).on("mouseout",{delegate:"tr"},t=>{let i=query(t.delegate).attr("index"),s=query(t.delegate).attr("recid");this.last.rec_out=!0,setTimeout(()=>{let e=()=>{this.trigger("mouseLeave",{target:this.name,originalEvent:t,index:i,recid:s}).finish()};i!==this.last.rec_over&&e(),setTimeout(()=>{this.last.rec_out&&(delete this.last.rec_out,delete this.last.rec_over,e())})})}),l.data("scroll",{lastDelta:0,lastTime:0}).find(".w2ui-grid-frecords").on("mousewheel DOMMouseScroll ",e=>{e.preventDefault();var t=l.data("scroll"),i=l.find(".w2ui-grid-records"),e=null!=typeof e.wheelDelta?-e.wheelDelta:e.detail||e.deltaY,s=i.prop("scrollTop");t.lastDelta+=e,e=Math.round(t.lastDelta),l.data("scroll",t),i.get(0).scroll({top:s+e,behavior:"smooth"})}),t.off(".body-global").on("scroll.body-global",{delegate:".w2ui-grid-records"},e=>{this.scroll(e)}),query(this.box).find(".w2ui-grid-body").off(".body-global").on("click.body-global dblclick.body-global contextmenu.body-global",{delegate:"td.w2ui-head"},e=>{var t=query(e.delegate).attr("col"),i=this.columns[t]??{field:t};switch(e.type){case"click":this.columnClick(i.field,e);break;case"dblclick":this.columnDblClick(i.field,e);break;case"contextmenu":this.show.columnMenu&&(w2menu.show({type:"check",anchor:document.body,originalEvent:e,items:this.initColumnOnOff()}).then(()=>{query("#w2overlay-context-menu .w2ui-grid-skip").off(".w2ui-grid").on("click.w2ui-grid",e=>{e.stopPropagation()}).on("keypress",e=>{13==e.keyCode&&(this.skip(e.target.value),this.toolbar.click("w2ui-column-on-off"))})}).select(e=>{var t=e.detail.item.id;["w2ui-stateSave","w2ui-stateReset"].includes(t)?this[t.substring(5)]():"w2ui-skip"!=t&&this.columnOnOff(e,e.detail.item.id),clearTimeout(this.last.kbd_timer)}),clearTimeout(this.last.kbd_timer)),e.preventDefault()}}).on("mouseover.body-global",{delegate:".w2ui-col-header"},e=>{let t=query(e.delegate).parent().attr("col");this.columnTooltipShow(t,e),query(e.delegate).off(".tooltip").on("mouseleave.tooltip",()=>{this.columnTooltipHide(t,e)})}).on("click.body-global",{delegate:"input.w2ui-select-all"},e=>{e.delegate.checked?this.selectAll():this.selectNone(),e.stopPropagation(),clearTimeout(this.last.kbd_timer)}).on("click.body-global",{delegate:".w2ui-show-children, .w2ui-col-expand"},e=>{e.stopPropagation(),this.toggle(query(e.target).parents("tr").attr("recid"))}).on("click.body-global mouseover.body-global",{delegate:".w2ui-info"},e=>{var t=query(e.delegate).closest("td"),i=t.parent(),s=this.columns[t.attr("col")],l=i.parents(".w2ui-grid-body").hasClass("w2ui-grid-summary");["mouseenter","mouseover"].includes(s.info?.showOn?.toLowerCase())&&"mouseover"==e.type?this.showBubble(i.attr("index"),t.attr("col"),l).then(()=>{query(e.delegate).off(".tooltip").on("mouseleave.tooltip",()=>{w2tooltip.hide(this.name+"-bubble")})}):"click"==e.type&&(w2tooltip.hide(this.name+"-bubble"),this.showBubble(i.attr("index"),t.attr("col"),l))}).on("mouseover.body-global",{delegate:".w2ui-clipboard-copy"},l=>{if(!l.delegate._tooltipShow){let t=query(l.delegate).parent(),i=t.parent();var e=this.columns[t.attr("col")];let s=i.parents(".w2ui-grid-body").hasClass("w2ui-grid-summary");w2tooltip.show({name:this.name+"-bubble",anchor:l.delegate,html:w2utils.lang("string"==typeof e.clipboardCopy?e.clipboardCopy:"Copy to clipboard"),position:"top|bottom",offsetY:-2}).hide(e=>{l.delegate._tooltipShow=!1,query(l.delegate).off(".tooltip")}),query(l.delegate).off(".tooltip").on("mouseleave.tooltip",e=>{w2tooltip.hide(this.name+"-bubble")}).on("click.tooltip",e=>{e.stopPropagation(),w2tooltip.update(this.name+"-bubble",w2utils.lang("Copied")),this.clipboardCopy(i.attr("index"),t.attr("col"),s)}),l.delegate._tooltipShow=!0}}).on("click.body-global",{delegate:".w2ui-editable-checkbox"},e=>{var t=query(e.delegate).data();this.editChange.call(this,e.delegate,t.changeind,t.colind,e),this.updateToolbar()}),0===this.records.length&&this.msgEmpty?query(this.box).find(`#grid_${this.name}_body`).append(`
    ${w2utils.lang(this.msgEmpty)}
    `):0=this.searches.length?(this.last.field="",this.last.label=""):(this.last.field=this.searches[e].field,this.last.label=this.searches[e].label)}if(query(this.box).attr("name",this.name).addClass("w2ui-reset w2ui-grid w2ui-inactive").html('
    "),"row"!=this.selectType&&query(this.box).addClass("w2ui-ss"),0{this.searchInitInput(this.last.field,1==e.length?e[0].value:null)},1)}query(this.box).find(`#grid_${this.name}_footer`).html(this.getFooterHTML()),this.last.state||(this.last.state=this.stateSave(!0)),this.stateRestore(),e&&(this.clear(),this.refresh());let t=!1;for(let e=0;e{this.searchReset()},1)):this.reload(),query(this.box).find(`#grid_${this.name}_focus`).on("focus",e=>{clearTimeout(this.last.kbd_timer),this.hasFocus||this.focus()}).on("blur",e=>{clearTimeout(this.last.kbd_timer),this.last.kbd_timer=setTimeout(()=>{this.hasFocus&&this.blur()},100)}).on("paste",i=>{var s=i.clipboardData||null;if(s){let e=s.items,t=[];for(var l in e=2==e.length&&2==(e=2==e.length&&"file"==e[1].kind?[e[1]]:e).length&&"text/plain"==e[0].type&&"text/html"==e[1].type?[e[1]]:e){l=e[l];if("file"===l.kind){var r=l.getAsFile();t.push({kind:"file",data:r})}else if("string"===l.kind&&("text/plain"===l.type||"text/html"===l.type)){i.preventDefault();let e=s.getData("text/plain");-1!=e.indexOf("\r")&&-1==e.indexOf("\n")&&(e=e.replace(/\r/g,"\n")),t.push({kind:"text/html"==l.type?"html":"text",data:e})}}1===t.length&&"file"!=t[0].kind&&(t=t[0].data),w2ui[this.name].paste(t,i),i.preventDefault()}}).on("keydown",function(e){w2ui[p.name].keydown.call(w2ui[p.name],e)});let c;return query(this.box).off("mousedown.mouseStart").on("mousedown.mouseStart",function(l){if(1==l.which&&("text"==p.last.userSelect&&(p.last.userSelect="",query(p.box).find(".w2ui-grid-body").css("user-select","none")),!("row"==p.selectType&&(query(l.target).parents().hasClass("w2ui-head")||query(l.target).hasClass("w2ui-head"))||p.last.move&&"expand"==p.last.move.type))){if(l.altKey)query(p.box).find(".w2ui-grid-body").css("user-select","text"),p.selectNone(),p.last.move={type:"text-select"},p.last.userSelect="text";else{let e=l.target;var r={x:l.offsetX-10,y:l.offsetY-10};let t=!1;for(;e&&(!e.classList||!e.classList.contains("w2ui-grid"));)e.tagName&&"TD"==e.tagName.toUpperCase()&&(t=!0),e.tagName&&"TR"!=e.tagName.toUpperCase()&&1==t&&(r.x+=e.offsetLeft,r.y+=e.offsetTop),e=e.parentNode;p.last.move={x:l.screenX,y:l.screenY,divX:0,divY:0,focusX:r.x,focusY:r.y,recid:query(l.target).parents("tr").attr("recid"),column:parseInt(("TD"==l.target.tagName.toUpperCase()?query(l.target):query(l.target).parents("td")).attr("col")),type:"select",ghost:!1,start:!0},null==p.last.move.recid&&(p.last.move.type="select-column");let i=l.target,s=query(p.box).find("#grid_"+p.name+"_focus");if(p.last.move){let e=p.last.move.focusX,t=p.last.move.focusY;var a=query(i).parents("table").parent();(a.hasClass("w2ui-grid-records")||a.hasClass("w2ui-grid-frecords")||a.hasClass("w2ui-grid-columns")||a.hasClass("w2ui-grid-fcolumns")||a.hasClass("w2ui-grid-summary"))&&(e=p.last.move.focusX-query(p.box).find("#grid_"+p.name+"_records").prop("scrollLeft"),t=p.last.move.focusY-query(p.box).find("#grid_"+p.name+"_records").prop("scrollTop")),(query(i).hasClass("w2ui-grid-footer")||0{p.last.inEditMode||(["INPUT","TEXTAREA","SELECT"].includes(i.tagName)?i.focus():s.get(0)!==document.active&&s.get(0)?.focus({preventScroll:!0}))},50),p.multiSelect||p.reorderRows||"drag"!=p.last.move.type||delete p.last.move}if(1==p.reorderRows){let e=l.target;var t,i,s,n;"TD"!=e.tagName.toUpperCase()&&(e=query(e).parents("td")[0]),query(e).hasClass("w2ui-col-number")||query(e).hasClass("w2ui-col-order")?(p.selectNone(),p.last.move.reorder=!0,a=query(p.box).find(".w2ui-even.w2ui-empty-record").css("background-color"),t=query(p.box).find(".w2ui-odd.w2ui-empty-record").css("background-color"),query(p.box).find(".w2ui-even td").filter(":not(.w2ui-col-number)").css("background-color",a),query(p.box).find(".w2ui-odd td").filter(":not(.w2ui-col-number)").css("background-color",t),t=p.last.move,i=query(p.box).find(".w2ui-grid-records"),t.ghost||(s=query(p.box).find(`#grid_${p.name}_rec_`+t.recid),n=s.parents("table").find("tr:first-child").get(0).cloneNode(!0),t.offsetY=l.offsetY,t.from=t.recid,t.pos={top:s.get(0).offsetTop-1,left:s.get(0).offsetLeft},t.ghost=query(s.get(0).cloneNode(!0)),t.ghost.removeAttr("id"),t.ghost.find("td").css({"border-top":"1px solid silver","border-bottom":"1px solid silver"}),s.find("td").remove(),s.append(`
    `),i.append('
    '),i.append('
    '),query(p.box).find("#grid_"+p.name+"_ghost").append(n).append(t.ghost)),query(p.box).find("#grid_"+p.name+"_ghost").css({top:t.pos.top+"px",left:t.pos.left+"px"})):p.last.move.reorder=!1}query(document).on("mousemove.w2ui-"+p.name,o).on("mouseup.w2ui-"+p.name,h),l.stopPropagation()}}),this.updateToolbar(),s.finish(),this.last.observeResize=new ResizeObserver(()=>{this.resize()}),this.last.observeResize.observe(this.box),Date.now()-i;function o(t){if(t.target.tagName){var r=p.last.move;if(r&&-1!=["select","select-column"].indexOf(r.type)&&(r.divX=t.screenX-r.x,r.divY=t.screenY-r.y,!(Math.abs(r.divX)<=1&&Math.abs(r.divY)<=1)))if(p.last.cancelClick=!0,1==p.reorderRows&&p.last.move.reorder){let e=query(t.target).parents("tr").attr("recid");(e="-none-"==e?"bottom":e)!=r.from&&(n=query(p.box).find("#grid_"+p.name+"_rec_"+e),query(p.box).find(".insert-before"),n.addClass("insert-before"),r.lastY=t.screenY,r.to=e,n={top:n.get(0)?.offsetTop,left:n.get(0)?.offsetLeft},query(p.box).find("#grid_"+p.name+"_ghost_line").css({top:n.top+"px",left:r.pos.left+"px","border-top":"2px solid #769EFC"})),void query(p.box).find("#grid_"+p.name+"_ghost").css({top:r.pos.top+r.divY+"px",left:r.pos.left+"px"})}else{r.start&&r.recid&&(p.selectNone(),r.start=!1);var a=[],n=("TR"==t.target.tagName.toUpperCase()?query(t.target):query(t.target).parents("tr")).attr("recid");if(null==n){if("row"!=p.selectType&&(!p.last.move||"select"!=p.last.move.type)){var o=parseInt(query(t.target).parents("td").attr("col"));if(isNaN(o))p.removeRange("column-selection"),query(p.box).find(".w2ui-grid-columns .w2ui-col-header, .w2ui-grid-fcolumns .w2ui-col-header").removeClass("w2ui-col-selected"),query(p.box).find(".w2ui-col-number").removeClass("w2ui-row-selected"),delete r.colRange;else{let e=o+"-"+o;r.columno?o+"-"+r.column:e).split("-");for(let e=parseInt(s[0]);e<=parseInt(s[1]);e++)i.push(e);if(r.colRange!=e&&!0!==(c=p.trigger("columnSelect",{target:p.name,columns:i})).isCancelled){null==r.colRange&&p.selectNone();var l=e.split("-");query(p.box).find(".w2ui-grid-columns .w2ui-col-header, .w2ui-grid-fcolumns .w2ui-col-header").removeClass("w2ui-col-selected");for(let e=parseInt(l[0]);e<=parseInt(l[1]);e++)query(p.box).find("#grid_"+p.name+"_column_"+e+" .w2ui-col-header").addClass("w2ui-col-selected");query(p.box).find(".w2ui-col-number").not(".w2ui-head").addClass("w2ui-row-selected"),r.colRange=e,p.removeRange("column-selection"),p.addRange({name:"column-selection",range:[{recid:p.records[0].recid,column:l[0]},{recid:p.records[p.records.length-1].recid,column:l[1]}],style:"background-color: rgba(90, 145, 234, 0.1)"})}}}}else{let l=p.get(r.recid,!0);if(!(null==l||p.records[l]&&p.records[l].recid!=r.recid)){let e=p.get(n,!0);if(null!=e){let i=parseInt(r.column),s=parseInt(("TD"==t.target.tagName.toUpperCase()?query(t.target):query(t.target).parents("td")).attr("col"));isNaN(i)&&isNaN(s)&&(i=0,s=p.columns.length-1),l>e&&(o=l,l=e,e=o);var h,n="ind1:"+l+",ind2;"+e+",col1:"+i+",col2:"+s;if(r.range!=n){r.range=n;for(let t=l;t<=e;t++)if(!(0s&&(h=i,i=s,s=h);for(let e=i;e<=s;e++)p.columns[e].hidden||a.push({recid:p.records[t].recid,column:parseInt(e)})}else a.push(p.records[t].recid);if("row"!=p.selectType){var d=p.getSelection();let e=[];for(let i=0;i{delete p.last.cancelClick},1),!query(t.target).parents().hasClass(".w2ui-head")&&!query(t.target).hasClass(".w2ui-head")){if(i&&-1!=["select","select-column"].indexOf(i.type)){if(null!=i.colRange&&!0!==c.isCancelled){var s=i.colRange.split("-"),l=[];for(let e=0;ee?p.records.splice(e,0,i):p.records.splice(e-1,0,i)),p.sortData=[],query(p.box).find(`#grid_${p.name}_columns .w2ui-col-header`).removeClass("w2ui-col-sorted"),n(),t.finish()}else n()}delete p.last.move,query(document).off(".w2ui-"+p.name)}}function n(){query(p.box).find(`#grid_${p.name}_ghost`).remove(),query(p.box).find(`#grid_${p.name}_ghost_line`).remove(),p.refresh(),delete p.last.move}}}destroy(){var e=this.trigger("destroy",{target:this.name});!0!==e.isCancelled&&(query(this.box).off(),"object"==typeof this.toolbar&&this.toolbar.destroy&&this.toolbar.destroy(),0`+w2utils.lang("records"),i.push({id:"w2ui-skip",text:e,group:!1,icon:"w2ui-icon-empty"})),this.show.saveRestoreState&&i.push({id:"w2ui-stateSave",text:w2utils.lang("Save Grid State"),icon:"w2ui-icon-empty",group:!1},{id:"w2ui-stateReset",text:w2utils.lang("Restore Default State"),icon:"w2ui-icon-empty",group:!1});let t=[];return i.forEach(e=>{e.text=w2utils.lang(e.text),e.checked&&t.push(e.id)}),this.toolbar.set("w2ui-column-on-off",{selected:t,items:i}),i}initColumnDrag(e){if(this.columnGroups&&this.columnGroups.length)throw"Draggable columns are not currently supported with column groups.";let r=this,a={pressed:!1,targetPos:null,columnHead:null},n=(t,e)=>{var i=["w2ui-col-number","w2ui-col-expand","w2ui-col-select"];!0!==e&&i.push("w2ui-head-last");for(let e=0;e{var e=query(r.box).find(".w2ui-grid-ghost");query(r.box).find(".w2ui-intersection-marker").hide(),query(a.ghost).remove(),e.remove(),query(document).off(".colDrag"),a={}};if(e.pageX==a.initialX&&e.pageY==a.initialY)r.columnClick(r.columns[a.originalPos].field,e),s();else{if(!0===(e=r.trigger("columnDragEnd",{originalEvent:e,target:a.columnHead[0],dragData:a})).isCancelled)return!1;t=r.columns[a.originalPos],i=r.columns,a.originalPos!=a.targetPos&&null!=a.targetPos&&(i.splice(a.targetPos,0,w2utils.clone(t)),i.splice(i.indexOf(t),1)),s(),r.refresh(),e.finish({targetColumn:NaN})}}}return query(r.box).off(".colDrag").on("mousedown.colDrag",function(e){if(!a.pressed&&0!==a.numberPreColumnsPresent&&0===e.button){var i,t;if(query(e.target).parents().hasClass("w2ui-head")&&!n(e.target)){if(a.pressed=!0,a.initialX=e.pageX,a.initialY=e.pageY,a.numberPreColumnsPresent=query(r.box).find(".w2ui-head.w2ui-col-number, .w2ui-head.w2ui-col-expand, .w2ui-head.w2ui-col-select").length,a.columnHead=s=query(e.target).closest(".w2ui-head"),a.originalPos=t=parseInt(s.attr("col"),10),!0===(t=r.trigger("columnDragStart",{originalEvent:e,origColumnNumber:t,target:s[0]})).isCancelled)return!1;i=a.columns=query(r.box).find(".w2ui-head:not(.w2ui-head-last)"),query(document).on("mouseup.colDrag",h),query(document).on("mousemove.colDrag",o);var s=r.columns[a.originalPos],s=w2utils.lang("function"==typeof s.text?s.text(s):s.text);a.ghost=query.html(`${s}`)[0],query(document.body).append(a.ghost),query(a.ghost).css({display:"none",left:e.pageX,top:e.pageY,opacity:1,margin:"3px 0 0 20px",padding:"3px","background-color":"white",position:"fixed","z-index":999999}).addClass(".w2ui-grid-ghost"),a.offsets=[];for(let e=0,t=i.length;e + ${this.buttons.search.html} +
    + + + x +
    + +
    + +
    + `,this.toolbar.items.push({id:"w2ui-search",type:"html",html:t,onRefresh:async e=>{await e.complete;var e=query(this.box).find(`#grid_${this.name}_search_all`),t=(w2utils.bindEvents(query(this.box).find(`#grid_${this.name}_search_all, .w2ui-action`),this),w2utils.debounce(e=>{var t=e.target.value;this.liveSearch&&this.last.liveText!=t&&(this.last.liveText=t,this.search(this.last.field,t)),40==e.keyCode&&this.searchSuggest(!0)},250));e.on("change",e=>{this.liveSearch||(this.search(this.last.field,e.target.value),this.searchSuggest(!0,!0,this))}).on("blur",()=>{this.last.liveText=""}).on("keyup",t)}})),Array.isArray(e)&&(t=e.map(e=>e.id),this.show.toolbarAdd&&!t.includes(this.buttons.add.id)&&this.toolbar.items.push(w2utils.extend({},this.buttons.add)),this.show.toolbarEdit&&!t.includes(this.buttons.edit.id)&&this.toolbar.items.push(w2utils.extend({},this.buttons.edit)),this.show.toolbarDelete&&!t.includes(this.buttons.delete.id)&&this.toolbar.items.push(w2utils.extend({},this.buttons.delete)),this.show.toolbarSave&&!t.includes(this.buttons.save.id)&&((this.show.toolbarAdd||this.show.toolbarDelete||this.show.toolbarEdit)&&this.toolbar.items.push({type:"break",id:"w2ui-break2"}),this.toolbar.items.push(w2utils.extend({},this.buttons.save))),e=e.map(e=>this.buttons[e.name]?w2utils.extend({},this.buttons[e.name],e):e)),this.toolbar.items.push(...e),this.toolbar.on("click",e=>{var i=this.trigger("toolbar",{target:e.target,originalEvent:e});if(!0!==i.isCancelled){let t;switch(e.detail.item.id){case"w2ui-reload":if(!0===(t=this.trigger("reload",{target:this.name})).isCancelled)return!1;this.reload(),t.finish();break;case"w2ui-column-on-off":e.detail.subItem?(s=e.detail.subItem.id,["w2ui-stateSave","w2ui-stateReset"].includes(s)?this[s.substring(5)]():"w2ui-skip"!=s&&this.columnOnOff(e,e.detail.subItem.id)):(this.initColumnOnOff(),setTimeout(()=>{query(`#w2overlay-${this.name}_toolbar-drop .w2ui-grid-skip`).off(".w2ui-grid").on("click.w2ui-grid",e=>{e.stopPropagation()}).on("keypress",e=>{13==e.keyCode&&(this.skip(e.target.value),this.toolbar.click("w2ui-column-on-off"))})},100));break;case"w2ui-add":if(!0===(t=this.trigger("add",{target:this.name,recid:null})).isCancelled)return!1;t.finish();break;case"w2ui-edit":{var s=this.getSelection();let e=null;if(1==s.length&&(e=s[0]),!0===(t=this.trigger("edit",{target:this.name,recid:e})).isCancelled)return!1;t.finish();break}case"w2ui-delete":this.delete();break;case"w2ui-save":this.save()}i.finish()}}),this.toolbar.on("refresh",e=>{if("w2ui-search"==e.target){let e=this.searchData;setTimeout(()=>{this.searchInitInput(this.last.field,1==e.length?e[0].value:null)},1)}})}}initResize(){let r=this;query(this.box).find(".w2ui-resizer").off(".grid-col-resize").on("click.grid-col-resize",function(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0,e.preventDefault&&e.preventDefault()}).on("mousedown.grid-col-resize",function(e){e=e||window.event,r.last.colResizing=!0,r.last.tmp={x:e.screenX,y:e.screenY,gx:e.screenX,gy:e.screenY,col:parseInt(query(this).attr("name"))},r.last.tmp.tds=query(r.box).find("#grid_"+r.name+'_body table tr:first-child td[col="'+r.last.tmp.col+'"]'),e.stopPropagation?e.stopPropagation():e.cancelBubble=!0,e.preventDefault&&e.preventDefault();for(let e=0;e{r.resizeRecords(),r.scroll()},100),r.last.tmp.tds.css({width:t}),r.last.tmp.x=e.screenX,r.last.tmp.y=e.screenY))}).on("mouseup.grid-col-resize",function(e){query(document).off(".grid-col-resize"),r.resizeRecords(),r.scroll(),i.finish({originalEvent:e}),setTimeout(()=>{r.last.colResizing=!1},1)})}).on("dblclick.grid-col-resize",function(e){let t=parseInt(query(this).attr("name")),i=r.columns[t],s=0;if(!1===i.autoResize)return!0;e.stopPropagation?e.stopPropagation():e.cancelBubble=!0,e.preventDefault&&e.preventDefault(),query(r.box).find('.w2ui-grid-records td[col="'+t+'"] > div',r.box).each(()=>{var e=this.offsetWidth-this.scrollWidth;e{var t=query(e).get(0).parentNode;query(e).css({height:t.clientHeight+"px","margin-left":t.clientWidth-3+"px"})})}resizeBoxes(){var e=query(this.box).find(`#grid_${this.name}_header`),t=query(this.box).find(`#grid_${this.name}_toolbar`),i=query(this.box).find(`#grid_${this.name}_fsummary`),s=query(this.box).find(`#grid_${this.name}_summary`),l=query(this.box).find(`#grid_${this.name}_footer`),r=query(this.box).find(`#grid_${this.name}_body`);this.show.header&&e.css({top:"0px",left:"0px",right:"0px"}),this.show.toolbar&&t.css({top:0+(this.show.header?w2utils.getSize(e,"height"):0)+"px",left:"0px",right:"0px"}),0 div.w2ui-grid-box"),r=query(this.box).find(`#grid_${this.name}_header`),a=query(this.box).find(`#grid_${this.name}_toolbar`),n=query(this.box).find(`#grid_${this.name}_summary`),o=query(this.box).find(`#grid_${this.name}_fsummary`),h=query(this.box).find(`#grid_${this.name}_footer`),d=query(this.box).find(`#grid_${this.name}_body`),u=query(this.box).find(`#grid_${this.name}_columns`),c=query(this.box).find(`#grid_${this.name}_fcolumns`),p=query(this.box).find(`#grid_${this.name}_records`),f=query(this.box).find(`#grid_${this.name}_frecords`),m=query(this.box).find(`#grid_${this.name}_scroll1`);let g=8*String(this.total).length+10,y=(g<34&&(g=34),null!=this.lineNumberWidth&&(g=this.lineNumberWidth),!1),w=!1,b=0;for(let e=0;e table")[0]?.clientHeight??0)+(y?w2utils.scrollBarSize():0)&&(w=!0),this.fixedBody?(e=l[0]?.clientHeight-(this.show.header?w2utils.getSize(r,"height"):0)-(this.show.toolbar?w2utils.getSize(a,"height"):0)-("none"!=n.css("display")?w2utils.getSize(n,"height"):0)-(this.show.footer?w2utils.getSize(h,"height"):0),d.css("height",e+"px")):(r=(e=w2utils.getSize(u,"height")+w2utils.getSize(query(this.box).find("#grid_"+this.name+"_records table"),"height")+(y?w2utils.scrollBarSize():0))+(this.show.header?w2utils.getSize(r,"height"):0)+(this.show.toolbar?w2utils.getSize(a,"height"):0)+("none"!=n.css("display")?w2utils.getSize(n,"height"):0)+(this.show.footer?w2utils.getSize(h,"height"):0),l.css("height",r+"px"),d.css("height",e+"px"),s.css("height",w2utils.getSize(l,"height")+"px"));let v=this.records.length;a="object"!=typeof this.url?this.url:this.url.get;if(0==this.searchData.length||a||(v=this.last.searchIds.length),this.fixedBody||(w=!1),y||w?(u.find(":scope > table > tbody > tr:nth-child(1) td.w2ui-head-last").css("width",w2utils.scrollBarSize()+"px").show(),p.css({top:(0 table > tbody > tr:nth-child(1) td.w2ui-head-last").hide(),p.css({top:(0=this.recordHeight&&(e-=this.recordHeight,t++),this.fixedBody){for(let e=v;e',l+='',i.show.lineNumbers&&(s+=''),i.show.selectColumn&&(s+=''),i.show.expandColumn&&(s+=''),l+='',i.reorderRows&&(l+='');for(let e=0;ei.last.colEnd)&&!a.frozen||(r='',a.frozen?s+=r:l+=r)}s+=' ',l+=' ',query(i.box).find("#grid_"+i.name+"_frecords > table").append(s),query(i.box).find("#grid_"+i.name+"_records > table").append(l)}let _,q;if(0_&&!0!==C.hidden&&(C.hidden=!0,i=!0),C.gridMinWidth<_)&&!0===C.hidden&&(C.hidden=!1,i=!0)}if(!0===i)return void this.refresh();for(let e=0;eparseInt(E.max)&&(E.sizeCalculated=E.max+"px"),$+=parseInt(E.sizeCalculated))}let z=parseInt(_)-parseInt($);if(0 table > tbody > tr:nth-child(1) td.w2ui-head-last").css("width",w2utils.scrollBarSize()+"px").show();let O=1;this.show.lineNumbers&&(O+=g),this.show.selectColumn&&(O+=26),this.show.expandColumn&&(O+=26);for(let e=0;e table > tbody > tr:nth-child(1) td").add(c.find(":scope > table > tbody > tr:nth-child(1) td")).each(e=>{query(e).hasClass("w2ui-col-number")&&query(e).css("width",g+"px");var t=query(e).attr("col");if(null!=t){if("start"==t){let t=0;for(let e=0;e table > tbody > tr").length&&u.find(":scope > table > tbody > tr:nth-child(1) td").add(c.find(":scope > table > tbody > tr:nth-child(1) td")).html("").css({height:"0",border:"0",padding:"0",margin:"0"}),p.find(":scope > table > tbody > tr:nth-child(1) td").add(f.find(":scope > table > tbody > tr:nth-child(1) td")).each(e=>{query(e).hasClass("w2ui-col-number")&&query(e).css("width",g+"px");var t=query(e).attr("col");if(null!=t){if("start"==t){let t=0;for(let e=0;e table > tbody > tr:nth-child(1) td").add(o.find(":scope > table > tbody > tr:nth-child(1) td")).each(e=>{query(e).hasClass("w2ui-col-number")&&query(e).css("width",g+"px");var t=query(e).attr("col");if(null!=t){if("start"==t){let t=0;for(let e=0;e + ${w2utils.lang("Advanced Search")} + + + + + + `;for(let t=0;t",s),s.label=s.caption);var l=``;i+=` + + + "}}return i+=` + + +
    ${w2utils.lang(s.label)||""}${l}`;let e;switch(s.type){case"text":case"alphanumeric":case"hex":case"color":case"list":case"combo":case"enum":e="width: 250px;",-1!=["hex","color"].indexOf(s.type)&&(e="width: 90px;"),i+=``;break;case"int":case"float":case"money":case"currency":case"percent":case"date":case"time":case"datetime":e="width: 90px;","datetime"==s.type&&(e="width: 140px;"),i+=` + `;break;case"select":i+=``}i+=s.text+"
    + + + + +
    `}getOperators(e,t){let i=this.operators[this.operatorsMap[e]]||[],s=(null!=t&&Array.isArray(t)&&(i=t),"");return i.forEach(e=>{let t=e,i=e;Array.isArray(e)?(t=e[1],i=e[0]):w2utils.isPlainObject(e)&&(t=e.text,i=e.oper),null==t&&(t=e),s+=` +`}),s}initOperator(e){let i;var t=this.searches[e],s=this.getSearchData(t.field),l=query(`#w2overlay-${this.name}-search-overlay`),r=l.find(`#grid_${this.name}_range_`+e);let a=l.find(`#grid_${this.name}_field_`+e),n=l.find(`#grid_${this.name}_field2_`+e);var o=l.find(`#grid_${this.name}_operator_`+e).val();switch(a.show(),r.hide(),o){case"between":r.show();break;case"null":case"not null":a.hide(),a.val(o),a.trigger("change")}switch(t.type){case"text":case"alphanumeric":var h=a[0]._w2field;h&&h.reset();break;case"int":case"float":case"hex":case"color":case"money":case"currency":case"percent":case"date":case"time":case"datetime":a[0]._w2field||(new w2field(t.type,{el:a[0],...t.options}),new w2field(t.type,{el:n[0],...t.options}),setTimeout(()=>{a.trigger("keydown"),n.trigger("keydown")},1));break;case"list":case"combo":case"enum":i=t.options,"list"==t.type&&(i.selected={}),"enum"==t.type&&(i.selected=[]),s&&(i.selected=s.value),a[0]._w2field||(h=new w2field(t.type,{el:a[0],...i}),s&&null!=s.text&&h.set({id:s.value,text:s.text}));break;case"select":i='';for(let e=0;e'+t+""}else i+='"}a.html(i)}}initSearches(){var s=query(`#w2overlay-${this.name}-search-overlay`);for(let t=0;t{w2utils.isPlainObject(e)&&(i[t]=e.oper)}),r&&r.operator&&(e=r.operator);var l=this.defaultOperator[this.operatorsMap[l.type]],l=(-1==i.indexOf(e)&&(e=l),s.find(`#grid_${this.name}_operator_`+t).val(e),this.initOperator(t),s.find(`#grid_${this.name}_field_`+t)),a=s.find(`#grid_${this.name}_field2_`+t);null!=r&&(Array.isArray(r.value)?["in","not in"].includes(r.operator)?l[0]._w2field.set(r.value):(l.val(r.value[0]).trigger("change"),a.val(r.value[1]).trigger("change")):null!=r.value&&l.val(r.value).trigger("change"))}s.find(".w2ui-grid-search-advanced *[rel=search]").on("keypress",e=>{13==e.keyCode&&(this.search(),w2tooltip.hide(this.name+"-search-overlay"))})}getColumnsHTML(){let h=this,e="",t="";var i,s,l;return this.show.columnHeaders&&(t=0 ",h.columnGroups[e]),h.columnGroups[e].text=h.columnGroups[e].caption);""!=h.columnGroups[h.columnGroups.length-1].text&&h.columnGroups.push({text:""});h.show.lineNumbers&&(t+='
     
    ');h.show.selectColumn&&(t+='
     
    ');h.show.expandColumn&&(t+='
     
    ');let r=0;s+=``,h.reorderRows&&(s+='
     
    ');for(let e=0;e",n),n.text=n.caption);let i=0;for(let e=r;e`);var o=w2utils.lang("function"==typeof n.text?n.text(n):n.text);l=``+e+`
    `+`
    `+(o||" ")+"
    "}else{o=w2utils.lang("function"==typeof a.text?a.text(a):a.text);l=``+`
    ${o||" "}
    `+""}n&&n.frozen?t+=l:s+=l}r+=a.span}return t+="",s+=``,[t,s]}(),s=r(!1),e=l[0]+i[0]+s[0],l[1]+i[1]+s[1]):(l=r(!0),e=l[0],l[1])),[e,t];function r(t){let i="",s="",l=(h.show.lineNumbers&&(i+='
    #
    '),h.show.selectColumn&&(i+='
    '+`
    "),h.show.expandColumn&&(i+='
     
    '),0),r=0,a;s+=``,h.reorderRows&&(s+='
     
    ');for(let e=0;e ",o),o.text=o.caption),null==o.size&&(o.size="100%"),e==r&&(a=h.columnGroups[l++]||{},r+=a.span),(eh.last.colEnd)&&!o.frozen||o.hidden||!0===a.main&&!t||(n=h.getColumnCellHTML(e),o&&o.frozen?i+=n:s+=n)}return i+='
     
    ',s+='
     
    ',i+="",s+="",[i,s]}}getColumnCellHTML(t){var i=this.columns[t];if(null==i)return"";var e=!this.reorderColumns||this.columnGroups&&this.columnGroups.length?"":" w2ui-col-reorderable ";let s="";for(let e=0;e'+(!1!==i.resizable?'
    ':"")+'
    '+(n||" ")+"
    "}columnTooltipShow(e,t){var i=query(this.box).find("#grid_"+this.name+"_column_"+e),e=this.columns[e],s=this.columnTooltip;w2tooltip.show({name:this.name+"-column-tooltip",anchor:i.get(0),html:e.tooltip,position:s})}columnTooltipHide(e,t){w2tooltip.hide(this.name+"-column-tooltip")}getRecordsHTML(){let e=this.records.length;var t="object"!=typeof this.url?this.url:this.url.get,t=((e=0==this.searchData.length||t?e:this.last.searchIds.length)>this.vs_start?this.last.show_extra=this.vs_extra:this.last.show_extra=this.vs_start,query(this.box).find(`#grid_${this.name}_records`));let i=Math.floor((t.get(0)?.clientHeight||0)/this.recordHeight)+this.last.show_extra+1;(!this.fixedBody||i>e)&&(i=e);var s=this.getRecordHTML(-1,0);let l=""+s[0],r="
    "+s[1];l+='',r+='';for(let e=0;e
    ',r+=' ',this.last.range_start=0,this.last.range_end=i,[l,r]}getSummaryHTML(){if(0!==this.summary.length){var s=this.getRecordHTML(-1,0);let t=""+s[0],i="
    "+s[1];for(let e=0;ethis.last.scrollLeft&&null==l&&(l=e),t+s-30>this.last.scrollLeft+a&&null==r&&(r=e),t+=s);null==r&&(r=this.columns.length-1)}if(null!=l&&(l<0&&(l=0),r<0&&(r=0),l==r&&(0this.last.colStart)for(let e=this.last.colStart;er;e--)n.find("#grid_"+this.name+"_columns #grid_"+this.name+"_column_"+e).remove(),n.find("#grid_"+this.name+'_records td[col="'+e+'"]').remove(),n.find("#grid_"+this.name+'_summary td[col="'+e+'"]').remove();if(l=l;s--)this.columns[s]&&(this.columns[s].frozen||this.columns[s].hidden)||(e.after(this.getColumnCellHTML(s)),f.each(e=>{var t=query(e).parent().attr("index");let i='';null!=t&&(i=this.getCellHTML(parseInt(t),s,!1)),query(e).after(i)}),g.each(e=>{var t=query(e).parent().attr("index");let i='';null!=t&&(i=this.getCellHTML(parseInt(t),s,!0)),query(e).after(i)}));if(r>this.last.colEnd)for(let s=this.last.colEnd+1;s<=r;s++)this.columns[s]&&(this.columns[s].frozen||this.columns[s].hidden)||(t.before(this.getColumnCellHTML(s)),m.each(e=>{var t=query(e).parent().attr("index");let i='';null!=t&&(i=this.getCellHTML(parseInt(t),s,!1)),query(e).before(i)}),y.each(e=>{var t=query(e).parent().attr("index")||-1,t=this.getCellHTML(parseInt(t),s,!0);query(e).before(t)}));this.last.colStart=l,this.last.colEnd=r}else{this.last.colStart=l,this.last.colEnd=r;var o=this.getColumnsHTML(),w=this.getRecordsHTML(),c=this.getSummaryHTML(),p=n.find(`#grid_${this.name}_columns`);let e=n.find(`#grid_${this.name}_records`);var b=n.find(`#grid_${this.name}_frecords`);let t=n.find(`#grid_${this.name}_summary`);p.find("tbody").html(o[1]),b.html(w[0]),e.prepend(w[1]),null!=c&&t.html(c[1]),setTimeout(()=>{e.find(":scope > table").filter(":not(table:first-child)").remove(),t[0]&&(t[0].scrollLeft=this.last.scrollLeft)},1)}this.resizeRecords()}let v=this.records.length;if(v>this.total&&-1!==this.total&&(v=this.total),0!==(v=0==this.searchData.length||i?v:this.last.searchIds.length)&&0!==d.length&&0!==d.prop("clientHeight")){v>this.vs_start?this.last.show_extra=this.vs_extra:this.last.show_extra=this.vs_start;let e=Math.round(d.prop("scrollTop")/this.recordHeight+1),t=e+(Math.round(d.prop("clientHeight")/this.recordHeight)-1);if(e>v&&(e=v),t>=v-1&&(t=v),query(this.box).find("#grid_"+this.name+"_footer .w2ui-footer-right").html((this.show.statusRange?w2utils.formatNumber(this.offset+e)+"-"+w2utils.formatNumber(this.offset+t)+(-1!=this.total?" "+w2utils.lang("of")+" "+w2utils.formatNumber(this.total):""):"")+(i&&this.show.statusBuffered?" ("+w2utils.lang("buffered")+" "+w2utils.formatNumber(v)+(0this.total&&-1!=this.total&&(i=this.total);var x=d.find("#grid_"+this.name+"_rec_top"),_=d.find("#grid_"+this.name+"_rec_bottom"),q=u.find("#grid_"+this.name+"_frec_top"),C=u.find("#grid_"+this.name+"_frec_bottom"),p=(-1!=String(x.next().prop("id")).indexOf("_expanded_row")&&(x.next().remove(),q.next().remove()),this.total>i&&-1!=String(_.prev().prop("id")).indexOf("_expanded_row")&&(_.prev().remove(),C.prev().remove()),parseInt(x.next().attr("line"))),o=parseInt(_.prev().attr("line"));let e,s,l,r,a;if(p=p-this.last.show_extra+2&&1i))break;s.remove(),l.remove()}e=d.find("#grid_"+this.name+"_rec_top").next(),"bottom"==(r=e.attr("line"))&&(r=i);for(let e=parseInt(r)-1;e>=t;e--)this.records[e-1]&&((l=this.records[e-1].w2ui)&&!Array.isArray(l.children)&&(l.expanded=!1),a=this.getRecordHTML(e-1,e),x.after(a[1]),q.after(a[0]))}k(),setTimeout(()=>{this.refreshRanges()},0);b=(t-1)*this.recordHeight;let n=(v-i)*this.recordHeight;function k(){h.markSearch&&(clearTimeout(h.last.marker_timer),h.last.marker_timer=setTimeout(()=>{var t=[];for(let e=0;e{var t=query(h.box).find('td[col="'+e.col+'"]:not(.w2ui-head)');w2utils.marker(t,e.search)})},50))}n<0&&(n=0),x.css("height",b+"px"),q.css("height",b+"px"),_.css("height",n+"px"),C.css("height",n+"px"),this.last.range_start=t,this.last.range_end=i,Math.floor(d.prop("scrollTop")/this.recordHeight)+Math.floor(d.prop("clientHeight")/this.recordHeight)+10>v&&!0!==this.last.pull_more&&(v
    '),h.last.pull_more=!0,h.last.fetch.offset+=h.limit,h.request("load")}).find("td").html(h.autoLoad?'
    ':'
    '+w2utils.lang("Load ${count} more...",{count:h.limit})+"
    "))}}}getRecordHTML(r,a,n){let o="",h="";var d=this.last.selection;let u;if(-1==r){o+='
    ',h+='',this.show.lineNumbers&&(o+=''),this.show.selectColumn&&(o+=''),this.show.expandColumn&&(o+=''),h+='',this.reorderRows&&(h+='');for(let e=0;e';t.frozen&&!t.hidden?o+=i:t.hidden||ethis.last.colEnd||(h+=i)}o+='',h+=''}else{var c="object"!=typeof this.url?this.url:this.url.get;if(!0!==n){if(0=this.last.searchIds.length)return"";r=this.last.searchIds[r]}else if(r>=this.records.length)return"";u=this.records[r]}else{if(r>=this.summary.length)return"";u=this.summary[r]}if(!u)return"";null==u.recid&&null!=this.recid&&null!=(c=this.parseField(u,this.recid))&&(u.recid=c);let e=!1,t=(-1!=d.indexes.indexOf(r)&&(e=!0),u.w2ui?u.w2ui.style:""),i=(null!=t&&"string"==typeof t||(t=""),u.w2ui?u.w2ui.class:"");if(null!=i&&"string"==typeof i||(i=""),o+='",h+='",this.show.lineNumbers&&(o+='"),this.show.selectColumn&&(o+='"),this.show.expandColumn){let e="";e=!0===u.w2ui?.expanded?"-":"+","none"!=u.w2ui?.expanded&&Array.isArray(u.w2ui.children)&&u.w2ui.children.length||(e="+"),"spinner"==u.w2ui?.expanded&&(e='
    '),o+='"}h+='',this.reorderRows&&(h+='");let s=0,l=0;for(;;){let e=1;var p,f=this.columns[s];if(null==f)break;if(f.hidden)s++,0this.last.colEnd)||f.frozen){if(u.w2ui&&"object"==typeof u.w2ui.colspan){var m=parseInt(u.w2ui.colspan[f.field])||null;if(1=this.columns.length);e++)this.columns[e].hidden&&t++;e=m-t,l=m-1}}var g=this.getCellHTML(r,s,n,e);f.frozen?o+=g:h+=g}s++}}o+='',h+=''}return o+="",h+="",[o,h]}getLineHTML(e){return"
    "+e+"
    "}getCellHTML(i,s,l,e){let r=this,a=this.columns[s];if(null==a)return"";let n=(!0!==l?this.records:this.summary)[i],{value:t,style:o,className:h,attr:d,divAttr:u}=this.getCellValue(i,s,l,!0);var c=-1!==i?this.getCellEditable(i,s):"";let p="max-height: "+parseInt(this.recordHeight)+"px;"+(a.clipboardCopy?"margin-right: 20px":"");var f=!l&&n?.w2ui?.changes&&null!=n.w2ui.changes[a.field],m=this.last.selection;let g=!1,y="";if(-1!=m.indexes.indexOf(i)&&(g=!0),null==e&&(e=n?.w2ui?.colspan&&n.w2ui.colspan[a.field]?n.w2ui.colspan[a.field]:1),0===s&&Array.isArray(n?.w2ui?.children)){let t=0,e=this.get(n.w2ui.parent_recid,!0);for(;;){if(null==e)break;t++;var w=this.records[e].w2ui;if(null==w||null==w.parent_recid)break;e=this.get(w.parent_recid,!0)}if(n.w2ui.parent_recid)for(let e=0;e';var b=0`}if(!0===a.info&&(a.info={}),null!=a.info){let e="w2ui-icon-info",t=("function"==typeof a.info.icon?e=a.info.icon(n,{self:this,index:i,colIndex:s,summary:!!l}):"object"==typeof a.info.icon?e=a.info.icon[this.parseField(n,a.field)]||"":"string"==typeof a.info.icon&&(e=a.info.icon),a.info.style||"");"function"==typeof a.info.style?t=a.info.style(n,{self:this,index:i,colIndex:s,summary:!!l}):"object"==typeof a.info.style?t=a.info.style[this.parseField(n,a.field)]||"":"string"==typeof a.info.style&&(t=a.info.style),y+=``}let v=t,x=(c&&-1!=["checkbox","check"].indexOf(c.type)&&(p+="text-align: center;",v=``,y=""),null==(v=`
    ${y}${String(v)}
    `)&&(v=""),"string"==typeof a.render&&(b=a.render.toLowerCase().split(":"),-1!=["number","int","float","money","currency","percent","size"].indexOf(b[0]))&&(o+="text-align: right;"),n?.w2ui&&("object"==typeof n.w2ui.style&&("string"==typeof n.w2ui.style[s]&&(o+=n.w2ui.style[s]+";"),"string"==typeof n.w2ui.style[a.field])&&(o+=n.w2ui.style[a.field]+";"),"object"==typeof n.w2ui.class)&&("string"==typeof n.w2ui.class[s]&&(h+=n.w2ui.class[s]+" "),"string"==typeof n.w2ui.class[a.field])&&(h+=n.w2ui.class[a.field]+" "),!1);g&&m.columns[i]?.includes(s)&&(x=!0);let _;return a.clipboardCopy&&(_=''),v='
    ",v=-1===i&&!0===l?'":v}clipboardCopy(e,t,i){var s=(i?this.summary:this.records)[e],l=this.columns[t];let r=l?this.parseField(s,l.field):"";"function"==typeof l.clipboardCopy&&(r=l.clipboardCopy(s,{self:this,index:e,colIndex:t,summary:!!i})),query(this.box).find("#grid_"+this.name+"_focus").text(r).get(0).select(),document.execCommand("copy")}showBubble(s,l,r){var a=this.columns[l].info;if(a){let i="";var n=this.records[s],e=query(this.box).find(`${r?".w2ui-grid-summary":""} #grid_${this.name}_data_${s}_${l} .w2ui-info`);if(this.last.bubbleEl&&w2tooltip.hide(this.name+"-bubble"),this.last.bubbleEl=e,null==a.fields){a.fields=[];for(let e=0;e';else{let e=this.getColumn(h[0]),t=(e=null==e?{field:h[0],caption:h[0]}:e)?this.parseField(n,e.field):"";1a.maxLength&&(t=t.substr(0,a.maxLength)+"..."),i+="")}}i+="
    "+(!0!==n?this.getLineHTML(a,u):"")+"'+(!0===n||u.w2ui&&!0===u.w2ui.hideCheckBox?"":'
    ')+"
    '+(!0!==n?`
    ${e}
    `:"")+"
    '+(!0!==n?'
     
    ':"")+"
    "+v+(_&&w2utils.stripTags(v)?_:"")+"
    "+e.text+""+((0===t?"0":t)||"")+"
    "}else if(w2utils.isPlainObject(t)){for(var d in i='',t){var u=t[d];if(""==u||"-"==u||"--"==u||"---"==u)i+='';else{var c=String(u).split(":");let e=this.getColumn(c[0]),t=(e=null==e?{field:c[0],caption:c[0]}:e)?this.parseField(n,e.field):"";1a.maxLength&&(t=t.substr(0,a.maxLength)+"..."),i+="")}}i+="
    "+d+""+((0===t?"0":t)||"")+"
    "}return w2tooltip.show(w2utils.extend({name:this.name+"-bubble",html:i,anchor:e.get(0),position:"top|bottom",class:"w2ui-info-bubble",style:"",hideOn:["doc-click"]},a.options??{})).hide(()=>[this.last.bubbleEl=null])}}getCellEditable(e,t){var i=this.columns[t],s=this.records[e];if(!s||!i)return null;let l=s.w2ui?s.w2ui.editable:null;return!1===l?null:(null!=l&&!0!==l||"function"==typeof(l=0 '}status(i){if(null!=i)query(this.box).find(`#grid_${this.name}_footer`).find(".w2ui-footer-left").html(i);else{let t="";i=this.getSelection();if(0{query(this.box).find("#grid_"+this.name+"_empty_msg").remove(),w2utils.lock(...i)},10)}unlock(e){setTimeout(()=>{query(this.box).find(".w2ui-message").hasClass("w2ui-closing")||w2utils.unlock(this.box,e)},25)}stateSave(e){var t={columns:[],show:w2utils.clone(this.show),last:{search:this.last.search,multi:this.last.multi,logic:this.last.logic,label:this.last.label,field:this.last.field,scrollTop:this.last.scrollTop,scrollLeft:this.last.scrollLeft},sortData:[],searchData:[]};let l;for(let e=0;e{this.stateColProps[e]&&(l=void 0!==i[e]?i[e]:this.colTemplate[e]||null,s[e]=l)}),t.columns.push(s)}for(let e=0;e{s||(0=this.columns.length)return null==(e=this.nextRow(e))?e:this.nextCell(e,-1,i);var s=this.records[e].w2ui,l=this.columns[t],s=s&&s.colspan&&!isNaN(s.colspan[l.field])?parseInt(s.colspan[l.field]):1;if(null==l)return null;if(l&&l.hidden||0===s)return this.nextCell(e,t,i);if(i){l=this.getCellEditable(e,t);if(null==l||-1!=["checkbox","check"].indexOf(l.type))return this.nextCell(e,t,i)}return{index:e,colIndex:t}}prevCell(e,t,i){t-=1;if(t<0)return null==(e=this.prevRow(e))?e:this.prevCell(e,this.columns.length,i);if(t<0)return null;var s=this.records[e].w2ui,l=this.columns[t],s=s&&s.colspan&&!isNaN(s.colspan[l.field])?parseInt(s.colspan[l.field]):1;if(null==l)return null;if(l&&l.hidden||0===s)return this.prevCell(e,t,i);if(i){l=this.getCellEditable(e,t);if(null==l||-1!=["checkbox","check"].indexOf(l.type))return this.prevCell(e,t,i)}return{index:e,colIndex:t}}nextRow(e,t,i){var s=this.last.searchIds;let l=null;if(-1==(i=null==i?1:i))return this.records.length-1;if(e+ithis.records.length)break;e+=i}var r=this.records[e].w2ui,a=this.columns[t],r=r&&r.colspan&&null!=a&&!isNaN(r.colspan[a.field])?parseInt(r.colspan[a.field]):1;l=0===r?this.nextRow(e,t,i):e}return l}prevRow(e,t,i){var s=this.last.searchIds;let l=null;if(-1==(i=null==i?1:i))return 0;if(0<=e-i&&0===s.length||0s[0]){if(e-=i,0{-1==i.indexOf(e)&&-1!=["label","attr","style","text","span","page","column","anchor","group","groupStyle","groupTitleStyle","groupCollapsible"].indexOf(e)&&(t.html[e]=t[e],delete t[e])}),t}function h(t,i){let s=["style","html"];Object.keys(t).forEach(e=>{-1==s.indexOf(e)&&-1!=["span","column","attr","text","label"].indexOf(e)&&t[e]&&!i.html[e]&&(i.html[e]=t[e])})}r=[],Object.keys(e).forEach(i=>{let s=e[i];if("group"==s.type){if(s.text=i,w2utils.isPlainObject(s.fields)){let i=s.fields;s.fields=[],Object.keys(i).forEach(e=>{let t=i[e];t.field=e,s.fields.push(o(t))})}r.push(s)}else if("tab"==s.type){let e={id:i,text:i},t=(s.style&&(e.style=s.style),n.push(e),l(s.fields).fields);t.forEach(e=>{e.html=e.html||{},e.html.page=n.length-1,h(s,e)}),r.push(...t)}else s.field=i,r.push(o(s))})}r.forEach(s=>{if("group"==s.type){let i={group:s.text||"",groupStyle:s.style||"",groupTitleStyle:s.titleStyle||"",groupCollapsible:!0===s.collapsible};Array.isArray(s.fields)&&s.fields.forEach(e=>{let t=w2utils.clone(e);null==t.html&&(t.html={}),w2utils.extend(t.html,i),Array("span","column","attr","label","page").forEach(e=>{null==t.html[e]&&null!=s[e]&&(t.html[e]=s[e])}),null==t.field&&null!=t.name&&(console.log("NOTICE: form field.name property is deprecated, please use field.field. Field ->",s),t.field=t.name),a.push(t)})}else{let e=w2utils.clone(s);null==e.field&&null!=e.name&&(console.log("NOTICE: form field.name property is deprecated, please use field.field. Field ->",s),e.field=e.name),a.push(e)}});return{fields:a,tabs:n}}(r),this.fields=e.fields,!n)&&0e.text()).then(e=>{this.formHTML=e,this.isGenerated=!0,this.box&&this.render(this.box)}):this.formURL||this.formHTML?this.formHTML&&(this.isGenerated=!0):(this.formHTML=this.generateHTML(),this.isGenerated=!0),"string"==typeof this.box&&(this.box=query(this.box).get(0)),this.box&&this.render(this.box)}get(t,i){if(0===arguments.length){var s=[];for(let e=0;ee[t],s)}catch(e){}return e}return this.record[t]}setValue(e,l){if((""===l||null==l||Array.isArray(l)&&0===l.length||w2utils.isPlainObject(l)&&0==Object.keys(l).length)&&(l=null),!this.nestedFields)return this.record[e]=l,!0;try{let s=this.record;return String(e).split(".").map((e,t,i)=>{i.length-1!==t?s=s[e]||(s[e]={},s[e]):s[e]=l}),!0}catch(e){return!1}}getFieldValue(e){let s=this.get(e);if(null!=s){var l=s.el;let t=this.getValue(e);e=this.getValue(e,!0);let i=l.value;["int","float","percent","money","currency"].includes(s.type)&&(i=s.w2field.clean(i)),["radio"].includes(s.type)&&(r=query(l).closest("div").find("input:checked").get(0),i=r?s.options.items[query(r).data("index")].id:null),["toggle","checkbox"].includes(s.type)&&(i=l.checked),-1!==["check","checks"].indexOf(s.type)&&(i=[],0<(r=query(l).closest("div").find("input:checked")).length&&r.each(e=>{e=s.options.items[query(e).data("index")];i.push(e.id)}),Array.isArray(t)||(t=[]));var r=l._w2field?.selected;if(["list","enum","file"].includes(s.type)&&r){var a=r,n=t;if(Array.isArray(a)){i=[];for(let e=0;e{var t=query(e).find(".w2ui-map.key").val(),e=query(e).find(".w2ui-map.value").val();"map"==s.type?i[t]=e:i.push(e)})),{current:i,previous:t,original:e}}}setFieldValue(e,r){let a=this.get(e);if(null!=a){var s=a.el;switch(a.type){case"toggle":case"checkbox":s.checked=!!r;break;case"radio":{r=r?.id??r;let i=query(s).closest("div").find("input");a.options.items.forEach((e,t)=>{e.id===r&&i.filter(`[data-index="${t}"]`).prop("checked",!0)});break}case"check":case"checks":{r=(r=Array.isArray(r)?r:null!=r?[r]:[]).map(e=>e?.id??e);let i=query(s).closest("div").find("input");a.options.items.forEach((e,t)=>{i.filter(`[data-index="${t}"]`).prop("checked",!!r.includes(e.id))});break}case"list":case"combo":let t=r;null==t?.id&&Array.isArray(a.options?.items)&&a.options.items.forEach(e=>{e.id===r&&(t=e)}),t!=r&&this.setValue(a.name,t),"list"==a.type?(a.w2field.selected=t,a.w2field.refresh()):a.el.value=t?.text??r;break;case"enum":case"file":{let s=[...r=Array.isArray(r)?r:null!=r?[r]:[]],l=!1;s.forEach((t,i)=>{null==t?.id&&Array.isArray(a.options.items)&&a.options.items.forEach(e=>{e.id==t&&(s[i]=e,l=!0)})}),l&&this.setValue(a.name,s),a.w2field.selected=s,a.w2field.refresh();break}case"map":case"array":"map"!=a.type||null!=r&&w2utils.isPlainObject(r)||(this.setValue(a.field,{}),r=this.getValue(a.field)),"array"!=a.type||null!=r&&Array.isArray(r)||(this.setValue(a.field,[]),r=this.getValue(a.field));var i=query(a.el).parent().find(".w2ui-map-container");a.el.mapRefresh(r,i);break;case"div":case"custom":query(s).html(r);break;case"html":case"empty":break;default:s.value=r??""}}}show(){var t=[];for(let e=0;e{!function(e){let t=!0;return e.each(e=>{"none"!=e.style.display&&(t=!1)}),t}(query(e).find(".w2ui-field"))?query(e).show():query(e).hide()})}change(){Array.from(arguments).forEach(e=>{e=this.get(e);e.$el&&e.$el.change()})}reload(e){return("object"!=typeof this.url?this.url:this.url.get)&&null!=this.recid?this.request(e):("function"==typeof e&&e(),new Promise(e=>{e()}))}clear(){0!=arguments.length?Array.from(arguments).forEach(e=>{let s=this.record;String(e).split(".").map((e,t,i)=>{i.length-1!==t?s=s[e]:delete s[e]}),this.refresh(e)}):(this.recid=null,this.record={},this.original=null,this.refresh(),this.hideErrors())}error(e){var t=this.trigger("error",{target:this.name,message:e,fetchCtrl:this.last.fetchCtrl,fetchOptions:this.last.fetchOptions});!0!==t.isCancelled&&(setTimeout(()=>{this.message(e)},1),t.finish())}message(e){return w2utils.message({owner:this,box:this.box,after:".w2ui-form-header"},e)}confirm(e){return w2utils.confirm({owner:this,box:this.box,after:".w2ui-form-header"},e)}validate(e){null==e&&(e=!0);var t=[];for(let e=0;e{var i=w2utils.extend({anchorClass:"w2ui-error",class:"w2ui-light",position:"right|left",hideOn:["input"]},t.options);if(null!=t.field){let e=t.field.el;"radio"===t.field.type?e=query(t.field.el).closest("div").get(0):["enum","file"].includes(t.field.type),w2tooltip.show(w2utils.extend({anchor:e,name:`${this.name}-${t.field.field}-error`,html:t.error},i))}}),query(e[0].field.$el).parents(".w2ui-page").off(".hideErrors").on("scroll.hideErrors",e=>{this.hideErrors()}))}hideErrors(){this.fields.forEach(e=>{w2tooltip.hide(`${this.name}-${e.field}-error`)})}getChanges(){let e={};return e=null!=this.original&&"object"==typeof this.original&&0!==Object.keys(this.record).length?function e(t,i,s){if(Array.isArray(t)&&Array.isArray(i))for(;t.length{if(-1!=["list","combo","enum"].indexOf(e.type)){var t={nestedFields:!0,record:s};let i=this.getValue.call(t,e.field);w2utils.isPlainObject(i)&&null!=i.id&&this.setValue.call(t,e.field,i.id),Array.isArray(i)&&i.forEach((e,t)=>{w2utils.isPlainObject(e)&&e.id&&(i[t]=e.id)})}var i;"map"==e.type&&(t={nestedFields:!0,record:s},(t=this.getValue.call(t,e.field))._order)&&delete t._order,"file"==e.type&&(t={nestedFields:!0,record:s},(i=this.getValue.call(t,e.field)??[]).forEach(e=>{delete e.file,delete e.modified}),this.setValue.call(t,e.field,i))}),!0===e&&Object.keys(s).forEach(e=>{this.get(e)||delete s[e]}),s}prepareParams(i,e){var t=this.dataType??w2utils.settings.dataType;let s=e.body;switch(t){case"HTTPJSON":s={request:s},l();break;case"HTTP":l();break;case"RESTFULL":"POST"==e.method?e.headers["Content-Type"]="application/json":l();break;case"JSON":"GET"==e.method?(s={request:s},l()):(e.headers["Content-Type"]="application/json",e.method="POST")}return e.body="string"==typeof e.body?e.body:JSON.stringify(e.body),e;function l(){Object.keys(s).forEach(e=>{let t=s[e];"object"==typeof t&&(t=JSON.stringify(t)),i.searchParams.append(e,t)}),delete e.body}}request(e,i){let s=this,l,r;var a=new Promise((e,t)=>{l=e,r=t});if("function"==typeof e&&(i=e,e=null),null==e&&(e={}),this.url&&("object"!=typeof this.url||this.url.get)){var n={action:"get"},e=(n.recid=this.recid,n.name=this.name,w2utils.extend(n,this.postData),w2utils.extend(n,e),this.trigger("request",{target:this.name,url:this.url,httpMethod:"GET",postData:n,httpHeaders:this.httpHeaders}));if(!0!==e.isCancelled){this.record={},this.original=null,this.lock(w2utils.lang(this.msgRefresh));let t=e.detail.url;if("object"==typeof t&&t.get&&(t=t.get),this.last.fetchCtrl)try{this.last.fetchCtrl.abort()}catch(e){}if(0!=Object.keys(this.routeData).length){var o=w2utils.parseRoute(t);if(0{200!=e?.status?e&&h(e):e.json().catch(h).then(e=>{var t=s.trigger("load",{target:s.name,fetchCtrl:this.last.fetchCtrl,fetchOptions:this.last.fetchOptions,data:e});!0!==t.isCancelled&&(null==e.error&&"error"===e.status&&(e.error=!0),e.record||Object.assign(e,{record:w2utils.clone(e)}),!0===e.error?s.error(w2utils.lang(e.message??this.msgServerError)):s.record=w2utils.clone(e.record),s.unlock(),t.finish(),s.refresh(),s.setFocus(),"function"==typeof i&&i(e),l(e))})}),e.finish(),a;function h(e){var t;"AbortError"!==e.name&&(s.unlock(),!0!==(t=s.trigger("error",{response:e,fetchCtrl:s.last.fetchCtrl,fetchOptions:s.last.fetchOptions})).isCancelled)&&(e.status&&200!=e.status?s.error(e.status+": "+e.statusText):(console.log("ERROR: Server request failed.",e,". ","Expected Response:",{error:!1,record:{field1:1,field2:"item"}},"OR:",{error:!0,message:"Error description"}),s.error(String(e))),t.finish(),r(e))}}}}submit(e,t){return this.save(e,t)}save(e,i){let s=this,l,r;var a=new Promise((e,t)=>{l=e,r=t}),n=("function"==typeof e&&(i=e,e=null),s.validate(!0));if(0===n.length)if(null==e&&(e={}),!s.url||"object"==typeof s.url&&!s.url.save)console.log("ERROR: Form cannot be saved because no url is defined.");else{s.lock(w2utils.lang(s.msgSaving)+' ');n={action:"save"},e=(n.recid=s.recid,n.name=s.name,w2utils.extend(n,s.postData),w2utils.extend(n,e),n.record=w2utils.clone(s.record),s.trigger("submit",{target:s.name,url:s.url,httpMethod:this.method??"POST",postData:n,httpHeaders:s.httpHeaders}));if(!0!==e.isCancelled){let t=e.detail.url;if("object"==typeof t&&t.save&&(t=t.save),s.last.fetchCtrl&&s.last.fetchCtrl.abort(),0{s.unlock(),200!=e?.status?h(e??{}):e.json().catch(h).then(e=>{var t=s.trigger("save",{target:s.name,fetchCtrl:this.last.fetchCtrl,fetchOptions:this.last.fetchOptions,data:e});!0!==t.isCancelled&&(!0===e.error?s.error(w2utils.lang(e.message??this.msgServerError)):s.original=null,t.finish(),s.refresh(),"function"==typeof i&&i(e),l(e))})}),e.finish(),a;function h(e){var t;"AbortError"!==e?.name&&(s.unlock(),!0!==(t=s.trigger("error",{response:e,fetchCtrl:s.last.fetchCtrl,fetchOptions:s.last.fetchOptions})).isCancelled)&&(e.status&&200!=e.status?s.error(e.status+": "+e.statusText):(console.log("ERROR: Server request failed.",e,". ","Expected Response:",{error:!1,record:{field1:1,field2:"item"}},"OR:",{error:!0,message:"Error description"}),s.error(String(e))),t.finish(),r())}}}}lock(e,t){var i=Array.from(arguments);i.unshift(this.box),w2utils.lock(...i)}unlock(e){var t=this.box;w2utils.unlock(t,e)}lockPage(e,t,i){e=query(this.box).find(".page-"+e);return!!e.length&&(w2utils.lock(e,t,i),!0)}unlockPage(e,t){e=query(this.box).find(".page-"+e);return!!e.length&&(w2utils.unlock(e,t),!0)}goto(e){this.page!==e&&(null!=e&&(this.page=e),!0===query(this.box).data("autoSize")&&(query(this.box).get(0).clientHeight=0),this.refresh())}generateHTML(){let s=[],t="",l,r,a,n;for(let e=0;e",h),h.html.label=h.html.caption),null==h.html.label&&(h.html.label=h.field),h.html=w2utils.extend({label:"",span:6,attr:"",text:"",style:"",page:0,column:0},h.html),null==l&&(l=h.html.page),null==r&&(r=h.html.column);let i=``;switch(h.type){case"pass":case"password":i=i.replace('type="text"','type="password"');break;case"checkbox":i=` + `;break;case"check":case"checks":{null==h.options.items&&null!=h.html.items&&(h.options.items=h.html.items);let t=h.options.items;i="",0<(t=Array.isArray(t)?t:[]).length&&(t=w2utils.normMenu.call(this,t,h));for(let e=0;e + +  ${t[e].text} + +
    `;break}case"radio":{i="",null==h.options.items&&null!=h.html.items&&(h.options.items=h.html.items);let t=h.options.items;0<(t=Array.isArray(t)?t:[]).length&&(t=w2utils.normMenu.call(this,t,h));for(let e=0;e + +  ${t[e].text} + +
    `;break}case"select":{i=`";break}case"textarea":i=``;break;case"toggle":i=` +
    `;break;case"map":case"array":h.html.key=h.html.key||{},h.html.value=h.html.value||{},h.html.tabindex_str=o,i=''+(h.html.text||"")+'
    ';break;case"div":case"custom":i='
    '+(h&&h.html&&h.html.html?h.html.html:"")+"
    ";break;case"html":case"empty":i=h&&h.html?(h.html.html||"")+(h.html.text||""):""}if(""!==t&&(l!=h.html.page||r!=h.html.column||h.html.group&&t!=h.html.group)&&(s[l][r]+="\n \n ",t=""),h.html.group&&t!=h.html.group){let e="";h.html.groupCollapsible&&(e=''),a+='\n
    \n
    "+e+w2utils.lang(h.html.group)+'
    \n
    ',t=h.html.group}if(null==h.html.anchor){let e=null!=h.html.span?"w2ui-span"+h.html.span:"",t=""+w2utils.lang("checkbox"!=h.type?h.html.label:h.html.text)+"";h.html.label||(t=""),a+='\n
    \n '+t+("empty"===h.type?i:"\n
    "+i+("array"!=h.type&&"map"!=h.type?w2utils.lang("checkbox"!=h.type?h.html.text:""):"")+"
    ")+"\n
    "}else s[h.html.page].anchors=s[h.html.page].anchors||{},s[h.html.page].anchors[h.html.anchor]='
    '+("empty"===h.type?i:"
    "+w2utils.lang("checkbox"!=h.type?h.html.label:h.html.text,!0)+i+w2utils.lang("checkbox"!=h.type?h.html.text:"")+"
    ")+"
    ";null==s[h.html.page]&&(s[h.html.page]={}),null==s[h.html.page][h.html.column]&&(s[h.html.page][h.html.column]=""),s[h.html.page][h.html.column]+=a,l=h.html.page,r=h.html.column}if(""!==t&&(s[l][r]+="\n
    \n
    "),this.tabs.tabs)for(let e=0;e",d),d.text=d.caption),d.text&&(u.text=d.text),d.style&&(u.style=d.style),d.class&&(u.class=d.class)):(u.text=i,-1!==["save","update","create"].indexOf(i.toLowerCase())?u.class="w2ui-btn-blue":u.class=""),e+='\n ",n++}e+="\n"}a="";for(let i=0;i',!s[i])return console.log(`ERROR: Page ${i} does not exist`),!1;s[i].before&&(a+=s[i].before),a+='
    ',Object.keys(s[i]).sort().forEach((e,t)=>{e==parseInt(e)&&(a+='
    '+(s[i][e]||"")+"\n
    ")}),a+="\n
    ",s[i].after&&(a+=s[i].after),a+="\n",s[i].anchors&&Object.keys(s[i].anchors).forEach((e,t)=>{a=a.replace(e,s[i].anchors[e])})}return a+=e}toggleGroup(e,t){var i,e=query(this.box).find('.w2ui-group-title[data-group="'+w2utils.base64encode(e)+'"]');0!==e.length&&(i=query(e.prop("nextElementSibling")),(t=void 0===t?"none"==i.css("display"):t)?(i.show(),e.find("span").addClass("w2ui-icon-collapse").removeClass("w2ui-icon-expand")):(i.hide(),e.find("span").addClass("w2ui-icon-expand").removeClass("w2ui-icon-collapse")))}action(e,t){var i=this.actions[e];let s=i;w2utils.isPlainObject(i)&&i.onClick&&(s=i.onClick);e=this.trigger("action",{target:e,action:i,originalEvent:t});!0!==e.isCancelled&&("function"==typeof s&&s.call(this,t),e.finish())}resize(){let o=this;var e=this.trigger("resize",{target:this.name});if(!0!==e.isCancelled){let s=query(this.box).find(":scope > div .w2ui-form-header"),l=query(this.box).find(":scope > div .w2ui-form-toolbar"),r=query(this.box).find(":scope > div .w2ui-form-tabs"),a=query(this.box).find(":scope > div .w2ui-page");var t=query(this.box).find(":scope > div .w2ui-page.page-"+this.page+" > div");let n=query(this.box).find(":scope > div .w2ui-buttons");var{headerHeight:i,tbHeight:h,tabsHeight:d}=u();function u(){var e=""!==o.header?w2utils.getSize(s,"height"):0,t=Array.isArray(o.toolbar?.items)&&0("string"!=typeof e&&console.log("ERROR: Arguments in refresh functions should be field names"),this.get(e,!0))).filter((e,t)=>null!=e):(query(this.box).find("input, textarea, select").each(e=>{var t=null!=query(e).attr("name")?query(e).attr("name"):query(e).attr("id"),i=this.get(t);if(i){var s=query(e).closest(".w2ui-page");if(0{query(e).off("click").on("click",function(e){let t=this.value;this.id&&(t=this.id),this.name&&(t=this.name),c.action(t,e)})});for(let e=0;e{t+=``}),s.$el.html(t)}this.W2FIELD_TYPES.includes(s.type)&&(s.w2field=s.w2field??new w2field(w2utils.extend({},s.options,{type:s.type})),s.w2field.render(s.el)),["map","array"].includes(s.type)&&!function(d){let u;d.el.mapAdd=function(e,t,i){var s=(e.disabled?" readOnly ":"")+(e.html.tabindex_str||""),i=` +
    + ${"map"==e.type?` + ${e.html.key.text||""} + `:""} + + ${e.html.value.text||""} +
    `;t.append(i)},d.el.mapRefresh=function(l,r){let a,n,o;var h;"map"==d.type&&(null==(l=w2utils.isPlainObject(l)?l:{})._order&&(l._order=Object.keys(l)),a=l._order),"array"==d.type&&(Array.isArray(l)||(l=[]),a=l.map((e,t)=>t));for(let e=r.find(".w2ui-map-field").length-1;e>=a.length;e--)r.find(`div[data-index='${e}']`).remove();for(let s=0;se.key==t)).length&&(i=h[0].value),n.val(t),o.val(i),!0!==d.disabled&&!1!==d.disabled||(n.prop("readOnly",!!d.disabled),o.prop("readOnly",!!d.disabled))}var e=a.length,t=r.find(`div[data-index='${e}']`),e=(0!==t.length||n&&""==n.val()&&""==o.val()||n&&(!0===n.prop("readOnly")||!0===n.prop("disabled"))||d.el.mapAdd(d,r,e),!0!==d.disabled&&!1!==d.disabled||(t.find(".key").prop("readOnly",!!d.disabled),t.find(".value").prop("readOnly",!!d.disabled)),query(d.el).get(0)?.nextSibling);query(e).find("input.w2ui-map").off(".mapChange").on("keyup.mapChange",function(e){var t=query(e.target).closest(".w2ui-map-field"),i=t.get(0).nextElementSibling,t=t.get(0).previousElementSibling,s=(13==e.keyCode&&((s=u??i)instanceof HTMLElement&&0<(s=query(s).find("input")).length&&s.get(0).focus(),u=void 0),query(e.target).hasClass("key")?"key":"value");38==e.keyCode&&t&&(query(t).find("input."+s).get(0).select(),e.preventDefault()),40==e.keyCode&&i&&(query(i).find("input."+s).get(0).select(),e.preventDefault())}).on("keydown.mapChange",function(e){38!=e.keyCode&&40!=e.keyCode||e.preventDefault()}).on("input.mapChange",function(e){var e=query(e.target).closest("div"),t=e.data("index"),i=e.get(0).nextElementSibling;if(""==e.find("input").val()||i){if(""==e.find("input").val()&&i){let t=!0;query(i).find("input").each(e=>{""!=e.value&&(t=!1)}),t&&query(i).remove()}}else d.el.mapAdd(d,r,parseInt(t)+1)}).on("change.mapChange",function(e){null==c.original&&(0{t._order.push(e.value)}),c.trigger("change",{target:d.field,field:d.field,originalEvent:e,value:{current:t,previous:i,original:s}}));!0!==l.isCancelled&&("map"==d.type&&(t._order=t._order.filter(e=>""!==e),delete t[""]),"array"==d.type&&(t=t.filter(e=>""!==e)),""==query(e.target).parent().find("input").val()&&(u=e.target),c.setValue(d.field,t),d.el.mapRefresh(t,r),l.finish())})}}(s),this.setFieldValue(s.field,this.getValue(s.name))}}return t.finish(),this.resize(),Date.now()-e}}}render(e){var t=Date.now();let i=this;"string"==typeof e&&(e=query(e).get(0));var s=this.trigger("render",{target:this.name,box:e??this.box});if(!0!==s.isCancelled&&(null!=e&&(0'+(""!==this.header?'
    '+w2utils.lang(this.header)+"
    ":"")+' '+this.formHTML+"",e=(query(this.box).attr("name",this.name).addClass("w2ui-reset w2ui-form").html(e),0this.refresh()):this.refresh(),this.last.observeResize=new ResizeObserver(()=>{this.resize()}),this.last.observeResize.observe(this.box),-1!=this.focus){let e=0,t=()=>{0 input, select, textarea, div > label:nth-child(1) > [type=radio]").filter(":not(.file-input)");null==i[e].offsetParent&&i.length>=e;)e++;i[e]&&(t=query(i[e]))}else"string"==typeof e&&(t=query(this.box).find(`[name='${e}']`));return 0 `,arrow:!1,advanced:null,transparent:!0},this.options=w2utils.extend({},e,t),t=this.options;break;case"date":e={format:w2utils.settings.dateFormat,keyboard:!0,autoCorrect:!0,start:null,end:null,blockDates:[],blockWeekdays:[],colored:{},btnNow:!0},this.options=w2utils.extend({type:"date"},e,t),t=this.options,null==query(this.el).attr("placeholder")&&query(this.el).attr("placeholder",t.format);break;case"time":e={format:w2utils.settings.timeFormat,keyboard:!0,autoCorrect:!0,start:null,end:null,btnNow:!0,noMinutes:!1},this.options=w2utils.extend({type:"time"},e,t),t=this.options,null==query(this.el).attr("placeholder")&&query(this.el).attr("placeholder",t.format);break;case"datetime":e={format:w2utils.settings.dateFormat+"|"+w2utils.settings.timeFormat,keyboard:!0,autoCorrect:!0,start:null,end:null,startTime:null,endTime:null,blockDates:[],blockWeekdays:[],colored:{},btnNow:!0,noMinutes:!1},this.options=w2utils.extend({type:"datetime"},e,t),t=this.options,null==query(this.el).attr("placeholder")&&query(this.el).attr("placeholder",t.placeholder||t.format);break;case"list":case"combo":e={items:[],selected:{},url:null,recId:null,recText:null,method:null,interval:350,postData:{},minLength:1,cacheMax:250,maxDropHeight:350,maxDropWidth:null,minDropWidth:null,match:"begins",icon:null,iconStyle:"",align:"both",altRows:!0,renderDrop:null,compare:null,filter:!0,hideSelected:!1,prefix:"",suffix:"",msgNoItems:"No matches",msgSearch:"Type to search...",openOnFocus:!1,markSearch:!1,onSearch:null,onRequest:null,onLoad:null,onError:null},"function"==typeof t.items&&(t._items_fun=t.items),t.items=w2utils.normMenu.call(this,t.items),"list"===this.type&&(query(this.el).addClass("w2ui-select"),!w2utils.isPlainObject(t.selected))&&Array.isArray(t.items)&&t.items.forEach(e=>{e&&e.id===t.selected&&(t.selected=w2utils.clone(e))}),t=w2utils.extend({},e,t),this.options=t,w2utils.isPlainObject(t.selected)||(t.selected={}),this.selected=t.selected,query(this.el).attr("autocapitalize","off").attr("autocomplete","off").attr("autocorrect","off").attr("spellcheck","false"),null!=t.selected.text&&query(this.el).val(t.selected.text);break;case"enum":e={items:[],selected:[],max:0,url:null,recId:null,recText:null,interval:350,method:null,postData:{},minLength:1,cacheMax:250,maxItemWidth:250,maxDropHeight:350,maxDropWidth:null,match:"contains",align:"",altRows:!0,openOnFocus:!1,markSearch:!1,renderDrop:null,renderItem:null,compare:null,filter:!0,hideSelected:!0,style:"",msgNoItems:"No matches",msgSearch:"Type to search...",onSearch:null,onRequest:null,onLoad:null,onError:null,onClick:null,onAdd:null,onNew:null,onRemove:null,onMouseEnter:null,onMouseLeave:null,onScroll:null},"function"==typeof(t=w2utils.extend({},e,t,{suffix:""})).items&&(t._items_fun=t.items),t.items=w2utils.normMenu.call(this,t.items),t.selected=w2utils.normMenu.call(this,t.selected),this.options=t,Array.isArray(t.selected)||(t.selected=[]),this.selected=t.selected;break;case"file":e={selected:[],max:0,maxSize:0,maxFileSize:0,maxItemWidth:250,maxDropHeight:350,maxDropWidth:null,readContent:!0,silent:!0,align:"both",altRows:!0,renderItem:null,style:"",onClick:null,onAdd:null,onRemove:null,onMouseEnter:null,onMouseLeave:null},t=w2utils.extend({},e,t),this.options=t,Array.isArray(t.selected)||(t.selected=[]),this.selected=t.selected,null==query(this.el).attr("placeholder")&&query(this.el).attr("placeholder",w2utils.lang("Attach files by dragging and dropping or Click to Select"))}query(this.el).css("box-sizing","border-box").addClass("w2field w2ui-input").off(".w2field").on("change.w2field",e=>{this.change(e)}).on("click.w2field",e=>{this.click(e)}).on("focus.w2field",e=>{this.focus(e)}).on("blur.w2field",e=>{"list"!==this.type&&this.blur(e)}).on("keydown.w2field",e=>{this.keyDown(e)}).on("keyup.w2field",e=>{this.keyUp(e)}),this.addPrefix(),this.addSuffix(),this.addSearch(),this.addMultiSearch(),this.change(new Event("change"))}else console.log("ERROR: w2field could only be applied to INPUT or TEXTAREA.",this.el)}get(){let e;return e=-1!==["list","enum","file"].indexOf(this.type)?this.selected:query(this.el).val()}set(e,t){-1!==["list","enum","file"].indexOf(this.type)?("list"!==this.type&&t?(Array.isArray(this.selected)||(this.selected=[]),this.selected.push(e),(t=w2menu.get(this.el.id+"_menu"))&&(t.options.selected=this.selected)):(null==e&&(e=[]),t="enum"!==this.type||Array.isArray(e)?e:[e],this.selected=t),query(this.el).trigger("input").trigger("change"),this.refresh()):query(this.el).val(e)}setIndex(e,t){if(-1!==["list","enum"].indexOf(this.type)){var i=this.options.items;if(i&&i[e])return"list"==this.type&&(this.selected=i[e]),"enum"==this.type&&(t||(this.selected=[]),this.selected.push(i[e])),(t=w2menu.get(this.el.id+"_menu"))&&(t.options.selected=this.selected),query(this.el).trigger("input").trigger("change"),this.refresh(),!0}return!1}refresh(){let s=this.options;var e=Date.now(),t=getComputedStyle(this.el);if("list"==this.type){if(query(this.el).parent().css("white-space","nowrap"),this.helpers.prefix&&this.helpers.prefix.hide(),!this.helpers.search)return;null==this.selected&&s.icon?s.prefix=` + + `:s.prefix="",this.addPrefix();let e=query(this.helpers.search_focus);var i=query(e[0].previousElementSibling);e.css({outline:"none"}),""===e.val()?(e.css("opacity",0),i.css("opacity",0),this.selected?.id?(a=this.selected.text,r=this.findItemIndex(s.items,this.selected.id),null!=a&&query(this.el).val(w2utils.lang(a)).data({selected:a,selectedIndex:r[0]})):(this.el.value="",query(this.el).removeData("selected selectedIndex"))):(e.css("opacity",1),i.css("opacity",1),query(this.el).val(""),setTimeout(()=>{this.helpers.prefix&&this.helpers.prefix.hide(),s.icon?(e.css("margin-left","17px"),query(this.helpers.search).find(".w2ui-icon-search").addClass("show-search")):(e.css("margin-left","0px"),query(this.helpers.search).find(".w2ui-icon-search").removeClass("show-search"))},1)),query(this.el).prop("readOnly")||query(this.el).prop("disabled")?setTimeout(()=>{this.helpers.prefix&&query(this.helpers.prefix).css("opacity","0.6"),this.helpers.suffix&&query(this.helpers.suffix).css("opacity","0.6")},1):setTimeout(()=>{this.helpers.prefix&&query(this.helpers.prefix).css("opacity","1"),this.helpers.suffix&&query(this.helpers.suffix).css("opacity","1")},1)}let l=this.helpers.multi;if(["enum","file"].includes(this.type)&&l){let i="";Array.isArray(this.selected)&&this.selected.forEach((e,t)=>{null!=e&&(i+=` +
    + ${"function"==typeof s.renderItem?s.renderItem(e,t,`
      
    `):` + ${e.icon?``:""} +
      
    + ${("enum"===this.type?e.text:e.name)??e.id??e} + ${e.size?` - ${w2utils.formatSize(e.size)}`:""} + `} +
    `)});var r,a=l.find(".w2ui-multi-items");s.style&&l.attr("style",l.attr("style")+";"+s.style),query(this.el).css("z-index","-1"),query(this.el).prop("readOnly")||query(this.el).prop("disabled")?setTimeout(()=>{l[0].scrollTop=0,l.addClass("w2ui-readonly").find(".li-item").css("opacity","0.9").parent().find(".li-search").hide().find("input").prop("readOnly",!0).closest(".w2ui-multi-items").find(".w2ui-list-remove").hide()},1):setTimeout(()=>{l.removeClass("w2ui-readonly").find(".li-item").css("opacity","1").parent().find(".li-search").show().find("input").prop("readOnly",!1).closest(".w2ui-multi-items").find(".w2ui-list-remove").show()},1),0${query(this.el).attr("placeholder")}`)),l.off(".w2item").on("scroll.w2item",e=>{e=this.trigger("scroll",{target:this.el,originalEvent:e});!0!==e.isCancelled&&(w2tooltip.hide(this.el.id+"_preview"),e.finish())}).find(".li-item").on("click.w2item",e=>{var i=query(e.target).closest(".li-item"),s=i.attr("index"),l=this.selected[s];if(!query(i).hasClass("li-search")){e.stopPropagation();let t;if(query(e.target).hasClass("w2ui-list-remove"))query(this.el).prop("readOnly")||query(this.el).prop("disabled")||!0!==(t=this.trigger("remove",{target:this.el,originalEvent:e,item:l})).isCancelled&&(this.selected.splice(s,1),query(this.el).trigger("input").trigger("change"),query(e.target).remove());else if(!0!==(t=this.trigger("click",{target:this.el,originalEvent:e.originalEvent,item:l})).isCancelled){let e=l.tooltip;if("file"===this.type&&(/image/i.test(l.type)&&(e=` +
    + +
    `),e+=` +
    +
    ${w2utils.lang("Name")}:
    +
    ${l.name}
    +
    ${w2utils.lang("Size")}:
    +
    ${w2utils.formatSize(l.size)}
    +
    ${w2utils.lang("Type")}:
    +
    ${l.type}
    +
    ${w2utils.lang("Modified")}:
    +
    ${w2utils.date(l.modified)}
    +
    `),e){let t=this.el.id+"_preview";w2tooltip.show({name:t,anchor:i.get(0),html:e,hideOn:["doc-click"],class:""}).show(e=>{query(`#w2overlay-${t} img`).on("load",function(e){var t=this.clientWidth,i=this.clientHeight;t<300&i<300||(i<=t&&300{var t=query(e.target).closest(".li-item");query(t).hasClass("li-search")||(t=this.selected[query(e.target).attr("index")],!0!==(e=this.trigger("mouseEnter",{target:this.el,originalEvent:e,item:t})).isCancelled&&e.finish())}).on("mouseleave.w2item",e=>{var t=query(e.target).closest(".li-item");query(t).hasClass("li-search")||(t=this.selected[query(e.target).attr("index")],!0!==(e=this.trigger("mouseLeave",{target:this.el,originalEvent:e,item:t})).isCancelled&&e.finish())}),"enum"===this.type?this.helpers.multi.find("input").css({width:"15px"}):this.helpers.multi.find(".li-search").hide(),this.resize()}return Date.now()-e}resize(){var e=this.el.clientWidth,t=getComputedStyle(this.el),i=this.helpers.search,s=this.helpers.multi,l=this.helpers.suffix,r=this.helpers.prefix,i=(i&&query(i).css("width",e),s&&query(s).css("width",e-parseInt(t["margin-left"],10)-parseInt(t["margin-right"],10)),l&&this.addSuffix(),r&&this.addPrefix(),this.helpers.multi);if(["enum","file"].includes(this.type)&&i){query(this.el).css("height","auto");let e=query(i).find(":scope div.w2ui-multi-items").get(0).clientHeight+5;(e=(e=e<20?20:e)>this.tmp["max-height"]?this.tmp["max-height"]:e)e&&(e=s),query(i).css({height:e+"px",overflow:e==this.tmp["max-height"]?"auto":"hidden"}),query(i).css("height",e+"px"),query(this.el).css({height:e+"px"})}this.tmp.current_width=e}reset(){null!=this.tmp&&(query(this.el).css("height","auto"),Array("padding-left","padding-right","background-color","border-color").forEach(e=>{this.tmp&&null!=this.tmp["old-"+e]&&(query(this.el).css(e,this.tmp["old-"+e]),delete this.tmp["old-"+e])}),clearInterval(this.tmp.sizeTimer)),query(this.el).val(this.clean(query(this.el).val())).removeClass("w2field").removeData("selected selectedIndex").off(".w2field"),Object.keys(this.helpers).forEach(e=>{query(this.helpers[e]).remove()}),this.helpers={}}clean(e){var t;return e="number"!=typeof e&&(t=this.options,e=String(e).trim(),["int","float","money","currency","percent"].includes(this.type))?""!==(e="string"==typeof e?(e=t.autoFormat&&(["money","currency"].includes(this.type)&&(e=String(e).replace(t.moneyRE,"")),"percent"===this.type&&(e=String(e).replace(t.percentRE,"")),["int","float"].includes(this.type))?String(e).replace(t.numberRE,""):e).replace(/\s+/g,"").replace(new RegExp(t.groupSymbol,"g"),"").replace(t.decimalSymbol,"."):e)&&w2utils.isFloat(e)?Number(e):"":e}format(e){var t=this.options;if(t.autoFormat&&""!==e){switch(this.type){case"money":case"currency":""!==(e=w2utils.formatNumber(e,t.currencyPrecision,!0))&&(e=t.currencyPrefix+e+t.currencySuffix);break;case"percent":""!==(e=w2utils.formatNumber(e,t.precision,!0))&&(e+="%");break;case"float":e=w2utils.formatNumber(e,t.precision,!0);break;case"int":e=w2utils.formatNumber(e,0,!0)}var i=parseInt(1e3).toLocaleString(w2utils.settings.locale,{useGrouping:!0}).slice(1,2);i!==this.options.groupSymbol&&(e=e.replaceAll(i,this.options.groupSymbol))}return e}change(e){if(-1!==["int","float","money","currency","percent"].indexOf(this.type)){var t=query(this.el).val(),i=this.format(this.clean(query(this.el).val()));if(""!==t&&t!=i)return query(this.el).val(i),e.stopPropagation(),e.preventDefault(),!1}if("color"===this.type){let e=query(this.el).val();"rgb"!==e.substr(0,3).toLowerCase()&&(e="#"+e,8!==(t=query(this.el).val().length))&&6!==t&&3!==t&&(e="");i=query(this.el).get(0).nextElementSibling;query(i).find("div").css("background-color",e),query(this.el).hasClass("has-focus")&&this.updateOverlay()}if(-1!==["list","enum","file"].indexOf(this.type)&&this.refresh(),-1!==["date","time","datetime"].indexOf(this.type)){let e=parseInt(this.el.value);w2utils.isInt(this.el.value)&&3e3{this.updateOverlay()},100)}var t;"file"==this.type&&(t=query(this.el).get(0).previousElementSibling,query(t).addClass("has-focus")),query(this.el).addClass("has-focus")}}blur(e){var i,s=query(this.el).val().trim();if(query(this.el).removeClass("has-focus"),["int","float","money","currency","percent"].includes(this.type)&&""!==s){let e=s,t="";this.isStrValid(s)?(i=this.clean(s),null!=this.options.min&&i= "+this.options.min),null!=this.options.max&&i>this.options.max&&(e=this.options.max,t="Should be <= "+this.options.max)):e="",this.options.autoCorrect&&(query(this.el).val(e).trigger("input").trigger("change"),t)&&(w2tooltip.show({name:this.el.id+"_error",anchor:this.el,html:t}),setTimeout(()=>{w2tooltip.hide(this.el.id+"_error")},3e3))}["date","time","datetime"].includes(this.type)&&this.options.autoCorrect&&""!==s&&(i="date"==this.type?w2utils.isDate:"time"==this.type?w2utils.isTime:w2utils.isDateTime,w2date.inRange(this.el.value,this.options)&&i.bind(w2utils)(this.el.value,this.options.format)||query(this.el).val("").trigger("input").trigger("change")),"enum"===this.type&&query(this.helpers.multi).find("input").val("").css("width","15px"),"file"==this.type&&(s=this.el.previousElementSibling,query(s).removeClass("has-focus")),"list"===this.type&&(this.el.value=this.selected?.text??"")}keyDown(t,i){var e,s=this.options,i=t.keyCode||i&&i.keyCode;let l=!1,r,a,n,o,h,d;if(["int","float","money","currency","percent","hex","bin","color","alphanumeric"].includes(this.type)&&!(t.metaKey||t.ctrlKey||t.altKey||this.isStrValid(t.key??"1",!0)||[9,8,13,27,37,38,39,40,46].includes(t.keyCode)))return t.preventDefault(),t.stopPropagation?t.stopPropagation():t.cancelBubble=!0,!1;if(["int","float","money","currency","percent"].includes(this.type)){if(!s.keyboard||query(this.el).prop("readOnly")||query(this.el).prop("disabled"))return;switch(r=parseFloat(query(this.el).val().replace(s.moneyRE,""))||0,a=s.step,(t.ctrlKey||t.metaKey)&&(a=10*s.step),i){case 38:t.shiftKey||(h=r+a<=s.max||null==s.max?Number((r+a).toFixed(12)):s.max,query(this.el).val(h).trigger("input").trigger("change"),l=!0);break;case 40:t.shiftKey||(h=r-a>=s.min||null==s.min?Number((r-a).toFixed(12)):s.min,query(this.el).val(h).trigger("input").trigger("change"),l=!0)}l&&(t.preventDefault(),this.moveCaret2end())}if(["date","datetime"].includes(this.type)){if(!s.keyboard||query(this.el).prop("readOnly")||query(this.el).prop("disabled"))return;var u=("date"==this.type?w2utils.isDate:w2utils.isDateTime).bind(w2utils),c=("date"==this.type?w2utils.formatDate:w2utils.formatDateTime).bind(w2utils);switch(n=864e5,a=1,(t.ctrlKey||t.metaKey)&&(a=10),(o=u(query(this.el).val(),s.format,!0))||(o=new Date,n=0),i){case 38:t.shiftKey||(10==a?o.setMonth(o.getMonth()+1):o.setTime(o.getTime()+n),d=c(o.getTime(),s.format),query(this.el).val(d).trigger("input").trigger("change"),l=!0);break;case 40:t.shiftKey||(10==a?o.setMonth(o.getMonth()-1):o.setTime(o.getTime()-n),d=c(o.getTime(),s.format),query(this.el).val(d).trigger("input").trigger("change"),l=!0)}l&&(t.preventDefault(),this.moveCaret2end(),this.updateOverlay())}if("time"===this.type){if(!s.keyboard||query(this.el).prop("readOnly")||query(this.el).prop("disabled"))return;a=t.ctrlKey||t.metaKey?60:1,r=query(this.el).val();let e=w2date.str2min(r)||w2date.str2min((new Date).getHours()+":"+((new Date).getMinutes()-1));switch(i){case 38:t.shiftKey||(e+=a,l=!0);break;case 40:t.shiftKey||(e-=a,l=!0)}l&&(t.preventDefault(),query(this.el).val(w2date.min2str(e)).trigger("input").trigger("change"),this.moveCaret2end())}if(["list","enum"].includes(this.type))switch(i){case 8:case 46:"list"==this.type?""==query(this.helpers.search_focus).val()&&(this.selected=null,w2menu.hide(this.el.id+"_menu"),query(this.el).val("").trigger("input").trigger("change")):""==query(this.helpers.multi).find("input").val()&&(w2menu.hide(this.el.id+"_menu"),this.selected.pop(),(e=w2menu.get(this.el.id+"_menu"))&&(e.options.selected=this.selected),this.refresh());break;case 9:case 16:break;case 27:w2menu.hide(this.el.id+"_menu"),this.refresh()}}keyUp(t){if("list"==this.type){let e=query(this.helpers.search_focus);""!==e.val()?query(this.el).attr("placeholder",""):query(this.el).attr("placeholder",this.tmp.pholder),13==t.keyCode?setTimeout(()=>{e.val(""),w2menu.hide(this.el.id+"_menu"),this.refresh()},1):[8,9,16,27,46].includes(t.keyCode)?w2menu.hide(this.el.id+"_menu"):this.updateOverlay(),this.refresh()}var e;"combo"==this.type&&this.updateOverlay(),"enum"==this.type&&(t=this.helpers.multi.find("input"),e=getComputedStyle(t.get(0)),e=w2utils.getStrWidth(t.val(),`font-family: ${e["font-family"]}; font-size: ${e["font-size"]};`),t.css({width:e+15+"px"}),this.resize())}findItemIndex(e,i,s){let l=[];return s=s||[],e.forEach((e,t)=>{e.id===i&&(l=s.concat([t]),this.options.index=[t]),0==l.length&&e.items&&0{e=e.detail.color;query(this.el).val(e).trigger("input").trigger("change")}).liveUpdate(e=>{e=e.detail.color;query(this.helpers.suffix).find(":scope > div").css("background-color","#"+e)})}if(["list","combo","enum"].includes(this.type)){var t;this.el;let s=this.el;if("enum"===this.type&&(t=this.helpers.multi.get(0),s=query(t).find("input").get(0)),"list"===this.type&&(t=this.selected,w2utils.isPlainObject(t)&&0{var t,i;["list","combo"].includes(this.type)?(this.selected=e.detail.item,query(s).val(""),query(this.el).val(this.selected.text).trigger("input").trigger("change"),this.focus({showMenu:!1})):(i=this.selected,(t=e.detail?.item)&&!0!==(e=this.trigger("add",{target:this.el,item:t,originalEvent:e})).isCancelled&&(i.length>=l.max&&0{e=e.detail.date;null!=e&&query(this.el).val(e).trigger("input").trigger("change")})}isStrValid(e,t){let i=!0;switch(this.type){case"int":i=!(!t||!["-",this.options.groupSymbol].includes(e))||w2utils.isInt(e.replace(this.options.numberRE,""));break;case"percent":e=e.replace(/%/g,"");case"float":i=!(!t||!["-","",this.options.decimalSymbol,this.options.groupSymbol].includes(e))||w2utils.isFloat(e.replace(this.options.numberRE,""));break;case"money":case"currency":i=!(!t||!["-",this.options.decimalSymbol,this.options.groupSymbol,this.options.currencyPrefix,this.options.currencySuffix].includes(e))||w2utils.isFloat(e.replace(this.options.moneyRE,""));break;case"bin":i=w2utils.isBin(e);break;case"color":case"hex":i=w2utils.isHex(e);break;case"alphanumeric":i=w2utils.isAlphaNumeric(e)}return i}addPrefix(){var e,t;this.options.prefix&&(t=getComputedStyle(this.el),null==this.tmp["old-padding-left"]&&(this.tmp["old-padding-left"]=t["padding-left"]),this.helpers.prefix&&query(this.helpers.prefix).remove(),query(this.el).before(`
    ${this.options.prefix}
    `),e=query(this.el).get(0).previousElementSibling,query(e).css({color:t.color,"font-family":t["font-family"],"font-size":t["font-size"],height:this.el.clientHeight+"px","padding-top":t["padding-top"],"padding-bottom":t["padding-bottom"],"padding-left":this.tmp["old-padding-left"],"padding-right":0,"margin-top":parseInt(t["margin-top"],10)+2+"px","margin-bottom":parseInt(t["margin-bottom"],10)+1+"px","margin-left":t["margin-left"],"margin-right":0,"z-index":1}),query(this.el).css("padding-left",e.clientWidth+"px !important"),this.helpers.prefix=e)}addSuffix(){if(this.options.suffix||this.options.arrow){let e,t=this;var i=getComputedStyle(this.el),s=(null==this.tmp["old-padding-right"]&&(this.tmp["old-padding-right"]=i["padding-right"]),parseInt(i["padding-right"]||0));this.options.arrow&&(this.helpers.arrow&&query(this.helpers.arrow).remove(),query(this.el).after('
     
    '),e=query(this.el).get(0).nextElementSibling,query(e).css({color:i.color,"font-family":i["font-family"],"font-size":i["font-size"],height:this.el.clientHeight+"px",padding:0,"margin-top":parseInt(i["margin-top"],10)+1+"px","margin-bottom":0,"border-left":"1px solid silver",width:"16px",transform:"translateX(-100%)"}).on("mousedown",function(e){query(e.target).hasClass("arrow-up")&&t.keyDown(e,{keyCode:38}),query(e.target).hasClass("arrow-down")&&t.keyDown(e,{keyCode:40})}),s+=e.clientWidth,query(this.el).css("padding-right",s+"px !important"),this.helpers.arrow=e),""!==this.options.suffix&&(this.helpers.suffix&&query(this.helpers.suffix).remove(),query(this.el).after(`
    ${this.options.suffix}
    `),e=query(this.el).get(0).nextElementSibling,query(e).css({color:i.color,"font-family":i["font-family"],"font-size":i["font-size"],height:this.el.clientHeight+"px","padding-top":i["padding-top"],"padding-bottom":i["padding-bottom"],"padding-left":0,"padding-right":i["padding-right"],"margin-top":parseInt(i["margin-top"],10)+2+"px","margin-bottom":parseInt(i["margin-bottom"],10)+1+"px",transform:"translateX(-100%)"}),query(this.el).css("padding-right",e.clientWidth+"px !important"),this.helpers.suffix=e)}}addSearch(){if("list"===this.type){this.helpers.search&&query(this.helpers.search).remove();let e=parseInt(query(this.el).attr("tabIndex")),t=(isNaN(e)||-1===e||(this.tmp["old-tabIndex"]=e),null!=(e=this.tmp["old-tabIndex"]?this.tmp["old-tabIndex"]:e)&&!isNaN(e)||(e=0),"");var i=` +
    + + +
    `,i=(query(this.el).attr("tabindex",-1).before(i),query(this.el).get(0).previousElementSibling),s=(this.helpers.search=i,this.helpers.search_focus=query(i).find("input").get(0),getComputedStyle(this.el));query(i).css({width:this.el.clientWidth+"px","margin-top":s["margin-top"],"margin-left":s["margin-left"],"margin-bottom":s["margin-bottom"],"margin-right":s["margin-right"]}).find("input").css({cursor:"default",width:"100%",opacity:1,padding:s.padding,margin:s.margin,border:"1px solid transparent","background-color":"transparent"}),query(i).find("input").off(".helper").on("focus.helper",e=>{query(e.target).val(""),this.tmp.pholder=query(this.el).attr("placeholder")??"",this.focus(e),e.stopPropagation()}).on("blur.helper",e=>{query(e.target).val(""),null!=this.tmp.pholder&&query(this.el).attr("placeholder",this.tmp.pholder),this.blur(e),e.stopPropagation()}).on("keydown.helper",e=>{this.keyDown(e)}).on("keyup.helper",e=>{this.keyUp(e)}),query(i).on("click",e=>{query(e.target).find("input").focus()})}}addMultiSearch(){if(["enum","file"].includes(this.type)){query(this.helpers.multi).remove();let e="";var l,r,a=getComputedStyle(this.el),n=w2utils.stripSpaces(` + margin-top: 0px; + margin-bottom: 0px; + margin-left: ${a["margin-left"]}; + margin-right: ${a["margin-right"]}; + width: ${w2utils.getSize(this.el,"width")-parseInt(a["margin-left"],10)-parseInt(a["margin-right"],10)}px; + `);null==this.tmp["min-height"]&&(l=this.tmp["min-height"]=parseInt(("none"!=a["min-height"]?a["min-height"]:0)||0),r=parseInt(a.height),this.tmp["min-height"]=Math.max(l,r)),null==this.tmp["max-height"]&&"none"!=a["max-height"]&&(this.tmp["max-height"]=parseInt(a["max-height"]));let t="",i=(null!=query(this.el).attr("id")&&(t=`id="${query(this.el).attr("id")}_search"`),parseInt(query(this.el).attr("tabIndex"))),s=(isNaN(i)||-1===i||(this.tmp["old-tabIndex"]=i),null!=(i=this.tmp["old-tabIndex"]?this.tmp["old-tabIndex"]:i)&&!isNaN(i)||(i=0),"enum"===this.type&&(e=` +
    +
    + +
    +
    `),"file"===this.type&&(e=` +
    +
    + +
    +
    + +
    +
    `),this.tmp["old-background-color"]=a["background-color"],this.tmp["old-border-color"]=a["border-color"],query(this.el).before(e).css({"border-color":"transparent","background-color":"transparent"}),query(this.el.previousElementSibling));this.helpers.multi=s,query(this.el).attr("tabindex",-1),s.on("click",e=>{this.focus(e)}),s.find("input:not(.file-input)").on("click",e=>{this.click(e)}).on("focus",e=>{this.focus(e)}).on("blur",e=>{this.blur(e)}).on("keydown",e=>{this.keyDown(e)}).on("keyup",e=>{this.keyUp(e)}),"file"===this.type&&s.find("input.file-input").off(".drag").on("click.drag",e=>{e.stopPropagation(),query(this.el).prop("readOnly")||query(this.el).prop("disabled")||this.focus(e)}).on("dragenter.drag",e=>{query(this.el).prop("readOnly")||query(this.el).prop("disabled")||s.addClass("w2ui-file-dragover")}).on("dragleave.drag",e=>{query(this.el).prop("readOnly")||query(this.el).prop("disabled")||s.removeClass("w2ui-file-dragover")}).on("drop.drag",e=>{query(this.el).prop("readOnly")||query(this.el).prop("disabled")||(s.removeClass("w2ui-file-dragover"),Array.from(e.dataTransfer.files).forEach(e=>{this.addFile(e)}),this.focus(e),e.preventDefault(),e.stopPropagation())}).on("dragover.drag",e=>{e.preventDefault(),e.stopPropagation()}).on("change.drag",e=>{void 0!==e.target.files&&Array.from(e.target.files).forEach(e=>{this.addFile(e)}),this.focus(e)}),this.refresh()}}addFile(t){var e=this.options,s=this.selected;let l={name:t.name,type:t.type,modified:t.lastModifiedDate,size:t.size,content:null,file:t},i=0,r=0,a=[],n=(Array.isArray(s)&&s.forEach(e=>{e.name==t.name&&e.size==t.size&&a.push(w2utils.lang('The file "${name}" (${size}) is already added.',{name:t.name,size:w2utils.formatSize(t.size)})),i+=e.size,r++}),0!==e.maxFileSize&&l.size>e.maxFileSize&&a.push(w2utils.lang("Maximum file size is ${size}",{size:w2utils.formatSize(e.maxFileSize)})),0!==e.maxSize&&i+l.size>e.maxSize&&a.push(w2utils.lang("Maximum total size is ${size}",{size:w2utils.formatSize(e.maxSize)})),0!==e.max&&r>=e.max&&a.push(w2utils.lang("Maximum number of files is ${count}",{count:e.max})),this.trigger("add",{target:this.el,file:l,total:r,totalSize:i,errors:a}));if(!0!==n.isCancelled)if(!0!==e.silent&&0")}),console.log("ERRORS (while adding files): ",a);else if(s.push(l),"undefined"!=typeof FileReader&&!0===e.readContent){s=new FileReader;let i=this;s.onload=function(e){var e=e.target.result,t=e.indexOf(",");l.content=e.substr(t+1),i.refresh(),query(i.el).trigger("input").trigger("change"),n.finish()},s.readAsDataURL(t)}else this.refresh(),query(this.el).trigger("input").trigger("change"),n.finish()}moveCaret2end(){setTimeout(()=>{this.el.setSelectionRange(this.el.value.length,this.el.value.length)},0)}}export{w2ui,w2utils,query,w2locale,w2event,w2base,w2popup,w2alert,w2confirm,w2prompt,Dialog,w2tooltip,w2menu,w2color,w2date,Tooltip,w2toolbar,w2sidebar,w2tabs,w2layout,w2grid,w2form,w2field}; \ No newline at end of file diff --git a/lib/w2ui/w2ui.js b/lib/w2ui/w2ui.js new file mode 100644 index 0000000..a0cdf35 --- /dev/null +++ b/lib/w2ui/w2ui.js @@ -0,0 +1,22350 @@ +/* w2ui 2.0.x (nightly) (1/16/2023, 8:50:38 AM) (c) http://w2ui.com, vitmalina@gmail.com */ +/** + * Part of w2ui 2.0 library + * - Dependencies: w2utils + * - on/off/trigger methods id not showing in help + * - refactored with event object + */ + +class w2event { + constructor(owner, edata) { + Object.assign(this, { + type: edata.type ?? null, + detail: edata, + owner, + target: edata.target ?? null, + phase: edata.phase ?? 'before', + object: edata.object ?? null, + execute: null, + isStopped: false, + isCancelled: false, + onComplete: null, + listeners: [] + }) + delete edata.type + delete edata.target + delete edata.object + this.complete = new Promise((resolve, reject) => { + this._resolve = resolve + this._reject = reject + }) + // needed empty catch function so that promise will not show error in the console + this.complete.catch(() => {}) + } + finish(detail) { + if (detail) { + w2utils.extend(this.detail, detail) + } + this.phase = 'after' + this.owner.trigger.call(this.owner, this) + } + done(func) { + this.listeners.push(func) + } + preventDefault() { + this._reject() + this.isCancelled = true + } + stopPropagation() { + this.isStopped = true + } +} +class w2base { + /** + * Initializes base object for w2ui, registers it with w2ui object + * + * @param {string} name - name of the object + * @returns + */ + constructor(name) { + this.activeEvents = [] // events that are currently processing + this.listeners = [] // event listeners + // register globally + if (typeof name !== 'undefined') { + if (!w2utils.checkName(name)) return + w2ui[name] = this + } + this.debug = false // if true, will trigger all events + } + /** + * Adds event listener, supports event phase and event scoping + * + * @param {*} edata - an object or string, if string "eventName:phase.scope" + * @param {*} handler + * @returns itself + */ + on(events, handler) { + if (typeof events == 'string') { + events = events.split(/[,\s]+/) // separate by comma or space + } else { + events = [events] + } + events.forEach(edata => { + let name = typeof edata == 'string' ? edata : (edata.type + ':' + edata.execute + '.' + edata.scope) + if (typeof edata == 'string') { + let [eventName, scope] = edata.split('.') + let [type, execute] = eventName.replace(':complete', ':after').replace(':done', ':after').split(':') + edata = { type, execute: execute ?? 'before', scope } + } + edata = w2utils.extend({ type: null, execute: 'before', onComplete: null }, edata) + // errors + if (!edata.type) { console.log('ERROR: You must specify event type when calling .on() method of '+ this.name); return } + if (!handler) { console.log('ERROR: You must specify event handler function when calling .on() method of '+ this.name); return } + if (!Array.isArray(this.listeners)) this.listeners = [] + this.listeners.push({ name, edata, handler }) + if (this.debug) { + console.log('w2base: add event', { name, edata, handler }) + } + }) + return this + } + /** + * Removes event listener, supports event phase and event scoping + * + * @param {*} edata - an object or string, if string "eventName:phase.scope" + * @param {*} handler + * @returns itself + */ + off(events, handler) { + if (typeof events == 'string') { + events = events.split(/[,\s]+/) // separate by comma or space + } else { + events = [events] + } + events.forEach(edata => { + let name = typeof edata == 'string' ? edata : (edata.type + ':' + edata.execute + '.' + edata.scope) + if (typeof edata == 'string') { + let [eventName, scope] = edata.split('.') + let [type, execute] = eventName.replace(':complete', ':after').replace(':done', ':after').split(':') + edata = { type: type || '*', execute: execute || '', scope: scope || '' } + } + edata = w2utils.extend({ type: null, execute: null, onComplete: null }, edata) + // errors + if (!edata.type && !edata.scope) { console.log('ERROR: You must specify event type when calling .off() method of '+ this.name); return } + if (!handler) { handler = null } + let count = 0 + // remove listener + this.listeners = this.listeners.filter(curr => { + if ( (edata.type === '*' || edata.type === curr.edata.type) + && (edata.execute === '' || edata.execute === curr.edata.execute) + && (edata.scope === '' || edata.scope === curr.edata.scope) + && (edata.handler == null || edata.handler === curr.edata.handler) + ) { + count++ // how many listeners removed + return false + } else { + return true + } + }) + if (this.debug) { + console.log(`w2base: remove event (${count})`, { name, edata, handler }) + } + }) + return this // needed for chaining + } + /** + * Triggers even listeners for a specific event, loops through this.listeners + * + * @param {Object} edata - Object + * @returns modified edata + */ + trigger(eventName, edata) { + if (arguments.length == 1) { + edata = eventName + } else { + edata.type = eventName + edata.target = edata.target ?? this + } + if (w2utils.isPlainObject(edata) && edata.phase == 'after') { + // find event + edata = this.activeEvents.find(event => { + if (event.type == edata.type && event.target == edata.target) { + return true + } + return false + }) + if (!edata) { + console.log(`ERROR: Cannot find even handler for "${edata.type}" on "${edata.target}".`) + return + } + console.log('NOTICE: This syntax "edata.trigger({ phase: \'after\' })" is outdated. Use edata.finish() instead.') + } else if (!(edata instanceof w2event)) { + edata = new w2event(this, edata) + this.activeEvents.push(edata) + } + let args, fun, tmp + if (!Array.isArray(this.listeners)) this.listeners = [] + if (this.debug) { + console.log(`w2base: trigger "${edata.type}:${edata.phase}"`, edata) + } + // process events in REVERSE order + for (let h = this.listeners.length-1; h >= 0; h--) { + let item = this.listeners[h] + if (item != null && (item.edata.type === edata.type || item.edata.type === '*') && + (item.edata.target === edata.target || item.edata.target == null) && + (item.edata.execute === edata.phase || item.edata.execute === '*' || item.edata.phase === '*')) + { + // add extra params if there + Object.keys(item.edata).forEach(key => { + if (edata[key] == null && item.edata[key] != null) { + edata[key] = item.edata[key] + } + }) + // check handler arguments + args = [] + tmp = new RegExp(/\((.*?)\)/).exec(String(item.handler).split('=>')[0]) + if (tmp) args = tmp[1].split(/\s*,\s*/) + if (args.length === 2) { + item.handler.call(this, edata.target, edata) // old way for back compatibility + if (this.debug) console.log(' - call (old)', item.handler) + } else { + item.handler.call(this, edata) // new way + if (this.debug) console.log(' - call', item.handler) + } + if (edata.isStopped === true || edata.stop === true) return edata // back compatibility edata.stop === true + } + } + // main object events + let funName = 'on' + edata.type.substr(0,1).toUpperCase() + edata.type.substr(1) + if (edata.phase === 'before' && typeof this[funName] === 'function') { + fun = this[funName] + // check handler arguments + args = [] + tmp = new RegExp(/\((.*?)\)/).exec(String(fun).split('=>')[0]) + if (tmp) args = tmp[1].split(/\s*,\s*/) + if (args.length === 2) { + fun.call(this, edata.target, edata) // old way for back compatibility + if (this.debug) console.log(' - call: on[Event] (old)', fun) + } else { + fun.call(this, edata) // new way + if (this.debug) console.log(' - call: on[Event]', fun) + } + if (edata.isStopped === true || edata.stop === true) return edata // back compatibility edata.stop === true + } + // item object events + if (edata.object != null && edata.phase === 'before' && typeof edata.object[funName] === 'function') { + fun = edata.object[funName] + // check handler arguments + args = [] + tmp = new RegExp(/\((.*?)\)/).exec(String(fun).split('=>')[0]) + if (tmp) args = tmp[1].split(/\s*,\s*/) + if (args.length === 2) { + fun.call(this, edata.target, edata) // old way for back compatibility + if (this.debug) console.log(' - call: edata.object (old)', fun) + } else { + fun.call(this, edata) // new way + if (this.debug) console.log(' - call: edata.object', fun) + } + if (edata.isStopped === true || edata.stop === true) return edata + } + // execute onComplete + if (edata.phase === 'after') { + if (typeof edata.onComplete === 'function') edata.onComplete.call(this, edata) + for (let i = 0; i < edata.listeners.length; i++) { + if (typeof edata.listeners[i] === 'function') { + edata.listeners[i].call(this, edata) + if (this.debug) console.log(' - call: done', fun) + } + } + edata._resolve(edata) + if (this.debug) { + console.log(`w2base: trigger "${edata.type}:${edata.phase}"`, edata) + } + } + return edata + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: none + * + * These are the master locale settings that will be used by w2utils + * + * "locale" should be the IETF language tag in the form xx-YY, + * where xx is the ISO 639-1 language code ( see https://en.wikipedia.org/wiki/ISO_639-1 ) and + * YY is the ISO 3166-1 alpha-2 country code ( see https://en.wikipedia.org/wiki/ISO_3166-2 ) + */ +const w2locale = { + 'locale' : 'en-US', + 'dateFormat' : 'm/d/yyyy', + 'timeFormat' : 'hh:mi pm', + 'datetimeFormat' : 'm/d/yyyy|hh:mi pm', + 'currencyPrefix' : '$', + 'currencySuffix' : '', + 'currencyPrecision' : 2, + 'groupSymbol' : ',', // aka "thousands separator" + 'decimalSymbol' : '.', + 'shortmonths' : ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'fullmonths' : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'shortdays' : ['M', 'T', 'W', 'T', 'F', 'S', 'S'], + 'fulldays' : ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], + 'weekStarts' : 'S', // can be "M" for Monday or "S" for Sunday + // phrases used in w2ui, should be empty for original language + // keep these up-to-date and in sorted order + // value = "---" to easier see what to translate + 'phrases': { + '${count} letters or more...': '---', + 'Add new record': '---', + 'Add New': '---', + 'Advanced Search': '---', + 'after': '---', + 'AJAX error. See console for more details.': '---', + 'All Fields': '---', + 'All': '---', + 'Any': '---', + 'Are you sure you want to delete ${count} ${records}?': '---', + 'Attach files by dragging and dropping or Click to Select': '---', + 'before': '---', + 'begins with': '---', + 'begins': '---', + 'between': '---', + 'buffered': '---', + 'Cancel': '---', + 'Close': '---', + 'Column': '---', + 'Confirmation': '---', + 'contains': '---', + 'Copied': '---', + 'Copy to clipboard': '---', + 'Current Date & Time': '---', + 'Delete selected records': '---', + 'Delete': '---', + 'Do you want to delete search item "${item}"?': '---', + 'Edit selected record': '---', + 'Edit': '---', + 'Empty list': '---', + 'ends with': '---', + 'ends': '---', + 'Field should be at least ${count} characters.': '---', + 'Hide': '---', + 'in': '---', + 'is not': '---', + 'is': '---', + 'less than': '---', + 'Line #': '---', + 'Load ${count} more...': '---', + 'Loading...': '---', + 'Maximum number of files is ${count}': '---', + 'Maximum total size is ${count}': '---', + 'Modified': '---', + 'more than': '---', + 'Multiple Fields': '---', + 'Name': '---', + 'No items found': '---', + 'No matches': '---', + 'No': '---', + 'none': '---', + 'Not a float': '---', + 'Not a hex number': '---', + 'Not a valid date': '---', + 'Not a valid email': '---', + 'Not alpha-numeric': '---', + 'Not an integer': '---', + 'Not in money format': '---', + 'not in': '---', + 'Notification': '---', + 'of': '---', + 'Ok': '---', + 'Opacity': '---', + 'Record ID': '---', + 'record': '---', + 'records': '---', + 'Refreshing...': '---', + 'Reload data in the list': '---', + 'Remove': '---', + 'Remove This Field': '---', + 'Request aborted.': '---', + 'Required field': '---', + 'Reset': '---', + 'Restore Default State': '---', + 'Returned data is not in valid JSON format.': '---', + 'Save changed records': '---', + 'Save Grid State': '---', + 'Save': '---', + 'Saved Searches': '---', + 'Saving...': '---', + 'Search took ${count} seconds': '---', + 'Search': '---', + 'Select Hour': '---', + 'Select Minute': '---', + 'selected': '---', + 'Server Response ${count} seconds': '---', + 'Show/hide columns': '---', + 'Show': '---', + 'Size': '---', + 'Skip': '---', + 'Sorting took ${count} seconds': '---', + 'Type to search...': '---', + 'Type': '---', + 'Yes': '---', + 'Yesterday': '---', + 'Your remote data source record count has changed, reloading from the first record.': '---' + } +} +/* mQuery 0.7 (nightly) (10/10/2022, 11:30:36 AM), vitmalina@gmail.com */ +class Query { + static version = 0.7 + constructor(selector, context, previous) { + this.context = context ?? document + this.previous = previous ?? null + let nodes = [] + if (Array.isArray(selector)) { + nodes = selector + } else if (selector instanceof Node || selector instanceof Window) { // any html element or Window + nodes = [selector] + } else if (selector instanceof Query) { + nodes = selector.nodes + } else if (typeof selector == 'string') { + if (typeof this.context.querySelector != 'function') { + throw new Error('Invalid context') + } + nodes = Array.from(this.context.querySelectorAll(selector)) + } else if (selector == null) { + nodes = [] + } else { + // if selector is itterable, then try to create nodes from it, also supports jQuery + let arr = Array.from(selector ?? []) + if (typeof selector == 'object' && Array.isArray(arr)) { + nodes = arr + } else { + throw new Error(`Invalid selector "${selector}"`) + } + } + this.nodes = nodes + this.length = nodes.length + // map nodes to object propoerties + this.each((node, ind) => { + this[ind] = node + }) + } + static _fragment(html) { + let tmpl = document.createElement('template') + tmpl.innerHTML = html + tmpl.content.childNodes.forEach(node => { + let newNode = Query._scriptConvert(node) + if (newNode != node) { + tmpl.content.replaceChild(newNode, node) + } + }) + return tmpl.content + } + // innerHTML, append, etc. script tags will not be executed unless they are proper script tags + static _scriptConvert(node) { + let convert = (txtNode) => { + let doc = txtNode.ownerDocument + let scNode = doc.createElement('script') + scNode.text = txtNode.text + let attrs = txtNode.attributes + for (let i = 0; i < attrs.length; i++) { + scNode.setAttribute(attrs[i].name, attrs[i].value) + } + return scNode + } + if (node.tagName == 'SCRIPT') { + node = convert(node) + } + if (node.querySelectorAll) { + node.querySelectorAll('script').forEach(textNode => { + textNode.parentNode.replaceChild(convert(textNode), textNode) + }) + } + return node + } + static _fixProp(name) { + let fixes = { + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing', + class: 'className', + colspan: 'colSpan', + contenteditable: 'contentEditable', + for: 'htmlFor', + frameborder: 'frameBorder', + maxlength: 'maxLength', + readonly: 'readOnly', + rowspan: 'rowSpan', + tabindex: 'tabIndex', + usemap: 'useMap' + } + return fixes[name] ? fixes[name] : name + } + _insert(method, html) { + let nodes = [] + let len = this.length + if (len < 1) return + let self = this + // TODO: need good unit test coverage for this function + if (typeof html == 'string') { + this.each(node => { + let clone = Query._fragment(html) + nodes.push(...clone.childNodes) + node[method](clone) + }) + } else if (html instanceof Query) { + let single = (len == 1) // if inserting into a single container, then move it there + html.each(el => { + this.each(node => { + // if insert before a single node, just move new one, else clone and move it + let clone = (single ? el : el.cloneNode(true)) + nodes.push(clone) + node[method](clone) + Query._scriptConvert(clone) + }) + }) + if (!single) html.remove() + } else if (html instanceof Node) { // any HTML element + this.each(node => { + // if insert before a single node, just move new one, else clone and move it + let clone = (len === 1 ? html : Query._fragment(html.outerHTML)) + nodes.push(...(len === 1 ? [html] : clone.childNodes)) + node[method](clone) + }) + if (len > 1) html.remove() + } else { + throw new Error(`Incorrect argument for "${method}(html)". It expects one string argument.`) + } + if (method == 'replaceWith') { + self = new Query(nodes, this.context, this) // must return a new collection + } + return self + } + _save(node, name, value) { + node._mQuery = node._mQuery ?? {} + if (Array.isArray(value)) { + node._mQuery[name] = node._mQuery[name] ?? [] + node._mQuery[name].push(...value) + } else if (value != null) { + node._mQuery[name] = value + } else { + delete node._mQuery[name] + } + } + get(index) { + if (index < 0) index = this.length + index + let node = this[index] + if (node) { + return node + } + if (index != null) { + return null + } + return this.nodes + } + eq(index) { + if (index < 0) index = this.length + index + let nodes = [this[index]] + if (nodes[0] == null) nodes = [] + return new Query(nodes, this.context, this) // must return a new collection + } + then(fun) { + let ret = fun(this) + return ret != null ? ret : this + } + find(selector) { + let nodes = [] + this.each(node => { + let nn = Array.from(node.querySelectorAll(selector)) + if (nn.length > 0) { + nodes.push(...nn) + } + }) + return new Query(nodes, this.context, this) // must return a new collection + } + filter(selector) { + let nodes = [] + this.each(node => { + if (node === selector + || (typeof selector == 'string' && node.matches && node.matches(selector)) + || (typeof selector == 'function' && selector(node)) + ) { + nodes.push(node) + } + }) + return new Query(nodes, this.context, this) // must return a new collection + } + next() { + let nodes = [] + this.each(node => { + let nn = node.nextElementSibling + if (nn) { nodes.push(nn) } + }) + return new Query(nodes, this.context, this) // must return a new collection + } + prev() { + let nodes = [] + this.each(node => { + let nn = node.previousElementSibling + if (nn) { nodes.push(nn)} + }) + return new Query(nodes, this.context, this) // must return a new collection + } + shadow(selector) { + let nodes = [] + this.each(node => { + // select shadow root if available + if (node.shadowRoot) nodes.push(node.shadowRoot) + }) + let col = new Query(nodes, this.context, this) + return selector ? col.find(selector) : col + } + closest(selector) { + let nodes = [] + this.each(node => { + let nn = node.closest(selector) + if (nn) { + nodes.push(nn) + } + }) + return new Query(nodes, this.context, this) // must return a new collection + } + host(all) { + let nodes = [] + // find shadow root or body + let top = (node) => { + if (node.parentNode) { + return top(node.parentNode) + } else { + return node + } + } + let fun = (node) => { + let nn = top(node) + nodes.push(nn.host ? nn.host : nn) + if (nn.host && all) fun(nn.host) + } + this.each(node => { + fun(node) + }) + return new Query(nodes, this.context, this) // must return a new collection + } + parent(selector) { + return this.parents(selector, true) + } + parents(selector, firstOnly) { + let nodes = [] + let add = (node) => { + if (nodes.indexOf(node) == -1) { + nodes.push(node) + } + if (!firstOnly && node.parentNode) { + return add(node.parentNode) + } + } + this.each(node => { + if (node.parentNode) add(node.parentNode) + }) + let col = new Query(nodes, this.context, this) + return selector ? col.filter(selector) : col + } + add(more) { + let nodes = more instanceof Query ? more.nodes : (Array.isArray(more) ? more : [more]) + return new Query(this.nodes.concat(nodes), this.context, this) // must return a new collection + } + each(func) { + this.nodes.forEach((node, ind) => { func(node, ind, this) }) + return this + } + append(html) { + return this._insert('append', html) + } + prepend(html) { + return this._insert('prepend', html) + } + after(html) { + return this._insert('after', html) + } + before(html) { + return this._insert('before', html) + } + replace(html) { + return this._insert('replaceWith', html) + } + remove() { + // remove from dom, but keep in current query + this.each(node => { node.remove() }) + return this + } + css(key, value) { + let css = key + let len = arguments.length + if (len === 0 || (len === 1 && typeof key == 'string')) { + if (this[0]) { + let st = this[0].style + // do not do computedStyleMap as it is not what on immediate element + if (typeof key == 'string') { + let pri = st.getPropertyPriority(key) + return st.getPropertyValue(key) + (pri ? '!' + pri : '') + } else { + return Object.fromEntries( + this[0].style.cssText + .split(';') + .filter(a => !!a) // filter non-empty + .map(a => { + return a.split(':').map(a => a.trim()) // trim strings + }) + ) + } + } else { + return undefined + } + } else { + if (typeof key != 'object') { + css = {} + css[key] = value + } + this.each((el, ind) => { + Object.keys(css).forEach(key => { + let imp = String(css[key]).toLowerCase().includes('!important') ? 'important' : '' + el.style.setProperty(key, String(css[key]).replace(/\!important/i, ''), imp) + }) + }) + return this + } + } + addClass(classes) { + this.toggleClass(classes, true) + return this + } + removeClass(classes) { + this.toggleClass(classes, false) + return this + } + toggleClass(classes, force) { + // split by comma or space + if (typeof classes == 'string') classes = classes.split(/[,\s]+/) + this.each(node => { + let classes2 = classes + // if not defined, remove all classes + if (classes2 == null && force === false) classes2 = Array.from(node.classList) + classes2.forEach(className => { + if (className !== '') { + let act = 'toggle' + if (force != null) act = force ? 'add' : 'remove' + node.classList[act](className) + } + }) + }) + return this + } + hasClass(classes) { + // split by comma or space + if (typeof classes == 'string') classes = classes.split(/[,\s]+/) + if (classes == null && this.length > 0) { + return Array.from(this[0].classList) + } + let ret = false + this.each(node => { + ret = ret || classes.every(className => { + return Array.from(node.classList ?? []).includes(className) + }) + }) + return ret + } + on(events, options, callback) { + if (typeof options == 'function') { + callback = options + options = undefined + } + let delegate + if (options?.delegate) { + delegate = options.delegate + delete options.delegate // not to pass to addEventListener + } + events = events.split(/[,\s]+/) // separate by comma or space + events.forEach(eventName => { + let [ event, scope ] = String(eventName).toLowerCase().split('.') + if (delegate) { + let fun = callback + callback = (event) => { + // event.target or any ancestors match delegate selector + let parent = query(event.target).parents(delegate) + if (parent.length > 0) { event.delegate = parent[0] } else { event.delegate = event.target } + if (event.target.matches(delegate) || parent.length > 0) { + fun(event) + } + } + } + this.each(node => { + this._save(node, 'events', [{ event, scope, callback, options }]) + node.addEventListener(event, callback, options) + }) + }) + return this + } + off(events, options, callback) { + if (typeof options == 'function') { + callback = options + options = undefined + } + events = (events ?? '').split(/[,\s]+/) // separate by comma or space + events.forEach(eventName => { + let [ event, scope ] = String(eventName).toLowerCase().split('.') + this.each(node => { + if (Array.isArray(node._mQuery?.events)) { + for (let i = node._mQuery.events.length - 1; i >= 0; i--) { + let evt = node._mQuery.events[i] + if (scope == null || scope === '') { + // if no scope, has to be exact match + if ((evt.event == event || event === '') && (evt.callback == callback || callback == null)) { + node.removeEventListener(evt.event, evt.callback, evt.options) + node._mQuery.events.splice(i, 1) + } + } else { + if ((evt.event == event || event === '') && evt.scope == scope) { + node.removeEventListener(evt.event, evt.callback, evt.options) + node._mQuery.events.splice(i, 1) + } + } + } + } + }) + }) + return this + } + trigger(name, options) { + let event, + mevent = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove'], + kevent = ['keydown', 'keyup', 'keypress'] + if (name instanceof Event || name instanceof CustomEvent) { + // MouseEvent and KeyboardEvent are instances of Event, no need to explicitly add + event = name + } else if (mevent.includes(name)) { + event = new MouseEvent(name, options) + } else if (kevent.includes(name)) { + event = new KeyboardEvent(name, options) + } else { + event = new Event(name, options) + } + this.each(node => { node.dispatchEvent(event) }) + return this + } + attr(name, value) { + if (value === undefined && typeof name == 'string') { + return this[0] ? this[0].getAttribute(name) : undefined + } else { + let obj = {} + if (typeof name == 'object') obj = name; else obj[name] = value + this.each(node => { + Object.entries(obj).forEach(([nm, val]) => { node.setAttribute(nm, val) }) + }) + return this + } + } + removeAttr() { + this.each(node => { + Array.from(arguments).forEach(attr => { + node.removeAttribute(attr) + }) + }) + return this + } + prop(name, value) { + if (value === undefined && typeof name == 'string') { + return this[0] ? this[0][name] : undefined + } else { + let obj = {} + if (typeof name == 'object') obj = name; else obj[name] = value + this.each(node => { + Object.entries(obj).forEach(([nm, val]) => { + let prop = Query._fixProp(nm) + node[prop] = val + if (prop == 'innerHTML') { + Query._scriptConvert(node) + } + }) + }) + return this + } + } + removeProp() { + this.each(node => { + Array.from(arguments).forEach(prop => { delete node[Query._fixProp(prop)] }) + }) + return this + } + data(key, value) { + if (key instanceof Object) { + Object.entries(key).forEach(item => { this.data(item[0], item[1]) }) + return + } + if (key && key.indexOf('-') != -1) { + console.error(`Key "${key}" contains "-" (dash). Dashes are not allowed in property names. Use camelCase instead.`) + } + if (arguments.length < 2) { + if (this[0]) { + let data = Object.assign({}, this[0].dataset) + Object.keys(data).forEach(key => { + if (data[key].startsWith('[') || data[key].startsWith('{')) { + try { data[key] = JSON.parse(data[key]) } catch (e) {} + } + }) + return key ? data[key] : data + } else { + return undefined + } + } else { + this.each(node => { + if (value != null) { + node.dataset[key] = value instanceof Object ? JSON.stringify(value) : value + } else { + delete node.dataset[key] + } + }) + return this + } + } + removeData(key) { + if (typeof key == 'string') key = key.split(/[,\s]+/) + this.each(node => { + key.forEach(k => { delete node.dataset[k] }) + }) + return this + } + show() { + return this.toggle(true) + } + hide() { + return this.toggle(false) + } + toggle(force) { + return this.each(node => { + let prev = node.style.display + let dsp = getComputedStyle(node).display + let isHidden = (prev == 'none' || dsp == 'none') + if (isHidden && (force == null || force === true)) { // show + node.style.display = node._mQuery?.prevDisplay ?? (prev == dsp && dsp != 'none' ? '' : 'block') + this._save(node, 'prevDisplay', null) + } + if (!isHidden && (force == null || force === false)) { // hide + if (dsp != 'none') this._save(node, 'prevDisplay', dsp) + node.style.setProperty('display', 'none') + } + }) + } + empty() { + return this.html('') + } + html(html) { + return this.prop('innerHTML', html) + } + text(text) { + return this.prop('textContent', text) + } + val(value) { + return this.prop('value', value) // must be prop + } + change() { + return this.trigger('change') + } + click() { + return this.trigger('click') + } +} +// create a new object each time +let query = function (selector, context) { + // if a function, use as onload event + if (typeof selector == 'function') { + if (document.readyState == 'complete') { + selector() + } else { + window.addEventListener('load', selector) + } + } else { + return new Query(selector, context) + } +} +// str -> doc-fragment +query.html = (str) => { let frag = Query._fragment(str); return query(frag.children, frag) } +query.version = Query.version +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2locale + * + * == TODO == + * - add w2utils.lang wrap for all captions in all buttons. + * - check transition (also with layout) + * - deprecate w2utils.tooltip + * + * == 2.0 changes + * - CSP - fixed inline events (w2utils.tooltip still has it) + * - transition returns a promise + * - removed jQuery + * - refactores w2utils.message() + * - added w2utils.confirm() + * - added isPlainObject + * - added stripSpaces + * - implemented marker + * - cssPrefix - deprecated + * - w2utils.debounce + */ + +// variable that holds all w2ui objects +let w2ui = {} +class Utils { + constructor () { + this.version = '2.0.x' + this.tmp = {} + this.settings = this.extend({}, { + 'dataType' : 'HTTPJSON', // can be HTTP, HTTPJSON, RESTFULL, JSON (case sensitive) + 'dateStartYear' : 1950, // start year for date-picker + 'dateEndYear' : 2030, // end year for date picker + 'macButtonOrder' : false, // if true, Yes on the right side + 'warnNoPhrase' : false, // call console.warn if lang() encounters a missing phrase + }, w2locale, { phrases: null }), // if there are no phrases, then it is original language + this.i18nCompare = Intl.Collator().compare + this.hasLocalStorage = testLocalStorage() + // some internal variables + this.isMac = /Mac/i.test(navigator.platform) + this.isMobile = /(iphone|ipod|ipad|mobile|android)/i.test(navigator.userAgent) + this.isIOS = /(iphone|ipod|ipad)/i.test(navigator.platform) + this.isAndroid = /(android)/i.test(navigator.userAgent) + this.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) + // Formatters: Primarily used in grid + this.formatters = { + 'number'(value, params) { + if (parseInt(params) > 20) params = 20 + if (parseInt(params) < 0) params = 0 + if (value == null || value === '') return '' + return w2utils.formatNumber(parseFloat(value), params, true) + }, + 'float'(value, params) { + return w2utils.formatters.number(value, params) + }, + 'int'(value, params) { + return w2utils.formatters.number(value, 0) + }, + 'money'(value, params) { + if (value == null || value === '') return '' + let data = w2utils.formatNumber(Number(value), w2utils.settings.currencyPrecision) + return (w2utils.settings.currencyPrefix || '') + data + (w2utils.settings.currencySuffix || '') + }, + 'currency'(value, params) { + return w2utils.formatters.money(value, params) + }, + 'percent'(value, params) { + if (value == null || value === '') return '' + return w2utils.formatNumber(value, params || 1) + '%' + }, + 'size'(value, params) { + if (value == null || value === '') return '' + return w2utils.formatSize(parseInt(value)) + }, + 'date'(value, params) { + if (params === '') params = w2utils.settings.dateFormat + if (value == null || value === 0 || value === '') return '' + let dt = w2utils.isDateTime(value, params, true) + if (dt === false) dt = w2utils.isDate(value, params, true) + return '' + w2utils.formatDate(dt, params) + '' + }, + 'datetime'(value, params) { + if (params === '') params = w2utils.settings.datetimeFormat + if (value == null || value === 0 || value === '') return '' + let dt = w2utils.isDateTime(value, params, true) + if (dt === false) dt = w2utils.isDate(value, params, true) + return '' + w2utils.formatDateTime(dt, params) + '' + }, + 'time'(value, params) { + if (params === '') params = w2utils.settings.timeFormat + if (params === 'h12') params = 'hh:mi pm' + if (params === 'h24') params = 'h24:mi' + if (value == null || value === 0 || value === '') return '' + let dt = w2utils.isDateTime(value, params, true) + if (dt === false) dt = w2utils.isDate(value, params, true) + return '' + w2utils.formatTime(value, params) + '' + }, + 'timestamp'(value, params) { + if (params === '') params = w2utils.settings.datetimeFormat + if (value == null || value === 0 || value === '') return '' + let dt = w2utils.isDateTime(value, params, true) + if (dt === false) dt = w2utils.isDate(value, params, true) + return dt.toString ? dt.toString() : '' + }, + 'gmt'(value, params) { + if (params === '') params = w2utils.settings.datetimeFormat + if (value == null || value === 0 || value === '') return '' + let dt = w2utils.isDateTime(value, params, true) + if (dt === false) dt = w2utils.isDate(value, params, true) + return dt.toUTCString ? dt.toUTCString() : '' + }, + 'age'(value, params) { + if (value == null || value === 0 || value === '') return '' + let dt = w2utils.isDateTime(value, null, true) + if (dt === false) dt = w2utils.isDate(value, null, true) + return '' + w2utils.age(value) + (params ? (' ' + params) : '') + '' + }, + 'interval'(value, params) { + if (value == null || value === 0 || value === '') return '' + return w2utils.interval(value) + (params ? (' ' + params) : '') + }, + 'toggle'(value, params) { + return (value ? 'Yes' : '') + }, + 'password'(value, params) { + let ret = '' + for (let i = 0; i < value.length; i++) { + ret += '*' + } + return ret + } + } + return + function testLocalStorage() { + // test if localStorage is available, see issue #1282 + let str = 'w2ui_test' + try { + localStorage.setItem(str, str) + localStorage.removeItem(str) + return true + } catch (e) { + return false + } + } + } + isBin(val) { + let re = /^[0-1]+$/ + return re.test(val) + } + isInt(val) { + let re = /^[-+]?[0-9]+$/ + return re.test(val) + } + isFloat(val) { + if (typeof val === 'string') { + val = val.replace(this.settings.groupSymbol, '') + .replace(this.settings.decimalSymbol, '.') + } + return (typeof val === 'number' || (typeof val === 'string' && val !== '')) && !isNaN(Number(val)) + } + isMoney(val) { + if (typeof val === 'object' || val === '') return false + if (this.isFloat(val)) return true + let se = this.settings + let re = new RegExp('^'+ (se.currencyPrefix ? '\\' + se.currencyPrefix + '?' : '') + + '[-+]?'+ (se.currencyPrefix ? '\\' + se.currencyPrefix + '?' : '') + + '[0-9]*[\\'+ se.decimalSymbol +']?[0-9]+'+ (se.currencySuffix ? '\\' + se.currencySuffix + '?' : '') +'$', 'i') + if (typeof val === 'string') { + val = val.replace(new RegExp(se.groupSymbol, 'g'), '') + } + return re.test(val) + } + isHex(val) { + let re = /^(0x)?[0-9a-fA-F]+$/ + return re.test(val) + } + isAlphaNumeric(val) { + let re = /^[a-zA-Z0-9_-]+$/ + return re.test(val) + } + isEmail(val) { + let email = /^[a-zA-Z0-9._%\-+]+@[а-яА-Яa-zA-Z0-9.-]+\.[а-яА-Яa-zA-Z]+$/ + return email.test(val) + } + isIpAddress(val) { + let re = new RegExp('^' + + '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' + + '$') + return re.test(val) + } + isDate(val, format, retDate) { + if (!val) return false + let dt = 'Invalid Date' + let month, day, year + if (format == null) format = this.settings.dateFormat + if (typeof val.getFullYear === 'function') { // date object + year = val.getFullYear() + month = val.getMonth() + 1 + day = val.getDate() + } else if (parseInt(val) == val && parseInt(val) > 0) { + val = new Date(parseInt(val)) + year = val.getFullYear() + month = val.getMonth() + 1 + day = val.getDate() + } else { + val = String(val) + // convert month formats + if (new RegExp('mon', 'ig').test(format)) { + format = format.replace(/month/ig, 'm').replace(/mon/ig, 'm').replace(/dd/ig, 'd').replace(/[, ]/ig, '/').replace(/\/\//g, '/').toLowerCase() + val = val.replace(/[, ]/ig, '/').replace(/\/\//g, '/').toLowerCase() + for (let m = 0, len = this.settings.fullmonths.length; m < len; m++) { + let t = this.settings.fullmonths[m] + val = val.replace(new RegExp(t, 'ig'), (parseInt(m) + 1)).replace(new RegExp(t.substr(0, 3), 'ig'), (parseInt(m) + 1)) + } + } + // format date + let tmp = val.replace(/-/g, '/').replace(/\./g, '/').toLowerCase().split('/') + let tmp2 = format.replace(/-/g, '/').replace(/\./g, '/').toLowerCase() + if (tmp2 === 'mm/dd/yyyy') { month = tmp[0]; day = tmp[1]; year = tmp[2] } + if (tmp2 === 'm/d/yyyy') { month = tmp[0]; day = tmp[1]; year = tmp[2] } + if (tmp2 === 'dd/mm/yyyy') { month = tmp[1]; day = tmp[0]; year = tmp[2] } + if (tmp2 === 'd/m/yyyy') { month = tmp[1]; day = tmp[0]; year = tmp[2] } + if (tmp2 === 'yyyy/dd/mm') { month = tmp[2]; day = tmp[1]; year = tmp[0] } + if (tmp2 === 'yyyy/d/m') { month = tmp[2]; day = tmp[1]; year = tmp[0] } + if (tmp2 === 'yyyy/mm/dd') { month = tmp[1]; day = tmp[2]; year = tmp[0] } + if (tmp2 === 'yyyy/m/d') { month = tmp[1]; day = tmp[2]; year = tmp[0] } + if (tmp2 === 'mm/dd/yy') { month = tmp[0]; day = tmp[1]; year = tmp[2] } + if (tmp2 === 'm/d/yy') { month = tmp[0]; day = tmp[1]; year = parseInt(tmp[2]) + 1900 } + if (tmp2 === 'dd/mm/yy') { month = tmp[1]; day = tmp[0]; year = parseInt(tmp[2]) + 1900 } + if (tmp2 === 'd/m/yy') { month = tmp[1]; day = tmp[0]; year = parseInt(tmp[2]) + 1900 } + if (tmp2 === 'yy/dd/mm') { month = tmp[2]; day = tmp[1]; year = parseInt(tmp[0]) + 1900 } + if (tmp2 === 'yy/d/m') { month = tmp[2]; day = tmp[1]; year = parseInt(tmp[0]) + 1900 } + if (tmp2 === 'yy/mm/dd') { month = tmp[1]; day = tmp[2]; year = parseInt(tmp[0]) + 1900 } + if (tmp2 === 'yy/m/d') { month = tmp[1]; day = tmp[2]; year = parseInt(tmp[0]) + 1900 } + } + if (!this.isInt(year)) return false + if (!this.isInt(month)) return false + if (!this.isInt(day)) return false + year = +year + month = +month + day = +day + dt = new Date(year, month - 1, day) + dt.setFullYear(year) + // do checks + if (month == null) return false + if (String(dt) === 'Invalid Date') return false + if ((dt.getMonth() + 1 !== month) || (dt.getDate() !== day) || (dt.getFullYear() !== year)) return false + if (retDate === true) return dt; else return true + } + isTime(val, retTime) { + // Both formats 10:20pm and 22:20 + if (val == null) return false + let max, am, pm + // -- process american format + val = String(val) + val = val.toUpperCase() + am = val.indexOf('AM') >= 0 + pm = val.indexOf('PM') >= 0 + let ampm = (pm || am) + if (ampm) max = 12; else max = 24 + val = val.replace('AM', '').replace('PM', '').trim() + // --- + let tmp = val.split(':') + let h = parseInt(tmp[0] || 0), m = parseInt(tmp[1] || 0), s = parseInt(tmp[2] || 0) + // accept edge case: 3PM is a good timestamp, but 3 (without AM or PM) is NOT: + if ((!ampm || tmp.length !== 1) && tmp.length !== 2 && tmp.length !== 3) { return false } + if (tmp[0] === '' || h < 0 || h > max || !this.isInt(tmp[0]) || tmp[0].length > 2) { return false } + if (tmp.length > 1 && (tmp[1] === '' || m < 0 || m > 59 || !this.isInt(tmp[1]) || tmp[1].length !== 2)) { return false } + if (tmp.length > 2 && (tmp[2] === '' || s < 0 || s > 59 || !this.isInt(tmp[2]) || tmp[2].length !== 2)) { return false } + // check the edge cases: 12:01AM is ok, as is 12:01PM, but 24:01 is NOT ok while 24:00 is (midnight; equivalent to 00:00). + // meanwhile, there is 00:00 which is ok, but 0AM nor 0PM are okay, while 0:01AM and 0:00AM are. + if (!ampm && max === h && (m !== 0 || s !== 0)) { return false } + if (ampm && tmp.length === 1 && h === 0) { return false } + if (retTime === true) { + if (pm && h !== 12) h += 12 // 12:00pm - is noon + if (am && h === 12) h += 12 // 12:00am - is midnight + return { + hours: h, + minutes: m, + seconds: s + } + } + return true + } + isDateTime(val, format, retDate) { + if (typeof val.getFullYear === 'function') { // date object + if (retDate !== true) return true + return val + } + let intVal = parseInt(val) + if (intVal === val) { + if (intVal < 0) return false + else if (retDate !== true) return true + else return new Date(intVal) + } + let tmp = String(val).indexOf(' ') + if (tmp < 0) { + if (String(val).indexOf('T') < 0 || String(new Date(val)) == 'Invalid Date') return false + else if (retDate !== true) return true + else return new Date(val) + } else { + if (format == null) format = this.settings.datetimeFormat + let formats = format.split('|') + let values = [val.substr(0, tmp), val.substr(tmp).trim()] + formats[0] = formats[0].trim() + if (formats[1]) formats[1] = formats[1].trim() + // check + let tmp1 = this.isDate(values[0], formats[0], true) + let tmp2 = this.isTime(values[1], true) + if (tmp1 !== false && tmp2 !== false) { + if (retDate !== true) return true + tmp1.setHours(tmp2.hours) + tmp1.setMinutes(tmp2.minutes) + tmp1.setSeconds(tmp2.seconds) + return tmp1 + } else { + return false + } + } + } + age(dateStr) { + let d1 + if (dateStr === '' || dateStr == null) return '' + if (typeof dateStr.getFullYear === 'function') { // date object + d1 = dateStr + } else if (parseInt(dateStr) == dateStr && parseInt(dateStr) > 0) { + d1 = new Date(parseInt(dateStr)) + } else { + d1 = new Date(dateStr) + } + if (String(d1) === 'Invalid Date') return '' + let d2 = new Date() + let sec = (d2.getTime() - d1.getTime()) / 1000 + let amount = '' + let type = '' + if (sec < 0) { + amount = 0 + type = 'sec' + } else if (sec < 60) { + amount = Math.floor(sec) + type = 'sec' + if (sec < 0) { amount = 0; type = 'sec' } + } else if (sec < 60*60) { + amount = Math.floor(sec/60) + type = 'min' + } else if (sec < 24*60*60) { + amount = Math.floor(sec/60/60) + type = 'hour' + } else if (sec < 30*24*60*60) { + amount = Math.floor(sec/24/60/60) + type = 'day' + } else if (sec < 365*24*60*60) { + amount = Math.floor(sec/30/24/60/60*10)/10 + type = 'month' + } else if (sec < 365*4*24*60*60) { + amount = Math.floor(sec/365/24/60/60*10)/10 + type = 'year' + } else if (sec >= 365*4*24*60*60) { + // factor in leap year shift (only older then 4 years) + amount = Math.floor(sec/365.25/24/60/60*10)/10 + type = 'year' + } + return amount + ' ' + type + (amount > 1 ? 's' : '') + } + interval(value) { + let ret = '' + if (value < 100) { + ret = '< 0.01 sec' + } else if (value < 1000) { + ret = (Math.floor(value / 10) / 100) + ' sec' + } else if (value < 10000) { + ret = (Math.floor(value / 100) / 10) + ' sec' + } else if (value < 60000) { + ret = Math.floor(value / 1000) + ' secs' + } else if (value < 3600000) { + ret = Math.floor(value / 60000) + ' mins' + } else if (value < 86400000) { + ret = Math.floor(value / 3600000 * 10) / 10 + ' hours' + } else if (value < 2628000000) { + ret = Math.floor(value / 86400000 * 10) / 10 + ' days' + } else if (value < 3.1536e+10) { + ret = Math.floor(value / 2628000000 * 10) / 10 + ' months' + } else { + ret = Math.floor(value / 3.1536e+9) / 10 + ' years' + } + return ret + } + date(dateStr) { + if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return '' + let d1 = new Date(dateStr) + if (this.isInt(dateStr)) d1 = new Date(Number(dateStr)) // for unix timestamps + if (String(d1) === 'Invalid Date') return '' + let months = this.settings.shortmonths + let d2 = new Date() // today + let d3 = new Date() + d3.setTime(d3.getTime() - 86400000) // yesterday + let dd1 = months[d1.getMonth()] + ' ' + d1.getDate() + ', ' + d1.getFullYear() + let dd2 = months[d2.getMonth()] + ' ' + d2.getDate() + ', ' + d2.getFullYear() + let dd3 = months[d3.getMonth()] + ' ' + d3.getDate() + ', ' + d3.getFullYear() + let time = (d1.getHours() - (d1.getHours() > 12 ? 12 :0)) + ':' + (d1.getMinutes() < 10 ? '0' : '') + d1.getMinutes() + ' ' + (d1.getHours() >= 12 ? 'pm' : 'am') + let time2 = (d1.getHours() - (d1.getHours() > 12 ? 12 :0)) + ':' + (d1.getMinutes() < 10 ? '0' : '') + d1.getMinutes() + ':' + (d1.getSeconds() < 10 ? '0' : '') + d1.getSeconds() + ' ' + (d1.getHours() >= 12 ? 'pm' : 'am') + let dsp = dd1 + if (dd1 === dd2) dsp = time + if (dd1 === dd3) dsp = this.lang('Yesterday') + return ''+ dsp +'' + } + formatSize(sizeStr) { + if (!this.isFloat(sizeStr) || sizeStr === '') return '' + sizeStr = parseFloat(sizeStr) + if (sizeStr === 0) return 0 + let sizes = ['Bt', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB'] + let i = parseInt( Math.floor( Math.log(sizeStr) / Math.log(1024) ) ) + return (Math.floor(sizeStr / Math.pow(1024, i) * 10) / 10).toFixed(i === 0 ? 0 : 1) + ' ' + (sizes[i] || '??') + } + formatNumber(val, fraction, useGrouping) { + if (val == null || val === '' || typeof val === 'object') return '' + let options = { + minimumFractionDigits: parseInt(fraction), + maximumFractionDigits: parseInt(fraction), + useGrouping: !!useGrouping + } + if (fraction == null || fraction < 0) { + options.minimumFractionDigits = 0 + options.maximumFractionDigits = 20 + } + return parseFloat(val).toLocaleString(this.settings.locale, options) + } + formatDate(dateStr, format) { // IMPORTANT dateStr HAS TO BE valid JavaScript Date String + if (!format) format = this.settings.dateFormat + if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return '' + let dt = new Date(dateStr) + if (this.isInt(dateStr)) dt = new Date(Number(dateStr)) // for unix timestamps + if (String(dt) === 'Invalid Date') return '' + let year = dt.getFullYear() + let month = dt.getMonth() + let date = dt.getDate() + return format.toLowerCase() + .replace('month', this.settings.fullmonths[month]) + .replace('mon', this.settings.shortmonths[month]) + .replace(/yyyy/g, ('000' + year).slice(-4)) + .replace(/yyy/g, ('000' + year).slice(-4)) + .replace(/yy/g, ('0' + year).slice(-2)) + .replace(/(^|[^a-z$])y/g, '$1' + year) // only y's that are not preceded by a letter + .replace(/mm/g, ('0' + (month + 1)).slice(-2)) + .replace(/dd/g, ('0' + date).slice(-2)) + .replace(/th/g, (date == 1 ? 'st' : 'th')) + .replace(/th/g, (date == 2 ? 'nd' : 'th')) + .replace(/th/g, (date == 3 ? 'rd' : 'th')) + .replace(/(^|[^a-z$])m/g, '$1' + (month + 1)) // only y's that are not preceded by a letter + .replace(/(^|[^a-z$])d/g, '$1' + date) // only y's that are not preceded by a letter + } + formatTime(dateStr, format) { // IMPORTANT dateStr HAS TO BE valid JavaScript Date String + if (!format) format = this.settings.timeFormat + if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return '' + let dt = new Date(dateStr) + if (this.isInt(dateStr)) dt = new Date(Number(dateStr)) // for unix timestamps + if (this.isTime(dateStr)) { + let tmp = this.isTime(dateStr, true) + dt = new Date() + dt.setHours(tmp.hours) + dt.setMinutes(tmp.minutes) + } + if (String(dt) === 'Invalid Date') return '' + let type = 'am' + let hour = dt.getHours() + let h24 = dt.getHours() + let min = dt.getMinutes() + let sec = dt.getSeconds() + if (min < 10) min = '0' + min + if (sec < 10) sec = '0' + sec + if (format.indexOf('am') !== -1 || format.indexOf('pm') !== -1) { + if (hour >= 12) type = 'pm' + if (hour > 12) hour = hour - 12 + if (hour === 0) hour = 12 + } + return format.toLowerCase() + .replace('am', type) + .replace('pm', type) + .replace('hhh', (hour < 10 ? '0' + hour : hour)) + .replace('hh24', (h24 < 10 ? '0' + h24 : h24)) + .replace('h24', h24) + .replace('hh', hour) + .replace('mm', min) + .replace('mi', min) + .replace('ss', sec) + .replace(/(^|[^a-z$])h/g, '$1' + hour) // only y's that are not preceded by a letter + .replace(/(^|[^a-z$])m/g, '$1' + min) // only y's that are not preceded by a letter + .replace(/(^|[^a-z$])s/g, '$1' + sec) // only y's that are not preceded by a letter + } + formatDateTime(dateStr, format) { + let fmt + if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return '' + if (typeof format !== 'string') { + fmt = [this.settings.dateFormat, this.settings.timeFormat] + } else { + fmt = format.split('|') + fmt[0] = fmt[0].trim() + fmt[1] = (fmt.length > 1 ? fmt[1].trim() : this.settings.timeFormat) + } + // older formats support + if (fmt[1] === 'h12') fmt[1] = 'h:m pm' + if (fmt[1] === 'h24') fmt[1] = 'h24:m' + return this.formatDate(dateStr, fmt[0]) + ' ' + this.formatTime(dateStr, fmt[1]) + } + stripSpaces(html) { + if (html == null) return html + switch (typeof html) { + case 'number': + break + case 'string': + html = String(html).replace(/(?:\r\n|\r|\n)/g, ' ').replace(/\s\s+/g, ' ').trim() + break + case 'object': + // does not modify original object, but creates a copy + if (Array.isArray(html)) { + html = this.extend([], html) + html.forEach((key, ind) => { + html[ind] = this.stripSpaces(key) + }) + } else { + html = this.extend({}, html) + Object.keys(html).forEach(key => { + html[key] = this.stripSpaces(html[key]) + }) + } + break + } + return html + } + stripTags(html) { + if (html == null) return html + switch (typeof html) { + case 'number': + break + case 'string': + html = String(html).replace(/<(?:[^>=]|='[^']*'|="[^"]*"|=[^'"][^\s>]*)*>/ig, '') + break + case 'object': + // does not modify original object, but creates a copy + if (Array.isArray(html)) { + html = this.extend([], html) + html.forEach((key, ind) => { + html[ind] = this.stripTags(key) + }) + } else { + html = this.extend({}, html) + Object.keys(html).forEach(key => { + html[key] = this.stripTags(html[key]) + }) + } + break + } + return html + } + encodeTags(html) { + if (html == null) return html + switch (typeof html) { + case 'number': + break + case 'string': + html = String(html).replace(/&/g, '&').replace(/>/g, '>').replace(/ { + html[ind] = this.encodeTags(key) + }) + } else { + html = this.extend({}, html) + Object.keys(html).forEach(key => { + html[key] = this.encodeTags(html[key]) + }) + } + break + } + return html + } + decodeTags(html) { + if (html == null) return html + switch (typeof html) { + case 'number': + break + case 'string': + html = String(html).replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/&/g, '&') + break + case 'object': + // does not modify original object, but creates a copy + if (Array.isArray(html)) { + html = this.extend([], html) + html.forEach((key, ind) => { + html[ind] = this.decodeTags(key) + }) + } else { + html = this.extend({}, html) + Object.keys(html).forEach(key => { + html[key] = this.decodeTags(html[key]) + }) + } + break + } + return html + } + escapeId(id) { + // This logic is borrowed from jQuery + if (id === '' || id == null) return '' + let re = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g + return (id + '').replace(re, (ch, asCodePoint) => { + if (asCodePoint) { + if (ch === '\0') return '\uFFFD' + return ch.slice( 0, -1 ) + '\\' + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + ' ' + } + return '\\' + ch + }) + } + unescapeId(id) { + // This logic is borrowed from jQuery + if (id === '' || id == null) return '' + let re = /\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\([^\r\n\f])/g + return id.replace(re, (escape, nonHex) => { + let high = '0x' + escape.slice( 1 ) - 0x10000 + return nonHex ? nonHex : high < 0 + ? String.fromCharCode(high + 0x10000 ) + : String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00) + }) + } + base64encode(input) { + // Fast Native support in Chrome since 2010 + return btoa(input) // binary to ascii + } + base64decode(input) { + // Fast Native support in Chrome since 2010 + return atob(input) // ascii to binary + } + async sha256(str) { + const utf8 = new TextEncoder().encode(str) + return crypto.subtle.digest('SHA-256', utf8).then((hashBuffer) => { + const hashArray = Array.from(new Uint8Array(hashBuffer)) + return hashArray.map((bytes) => bytes.toString(16).padStart(2, '0')).join('') + }) + } + transition(div_old, div_new, type, callBack) { + return new Promise((resolve, reject) => { + let styles = getComputedStyle(div_old) + let width = parseInt(styles.width) + let height = parseInt(styles.height) + let time = 0.5 + if (!div_old || !div_new) { + console.log('ERROR: Cannot do transition when one of the divs is null') + return + } + div_old.parentNode.style.cssText += 'perspective: 900px; overflow: hidden;' + div_old.style.cssText += '; position: absolute; z-index: 1019; backface-visibility: hidden' + div_new.style.cssText += '; position: absolute; z-index: 1020; backface-visibility: hidden' + switch (type) { + case 'slide-left': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)' + div_new.style.cssText += 'overflow: hidden; transform: translate3d('+ width + 'px, 0, 0)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)' + div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d(-'+ width +'px, 0, 0)' + }, 1) + break + case 'slide-right': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)' + div_new.style.cssText += 'overflow: hidden; transform: translate3d(-'+ width +'px, 0, 0)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0px, 0, 0)' + div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d('+ width +'px, 0, 0)' + }, 1) + break + case 'slide-down': + // init divs + div_old.style.cssText += 'overflow: hidden; z-index: 1; transform: translate3d(0, 0, 0)' + div_new.style.cssText += 'overflow: hidden; z-index: 0; transform: translate3d(0, 0, 0)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)' + div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, '+ height +'px, 0)' + }, 1) + break + case 'slide-up': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)' + div_new.style.cssText += 'overflow: hidden; transform: translate3d(0, '+ height +'px, 0)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)' + div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)' + }, 1) + break + case 'flip-left': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: rotateY(0deg)' + div_new.style.cssText += 'overflow: hidden; transform: rotateY(-180deg)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: rotateY(0deg)' + div_old.style.cssText += 'transition: '+ time +'s; transform: rotateY(180deg)' + }, 1) + break + case 'flip-right': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: rotateY(0deg)' + div_new.style.cssText += 'overflow: hidden; transform: rotateY(180deg)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: rotateY(0deg)' + div_old.style.cssText += 'transition: '+ time +'s; transform: rotateY(-180deg)' + }, 1) + break + case 'flip-down': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: rotateX(0deg)' + div_new.style.cssText += 'overflow: hidden; transform: rotateX(180deg)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: rotateX(0deg)' + div_old.style.cssText += 'transition: '+ time +'s; transform: rotateX(-180deg)' + }, 1) + break + case 'flip-up': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: rotateX(0deg)' + div_new.style.cssText += 'overflow: hidden; transform: rotateX(-180deg)' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: rotateX(0deg)' + div_old.style.cssText += 'transition: '+ time +'s; transform: rotateX(180deg)' + }, 1) + break + case 'pop-in': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)' + div_new.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0); transform: scale(.8); opacity: 0;' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; transform: scale(1); opacity: 1;' + div_old.style.cssText += 'transition: '+ time +'s;' + }, 1) + break + case 'pop-out': + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0); transform: scale(1); opacity: 1;' + div_new.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0); opacity: 0;' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; opacity: 1;' + div_old.style.cssText += 'transition: '+ time +'s; transform: scale(1.7); opacity: 0;' + }, 1) + break + default: + // init divs + div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)' + div_new.style.cssText += 'overflow: hidden; translate3d(0, 0, 0); opacity: 0;' + query(div_new).show() + // -- need a timing function because otherwise not working + setTimeout(() => { + div_new.style.cssText += 'transition: '+ time +'s; opacity: 1;' + div_old.style.cssText += 'transition: '+ time +'s' + }, 1) + break + } + setTimeout(() => { + if (type === 'slide-down') { + query(div_old).css('z-index', '1019') + query(div_new).css('z-index', '1020') + } + if (div_new) { + query(div_new) + .css({ 'opacity': '1' }) + .css({ 'transition': '', 'transform' : '' }) + } + if (div_old) { + query(div_old) + .css({ 'opacity': '1' }) + .css({ 'transition': '', 'transform' : '' }) + } + if (typeof callBack === 'function') callBack() + resolve() + }, time * 1000) + }) + } + lock(box, options = {}) { + if (box == null) return + if (typeof options == 'string') { + options = { msg: options } + } + if (arguments[2]) { + options.spinner = arguments[2] + } + options = this.extend({ + spinner: false + }, options) + // for backward compatibility + if (box?.[0] instanceof Node) { + box = Array.isArray(box) ? box : box.get() + } + if (!options.msg && options.msg !== 0) options.msg = '' + this.unlock(box) + let el = query(box).get(0) + let pWidth = el.scrollWidth + let pHeight = el.scrollHeight + // if it is body and only has absolute elements, its height will be 0, need to lock entire window + if (el.tagName == 'BODY') { + if (pWidth < innerWidth) pWidth = innerWidth + if (pHeight < innerHeight) pHeight = innerHeight + } + query(box).prepend( + `
    ` + + '
    ' + ) + let $lock = query(box).find('.w2ui-lock') + let $mess = query(box).find('.w2ui-lock-msg') + if (!options.msg) { + $mess.css({ + 'background-color': 'transparent', + 'background-image': 'none', + 'border': '0px', + 'box-shadow': 'none' + }) + } + if (options.spinner === true) { + options.msg = `
    ` + + options.msg + } + if (options.msg) { + $mess.html(options.msg).css('display', 'block') + } else { + $mess.remove() + } + if (options.opacity != null) { + $lock.css('opacity', options.opacity) + } + $lock.css({ display: 'block' }) + if (options.bgColor) { + $lock.css({ 'background-color': options.bgColor }) + } + let styles = getComputedStyle($lock.get(0)) + let opacity = styles.opacity ?? 0.15 + $lock + .on('mousedown', function() { + if (typeof options.onClick == 'function') { + options.onClick() + } else { + $lock.css({ + 'transition': '.2s', + 'opacity': opacity * 1.5 + }) + } + }) + .on('mouseup', function() { + if (typeof options.onClick !== 'function') { + $lock.css({ + 'transition': '.2s', + 'opacity': opacity + }) + } + }) + .on('mousewheel', function(event) { + if (event) { + event.stopPropagation() + event.preventDefault() + } + }) + } + unlock(box, speed) { + if (box == null) return + clearTimeout(box._prevUnlock) + // for backward compatibility + if (box?.[0] instanceof Node) { + box = Array.isArray(box) ? box : box.get() + } + if (this.isInt(speed) && speed > 0) { + query(box).find('.w2ui-lock').css({ + transition: (speed/1000) + 's', + opacity: 0, + }) + let _box = query(box).get(0) + clearTimeout(_box._prevUnlock) + _box._prevUnlock = setTimeout(() => { + query(box).find('.w2ui-lock').remove() + }, speed) + query(box).find('.w2ui-lock-msg').remove() + } else { + query(box).find('.w2ui-lock').remove() + query(box).find('.w2ui-lock-msg').remove() + } + } + /** + * Opens a context message, similar in parameters as w2popup.open() + * + * Sample Calls + * w2utils.message({ box: '#div' }, 'message').ok(() => {}) + * w2utils.message({ box: '#div' }, { text: 'message', width: 300 }).ok(() => {}) + * w2utils.message({ box: '#div' }, { text: 'message', actions: ['Save'] }).Save(() => {}) + * + * Used in w2grid, w2form, w2layout (should be in w2popup too) + * should be called with .call(...) method + * + * @param where = { + * box, // where to open + * after, // title if any, adds title heights + * param // additional parameters, used in layouts for panel + * } + * @param options { + * width, // (int), width in px, if negative, then it is maxWidth - width + * height, // (int), height in px, if negative, then it is maxHeight - height + * text, // centered text + * body, // body of the message + * buttons, // buttons of the message + * html, // if body & buttons are not defined, then html is the entire message + * focus, // int or id with a selector, default is 0 + * hideOn, // ['esc', 'click'], default is ['esc'] + * actions, // array of actions (only if buttons is not defined) + * onOpen, // event when opened + * onClose, // event when closed + * onAction, // event on action + * } + */ + message(where, options) { + let closeTimer, openTimer, edata + let removeLast = () => { + let msgs = query(where?.box).find('.w2ui-message') + if (msgs.length == 0) return // no messages already + options = msgs.get(0)._msg_options || {} + if (typeof options?.close == 'function') { + options.close() + } + } + let closeComplete = (options) => { + let focus = options.box._msg_prevFocus + if (query(where.box).find('.w2ui-message').length <= 1) { + if (where.owner) { + where.owner.unlock(where.param, 150) + } else { + this.unlock(where.box, 150) + } + } else { + query(where.box).find(`#w2ui-message-${where.owner?.name}-${options.msgIndex-1}`).css('z-index', 1500) + } + if (focus) { + let msg = query(focus).closest('.w2ui-message') + if (msg.length > 0) { + let opt = msg.get(0)._msg_options + opt.setFocus(focus) + } else { + focus.focus() + } + } else { + if (typeof where.owner?.focus == 'function') where.owner.focus() + } + query(options.box).remove() + if (options.msgIndex === 0) { + head.css('z-index', options.tmp.zIndex) + query(where.box).css('overflow', options.tmp.overflow) + } + // event after + if (options.trigger) { + edata.finish() + } + } + if (typeof options == 'string' || typeof options == 'number') { + options = { + width : (String(options).length < 300 ? 350 : 550), + height: (String(options).length < 300 ? 170: 250), + text : String(options), + } + } + if (typeof options != 'object') { + removeLast() + return + } + if (options.text != null) options.body = `
    ${options.text}
    ` + if (options.width == null) options.width = 350 + if (options.height == null) options.height = 170 + if (options.hideOn == null) options.hideOn = ['esc'] + // mix in events + if (options.on == null) { + let opts = options + options = new w2base() + w2utils.extend(options, opts) // needs to be w2utils + } + options.on('open', (event) => { + w2utils.bindEvents(query(options.box).find('.w2ui-eaction'), options) // options is w2base object + query(event.detail.box).find('button, input, textarea, [name=hidden-first]') + .off('.message') + .on('keydown.message', function(evt) { + if (evt.keyCode == 27 && options.hideOn.includes('esc')) { + if (options.cancelAction) { + options.action(options.cancelAction) + } else { + options.close() + } + } + }) + // timeout is needed because messages opens over 0.3 seconds + setTimeout(() => options.setFocus(options.focus), 300) + }) + options.off('.prom') + let prom = { + self: options, + action(callBack) { + options.on('action.prom', callBack) + return prom + }, + close(callBack) { + options.on('close.prom', callBack) + return prom + }, + open(callBack) { + options.on('open.prom', callBack) + return prom + }, + then(callBack) { + options.on('open:after.prom', callBack) + return prom + } + } + if (options.actions == null && options.buttons == null && options.html == null) { + options.actions = { Ok(event) { event.detail.self.close() }} + } + options.off('.buttons') + if (options.actions != null) { + options.buttons = '' + Object.keys(options.actions).forEach((action) => { + let handler = options.actions[action] + let btnAction = action + if (typeof handler == 'function') { + options.buttons += `` + } + if (typeof handler == 'object') { + options.buttons += `` + btnAction = Array.isArray(options.actions) ? handler.text : action + } + if (typeof handler == 'string') { + options.buttons += `` + btnAction = handler + } + if (typeof btnAction == 'string') { + btnAction = btnAction[0].toLowerCase() + btnAction.substr(1).replace(/\s+/g, '') + } + prom[btnAction] = function (callBack) { + options.on('action.buttons', (event) => { + let target = event.detail.action[0].toLowerCase() + event.detail.action.substr(1).replace(/\s+/g, '') + if (target == btnAction) callBack(event) + }) + return prom + } + }) + } + // trim if any + Array('html', 'body', 'buttons').forEach(param => { + options[param] = String(options[param] ?? '').trim() + }) + if (options.body !== '' || options.buttons !== '') { + options.html = ` +
    ${options.body || ''}
    +
    ${options.buttons || ''}
    + ` + } + let styles = getComputedStyle(query(where.box).get(0)) + let pWidth = parseFloat(styles.width) + let pHeight = parseFloat(styles.height) + let titleHeight = 0 + if (query(where.after).length > 0) { + styles = getComputedStyle(query(where.after).get(0)) + titleHeight = parseInt(styles.display != 'none' ? parseInt(styles.height) : 0) + } + if (options.width > pWidth) options.width = pWidth - 10 + if (options.height > pHeight - titleHeight) options.height = pHeight - 10 - titleHeight + options.originalWidth = options.width + options.originalHeight = options.height + if (parseInt(options.width) < 0) options.width = pWidth + options.width + if (parseInt(options.width) < 10) options.width = 10 + if (parseInt(options.height) < 0) options.height = pHeight + options.height - titleHeight + if (parseInt(options.height) < 10) options.height = 10 + // negative value means margin + if (options.originalHeight < 0) options.height = pHeight + options.originalHeight - titleHeight + if (options.originalWidth < 0) options.width = pWidth + options.originalWidth * 2 // x 2 because there is left and right margin + let head = query(where.box).find(where.after) // needed for z-index manipulations + if (!options.tmp) { + options.tmp = { + zIndex: head.css('z-index'), + overflow: styles.overflow + } + } + // remove message + if (options.html === '' && options.body === '' && options.buttons === '') { + removeLast() + } else { + options.msgIndex = query(where.box).find('.w2ui-message').length + if (options.msgIndex === 0 && typeof this.lock == 'function') { + query(where.box).css('overflow', 'hidden') + if (where.owner) { // where.praram is used in the panel + where.owner.lock(where.param) + } else { + this.lock(where.box) + } + } + // send back previous messages + query(where.box).find('.w2ui-message').css('z-index', 1390) + head.css('z-index', 1501) + // add message + let content = ` +
    + + ${options.html} + +
    ` + if (query(where.after).length > 0) { + query(where.box).find(where.after).after(content) + } else { + query(where.box).prepend(content) + } + options.box = query(where.box).find(`#w2ui-message-${where.owner?.name}-${options.msgIndex}`)[0] + w2utils.bindEvents(options.box, this) + query(options.box) + .addClass('animating') + // remember options and prev focus + options.box._msg_options = options + options.box._msg_prevFocus = document.activeElement + // timeout is needs so that callBacks are setup + setTimeout(() => { + // before event + edata = options.trigger('open', { target: this.name, box: options.box, self: options }) + if (edata.isCancelled === true) { + query(where.box).find(`#w2ui-message-${where.owner?.name}-${options.msgIndex}`).remove() + if (options.msgIndex === 0) { + head.css('z-index', options.tmp.zIndex) + query(where.box).css('overflow', options.tmp.overflow) + } + return + } + // slide down + query(options.box).css({ + transition: '0.3s', + transform: 'translateY(0px)' + }) + }, 0) + // timeout is needed so that animation can finish + openTimer = setTimeout(() => { + // has to be on top of lock + query(where.box) + .find(`#w2ui-message-${where.owner?.name}-${options.msgIndex}`) + .removeClass('animating') + .css({ 'transition': '0s' }) + // event after + edata.finish() + }, 300) + } + // action handler + options.action = (action, event) => { + let click = options.actions[action] + if (click instanceof Object && click.onClick) click = click.onClick + // event before + let edata = options.trigger('action', { target: this.name, action, self: options, + originalEvent: event, value: options.input ? options.input.value : null }) + if (edata.isCancelled === true) return + // default actions + if (typeof click === 'function') click(edata) + // event after + edata.finish() + } + options.close = () => { + edata = options.trigger('close', { target: 'self', box: options.box, self: options }) + if (edata.isCancelled === true) return + clearTimeout(openTimer) + if (query(options.box).hasClass('animating')) { + clearTimeout(closeTimer) + closeComplete(options) + return + } + // default behavior + query(options.box) + .addClass('w2ui-closing animating') + .css({ + 'transition': '0.15s', + 'transform': 'translateY(-' + options.height + 'px)' + }) + if (options.msgIndex !== 0) { + // previous message + query(where.box).find(`#w2ui-message-${where.owner?.name}-${options.msgIndex-1}`).css('z-index', 1499) + } + closeTimer = setTimeout(() => { closeComplete(options) }, 150) + } + options.setFocus = (focus) => { + // in message or popup + let cnt = query(where.box).find('.w2ui-message').length - 1 + let box = query(where.box).find(`#w2ui-message-${where.owner?.name}-${cnt}`) + let sel = 'input, button, select, textarea, [contentEditable], .w2ui-input' + if (focus != null) { + let el = isNaN(focus) + ? box.find(sel).filter(focus).get(0) + : box.find(sel).get(focus) + el?.focus() + } else { + box.find('[name=hidden-first]').get(0)?.focus() + } + // clear focus if there are other messages + query(where.box) + .find('.w2ui-message') + .find(sel + ',[name=hidden-first],[name=hidden-last]') + .off('.keep-focus') + // keep focus/blur inside popup + query(box) + .find(sel + ',[name=hidden-first],[name=hidden-last]') + .on('blur.keep-focus', function (event) { + setTimeout(() => { + let focus = document.activeElement + let inside = query(box).find(sel).filter(focus).length > 0 + let name = query(focus).attr('name') + if (!inside && focus && focus !== document.body) { + query(box).find(sel).get(0)?.focus() + } + if (name == 'hidden-last') { + query(box).find(sel).get(0)?.focus() + } + if (name == 'hidden-first') { + query(box).find(sel).get(-1)?.focus() + } + }, 1) + }) + } + return prom + } + /** + * Shows small notification message at the bottom of the page, or containter that you specify + * in options.where (could be element or a selector) + * + * w2utils.notify('Document saved') + * w2utils.notify('Mesage sent ${udon}', { actions: { undo: function () {...} }}) + * + * @param {String/Object} options can be { + * text: string, // message, can be html + * where: el/selector, // element or selector where to show, default is document.body + * timeout: int, // timeout when to hide, if 0 - indefinite + * error: boolean, // add error clases + * class: string, // additional class strings + * actions: object // object with action functions, it should correspot to templated text: '... ${action} ...' + * } + * @returns promise + */ + notify(text, options) { + return new Promise(resolve => { + if (typeof text == 'object') { + options = text + text = options.text + } + options = options || {} + options.where = options.where ?? document.body + options.timeout = options.timeout ?? 15_000 // 15 secodns or will be hidden on route change + if (typeof this.tmp.notify_resolve == 'function') { + this.tmp.notify_resolve() + query(this.tmp.notify_where).find('#w2ui-notify').remove() + } + this.tmp.notify_resolve = resolve + this.tmp.notify_where = options.where + clearTimeout(this.tmp.notify_timer) + if (text) { + if (typeof options.actions == 'object') { + let actions = {} + Object.keys(options.actions).forEach(action => { + actions[action] = `${action}` + }) + text = this.execTemplate(text, actions) + } + let html = ` +
    +
    + ${text} + +
    +
    ` + query(options.where).append(html) + query(options.where).find('#w2ui-notify').find('.w2ui-notify-close') + .on('click', event => { + query(options.where).find('#w2ui-notify').remove() + resolve() + }) + if (options.actions) { + query(options.where).find('#w2ui-notify .w2ui-notify-link') + .on('click', event => { + let value = query(event.target).attr('value') + options.actions[value]() + query(options.where).find('#w2ui-notify').remove() + resolve() + }) + } + if (options.timeout > 0) { + this.tmp.notify_timer = setTimeout(() => { + query(options.where).find('#w2ui-notify').remove() + resolve() + }, options.timeout) + } + } + }) + } + confirm(where, options) { + if (typeof options == 'string') { + options = { text: options } + } + w2utils.normButtons(options, { yes: 'Yes', no: 'No' }) + let prom = w2utils.message(where, options) + if (prom) { + prom.action(event => { + event.detail.self.close() + }) + } + return prom + } + /** + * Normalizes yes, no buttons for confirmation dialog + * + * @param {*} options + * @returns options + */ + normButtons(options, btn) { + options.actions = options.actions ?? {} + let btns = Object.keys(btn) + btns.forEach(name => { + let action = options['btn_' + name] + if (action) { + btn[name] = { + text: w2utils.lang(action.text ?? ''), + class: action.class ?? '', + style: action.style ?? '', + attrs: action.attrs ?? '' + } + delete options['btn_' + name] + } + Array('text', 'class', 'style', 'attrs').forEach(suffix => { + if (options[name + '_' + suffix]) { + if (typeof btn[name] == 'string') { + btn[name] = { text: btn[name] } + } + btn[name][suffix] = options[name + '_' + suffix] + delete options[name + '_' + suffix] + } + }) + }) + if (btns.includes('yes') && btns.includes('no')) { + if (w2utils.settings.macButtonOrder) { + w2utils.extend(options.actions, { no: btn.no, yes: btn.yes }) + } else { + w2utils.extend(options.actions, { yes: btn.yes, no: btn.no }) + } + } + if (btns.includes('ok') && btns.includes('cancel')) { + if (w2utils.settings.macButtonOrder) { + w2utils.extend(options.actions, { cancel: btn.cancel, ok: btn.ok }) + } else { + w2utils.extend(options.actions, { ok: btn.ok, cancel: btn.cancel }) + } + } + return options + } + getSize(el, type) { + el = query(el) // for backward compatibility + let ret = 0 + if (el.length > 0) { + el = el[0] + let styles = getComputedStyle(el) + switch (type) { + case 'width' : + ret = parseFloat(styles.width) + if (styles.width === 'auto') ret = 0 + break + case 'height' : + ret = parseFloat(styles.height) + if (styles.height === 'auto') ret = 0 + break + default: + ret = parseFloat(styles[type] ?? 0) || 0 + break + } + } + return ret + } + getStrWidth(str, styles) { + query('body').append(` +
    + ${this.encodeTags(str)} +
    `) + let width = query('#_tmp_width')[0].clientWidth + query('#_tmp_width').remove() + return width + } + execTemplate(str, replace_obj) { + if (typeof str !== 'string' || !replace_obj || typeof replace_obj !== 'object') { + return str + } + return str.replace(/\${([^}]+)?}/g, function($1, $2) { return replace_obj[$2]||$2 }) + } + marker(el, items, options = { onlyFirst: false, wholeWord: false }) { + if (!Array.isArray(items)) { + if (items != null && items !== '') { + items = [items] + } else { + items = [] + } + } + let ww = options.wholeWord + query(el).each(el => { + clearMerkers(el) + items.forEach(str => { + if (typeof str !== 'string') str = String(str) + let replaceValue = (matched) => { // mark new + return '' + matched + '' + } + // escape regex special chars + str = str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&').replace(/&/g, '&') + .replace(//g, '<') + let regex = new RegExp((ww ? '\\b' : '') + str + (ww ? '\\b' : '')+ '(?!([^<]+)?>)', + 'i' + (!options.onlyFirst ? 'g' : '')) // only outside tags + el.innerHTML = el.innerHTML.replace(regex, replaceValue) + }) + }) + function clearMerkers(el) { + let markerRE = /\((.|\n|\r)*)\<\/span\>/ig + while (el.innerHTML.indexOf('='.includes(phrase)) { + return this.execTemplate(phrase, params) + } + let translation = this.settings.phrases[phrase] + if (translation == null) { + translation = phrase + if (this.settings.warnNoPhrase) { + if (!this.settings.missing) { + this.settings.missing = {} + } + this.settings.missing[phrase] = '---' // collect phrases for translation, warn once + this.settings.phrases[phrase] = '---' + console.log(`Missing translation for "%c${phrase}%c", see %c w2utils.settings.phrases %c with value "---"`, + 'color: orange', '', + 'color: #999', '') + } + } else if (translation === '---' && !this.settings.warnNoPhrase) { + translation = phrase + } + if (translation === '---') { + translation = `---` + } + return this.execTemplate(translation, params) + } + locale(locale, keepPhrases, noMerge) { + return new Promise((resolve, reject) => { + // if locale is an array we call this function recursively and merge the results + if (Array.isArray(locale)) { + this.settings.phrases = {} + let proms = [] + let files = {} + locale.forEach((file, ind) => { + if (file.length === 5) { + file = 'locale/'+ file.toLowerCase() +'.json' + locale[ind] = file + } + proms.push(this.locale(file, true, false)) + }) + Promise.allSettled(proms) + .then(res => { + // order of files is important to merge + res.forEach(r => { if (r.value) files[r.value.file] = r.value.data }) + locale.forEach(file => { + this.settings = this.extend({}, this.settings, files[file]) + }) + resolve() + }) + return + } + if (!locale) locale = 'en-us' + // if locale is an object, then merge it with w2utils.settings + if (locale instanceof Object) { + this.settings = this.extend({}, this.settings, w2locale, locale) + return + } + if (locale.length === 5) { + locale = 'locale/'+ locale.toLowerCase() +'.json' + } + // load from the file + fetch(locale, { method: 'GET' }) + .then(res => res.json()) + .then(data => { + if (noMerge !== true) { + if (keepPhrases) { + // keep phrases, useful for recursive calls + this.settings = this.extend({}, this.settings, data) + } else { + // clear phrases from language before merging + this.settings = this.extend({}, this.settings, w2locale, { phrases: {} }, data) + } + } + resolve({ file: locale, data }) + }) + .catch((err) => { + console.log('ERROR: Cannot load locale '+ locale) + reject(err) + }) + }) + } + scrollBarSize() { + if (this.tmp.scrollBarSize) return this.tmp.scrollBarSize + let html = ` +
    +
    1
    +
    + ` + query('body').append(html) + this.tmp.scrollBarSize = 100 - query('#_scrollbar_width > div')[0].clientWidth + query('#_scrollbar_width').remove() + return this.tmp.scrollBarSize + } + checkName(name) { + if (name == null) { + console.log('ERROR: Property "name" is required but not supplied.') + return false + } + if (w2ui[name] != null) { + console.log(`ERROR: Object named "${name}" is already registered as w2ui.${name}.`) + return false + } + if (!this.isAlphaNumeric(name)) { + console.log('ERROR: Property "name" has to be alpha-numeric (a-z, 0-9, dash and underscore).') + return false + } + return true + } + checkUniqueId(id, items, desc, obj) { + if (!Array.isArray(items)) items = [items] + let isUnique = true + items.forEach(item => { + if (item.id === id) { + console.log(`ERROR: The item id="${id}" is not unique within the ${desc} "${obj}".`, items) + isUnique = false + } + }) + return isUnique + } + /** + * Takes an object and encodes it into params string to be passed as a url + * { a: 1, b: 'str'} => "a=1&b=str" + * { a: 1, b: { c: 2 }} => "a=1&b[c]=2" + * { a: 1, b: {c: { k: 'dfdf' } } } => "a=1&b[c][k]=dfdf" + */ + encodeParams(obj, prefix = '') { + let str = '' + Object.keys(obj).forEach(key => { + if (str != '') str += '&' + if (typeof obj[key] == 'object') { + str += this.encodeParams(obj[key], prefix + key + (prefix ? ']' : '') + '[') + } else { + str += `${prefix}${key}${prefix ? ']' : ''}=${obj[key]}` + } + }) + return str + } + parseRoute(route) { + let keys = [] + let path = route + .replace(/\/\(/g, '(?:/') + .replace(/\+/g, '__plus__') + .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, (_, slash, format, key, capture, optional) => { + keys.push({ name: key, optional: !! optional }) + slash = slash || '' + return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' + (optional || '') + }) + .replace(/([\/.])/g, '\\$1') + .replace(/__plus__/g, '(.+)') + .replace(/\*/g, '(.*)') + return { + path : new RegExp('^' + path + '$', 'i'), + keys : keys + } + } + getCursorPosition(input) { + if (input == null) return null + let caretOffset = 0 + let doc = input.ownerDocument || input.document + let win = doc.defaultView || doc.parentWindow + let sel + if (['INPUT', 'TEXTAREA'].includes(input.tagName)) { + caretOffset = input.selectionStart + } else { + if (win.getSelection) { + sel = win.getSelection() + if (sel.rangeCount > 0) { + let range = sel.getRangeAt(0) + let preCaretRange = range.cloneRange() + preCaretRange.selectNodeContents(input) + preCaretRange.setEnd(range.endContainer, range.endOffset) + caretOffset = preCaretRange.toString().length + } + } else if ( (sel = doc.selection) && sel.type !== 'Control') { + let textRange = sel.createRange() + let preCaretTextRange = doc.body.createTextRange() + preCaretTextRange.moveToElementText(input) + preCaretTextRange.setEndPoint('EndToEnd', textRange) + caretOffset = preCaretTextRange.text.length + } + } + return caretOffset + } + setCursorPosition(input, pos, posEnd) { + if (input == null) return + let range = document.createRange() + let el, sel = window.getSelection() + if (['INPUT', 'TEXTAREA'].includes(input.tagName)) { + input.setSelectionRange(pos, posEnd ?? pos) + } else { + for (let i = 0; i < input.childNodes.length; i++) { + let tmp = query(input.childNodes[i]).text() + if (input.childNodes[i].tagName) { + tmp = query(input.childNodes[i]).html() + tmp = tmp.replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/ /g, ' ') + } + if (pos <= tmp.length) { + el = input.childNodes[i] + if (el.childNodes && el.childNodes.length > 0) el = el.childNodes[0] + if (el.childNodes && el.childNodes.length > 0) el = el.childNodes[0] + break + } else { + pos -= tmp.length + } + } + if (el == null) return + if (pos > el.length) pos = el.length + range.setStart(el, pos) + if (posEnd) { + range.setEnd(el, posEnd) + } else { + range.collapse(true) + } + sel.removeAllRanges() + sel.addRange(range) + } + } + parseColor(str) { + if (typeof str !== 'string') return null; else str = str.trim().toUpperCase() + if (str[0] === '#') str = str.substr(1) + let color = {} + if (str.length === 3) { + color = { + r: parseInt(str[0] + str[0], 16), + g: parseInt(str[1] + str[1], 16), + b: parseInt(str[2] + str[2], 16), + a: 1 + } + } else if (str.length === 6) { + color = { + r: parseInt(str.substr(0, 2), 16), + g: parseInt(str.substr(2, 2), 16), + b: parseInt(str.substr(4, 2), 16), + a: 1 + } + } else if (str.length === 8) { + color = { + r: parseInt(str.substr(0, 2), 16), + g: parseInt(str.substr(2, 2), 16), + b: parseInt(str.substr(4, 2), 16), + a: Math.round(parseInt(str.substr(6, 2), 16) / 255 * 100) / 100 // alpha channel 0-1 + } + } else if (str.length > 4 && str.substr(0, 4) === 'RGB(') { + let tmp = str.replace('RGB', '').replace(/\(/g, '').replace(/\)/g, '').split(',') + color = { + r: parseInt(tmp[0], 10), + g: parseInt(tmp[1], 10), + b: parseInt(tmp[2], 10), + a: 1 + } + } else if (str.length > 5 && str.substr(0, 5) === 'RGBA(') { + let tmp = str.replace('RGBA', '').replace(/\(/g, '').replace(/\)/g, '').split(',') + color = { + r: parseInt(tmp[0], 10), + g: parseInt(tmp[1], 10), + b: parseInt(tmp[2], 10), + a: parseFloat(tmp[3]) + } + } else { + // word color + return null + } + return color + } + // h=0..360, s=0..100, v=0..100 + hsv2rgb(h, s, v, a) { + let r, g, b, i, f, p, q, t + if (arguments.length === 1) { + s = h.s; v = h.v; a = h.a; h = h.h + } + h = h / 360 + s = s / 100 + v = v / 100 + i = Math.floor(h * 6) + f = h * 6 - i + p = v * (1 - s) + q = v * (1 - f * s) + t = v * (1 - (1 - f) * s) + switch (i % 6) { + case 0: r = v, g = t, b = p; break + case 1: r = q, g = v, b = p; break + case 2: r = p, g = v, b = t; break + case 3: r = p, g = q, b = v; break + case 4: r = t, g = p, b = v; break + case 5: r = v, g = p, b = q; break + } + return { + r: Math.round(r * 255), + g: Math.round(g * 255), + b: Math.round(b * 255), + a: (a != null ? a : 1) + } + } + // r=0..255, g=0..255, b=0..255 + rgb2hsv(r, g, b, a) { + if (arguments.length === 1) { + g = r.g; b = r.b; a = r.a; r = r.r + } + let max = Math.max(r, g, b), min = Math.min(r, g, b), + d = max - min, + h, + s = (max === 0 ? 0 : d / max), + v = max / 255 + switch (max) { + case min: h = 0; break + case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break + case g: h = (b - r) + d * 2; h /= 6 * d; break + case b: h = (r - g) + d * 4; h /= 6 * d; break + } + return { + h: Math.round(h * 360), + s: Math.round(s * 100), + v: Math.round(v * 100), + a: (a != null ? a : 1) + } + } + tooltip(html, options) { + let actions + let showOn = 'mouseenter' + let hideOn = 'mouseleave' + if (typeof html == 'object') { + options = html + } + options = options || {} + if (typeof html == 'string') { + options.html = html + } + if (options.showOn) { + showOn = options.showOn + delete options.showOn + } + if (options.hideOn) { + hideOn = options.hideOn + delete options.hideOn + } + if (!options.name) options.name = 'no-name' + // base64 is needed to avoid '"<> and other special chars conflicts + actions = ` on${showOn}="w2tooltip.show(this, ` + + `JSON.parse(w2utils.base64decode('${this.base64encode(JSON.stringify(options))}')))" ` + + `on${hideOn}="w2tooltip.hide('${options.name}')"` + return actions + } + // determins if it is plain Object, not DOM element, nor a function, event, etc. + isPlainObject(value) { + if (value == null) { // null or undefined + return false + } + if (Object.prototype.toString.call(value) !== '[object Object]') { + return false + } + if (value.constructor === undefined) { + return true + } + let proto = Object.getPrototypeOf(value) + return proto === null || proto === Object.prototype + } + /** + * Deep copy of an object or an array. Function, events and HTML elements will not be cloned, + * you can choose to include them or not, by default they are included. + * You can also exclude certain elements from final object if used with options: { exclude } + */ + clone(obj, options) { + let ret + options = Object.assign({ functions: true, elements: true, events: true, exclude: [] }, options ?? {}) + if (Array.isArray(obj)) { + ret = Array.from(obj) + ret.forEach((value, ind) => { + ret[ind] = this.clone(value, options) + }) + } else if (this.isPlainObject(obj)) { + ret = {} + Object.assign(ret, obj) + if (options.exclude) { + options.exclude.forEach(key => { delete ret[key] }) // delete excluded keys + } + Object.keys(ret).forEach(key => { + ret[key] = this.clone(ret[key], options) + if (ret[key] === undefined) delete ret[key] // do not include undefined elements + }) + } else { + if ((obj instanceof Function && !options.functions) + || (obj instanceof Node && !options.elements) + || (obj instanceof Event && !options.events) + ) { + // do not include these objects, otherwise include them uncloned + } else { + // primitive variable or function, event, dom element, etc, - all these are not cloned + ret = obj + } + } + return ret + } + /** + * Deep extend an object, if an array, it overwrrites it, cloning objects in the process + * target, source1, source2, ... + */ + extend(target, source) { + if (Array.isArray(target)) { + if (Array.isArray(source)) { + target.splice(0, target.length) // empty array but keep the reference + source.forEach(s => { target.push(this.clone(s)) }) + } else { + throw new Error('Arrays can be extended with arrays only') + } + } else if (target instanceof Node || target instanceof Event) { + throw new Error('HTML elmenents and events cannot be extended') + } else if (target && typeof target == 'object' && source != null) { + if (typeof source != 'object') { + throw new Error('Object can be extended with other objects only.') + } + Object.keys(source).forEach(key => { + if (target[key] != null && typeof target[key] == 'object' + && source[key] != null && typeof source[key] == 'object') { + let src = this.clone(source[key]) + // do not extend HTML elements and events, but overwrite them + if (target[key] instanceof Node || target[key] instanceof Event) { + target[key] = src + } else { + // if an array needs to be extended with an object, then convert it to empty object + if (Array.isArray(target[key]) && this.isPlainObject(src)) { + target[key] = {} + } + this.extend(target[key], src) + } + } else { + target[key] = this.clone(source[key]) + } + }) + } else if (source != null) { + throw new Error('Object is not extendable, only {} or [] can be extended.') + } + // other arguments + if (arguments.length > 2) { + for (let i = 2; i < arguments.length; i++) { + this.extend(target, arguments[i]) + } + } + return target + } + /* + * @author Lauri Rooden (https://github.com/litejs/natural-compare-lite) + * @license MIT License + */ + naturalCompare(a, b) { + let i, codeA + , codeB = 1 + , posA = 0 + , posB = 0 + , alphabet = String.alphabet + function getCode(str, pos, code) { + if (code) { + for (i = pos; code = getCode(str, i), code < 76 && code > 65;) ++i + return +str.slice(pos - 1, i) + } + code = alphabet && alphabet.indexOf(str.charAt(pos)) + return code > -1 ? code + 76 : ((code = str.charCodeAt(pos) || 0), code < 45 || code > 127) ? code + : code < 46 ? 65 // - + : code < 48 ? code - 1 + : code < 58 ? code + 18 // 0-9 + : code < 65 ? code - 11 + : code < 91 ? code + 11 // A-Z + : code < 97 ? code - 37 + : code < 123 ? code + 5 // a-z + : code - 63 + } + + if ((a+='') != (b+='')) for (;codeB;) { + codeA = getCode(a, posA++) + codeB = getCode(b, posB++) + if (codeA < 76 && codeB < 76 && codeA > 66 && codeB > 66) { + codeA = getCode(a, posA, posA) + codeB = getCode(b, posB, posA = i) + posB = i + } + if (codeA != codeB) return (codeA < codeB) ? -1 : 1 + } + return 0 + } + normMenu(menu, el) { + if (Array.isArray(menu)) { + menu.forEach((it, m) => { + if (typeof it === 'string' || typeof it === 'number') { + menu[m] = { id: it, text: String(it) } + } else if (it != null) { + if (it.caption != null && it.text == null) it.text = it.caption + if (it.text != null && it.id == null) it.id = it.text + if (it.text == null && it.id != null) it.text = it.id + } else { + menu[m] = { id: null, text: 'null' } + } + }) + return menu + } else if (typeof menu === 'function') { + let newMenu = menu.call(this, menu, el) + return w2utils.normMenu.call(this, newMenu) + } else if (typeof menu === 'object') { + return Object.keys(menu).map(key => { return { id: key, text: menu[key] } }) + } + } + bindEvents(selector, subject) { + // format is + //
    ='["","param1","param2",...]'> -- should be valid JSON (no undefined) + //
    ="|param1|param2"> + // -- can have "event", "this", "stop", "stopPrevent", "alert" - as predefined objects + if (selector.length == 0) return + // for backward compatibility + if (selector?.[0] instanceof Node) { + selector = Array.isArray(selector) ? selector : selector.get() + } + query(selector).each((el) => { + let actions = query(el).data() + Object.keys(actions).forEach(name => { + let events = ['click', 'dblclick', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout', 'mousedown', 'mousemove', 'mouseup', + 'contextmenu', 'focus', 'focusin', 'focusout', 'blur', 'input', 'change', 'keydown', 'keyup', 'keypress'] + if (events.indexOf(String(name).toLowerCase()) == -1) { + return + } + let params = actions[name] + if (typeof params == 'string') { + params = params.split('|').map(key => { + if (key === 'true') key = true + if (key === 'false') key = false + if (key === 'undefined') key = undefined + if (key === 'null') key = null + if (parseFloat(key) == key) key = parseFloat(key) + let quotes = ['\'', '"', '`'] + if (typeof key == 'string' && quotes.includes(key[0]) && quotes.includes(key[key.length-1])) { + key = key.substring(1, key.length-1) + } + return key + }) + } + let method = params[0] + params = params.slice(1) // should be new array + query(el) + .off(name + '.w2utils-bind') + .on(name + '.w2utils-bind', function(event) { + switch (method) { + case 'alert': + alert(params[0]) // for testing purposes + break + case 'stop': + event.stopPropagation() + break + case 'prevent': + event.preventDefault() + break + case 'stopPrevent': + event.stopPropagation() + event.preventDefault() + return false + break + default: + if (subject[method] == null) { + throw new Error(`Cannot dispatch event as the method "${method}" does not exist.`) + } + subject[method].apply(subject, params.map((key, ind) => { + switch (String(key).toLowerCase()) { + case 'event': + return event + case 'this': + return this + default: + return key + } + })) + } + }) + }) + }) + } + debounce(func, wait = 250) { + let timeout + return (...args) => { + clearTimeout(timeout) + timeout = setTimeout(() => { func(...args) }, wait) + } + } +} +var w2utils = new Utils() // eslint-disable-line -- needs to be functional/module scope variable +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base + * + * == 2.0 changes + * - CSP - fixed inline events + * - removed jQuery dependency + * - popup.open - returns promise like object + * - popup.confirm - refactored + * - popup.message - refactored + * - removed popup.options.mutliple + * - refactores w2alert, w2confirm, w2prompt + * - add w2popup.open().on('') + * - removed w2popup.restoreTemplate + * - deprecated onMsgOpen and onMsgClose + * - deprecated options.bgColor + * - rename focus -> setFocus + * - added center() // will auto center on window resize + * - close(immediate), also refactored if popup is closed when opening + */ + +class Dialog extends w2base { + constructor() { + super() + this.defaults = { + title: '', + text: '', // just a text (will be centered) + body: '', + buttons: '', + width: 450, + height: 250, + focus: null, // brings focus to the element, can be a number or selector + actions: null, // actions object + style: '', // style of the message div + speed: 0.3, + modal: false, + maximized: false, // this is a flag to show the state - to open the popup maximized use openMaximized instead + keyboard: true, // will close popup on esc if not modal + showClose: true, + showMax: false, + transition: null, + openMaximized: false, + moved: false + } + this.name = 'popup' + this.status = 'closed' // string that describes current status + this.onOpen = null + this.onClose = null + this.onMax = null + this.onMin = null + this.onToggle = null + this.onKeydown = null + this.onAction = null + this.onMove = null + this.tmp = {} + // event handler for resize + this.handleResize = (event) => { + // if it was moved by the user, do not auto resize + if (!this.options.moved) { + this.center(undefined, undefined, true) + } + } + } + /** + * Sample calls + * - w2popup.open('ddd').ok(() => { w2popup.close() }) + * - w2popup.open('ddd', { height: 120 }).ok(() => { w2popup.close() }) + * - w2popup.open({ body: 'text', title: 'caption', actions: ["Close"] }).close(() => { w2popup.close() }) + * - w2popup.open({ body: 'text', title: 'caption', actions: { Close() { w2popup.close() }} }) + */ + open(options) { + let self = this + if (this.status == 'closing' || query('#w2ui-popup').hasClass('animating')) { + // if called when previous is closing + this.close(true) + } + // get old options and merge them + let old_options = this.options + if (['string', 'number'].includes(typeof options)) { + options = w2utils.extend({ + title: 'Notification', + body: `
    ${options}
    `, + actions: { Ok() { self.close() }}, + cancelAction: 'ok' + }, arguments[1] ?? {}) + } + if (options.text != null) options.body = `
    ${options.text}
    ` + options = Object.assign({}, this.defaults, old_options, { title: '', body : '' }, options, { maximized: false }) + this.options = options + // if new - reset event handlers + if (query('#w2ui-popup').length === 0) { + this.off('*') + Object.keys(this).forEach(key => { + if (key.startsWith('on') && key != 'on') this[key] = null + }) + } + // reassign events + Object.keys(options).forEach(key => { + if (key.startsWith('on') && key != 'on' && options[key]) { + this[key] = options[key] + } + }) + options.width = parseInt(options.width) + options.height = parseInt(options.height) + let edata, msg, tmp + let { top, left } = this.center() + let prom = { + self: this, + action(callBack) { + self.on('action.prom', callBack) + return prom + }, + close(callBack) { + self.on('close.prom', callBack) + return prom + }, + then(callBack) { + self.on('open:after.prom', callBack) + return prom + } + } + // convert action arrays into buttons + if (options.actions != null && !options.buttons) { + options.buttons = '' + Object.keys(options.actions).forEach((action) => { + let handler = options.actions[action] + let btnAction = action + if (typeof handler == 'function') { + options.buttons += `` + } + if (typeof handler == 'object') { + options.buttons += `` + btnAction = Array.isArray(options.actions) ? handler.text : action + } + if (typeof handler == 'string') { + options.buttons += `` + btnAction = handler + } + if (typeof btnAction == 'string') { + btnAction = btnAction[0].toLowerCase() + btnAction.substr(1).replace(/\s+/g, '') + } + prom[btnAction] = function (callBack) { + self.on('action.buttons', (event) => { + let target = event.detail.action[0].toLowerCase() + event.detail.action.substr(1).replace(/\s+/g, '') + if (target == btnAction) callBack(event) + }) + return prom + } + }) + } + // check if message is already displayed + if (query('#w2ui-popup').length === 0) { + // trigger event + edata = this.trigger('open', { target: 'popup', present: false }) + if (edata.isCancelled === true) return + this.status = 'opening' + // output message + w2utils.lock(document.body, { + opacity: 0.3, + onClick: options.modal ? null : () => { this.close() } + }) + let btn = '' + if (options.showClose) { + btn += `
    + +
    ` + } + if (options.showMax) { + btn += `
    + +
    ` + } + // first insert just body + let styles = ` + left: ${left}px; + top: ${top}px; + width: ${parseInt(options.width)}px; + height: ${parseInt(options.height)}px; + transition: ${options.speed}s + ` + msg = `
    ` + query('body').append(msg) + query('#w2ui-popup')[0]._w2popup = { + self: this, + created: new Promise((resolve) => { this._promCreated = resolve }), + opened: new Promise((resolve) => { this._promOpened = resolve }), + closing: new Promise((resolve) => { this._promClosing = resolve }), + closed: new Promise((resolve) => { this._promClosed = resolve }), + } + // then content + styles = `${!options.title ? 'top: 0px !important;' : ''} ${!options.buttons ? 'bottom: 0px !important;' : ''}` + msg = ` + +
    ${btn}
    +
    +
    +
    +
    +
    +
    + + ` + query('#w2ui-popup').html(msg) + if (options.title) query('#w2ui-popup .w2ui-popup-title').append(w2utils.lang(options.title)) + if (options.buttons) query('#w2ui-popup .w2ui-popup-buttons').append(options.buttons) + if (options.body) query('#w2ui-popup .w2ui-popup-body').append(options.body) + // allow element to render + setTimeout(() => { + query('#w2ui-popup') + .css('transition', options.speed + 's') + .removeClass('w2ui-anim-open') + w2utils.bindEvents('#w2ui-popup .w2ui-eaction', this) + query('#w2ui-popup').find('.w2ui-popup-body').show() + this._promCreated() + }, 1) + // clean transform + clearTimeout(this._timer) + this._timer = setTimeout(() => { + this.status = 'open' + self.setFocus(options.focus) + // event after + edata.finish() + this._promOpened() + query('#w2ui-popup').removeClass('animating') + }, options.speed * 1000) + } else { + // trigger event + edata = this.trigger('open', { target: 'popup', present: true }) + if (edata.isCancelled === true) return + // check if size changed + this.status = 'opening' + if (old_options != null) { + if (!old_options.maximized && (old_options.width != options.width || old_options.height != options.height)) { + this.resize(options.width, options.height) + } + options.prevSize = options.width + 'px:' + options.height + 'px' + options.maximized = old_options.maximized + } + // show new items + let cloned = query('#w2ui-popup .w2ui-box').get(0).cloneNode(true) + query(cloned).removeClass('w2ui-box').addClass('w2ui-box-temp').find('.w2ui-popup-body').empty().append(options.body) + query('#w2ui-popup .w2ui-box').after(cloned) + if (options.buttons) { + query('#w2ui-popup .w2ui-popup-buttons').show().html('').append(options.buttons) + query('#w2ui-popup .w2ui-popup-body').removeClass('w2ui-popup-no-buttons') + query('#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp').css('bottom', '') + } else { + query('#w2ui-popup .w2ui-popup-buttons').hide().html('') + query('#w2ui-popup .w2ui-popup-body').addClass('w2ui-popup-no-buttons') + query('#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp').css('bottom', '0px') + } + if (options.title) { + query('#w2ui-popup .w2ui-popup-title') + .show() + .html((options.showClose + ? `
    + +
    ` + : '') + + (options.showMax + ? `
    + +
    ` + : '')) + .append(options.title) + query('#w2ui-popup .w2ui-popup-body').removeClass('w2ui-popup-no-title') + query('#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp').css('top', '') + } else { + query('#w2ui-popup .w2ui-popup-title').hide().html('') + query('#w2ui-popup .w2ui-popup-body').addClass('w2ui-popup-no-title') + query('#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp').css('top', '0px') + } + // transition + let div_old = query('#w2ui-popup .w2ui-box')[0] + let div_new = query('#w2ui-popup .w2ui-box-temp')[0] + query('#w2ui-popup').addClass('animating') + w2utils.transition(div_old, div_new, options.transition, () => { + // clean up + query(div_old).remove() + query(div_new).removeClass('w2ui-box-temp').addClass('w2ui-box') + let $body = query(div_new).find('.w2ui-popup-body') + if ($body.length == 1) { + $body[0].style.cssText = options.style + $body.show() + } + // focus on first button + self.setFocus(options.focus) + query('#w2ui-popup').removeClass('animating') + }) + // call event onOpen + this.status = 'open' + edata.finish() + w2utils.bindEvents('#w2ui-popup .w2ui-eaction', this) + query('#w2ui-popup').find('.w2ui-popup-body').show() + } + if (options.openMaximized) { + this.max() + } + // save new options + options._last_focus = document.activeElement + // keyboard events + if (options.keyboard) { + query(document.body).on('keydown', (event) => { + this.keydown(event) + }) + } + query(window).on('resize', this.handleResize) + // initialize move + tmp = { + resizing : false, + mvMove : mvMove, + mvStop : mvStop + } + query('#w2ui-popup .w2ui-popup-title').on('mousedown', function(event) { + if (!self.options.maximized) mvStart(event) + }) + return prom + // handlers + function mvStart(evt) { + if (!evt) evt = window.event + self.status = 'moving' + let rect = query('#w2ui-popup').get(0).getBoundingClientRect() + Object.assign(tmp, { + resizing: true, + isLocked: query('#w2ui-popup > .w2ui-lock').length == 1 ? true : false, + x : evt.screenX, + y : evt.screenY, + pos_x : rect.x, + pos_y : rect.y, + }) + if (!tmp.isLocked) self.lock({ opacity: 0 }) + query(document.body) + .on('mousemove.w2ui-popup', tmp.mvMove) + .on('mouseup.w2ui-popup', tmp.mvStop) + if (evt.stopPropagation) evt.stopPropagation(); else evt.cancelBubble = true + if (evt.preventDefault) evt.preventDefault(); else return false + } + function mvMove(evt) { + if (tmp.resizing != true) return + if (!evt) evt = window.event + tmp.div_x = evt.screenX - tmp.x + tmp.div_y = evt.screenY - tmp.y + // trigger event + let edata = self.trigger('move', { target: 'popup', div_x: tmp.div_x, div_y: tmp.div_y, originalEvent: evt }) + if (edata.isCancelled === true) return + // default behavior + query('#w2ui-popup').css({ + 'transition': 'none', + 'transform' : 'translate3d('+ tmp.div_x +'px, '+ tmp.div_y +'px, 0px)' + }) + self.options.moved = true + // event after + edata.finish() + } + function mvStop(evt) { + if (tmp.resizing != true) return + if (!evt) evt = window.event + self.status = 'open' + tmp.div_x = (evt.screenX - tmp.x) + tmp.div_y = (evt.screenY - tmp.y) + query('#w2ui-popup') + .css({ + 'left': (tmp.pos_x + tmp.div_x) + 'px', + 'top' : (tmp.pos_y + tmp.div_y) + 'px' + }) + .css({ + 'transition': 'none', + 'transform' : 'translate3d(0px, 0px, 0px)' + }) + tmp.resizing = false + query(document.body).off('.w2ui-popup') + if (!tmp.isLocked) self.unlock() + } + } + load(options) { + return new Promise((resolve, reject) => { + if (typeof options == 'string') { + options = { url: options } + } + if (options.url == null) { + console.log('ERROR: The url is not defined.') + reject('The url is not defined') + return + } + this.status = 'loading' + let [url, selector] = String(options.url).split('#') + if (url) { + fetch(url).then(res => res.text()).then(html => { + resolve(this.template(html, selector, options)) + }) + } + }) + } + template(data, id, options = {}) { + let html + try { + html = query(data) + } catch (e) { + html = query.html(data) + } + if (id) html = html.filter('#' + id) + Object.assign(options, { + width: parseInt(query(html).css('width')), + height: parseInt(query(html).css('height')), + title: query(html).find('[rel=title]').html(), + body: query(html).find('[rel=body]').html(), + buttons: query(html).find('[rel=buttons]').html(), + style: query(html).find('[rel=body]').get(0).style.cssText, + }) + return this.open(options) + } + action(action, event) { + let click = this.options.actions[action] + if (click instanceof Object && click.onClick) click = click.onClick + // event before + let edata = this.trigger('action', { action, target: 'popup', self: this, + originalEvent: event, value: this.input ? this.input.value : null }) + if (edata.isCancelled === true) return + // default actions + if (typeof click === 'function') click.call(this, event) + // event after + edata.finish() + } + keydown(event) { + if (this.options && !this.options.keyboard) return + // trigger event + let edata = this.trigger('keydown', { target: 'popup', originalEvent: event }) + if (edata.isCancelled === true) return + // default behavior + switch (event.keyCode) { + case 27: + event.preventDefault() + if (query('#w2ui-popup .w2ui-message').length == 0) { + if (this.options.cancelAction) { + this.action(this.options.cancelAction) + } else { + this.close() + } + } + break + } + // event after + edata.finish() + } + close(immediate) { + // trigger event + let edata = this.trigger('close', { target: 'popup' }) + if (edata.isCancelled === true) return + let cleanUp = () => { + // return template + query('#w2ui-popup').remove() + // restore active + if (this.options._last_focus && this.options._last_focus.length > 0) this.options._last_focus.focus() + this.status = 'closed' + this.options = {} + // event after + edata.finish() + this._promClosed() + } + if (query('#w2ui-popup').length === 0 || this.status == 'closed') { // already closed + return + } + if (this.status == 'opening') { // if it is opening + immediate = true + } + if (this.status == 'closing' && immediate === true) { + cleanUp() + clearTimeout(this.tmp.closingTimer) + w2utils.unlock(document.body, 0) + return + } + // default behavior + this.status = 'closing' + query('#w2ui-popup') + .css('transition', this.options.speed + 's') + .addClass('w2ui-anim-close animating') + w2utils.unlock(document.body, 300) + this._promClosing() + if (immediate) { + cleanUp() + } else { + this.tmp.closingTimer = setTimeout(cleanUp, this.options.speed * 1000) + } + // remove keyboard events + if (this.options.keyboard) { + query(document.body).off('keydown', this.keydown) + } + query(window).off('resize', this.handleResize) + } + toggle() { + let edata = this.trigger('toggle', { target: 'popup' }) + if (edata.isCancelled === true) return + // default action + if (this.options.maximized === true) this.min(); else this.max() + // event after + setTimeout(() => { + edata.finish() + }, (this.options.speed * 1000) + 50) + } + max() { + if (this.options.maximized === true) return + // trigger event + let edata = this.trigger('max', { target: 'popup' }) + if (edata.isCancelled === true) return + // default behavior + this.status = 'resizing' + let rect = query('#w2ui-popup').get(0).getBoundingClientRect() + this.options.prevSize = rect.width + ':' + rect.height + // do resize + this.resize(10000, 10000, () => { + this.status = 'open' + this.options.maximized = true + edata.finish() + }) + } + min() { + if (this.options.maximized !== true) return + let size = this.options.prevSize.split(':') + // trigger event + let edata = this.trigger('min', { target: 'popup' }) + if (edata.isCancelled === true) return + // default behavior + this.status = 'resizing' + // do resize + this.options.maximized = false + this.resize(parseInt(size[0]), parseInt(size[1]), () => { + this.status = 'open' + this.options.prevSize = null + edata.finish() + }) + } + clear() { + query('#w2ui-popup .w2ui-popup-title').html('') + query('#w2ui-popup .w2ui-popup-body').html('') + query('#w2ui-popup .w2ui-popup-buttons').html('') + } + reset() { + this.open(this.defaults) + } + message(options) { + return w2utils.message({ + owner: this, + box : query('#w2ui-popup').get(0), + after: '.w2ui-popup-title' + }, options) + } + confirm(options) { + return w2utils.confirm({ + owner: this, + box : query('#w2ui-popup'), + after: '.w2ui-popup-title' + }, options) + } + setFocus(focus) { + let box = query('#w2ui-popup') + let sel = 'input, button, select, textarea, [contentEditable], .w2ui-input' + if (focus != null) { + let el = isNaN(focus) + ? box.find(sel).filter(focus).get(0) + : box.find(sel).get(focus) + el?.focus() + } else { + let el = box.find('[name=hidden-first]').get(0) + if (el) el.focus() + } + // keep focus/blur inside popup + query(box).find(sel + ',[name=hidden-first],[name=hidden-last]') + .off('.keep-focus') + .on('blur.keep-focus', function (event) { + setTimeout(() => { + let focus = document.activeElement + let inside = query(box).find(sel).filter(focus).length > 0 + let name = query(focus).attr('name') + if (!inside && focus && focus !== document.body) { + query(box).find(sel).get(0)?.focus() + } + if (name == 'hidden-last') { + query(box).find(sel).get(0)?.focus() + } + if (name == 'hidden-first') { + query(box).find(sel).get(-1)?.focus() + } + }, 1) + }) + } + lock(msg, showSpinner) { + let args = Array.from(arguments) + args.unshift(query('#w2ui-popup')) + w2utils.lock(...args) + } + unlock(speed) { + w2utils.unlock(query('#w2ui-popup'), speed) + } + center(width, height, force) { + let maxW, maxH + if (window.innerHeight == undefined) { + maxW = parseInt(document.documentElement.offsetWidth) + maxH = parseInt(document.documentElement.offsetHeight) + } else { + maxW = parseInt(window.innerWidth) + maxH = parseInt(window.innerHeight) + } + width = parseInt(width ?? this.options.width) + height = parseInt(height ?? this.options.height) + if (this.options.maximized === true) { + width = maxW + height = maxH + } + if (maxW - 10 < width) width = maxW - 10 + if (maxH - 10 < height) height = maxH - 10 + let top = (maxH - height) / 2 + let left = (maxW - width) / 2 + if (force) { + query('#w2ui-popup').css({ + 'transition': 'none', + 'top' : top + 'px', + 'left' : left + 'px', + 'width' : width + 'px', + 'height': height + 'px' + }) + this.resizeMessages() // then messages resize nicely + } + return { top, left, width, height } + } + resize(newWidth, newHeight, callBack) { + let self = this + if (this.options.speed == null) this.options.speed = 0 + // calculate new position + let { top, left, width, height } = this.center(newWidth, newHeight) + let speed = this.options.speed + query('#w2ui-popup').css({ + 'transition': `${speed}s width, ${speed}s height, ${speed}s left, ${speed}s top`, + 'top' : top + 'px', + 'left' : left + 'px', + 'width' : width + 'px', + 'height': height + 'px' + }) + let tmp_int = setInterval(() => { self.resizeMessages() }, 10) // then messages resize nicely + setTimeout(() => { + clearInterval(tmp_int) + self.resizeMessages() + if (typeof callBack == 'function') callBack() + }, (this.options.speed * 1000) + 50) // give extra 50 ms + } + // internal function + resizeMessages() { + // see if there are messages and resize them + query('#w2ui-popup .w2ui-message').each(msg => { + let mopt = msg._msg_options + let popup = query('#w2ui-popup') + if (parseInt(mopt.width) < 10) mopt.width = 10 + if (parseInt(mopt.height) < 10) mopt.height = 10 + let rect = popup[0].getBoundingClientRect() + let titleHeight = parseInt(popup.find('.w2ui-popup-title')[0].clientHeight) + let pWidth = parseInt(rect.width) + let pHeight = parseInt(rect.height) + // re-calc width + mopt.width = mopt.originalWidth + if (mopt.width > pWidth - 10) { + mopt.width = pWidth - 10 + } + // re-calc height + mopt.height = mopt.originalHeight + if (mopt.height > pHeight - titleHeight - 5) { + mopt.height = pHeight - titleHeight - 5 + } + if (mopt.originalHeight < 0) mopt.height = pHeight + mopt.originalHeight - titleHeight + if (mopt.originalWidth < 0) mopt.width = pWidth + mopt.originalWidth * 2 // x 2 because there is left and right margin + query(msg).css({ + left : ((pWidth - mopt.width) / 2) + 'px', + width : mopt.width + 'px', + height : mopt.height + 'px' + }) + }) + } +} +function w2alert(msg, title, callBack) { + let prom + let options = { + title: w2utils.lang(title ?? 'Notification'), + body: `
    ${msg}
    `, + showClose: false, + actions: ['Ok'], + cancelAction: 'ok' + } + if (query('#w2ui-popup').length > 0 && w2popup.status != 'closing') { + prom = w2popup.message(options) + } else { + prom = w2popup.open(options) + } + prom.ok((event) => { + if (typeof event.detail.self?.close == 'function') { + event.detail.self.close() + } + if (typeof callBack == 'function') callBack() + }) + return prom +} +function w2confirm(msg, title, callBack) { + let prom + let options = msg + if (['string', 'number'].includes(typeof options)) { + options = { msg: options } + } + if (options.msg) { + options.body = `
    ${options.msg}
    `, + delete options.msg + } + w2utils.extend(options, { + title: w2utils.lang(title ?? 'Confirmation'), + showClose: false, + modal: true, + cancelAction: 'no' + }) + w2utils.normButtons(options, { yes: 'Yes', no: 'No' }) + if (query('#w2ui-popup').length > 0 && w2popup.status != 'closing') { + prom = w2popup.message(options) + } else { + prom = w2popup.open(options) + } + prom.self + .off('.confirm') + .on('action:after.confirm', (event) => { + if (typeof event.detail.self?.close == 'function') { + event.detail.self.close() + } + if (typeof callBack == 'function') callBack(event.detail.action) + }) + return prom +} +function w2prompt(label, title, callBack) { + let prom + let options = label + if (['string', 'number'].includes(typeof options)) { + options = { label: options } + } + if (options.label) { + options.focus = 0 + options.body = (options.textarea + ? `
    +
    ${options.label}
    + +
    ` + : `
    + + +
    ` + ) + } + w2utils.extend(options, { + title: w2utils.lang(title ?? 'Notification'), + showClose: false, + modal: true, + cancelAction: 'cancel' + }) + w2utils.normButtons(options, { ok: 'Ok', cancel: 'Cancel' }) + if (query('#w2ui-popup').length > 0 && w2popup.status != 'closing') { + prom = w2popup.message(options) + } else { + prom = w2popup.open(options) + } + if (prom.self.box) { + prom.self.input = query(prom.self.box).find('#w2prompt').get(0) + } else { + prom.self.input = query('#w2ui-popup .w2ui-popup-body #w2prompt').get(0) + } + if (options.value !== null) { + prom.self.input.select() + } + prom.change = function (callback) { + prom.self.on('change', callback) + return this + } + prom.self + .off('.prompt') + .on('open:after.prompt', (event) => { + let box = event.detail.box ? event.detail.box : query('#w2ui-popup .w2ui-popup-body').get(0) + w2utils.bindEvents(query(box).find('#w2prompt'), { + keydown(evt) { + if (evt.keyCode == 27) evt.stopPropagation() + }, + change(evt) { + let edata = prom.self.trigger('change', { target: 'prompt', originalEvent: evt }) + if (edata.isCancelled === true) return + if (evt.keyCode == 13 && evt.ctrlKey) { + prom.self.action('Ok', evt) + } + if (evt.keyCode == 27) { + prom.self.action('Cancel', evt) + } + edata.finish() + } + }) + query(box).find('.w2ui-eaction').trigger('keyup') + }) + .on('action:after.prompt', (event) => { + if (typeof event.detail.self?.close == 'function') { + event.detail.self.close() + } + if (typeof callBack == 'function') callBack(event.detail.action) + }) + return prom +} +let w2popup = new Dialog() +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base + * + * 2.0 Changes + * - multiple tooltips to the same anchor + * + * TODO + * - load menu items from URL + */ + +class Tooltip { + // no need to extend w2base, as each individual tooltip extends it + static active = {} // all defined tooltips + constructor() { + this.defaults = { + name : null, // name for the overlay, otherwise input id is used + html : '', // text or html + style : '', // additional style for the overlay + class : '', // add class for w2ui-tooltip-body + position : 'top|bottom', // can be left, right, top, bottom + align : '', // can be: both, both:XX left, right, both, top, bottom + anchor : null, // element it is attached to, if anchor is body, then it is context menu + anchorClass : '', // add class for anchor when tooltip is shown + anchorStyle : '', // add style for anchor when tooltip is shown + autoShow : false, // if autoShow true, then tooltip will show on mouseEnter and hide on mouseLeave + autoShowOn : null, // when options.autoShow = true, mouse event to show on + autoHideOn : null, // when options.autoShow = true, mouse event to hide on + arrowSize : 8, // size of the carret + margin : 0, // extra margin from the anchor + screenMargin : 2, // min margin from screen to tooltip + autoResize : true, // auto resize based on content size and available size + margin : 1, // distance from the anchor + offsetX : 0, // delta for left coordinate + offsetY : 0, // delta for top coordinate + maxWidth : null, // max width + maxHeight : null, // max height + watchScroll : null, // attach to onScroll event // TODO: + watchResize : null, // attach to onResize event // TODO: + hideOn : null, // events when to hide tooltip, ['click', 'change', 'key', 'focus', 'blur'], + onThen : null, // called when displayed + onShow : null, // callBack when shown + onHide : null, // callBack when hidden + onUpdate : null, // callback when tooltip gets updated + onMove : null // callback when tooltip is moved + } + } + static observeRemove = new MutationObserver((mutations) => { + let cnt = 0 + Object.keys(Tooltip.active).forEach(name => { + let overlay = Tooltip.active[name] + if (overlay.displayed) { + if (!overlay.anchor || !overlay.anchor.isConnected) { + overlay.hide() + } else { + cnt++ + } + } + }) + // remove observer, as there is no active tooltips + if (cnt === 0) { + Tooltip.observeRemove.disconnect() + } + }) + trigger(event, data) { + if (arguments.length == 2) { + let type = event + event = data + data.type = type + } + if (event.overlay) { + return event.overlay.trigger(event) + } else { + console.log('ERROR: cannot find overlay where to trigger events') + } + } + get(name) { + if (arguments.length == 0) { + return Object.keys(Tooltip.active) + } else if (name === true) { + return Tooltip.active + } else { + return Tooltip.active[name.replace(/[\s\.#]/g, '_')] + } + } + attach(anchor, text) { + let options, overlay + let self = this + if (arguments.length == 0) { + return + } else if (arguments.length == 1 && anchor.anchor) { + options = anchor + anchor = options.anchor + } else if (arguments.length === 2 && typeof text === 'string') { + options = { anchor, html: text } + text = options.html + } else if (arguments.length === 2 && text != null && typeof text === 'object') { + options = text + text = options.html + } + options = w2utils.extend({}, this.defaults, options || {}) + if (!text && options.text) text = options.text + if (!text && options.html) text = options.html + // anchor is func var + delete options.anchor + // define tooltip + let name = (options.name ? options.name : anchor.id) + if (anchor == document || anchor == document.body) { + anchor = document.body + name = 'context-menu' + } + if (!name) { + name = 'noname-' + Object.keys(Tooltip.active).length + console.log('NOTICE: name property is not defined for tooltip, could lead to too many instances') + } + // clean name as it is used as id and css selector + name = name.replace(/[\s\.#]/g, '_') + if (Tooltip.active[name]) { + overlay = Tooltip.active[name] + overlay.prevOptions = overlay.options + overlay.options = options // do not merge or extend, otherwiser menu items get merged too + // overlay.options = w2utils.extend({}, overlay.options, options) + overlay.anchor = anchor // as HTML elements are not copied + if (overlay.prevOptions.html != overlay.options.html || overlay.prevOptions.class != overlay.options.class + || overlay.prevOptions.style != overlay.options.style) { + overlay.needsUpdate = true + } + options = overlay.options // it was recreated + } else { + overlay = new w2base() + Object.assign(overlay, { + id: 'w2overlay-' + name, name, options, anchor, + displayed: false, + tmp: { + observeResize: new ResizeObserver(() => { + this.resize(overlay.name) + }) + }, + hide() { + self.hide(name) + } + }) + Tooltip.active[name] = overlay + } + // move events on to overlay layer + Object.keys(overlay.options).forEach(key => { + let val = overlay.options[key] + if (key.startsWith('on') && typeof val == 'function') { + overlay[key] = val + delete overlay.options[key] + } + }) + // add event for auto show/hide + if (options.autoShow === true) { + options.autoShowOn = options.autoShowOn ?? 'mouseenter' + options.autoHideOn = options.autoHideOn ?? 'mouseleave' + options.autoShow = false + } + if (options.autoShowOn) { + let scope = 'autoShow-' + overlay.name + query(anchor) + .off(`.${scope}`) + .on(`${options.autoShowOn}.${scope}`, event => { + self.show(overlay.name) + event.stopPropagation() + }) + delete options.autoShowOn + } + if (options.autoHideOn) { + let scope = 'autoHide-' + overlay.name + query(anchor) + .off(`.${scope}`) + .on(`${options.autoHideOn}.${scope}`, event => { + self.hide(overlay.name) + event.stopPropagation() + }) + delete options.autoHideOn + } + overlay.off('.attach') + let ret = { + overlay, + then: (callback) => { + overlay.on('show:after.attach', event => { callback(event) }) + return ret + }, + show: (callback) => { + overlay.on('show.attach', event => { callback(event) }) + return ret + }, + hide: (callback) => { + overlay.on('hide.attach', event => { callback(event) }) + return ret + }, + update: (callback) => { + overlay.on('update.attach', event => { callback(event) }) + return ret + }, + move: (callback) => { + overlay.on('move.attach', event => { callback(event) }) + return ret + } + } + return ret + } + update(name, html) { + let overlay = Tooltip.active[name] + if (overlay) { + overlay.needsUpdate = true + overlay.options.html = html + this.show(name) + } else { + console.log(`Tooltip "${name}" is not displayed. Cannot update it.`) + } + } + show(name) { + if (name instanceof HTMLElement || name instanceof Object) { + let options = name + if (name instanceof HTMLElement) { + options = arguments[1] || {} + options.anchor = name + } + let ret = this.attach(options) + query(ret.overlay.anchor) + .off('.autoShow-' + ret.overlay.name) + .off('.autoHide-' + ret.overlay.name) + // need a timer, so that events would be preperty set + setTimeout(() => { this.show(ret.overlay.name) }, 1) + return ret + } + let edata + let self = this + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + if (!overlay) return + let options = overlay.options + if (!overlay || (overlay.displayed && !overlay.needsUpdate)) { + this.resize(overlay?.name) + return + } + let position = options.position.split('|') + let isVertical = ['top', 'bottom'].includes(position[0]) + // enforce nowrap only when align=both and vertical + let overlayStyles = (options.align == 'both' && isVertical ? '' : 'white-space: nowrap;') + if (options.maxWidth && w2utils.getStrWidth(options.html, '') > options.maxWidth) { + overlayStyles = 'width: '+ options.maxWidth + 'px; white-space: inherit; overflow: auto;' + } + overlayStyles += ' max-height: '+ (options.maxHeight ? options.maxHeight : window.innerHeight - 40) + 'px;' + // if empty content - then hide it + if (options.html === '' || options.html == null) { + self.hide(name) + return + } else if (overlay.box) { + // if already present, update it + edata = this.trigger('update', { target: name, overlay }) + if (edata.isCancelled === true) { + // restore previous options + if (overlay.prevOptions) { + overlay.options = overlay.prevOptions + delete overlay.prevOptions + } + return + } + query(overlay.box) + .find('.w2ui-overlay-body') + .attr('style', (options.style || '') + '; ' + overlayStyles) + .removeClass() // removes all classes + .addClass('w2ui-overlay-body ' + options.class) + .html(options.html) + this.resize(overlay.name) + } else { + // event before + edata = this.trigger('show', { target: name, overlay }) + if (edata.isCancelled === true) return + // normal processing + query('body').append( + // pointer-events will be re-enabled leter + ``) + overlay.box = query('#'+w2utils.escapeId(overlay.id))[0] + overlay.displayed = true + let names = query(overlay.anchor).data('tooltipName') ?? [] + names.push(name) + query(overlay.anchor).data('tooltipName', names) // make available to element overlay attached to + w2utils.bindEvents(overlay.box, {}) + // remember anchor's original styles + overlay.tmp.originalCSS = '' + if (query(overlay.anchor).length > 0) { + overlay.tmp.originalCSS = query(overlay.anchor)[0].style.cssText + } + this.resize(overlay.name) + } + if (options.anchorStyle) { + overlay.anchor.style.cssText += ';' + options.anchorStyle + } + if (options.anchorClass) { + // do not add w2ui-focus to body + if (!(options.anchorClass == 'w2ui-focus' && overlay.anchor == document.body)) { + query(overlay.anchor).addClass(options.anchorClass) + } + } + // add on hide events + if (typeof options.hideOn == 'string') options.hideOn = [options.hideOn] + if (!Array.isArray(options.hideOn)) options.hideOn = [] + // initial scroll + Object.assign(overlay.tmp, { + scrollLeft: document.body.scrollLeft, + scrollTop: document.body.scrollTop + }) + addHideEvents() + addWatchEvents(document.body) + // first show empty tooltip, so it will popup up in the right position + query(overlay.box).show() + overlay.tmp.observeResize.observe(overlay.box) + // observer element removal from DOM + Tooltip.observeRemove.observe(document.body, { subtree: true, childList: true }) + // then insert html and it will adjust + query(overlay.box) + .css('opacity', 1) + .find('.w2ui-overlay-body') + .html(options.html) + /** + * pointer-events: none is needed to avoid cases when popup is shown right under the cursor + * or it will trigger onmouseout, onmouseleave and other events. + */ + setTimeout(() => { query(overlay.box).css({ 'pointer-events': 'auto' }).data('ready', 'yes') }, 100) + delete overlay.needsUpdate + // expose overlay to DOM element + overlay.box.overlay = overlay + // event after + if (edata) edata.finish() + return { overlay } + function addWatchEvents(el) { + let scope = 'tooltip-' + overlay.name + let queryEl = el + if (el.tagName == 'BODY') { + queryEl = el.ownerDocument + } + query(queryEl) + .off(`.${scope}`) + .on(`scroll.${scope}`, event => { + Object.assign(overlay.tmp, { + scrollLeft: el.scrollLeft, + scrollTop: el.scrollTop + }) + self.resize(overlay.name) + }) + } + function addHideEvents() { + let hide = (event) => { self.hide(overlay.name) } + let $anchor = query(overlay.anchor) + let scope = 'tooltip-' + overlay.name + // document click + query('body').off(`.${scope}`) + if (options.hideOn.includes('doc-click')) { + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + // otherwise hides on click to focus + $anchor + .off(`.${scope}-doc`) + .on(`click.${scope}-doc`, (event) => { event.stopPropagation() }) + } + query('body').on(`click.${scope}`, hide) + } + if (options.hideOn.includes('focus-change')) { + query('body') + .on(`focusin.${scope}`, (e) => { + if (document.activeElement != overlay.anchor) { + self.hide(overlay.name) + } + }) + } + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + $anchor.off(`.${scope}`) + options.hideOn.forEach(event => { + if (['doc-click', 'focus-change'].indexOf(event) == -1) { + $anchor.on(`${event}.${scope}`, { once: true }, hide) + } + }) + } + } + } + hide(name) { + let overlay + if (arguments.length == 0) { + // hide all tooltips + Object.keys(Tooltip.active).forEach(name => { this.hide(name) }) + return + } + if (name instanceof HTMLElement) { + let names = query(name).data('tooltipName') ?? [] + names.forEach(name => { this.hide(name) }) + return + } + if (typeof name == 'string') { + name = name.replace(/[\s\.#]/g, '_') + overlay = Tooltip.active[name] + } + if (!overlay || !overlay.box) return + delete Tooltip.active[name] + // event before + let edata = this.trigger('hide', { target: name, overlay }) + if (edata.isCancelled === true) return + let scope = 'tooltip-' + overlay.name + // normal processing + overlay.tmp.observeResize?.disconnect() + if (overlay.options.watchScroll) { + query(overlay.options.watchScroll) + .off('.w2scroll-' + overlay.name) + } + // if no active tooltip then disable observeRemove + let cnt = 0 + Object.keys(Tooltip.active).forEach(key => { + let overlay = Tooltip.active[key] + if (overlay.displayed) { + cnt++ + } + }) + if (cnt == 0) { + Tooltip.observeRemove.disconnect() + } + query('body').off(`.${scope}`) // hide to click event here + query(document).off(`.${scope}`) // scroll event here + // remove element + overlay.box.remove() + overlay.box = null + overlay.displayed = false + // remove name from anchor properties + let names = query(overlay.anchor).data('tooltipName') ?? [] + let ind = names.indexOf(overlay.name) + if (ind != -1) names.splice(names.indexOf(overlay.name), 1) + if (names.length == 0) { + query(overlay.anchor).removeData('tooltipName') + } else { + query(overlay.anchor).data('tooltipName', names) + } + // restore original CSS + overlay.anchor.style.cssText = overlay.tmp.originalCSS + query(overlay.anchor) + .off(`.${scope}`) + .removeClass(overlay.options.anchorClass) + // event after + edata.finish() + } + resize(name) { + if (arguments.length == 0) { + Object.keys(Tooltip.active).forEach(key => { + let overlay = Tooltip.active[key] + if (overlay.displayed) this.resize(overlay.name) + }) + return + } + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + let pos = this.getPosition(overlay.name) + let newPos = pos.left + 'x' + pos.top + let edata + if (overlay.tmp.lastPos != newPos) { + edata = this.trigger('move', { target: name, overlay, pos }) + } + query(overlay.box) + .css({ + left: pos.left + 'px', + top : pos.top + 'px' + }) + .then(query => { + if (pos.width != null) { + query.css('width', pos.width + 'px') + .find('.w2ui-overlay-body') + .css('width', '100%') + } + if (pos.height != null) { + query.css('height', pos.height + 'px') + .find('.w2ui-overlay-body') + .css('height', '100%') + } + }) + .find('.w2ui-overlay-body') + .removeClass('w2ui-arrow-right w2ui-arrow-left w2ui-arrow-top w2ui-arrow-bottom') + .addClass(pos.arrow.class) + .closest('.w2ui-overlay') + .find('style') + .text(pos.arrow.style) + if (overlay.tmp.lastPos != newPos && edata) { + overlay.tmp.lastPos = newPos + edata.finish() + } + } + getPosition(name) { + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + if (!overlay || !overlay.box) { + return + } + let options = overlay.options + if (overlay.tmp.resizedY || overlay.tmp.resizedX) { + query(overlay.box).css({ width: '', height: '', scroll: 'auto' }) + } + let scrollSize = w2utils.scrollBarSize() + let hasScrollBarX = !(document.body.scrollWidth == document.body.clientWidth) + let hasScrollBarY = !(document.body.scrollHeight == document.body.clientHeight) + let max = { + width: window.innerWidth - (hasScrollBarY ? scrollSize : 0), + height: window.innerHeight - (hasScrollBarX ? scrollSize : 0) + } + let position = options.position == 'auto' ? 'top|bottom|right|left'.split('|') : options.position.split('|') + let isVertical = ['top', 'bottom'].includes(position[0]) + let content = overlay.box.getBoundingClientRect() + let anchor = overlay.anchor.getBoundingClientRect() + if (overlay.anchor == document.body) { + // context menu + let { x, y, width, height } = options.originalEvent + anchor = { left: x - 2, top: y - 4, width, height, arrow: 'none' } + } + let arrowSize = options.arrowSize + if (anchor.arrow == 'none') arrowSize = 0 + // space available + let available = { // tipsize adjustment should be here, not in max.width/max.height + top: anchor.top, + bottom: max.height - (anchor.top + anchor.height) - + (hasScrollBarX ? scrollSize : 0), + left: anchor.left, + right: max.width - (anchor.left + anchor.width) + (hasScrollBarY ? scrollSize : 0), + } + // size of empty tooltip + if (content.width < 22) content.width = 22 + if (content.height < 14) content.height = 14 + let left, top, width, height // tooltip position + let found = '' + let arrow = { + offset: 0, + class: '', + style: `#${overlay.id} { --tip-size: ${arrowSize}px; }` + } + let adjust = { left: 0, top: 0 } + let bestFit = { posX: '', x: 0, posY: '', y: 0 } + // find best position + position.forEach(pos => { + if (['top', 'bottom'].includes(pos)) { + if (!found && (content.height + arrowSize/1.893) < available[pos]) { // 1.893 = 1 + sin(90) + found = pos + } + if (available[pos] > bestFit.y) { + Object.assign(bestFit, { posY: pos, y: available[pos] }) + } + } + if (['left', 'right'].includes(pos)) { + if (!found && (content.width + arrowSize/1.893) < available[pos]) { // 1.893 = 1 + sin(90) + found = pos + } + if (available[pos] > bestFit.x) { + Object.assign(bestFit, { posX: pos, x: available[pos] }) + } + } + }) + // if not found, use best (greatest available space) position + if (!found) { + if (isVertical) { + found = bestFit.posY + } else { + found = bestFit.posX + } + } + if (options.autoResize) { + if (['top', 'bottom'].includes(found)) { + if (content.height > available[found]) { + height = available[found] + overlay.tmp.resizedY = true + } else { + overlay.tmp.resizedY = false + } + } + if (['left', 'right'].includes(found)) { + if (content.width > available[found]) { + width = available[found] + overlay.tmp.resizedX = true + } else { + overlay.tmp.resizedX = false + } + } + } + usePosition(found) + if (isVertical) anchorAlignment() + screenAdjust() + let extraTop = (found == 'top' ? -options.margin : (found == 'bottom' ? options.margin : 0)) + let extraLeft = (found == 'left' ? -options.margin : (found == 'right' ? options.margin : 0)) + // adjust for scrollbar + top = Math.floor((top + parseFloat(options.offsetY) + parseFloat(extraTop)) * 100) / 100 + left = Math.floor((left + parseFloat(options.offsetX) + parseFloat(extraLeft)) * 100) / 100 + return { left, top, arrow, adjust, width, height, pos: found } + function usePosition(pos) { + arrow.class = anchor.arrow ? anchor.arrow : `w2ui-arrow-${pos}` + switch (pos) { + case 'top': { + left = anchor.left + (anchor.width - (width ?? content.width)) / 2 + top = anchor.top - (height ?? content.height) - arrowSize / 1.5 + 1 + break + } + case 'bottom': { + left = anchor.left + (anchor.width - (width ?? content.width)) / 2 + top = anchor.top + anchor.height + arrowSize / 1.25 + 1 + break + } + case 'left': { + left = anchor.left - (width ?? content.width) - arrowSize / 1.2 - 1 + top = anchor.top + (anchor.height - (height ?? content.height)) / 2 + break + } + case 'right': { + left = anchor.left + anchor.width + arrowSize / 1.2 + 1 + top = anchor.top + (anchor.height - (height ?? content.height)) / 2 + break + } + } + } + function anchorAlignment() { + // top/bottom alignments + if (options.align == 'left') { + adjust.left = anchor.left - left + left = anchor.left + } + if (options.align == 'right') { + adjust.left = (anchor.left + anchor.width - (width ?? content.width)) - left + left = anchor.left + anchor.width - (width ?? content.width) + } + if (['top', 'bottom'].includes(found) && options.align.startsWith('both')) { + let minWidth = options.align.split(':')[1] ?? 50 + if (anchor.width >= minWidth) { + left = anchor.left + width = anchor.width + } + } + // left/right alignments + if (options.align == 'top') { + adjust.top = anchor.top - top + top = anchor.top + } + if (options.align == 'bottom') { + adjust.top = (anchor.top + anchor.height - (height ?? content.height)) - top + top = anchor.top + anchor.height - (height ?? content.height) + } + if (['left', 'right'].includes(found) && options.align.startsWith('both')) { + let minHeight = options.align.split(':')[1] ?? 50 + if (anchor.height >= minHeight) { + top = anchor.top + height = anchor.height + } + } + } + function screenAdjust() { + let adjustArrow + // adjust tip if needed after alignment + if ((['left', 'right'].includes(options.align) && anchor.width < (width ?? content.width)) + || (['top', 'bottom'].includes(options.align) && anchor.height < (height ?? content.height)) + ) { + adjustArrow = true + } + // if off screen then adjust + let minLeft = (found == 'right' ? arrowSize : options.screenMargin) + let minTop = (found == 'bottom' ? arrowSize : options.screenMargin) + let maxLeft = max.width - (width ?? content.width) - (found == 'left' ? arrowSize : options.screenMargin) + let maxTop = max.height - (height ?? content.height) - (found == 'top' ? arrowSize : options.screenMargin) + 3 + // adjust X + if (['top', 'bottom'].includes(found) || options.autoResize) { + if (left < minLeft) { + adjustArrow = true + adjust.left -= left + left = minLeft + } + if (left > maxLeft) { + adjustArrow = true + adjust.left -= left - maxLeft + left += maxLeft - left + } + } + // adjust Y + if (['left', 'right'].includes(found) || options.autoResize) { + if (top < minTop) { + adjustArrow = true + adjust.top -= top + top = minTop + } + if (top > maxTop) { + adjustArrow = true + adjust.top -= top - maxTop + top += maxTop - top + } + } + // moves carret to adjust it with element width + if (adjustArrow) { + let aType = isVertical ? 'left' : 'top' + let sType = isVertical ? 'width' : 'height' + arrow.offset = -adjust[aType] + let maxOffset = content[sType] / 2 - arrowSize + if (Math.abs(arrow.offset) > maxOffset + arrowSize) { + arrow.class = '' // no arrow + } + if (Math.abs(arrow.offset) > maxOffset) { + arrow.offset = arrow.offset < 0 ? -maxOffset : maxOffset + } + arrow.style = w2utils.stripSpaces(`#${overlay.id} .w2ui-overlay-body:after, + #${overlay.id} .w2ui-overlay-body:before { + --tip-size: ${arrowSize}px; + margin-${aType}: ${arrow.offset}px; + }`) + } + } + } +} +class ColorTooltip extends Tooltip { + constructor() { + super() + this.palette = [ + ['000000', '333333', '555555', '777777', '888888', '999999', 'AAAAAA', 'CCCCCC', 'DDDDDD', 'EEEEEE', 'F7F7F7', 'FFFFFF'], + ['FF011B', 'FF9838', 'FFC300', 'FFFD59', '86FF14', '14FF7A', '2EFFFC', '2693FF', '006CE7', '9B24F4', 'FF21F5', 'FF0099'], + ['FFEAEA', 'FCEFE1', 'FCF4DC', 'FFFECF', 'EBFFD9', 'D9FFE9', 'E0FFFF', 'E8F4FF', 'ECF4FC', 'EAE6F4', 'FFF5FE', 'FCF0F7'], + ['F4CCCC', 'FCE5CD', 'FFF1C2', 'FFFDA1', 'D5FCB1', 'B5F7D0', 'BFFFFF', 'D6ECFF', 'CFE2F3', 'D9D1E9', 'FFE3FD', 'FFD9F0'], + ['EA9899', 'F9CB9C', 'FFE48C', 'F7F56F', 'B9F77E', '84F0B1', '83F7F7', 'B5DAFF', '9FC5E8', 'B4A7D6', 'FAB9F6', 'FFADDE'], + ['E06666', 'F6B26B', 'DEB737', 'E0DE51', '8FDB48', '52D189', '4EDEDB', '76ACE3', '6FA8DC', '8E7CC3', 'E07EDA', 'F26DBD'], + ['CC0814', 'E69138', 'AB8816', 'B5B20E', '6BAB30', '27A85F', '1BA8A6', '3C81C7', '3D85C6', '674EA7', 'A14F9D', 'BF4990'], + ['99050C', 'B45F17', '80650E', '737103', '395E14', '10783D', '13615E', '094785', '0A5394', '351C75', '780172', '782C5A'] + ] + this.defaults = w2utils.extend({}, this.defaults, { + advanced : false, + transparent : true, + position : 'top|bottom', + class : 'w2ui-white', + color : '', + liveUpdate : true, + arrowSize : 12, + autoResize : false, + anchorClass : 'w2ui-focus', + autoShowOn : 'focus', + hideOn : ['doc-click', 'focus-change'], + onSelect : null, + onLiveUpdate: null + }) + } + attach(anchor, text) { + let options + if (arguments.length == 1 && anchor.anchor) { + options = anchor + anchor = options.anchor + } else if (arguments.length === 2 && text != null && typeof text === 'object') { + options = text + options.anchor = anchor + } + let prevHideOn = options.hideOn + options = w2utils.extend({}, this.defaults, options || {}) + if (prevHideOn) { + options.hideOn = prevHideOn + } + options.style += '; padding: 0;' + // add remove transparent color + if (options.transparent && this.palette[0][1] == '333333') { + this.palette[0].splice(1, 1) + this.palette[0].push('') + } + if (!options.transparent && this.palette[0][1] != '333333') { + this.palette[0].splice(1, 0, '333333') + this.palette[0].pop() + } + if (options.color) options.color = String(options.color).toUpperCase() + if (typeof options.color === 'string' && options.color.substr(0,1) === '#') options.color = options.color.substr(1) + // needed for keyboard navigation + this.index = [-1, -1] + let ret = super.attach(options) + let overlay = ret.overlay + overlay.options.html = this.getColorHTML(overlay.name, options) + overlay.on('show.attach', event => { + let overlay = event.detail.overlay + let anchor = overlay.anchor + let options = overlay.options + if (['INPUT', 'TEXTAREA'].includes(anchor.tagName) && !options.color && anchor.value) { + overlay.tmp.initColor = anchor.value + } + delete overlay.newColor + }) + overlay.on('show:after.attach', event => { + if (ret.overlay?.box) { + let actions = query(ret.overlay.box).find('.w2ui-eaction') + w2utils.bindEvents(actions, this) + this.initControls(ret.overlay) + } + }) + overlay.on('update:after.attach', event => { + if (ret.overlay?.box) { + let actions = query(ret.overlay.box).find('.w2ui-eaction') + w2utils.bindEvents(actions, this) + this.initControls(ret.overlay) + } + }) + overlay.on('hide.attach', event => { + let overlay = event.detail.overlay + let anchor = overlay.anchor + let color = overlay.newColor ?? overlay.options.color ?? '' + if (['INPUT', 'TEXTAREA'].includes(anchor.tagName) && anchor.value != color) { + anchor.value = color + } + let edata = this.trigger('select', { color, target: overlay.name, overlay }) + if (edata.isCancelled === true) return + // event after + edata.finish() + }) + ret.liveUpdate = (callback) => { + overlay.on('liveUpdate.attach', (event) => { callback(event) }) + return ret + } + ret.select = (callback) => { + overlay.on('select.attach', (event) => { callback(event) }) + return ret + } + return ret + } + // regular panel handler, adds selection class + select(color, name) { + let target + this.index = [-1, -1] + if (typeof name != 'string') { + target = name.target + this.index = query(target).attr('index').split(':') + name = query(target).closest('.w2ui-overlay').attr('name') + } + let overlay = this.get(name) + // event before + let edata = this.trigger('liveUpdate', { color, target: name, overlay, param: arguments[1] }) + if (edata.isCancelled === true) return + // if anchor is input - live update + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName) && overlay.options.liveUpdate) { + query(overlay.anchor).val(color) + } + overlay.newColor = color + query(overlay.box).find('.w2ui-selected').removeClass('w2ui-selected') + if (target) { + query(target).addClass('w2ui-selected') + } + // event after + edata.finish() + } + // used for keyboard navigation, if any + nextColor(direction) { // TODO: check it + let pal = this.palette + switch (direction) { + case 'up': + this.index[0]-- + break + case 'down': + this.index[0]++ + break + case 'right': + this.index[1]++ + break + case 'left': + this.index[1]-- + break + } + if (this.index[0] < 0) this.index[0] = 0 + if (this.index[0] > pal.length - 2) this.index[0] = pal.length - 2 + if (this.index[1] < 0) this.index[1] = 0 + if (this.index[1] > pal[0].length - 1) this.index[1] = pal[0].length - 1 + return pal[this.index[0]][this.index[1]] + } + tabClick(index, name) { + if (typeof name != 'string') { + name = query(name.target).closest('.w2ui-overlay').attr('name') + } + let overlay = this.get(name) + let tab = query(overlay.box).find(`.w2ui-color-tab:nth-child(${index})`) + query(overlay.box).find('.w2ui-color-tab').removeClass('w2ui-selected') + query(tab).addClass('w2ui-selected') + query(overlay.box) + .find('.w2ui-tab-content') + .hide() + .closest('.w2ui-colors') + .find('.tab-'+ index) + .show() + } + // generate HTML with color pallent and controls + getColorHTML(name, options) { + let html = ` +
    +
    ` + for (let i = 0; i < this.palette.length; i++) { + html += '
    ' + for (let j = 0; j < this.palette[i].length; j++) { + let color = this.palette[i][j] + let border = '' + if (color === 'FFFFFF') border = '; border: 1px solid #efefef' + html += ` +
      +
    ` + } + html += '
    ' + if (i < 2) html += '
    ' + } + html += '
    ' + // advanced tab + html += ` + ` + // color tabs on the bottom + html += ` +
    +
    +
    +
    + ${(typeof options.html == 'string' ? options.html : '')} +
    +
    ` + return html + } + // bind advanced tab controls + initControls(overlay) { + let initial // used for mouse events + let self = this + let options = overlay.options + let rgb = w2utils.parseColor(options.color || overlay.tmp.initColor) + if (rgb == null) { + rgb = { r: 140, g: 150, b: 160, a: 1 } + } + let hsv = w2utils.rgb2hsv(rgb) + if (options.advanced === true) { + this.tabClick(2, overlay.name) + } + setColor(hsv, true, true) + // even for rgb, hsv inputs + query(overlay.box).find('input') + .off('.w2color') + .on('change.w2color', (event) => { + let el = query(event.target) + let val = parseFloat(el.val()) + let max = parseFloat(el.attr('max')) + if (isNaN(val)) { + val = 0 + el.val(0) + } + if (max > 1) val = parseInt(val) // trancate fractions + if (max > 0 && val > max) { + el.val(max) + val = max + } + if (val < 0) { + el.val(0) + val = 0 + } + let name = el.attr('name') + let color = {} + if (['r', 'g', 'b', 'a'].indexOf(name) !== -1) { + rgb[name] = val + hsv = w2utils.rgb2hsv(rgb) + } else if (['h', 's', 'v'].indexOf(name) !== -1) { + color[name] = val + } + setColor(color, true) + }) + // click on original color resets it + query(overlay.box).find('.color-original') + .off('.w2color') + .on('click.w2color', (event) => { + let tmp = w2utils.parseColor(query(event.target).css('background-color')) + if (tmp != null) { + rgb = tmp + hsv = w2utils.rgb2hsv(rgb) + setColor(hsv, true) + } + }) + // color sliders events + let mDown = `${!w2utils.isIOS ? 'mousedown' : 'touchstart'}.w2color` + let mUp = `${!w2utils.isIOS ? 'mouseup' : 'touchend'}.w2color` + let mMove = `${!w2utils.isIOS ? 'mousemove' : 'touchmove'}.w2color` + query(overlay.box).find('.palette, .rainbow, .alpha') + .off('.w2color') + .on(`${mDown}.w2color`, mouseDown) + return + function setColor(color, fullUpdate, initial) { + if (color.h != null) hsv.h = color.h + if (color.s != null) hsv.s = color.s + if (color.v != null) hsv.v = color.v + if (color.a != null) { rgb.a = color.a; hsv.a = color.a } + rgb = w2utils.hsv2rgb(hsv) + let newColor = 'rgba('+ rgb.r +','+ rgb.g +','+ rgb.b +','+ rgb.a +')' + let cl = [ + Number(rgb.r).toString(16).toUpperCase(), + Number(rgb.g).toString(16).toUpperCase(), + Number(rgb.b).toString(16).toUpperCase(), + (Math.round(Number(rgb.a)*255)).toString(16).toUpperCase() + ] + cl.forEach((item, ind) => { if (item.length === 1) cl[ind] = '0' + item }) + newColor = cl[0] + cl[1] + cl[2] + cl[3] + if (rgb.a === 1) { + newColor = cl[0] + cl[1] + cl[2] + } + query(overlay.box).find('.color-preview').css('background-color', '#' + newColor) + query(overlay.box).find('input').each(el => { + if (el.name) { + if (rgb[el.name] != null) el.value = rgb[el.name] + if (hsv[el.name] != null) el.value = hsv[el.name] + if (el.name === 'a') el.value = rgb.a + } + }) + // if it is in pallette + if (initial) { + let color = overlay.tmp?.initColor || newColor + query(overlay.box).find('.color-original') + .css('background-color', '#'+color) + query(overlay.box).find('.w2ui-colors .w2ui-selected') + .removeClass('w2ui-selected') + query(overlay.box).find(`.w2ui-colors [name="${color}"]`) + .addClass('w2ui-selected') + // if has transparent color, open advanced tab + if (newColor.length == 8) { + self.tabClick(2, overlay.name) + } + } else { + self.select(newColor, overlay.name) + } + if (fullUpdate) { + updateSliders() + refreshPalette() + } + } + function updateSliders() { + let el1 = query(overlay.box).find('.palette .value1') + let el2 = query(overlay.box).find('.rainbow .value2') + let el3 = query(overlay.box).find('.alpha .value2') + let offset1 = parseInt(el1[0].clientWidth) / 2 + let offset2 = parseInt(el2[0].clientWidth) / 2 + el1.css({ + 'left': (hsv.s * 150 / 100 - offset1) + 'px', + 'top': ((100 - hsv.v) * 125 / 100 - offset1) + 'px' + }) + el2.css('left', (hsv.h/(360/150) - offset2) + 'px') + el3.css('left', (rgb.a*150 - offset2) + 'px') + } + function refreshPalette() { + let cl = w2utils.hsv2rgb(hsv.h, 100, 100) + let rgb = `${cl.r},${cl.g},${cl.b}` + query(overlay.box).find('.palette') + .css('background-image', `linear-gradient(90deg, rgba(${rgb},0) 0%, rgba(${rgb},1) 100%)`) + } + function mouseDown(event) { + let el = query(this).find('.value1, .value2') + let offset = parseInt(el.prop('clientWidth')) / 2 + if (el.hasClass('move-x')) el.css({ left: (event.offsetX - offset) + 'px' }) + if (el.hasClass('move-y')) el.css({ top: (event.offsetY - offset) + 'px' }) + initial = { + el : el, + x : event.pageX, + y : event.pageY, + width : el.prop('parentNode').clientWidth, + height : el.prop('parentNode').clientHeight, + left : parseInt(el.css('left')), + top : parseInt(el.css('top')) + } + mouseMove(event) + query('body') + .off('.w2color') + .on(mMove, mouseMove) + .on(mUp, mouseUp) + } + function mouseUp(event) { + query('body').off('.w2color') + } + function mouseMove(event) { + let el = initial.el + let divX = event.pageX - initial.x + let divY = event.pageY - initial.y + let newX = initial.left + divX + let newY = initial.top + divY + let offset = parseInt(el.prop('clientWidth')) / 2 + if (newX < -offset) newX = -offset + if (newY < -offset) newY = -offset + if (newX > initial.width - offset) newX = initial.width - offset + if (newY > initial.height - offset) newY = initial.height - offset + if (el.hasClass('move-x')) el.css({ left : newX + 'px' }) + if (el.hasClass('move-y')) el.css({ top : newY + 'px' }) + // move + let name = query(el.get(0).parentNode).attr('name') + let x = parseInt(el.css('left')) + offset + let y = parseInt(el.css('top')) + offset + if (name === 'palette') { + setColor({ + s: Math.round(x / initial.width * 100), + v: Math.round(100 - (y / initial.height * 100)) + }) + } + if (name === 'rainbow') { + let h = Math.round(360 / 150 * x) + setColor({ h: h }) + refreshPalette() + } + if (name === 'alpha') { + setColor({ a: parseFloat(Number(x / 150).toFixed(2)) }) + } + } + } +} +class MenuTooltip extends Tooltip { + constructor() { + super() + // ITEM STRUCTURE + // item : { + // id : null, + // text : '', + // style : '', + // icon : '', + // count : '', + // tooltip : '', + // hotkey : '', + // remove : false, + // items : [] + // indent : 0, + // type : null, // check/radio + // group : false, // groupping for checks + // expanded : false, + // hidden : false, + // checked : null, + // disabled : false + // ... + // } + this.defaults = w2utils.extend({}, this.defaults, { + type : 'normal', // can be normal, radio, check + items : [], + index : null, // current selected + render : null, + spinner : false, + msgNoItems : w2utils.lang('No items found'), + topHTML : '', + menuStyle : '', + filter : false, + markSearch : false, + match : 'contains', // is, begins, ends, contains + search : false, // top search TODO: Check + altRows : false, + arrowSize : 10, + align : 'left', + position : 'bottom|top', + class : 'w2ui-white', + anchorClass : 'w2ui-focus', + autoShowOn : 'focus', + hideOn : ['doc-click', 'focus-change', 'select'], // also can 'item-remove' + onSelect : null, + onSubMenu : null, + onRemove : null + }) + } + attach(anchor, text) { + let options + if (arguments.length == 1 && anchor.anchor) { + options = anchor + anchor = options.anchor + } else if (arguments.length === 2 && text != null && typeof text === 'object') { + options = text + options.anchor = anchor + } + let prevHideOn = options.hideOn + options = w2utils.extend({}, this.defaults, options || {}) + if (prevHideOn) { + options.hideOn = prevHideOn + } + options.style += '; padding: 0;' + if (options.items == null) { + options.items = [] + } + options.html = this.getMenuHTML(options) + let ret = super.attach(options) + let overlay = ret.overlay + overlay.on('show:after.attach, update:after.attach', event => { + if (ret.overlay?.box) { + let search = '' + // reset selected and active chain + overlay.selected = null + overlay.options.items = w2utils.normMenu(overlay.options.items) + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + search = overlay.anchor.value + overlay.selected = overlay.anchor.dataset.selectedIndex + } + let actions = query(ret.overlay.box).find('.w2ui-eaction') + w2utils.bindEvents(actions, this) + let count = this.applyFilter(overlay.name, null, search) + overlay.tmp.searchCount = count + overlay.tmp.search = search + this.refreshSearch(overlay.name) + this.initControls(ret.overlay) + this.refreshIndex(overlay.name) + } + }) + overlay.on('hide:after.attach', event => { + w2tooltip.hide(overlay.name + '-tooltip') + }) + ret.select = (callback) => { + overlay.on('select.attach', (event) => { callback(event) }) + return ret + } + ret.remove = (callback) => { + overlay.on('remove.attach', (event) => { callback(event) }) + return ret + } + ret.subMenu = (callback) => { + overlay.on('subMenu.attach', (event) => { callback(event) }) + return ret + } + return ret + } + update(name, items) { + let overlay = Tooltip.active[name] + if (overlay) { + let options = overlay.options + if (options.items != items) { + options.items = items + } + let menuHTML = this.getMenuHTML(options) + if (options.html != menuHTML) { + options.html = menuHTML + overlay.needsUpdate = true + this.show(name) + } + } else { + console.log(`Tooltip "${name}" is not displayed. Cannot update it.`) + } + } + initControls(overlay) { + query(overlay.box).find('.w2ui-menu:not(.w2ui-sub-menu)') + .off('.w2menu') + .on('mouseDown.w2menu', { delegate: '.w2ui-menu-item' }, event => { + let dt = event.delegate.dataset + this.menuDown(overlay, event, dt.index, dt.parents) + }) + .on((w2utils.isIOS ? 'touchStart' : 'click') + '.w2menu', { delegate: '.w2ui-menu-item' }, event => { + let dt = event.delegate.dataset + this.menuClick(overlay, event, parseInt(dt.index), dt.parents) + }) + .find('.w2ui-menu-item') + .off('.w2menu') + .on('mouseEnter.w2menu', event => { + let dt = event.target.dataset + let tooltip = overlay.options.items[dt.index]?.tooltip + if (tooltip) { + w2tooltip.show({ + name: overlay.name + '-tooltip', + anchor: event.target, + html: tooltip, + position: 'right|left', + hideOn: ['doc-click'] + }) + } + }) + .on('mouseLeave.w2menu', event => { + w2tooltip.hide(overlay.name + '-tooltip') + }) + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + query(overlay.anchor) + .off('.w2menu') + .on('input.w2menu', event => { + // if user types, clear selection + // let dt = event.target.dataset + // delete dt.selected + // delete dt.selectedIndex + }) + .on('keyup.w2menu', event => { + event._searchType = 'filter' + this.keyUp(overlay, event) + }) + } + if (overlay.options.search) { + query(overlay.box).find('#menu-search') + .off('.w2menu') + .on('keyup.w2menu', event => { + event._searchType = 'search' + this.keyUp(overlay, event) + }) + } + } + getCurrent(name, id) { + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + let options = overlay.options + let selected = (id ? id : overlay.selected ?? '').split('-') + let last = selected.length-1 + let index = selected[last] + let parents = selected.slice(0, selected.length-1).join('-') + index = w2utils.isInt(index) ? parseInt(index) : 0 + // items + let items = options.items + selected.forEach((id, ind) => { + // do not go to the last one + if (ind < selected.length - 1) { + items = items[id].items + } + }) + return { last, index, items, item: items[index], parents } + } + getMenuHTML(options, items, subMenu, parentIndex) { + if (options.spinner) { + return ` +
    +
    +
    + ${w2utils.lang('Loading...')} +
    +
    ` + } + if (!parentIndex) parentIndex = [] + if (items == null) { + items = options.items + } + if (!Array.isArray(items)) items = [] + let count = 0 + let icon = null + let topHTML = '' + if (!subMenu && options.search) { + topHTML += ` + ` + items.forEach(item => item.hidden = false) + } + if (!subMenu && options.topHTML) { + topHTML += `
    ${options.topHTML}
    ` + } + let menu_html = ` + ${topHTML} +
    + ` + items.forEach((mitem, f) => { + icon = mitem.icon + let index = (parentIndex.length > 0 ? parentIndex.join('-') + '-' : '') + f + if (icon == null) icon = null // icon might be undefined + if (['radio', 'check'].indexOf(options.type) != -1 && !Array.isArray(mitem.items) && mitem.group !== false) { + if (mitem.checked === true) icon = 'w2ui-icon-check'; else icon = 'w2ui-icon-empty' + } + if (mitem.hidden !== true) { + let txt = mitem.text + let icon_dsp = '' + let subMenu_dsp = '' + if (typeof options.render === 'function') txt = options.render(mitem, options) + if (typeof txt == 'function') txt = txt(mitem, options) + if (icon) { + if (String(icon).slice(0, 1) !== '<') { + icon = `` + } + icon_dsp = `` + } + // render only if non-empty + if (mitem.type !== 'break' && txt != null && txt !== '' && String(txt).substr(0, 2) != '--') { + let classes = ['w2ui-menu-item'] + if (options.altRows == true) { + classes.push(count % 2 === 0 ? 'w2ui-even' : 'w2ui-odd') + } + let colspan = 1 + if (icon_dsp === '') colspan++ + if (mitem.count == null && mitem.hotkey == null && mitem.remove !== true && mitem.items == null) colspan++ + if (mitem.tooltip == null && mitem.hint != null) mitem.tooltip = mitem.hint // for backward compatibility + let count_dsp = '' + if (mitem.remove === true) { + count_dsp = 'x' + } else if (mitem.items != null) { + let _items = [] + if (typeof mitem.items == 'function') { + _items = mitem.items(mitem) + } else if (Array.isArray(mitem.items)) { + _items = mitem.items + } + count_dsp = '' + subMenu_dsp = ` +
    + ${this.getMenuHTML(options, _items, true, parentIndex.concat(f))} +
    ` + } else { + if (mitem.count != null) count_dsp += '' + mitem.count + '' + if (mitem.hotkey != null) count_dsp += '' + mitem.hotkey + '' + } + if (mitem.disabled === true) classes.push('w2ui-disabled') + if (mitem._noSearchInside === true) classes.push('w2ui-no-search-inside') + if (subMenu_dsp !== '') { + classes.push('has-sub-menu') + if (mitem.expanded) { + classes.push('expanded') + } else { + classes.push('collapsed') + } + } + menu_html += ` +
    +
    + ${icon_dsp} + + +
    + ${subMenu_dsp}` + count++ + } else { + // horizontal line + let divText = (txt ?? '').replace(/^-+/g, '') + menu_html += ` +
    +
    + ${divText ? `
    ${divText}
    ` : ''} +
    ` + } + } + items[f] = mitem + }) + if (count === 0 && options.msgNoItems) { + menu_html += ` +
    + ${w2utils.lang(options.msgNoItems)} +
    ` + } + menu_html += '
    ' + return menu_html + } + // Refreshed only selected item highligh, used in keyboard navigation + refreshIndex(name) { + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + if (!overlay) return + if (!overlay.displayed) { + this.show(overlay.name) + } + let view = query(overlay.box).find('.w2ui-overlay-body').get(0) + let search = query(overlay.box).find('.w2ui-menu-search, .w2ui-menu-top').get(0) + query(overlay.box).find('.w2ui-menu-item.w2ui-selected') + .removeClass('w2ui-selected') + let el = query(overlay.box).find(`.w2ui-menu-item[index="${overlay.selected}"]`) + .addClass('w2ui-selected') + .get(0) + if (el) { + if (el.offsetTop + el.clientHeight > view.clientHeight + view.scrollTop) { + el.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'start' }) + } + if (el.offsetTop < view.scrollTop + (search ? search.clientHeight : 0)) { + el.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' }) + } + } + } + // show/hide searched items + refreshSearch(name) { + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + if (!overlay) return + if (!overlay.displayed) { + this.show(overlay.name) + } + query(overlay.box).find('.w2ui-no-items').hide() + query(overlay.box).find('.w2ui-menu-item, .w2ui-menu-divider').each(el => { + let cur = this.getCurrent(name, el.getAttribute('index')) + if (cur.item.hidden) { + query(el).hide() + } else { + let search = overlay.tmp?.search + if (search && overlay.options.markSearch) { + w2utils.marker(el, search, { onlyFirst: overlay.options.match == 'begins' }) + } + query(el).show() + } + }) + // hide empty menus + query(overlay.box).find('.w2ui-sub-menu').each(sub => { + let hasItems = query(sub).find('.w2ui-menu-item').get().some(el => { + return el.style.display != 'none' ? true : false + }) + let parent = this.getCurrent(name, sub.dataset.parent) + // only if parent is expaneded + if (parent.item.expanded) { + if (!hasItems) { + query(sub).parent().hide() + } else { + query(sub).parent().show() + } + } + }) + // show empty message + if (overlay.tmp.searchCount == 0 || overlay.options?.items?.length == 0) { + if (query(overlay.box).find('.w2ui-no-items').length == 0) { + query(overlay.box).find('.w2ui-menu:not(.w2ui-sub-menu)').append(` +
    + ${w2utils.lang(overlay.options.msgNoItems)} +
    `) + } + query(overlay.box).find('.w2ui-no-items').show() + } + } + /** + * Loops through the items and markes item.hidden for those that need to be hidden. + * Return the number of visible items. + */ + applyFilter(name, items, search) { + let count = 0 + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + let options = overlay.options + if (options.filter === false) { + return + } + if (items == null) items = overlay.options.items + if (search == null) { + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + search = overlay.anchor.value + } else { + search = '' + } + } + let selectedIds = [] + if (options.selected) { + if (Array.isArray(options.selected)) { + selectedIds = options.selected.map(item => { + return item?.id ?? item + }) + } else if (options.selected?.id) { + selectedIds = [options.selected.id] + } + } + items.forEach(item => { + let prefix = '' + let suffix = '' + if (['is', 'begins', 'begins with'].indexOf(options.match) !== -1) prefix = '^' + if (['is', 'ends', 'ends with'].indexOf(options.match) !== -1) suffix = '$' + try { + let re = new RegExp(prefix + search + suffix, 'i') + if (re.test(item.text) || item.text === '...') { + item.hidden = false + } else { + item.hidden = true + } + } catch (e) {} + // do not show selected items + if (options.hideSelected && selectedIds.includes(item.id)) { + item.hidden = true + } + // search nested items + if (Array.isArray(item.items) && item.items.length > 0) { + delete item._noSearchInside + let subCount = this.applyFilter(name, item.items, search) + if (subCount > 0) { + count += subCount + if (item.hidden) item._noSearchInside = true + // only expand items if search is not empty + if (search) item.expanded = true + item.hidden = false + } + } + if (item.hidden !== true) count++ + }) + overlay.tmp.activeChain = this.getActiveChain(name, items) + overlay.selected = null + return count + } + /** + * Builds an array of item ids that sequencial in navigation with up/down keys. + * Skips hidden and disabled items and goes into nested structures. + */ + getActiveChain(name, items, parents = [], res = [], noSave) { + let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')] + if (overlay.tmp.activeChain != null) { + return overlay.tmp.activeChain + } + if (items == null) items = overlay.options.items + items.forEach((item, ind) => { + if (!item.hidden && !item.disabled && !item?.text?.startsWith('--')) { + res.push(parents.concat([ind]).join('-')) + if (Array.isArray(item.items) && item.items.length > 0 && item.expanded) { + parents.push(ind) + this.getActiveChain(name, item.items, parents, res, true) + parents.pop() + } + } + }) + if (noSave == null) { + overlay.tmp.activeChain = res + } + return res + } + menuDown(overlay, event, index, parentIndex) { + let options = overlay.options + let items = options.items + let icon = query(event.delegate).find('.w2ui-icon') + let menu = query(event.target).closest('.w2ui-menu:not(.w2ui-sub-menu)') + if (typeof parentIndex == 'string' && parentIndex !== '') { + let ids = parentIndex.split('-') + ids.forEach(id => { + items = items[id].items + }) + } + let item = items[index] + if (item.disabled) { + return + } + let uncheck = (items, parent) => { + items.forEach((other, ind) => { + if (other.id == item.id) return + if (other.group === item.group && other.checked) { + menu + .find(`.w2ui-menu-item[index="${(parent ? parent + '-' : '') + ind}"] .w2ui-icon`) + .removeClass('w2ui-icon-check') + .addClass('w2ui-icon-empty') + items[ind].checked = false + } + if (Array.isArray(other.items)) { + uncheck(other.items, ind) + } + }) + } + if ((options.type === 'check' || options.type === 'radio') && item.group !== false + && !query(event.target).hasClass('remove') + && !query(event.target).closest('.w2ui-menu-item').hasClass('has-sub-menu')) { + item.checked = options.type == 'radio' ? true : !item.checked + if (item.checked) { + if (options.type === 'radio') { + query(event.target).closest('.w2ui-menu').find('.w2ui-icon') + .removeClass('w2ui-icon-check') + .addClass('w2ui-icon-empty') + } + if (options.type === 'check' && item.group != null) { + uncheck(options.items) + } + icon.removeClass('w2ui-icon-empty').addClass('w2ui-icon-check') + } else if (options.type === 'check') { + icon.removeClass('w2ui-icon-check').addClass('w2ui-icon-empty') + } + } + // highlight record + if (!query(event.target).hasClass('remove')) { + menu.find('.w2ui-menu-item').removeClass('w2ui-selected') + query(event.delegate).addClass('w2ui-selected') + } + } + menuClick(overlay, event, index, parentIndex) { + let options = overlay.options + let items = options.items + let $item = query(event.delegate).closest('.w2ui-menu-item') + let keepOpen = options.hideOn.includes('select') ? false : true + if (event.shiftKey || event.metaKey || event.ctrlKey) { + keepOpen = true + } + if (typeof parentIndex == 'string' && parentIndex !== '') { + let ids = parentIndex.split('-') + ids.forEach(id => { + items = items[id].items + }) + } else { + parentIndex = null + } + if (typeof items == 'function') { + items = items({ overlay, index, parentIndex, event }) + } + let item = items[index] + if (item.disabled && !query(event.target).hasClass('remove')) { + return + } + let edata + if (query(event.target).hasClass('remove')) { + edata = this.trigger('remove', { originalEvent: event, target: overlay.name, + overlay, item, index, parentIndex, el: $item[0] }) + if (edata.isCancelled === true) { + return + } + keepOpen = !options.hideOn.includes('item-remove') + $item.remove() + } else if ($item.hasClass('has-sub-menu')) { + edata = this.trigger('subMenu', { originalEvent: event, target: overlay.name, + overlay, item, index, parentIndex, el: $item[0] }) + if (edata.isCancelled === true) { + return + } + keepOpen = true + if ($item.hasClass('expanded')) { + item.expanded = false + $item.removeClass('expanded').addClass('collapsed') + query($item.get(0).nextElementSibling).hide() + overlay.selected = parseInt($item.attr('index')) + } else { + item.expanded = true + $item.addClass('expanded').removeClass('collapsed') + query($item.get(0).nextElementSibling).show() + overlay.selected = parseInt($item.attr('index')) + } + } else { + // find items that are selected + let selected = this.findChecked(options.items) + overlay.selected = parseInt($item.attr('index')) + edata = this.trigger('select', { originalEvent: event, target: overlay.name, + overlay, item, index, parentIndex, selected, keepOpen, el: $item[0] }) + if (edata.isCancelled === true) { + return + } + if (item.keepOpen != null) { + keepOpen = item.keepOpen + } + if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + overlay.anchor.dataset.selected = item.id + overlay.anchor.dataset.selectedIndex = overlay.selected + } + } + if (!keepOpen) { + this.hide(overlay.name) + } + // if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) { + // overlay.anchor.focus() + // } + // event after + edata.finish() + } + findChecked(items) { + let found = [] + items.forEach(item => { + if (item.checked) found.push(item) + if (Array.isArray(item.items)) { + found = found.concat(this.findChecked(item.items)) + } + }) + return found + } + keyUp(overlay, event) { + let options = overlay.options + let search = event.target.value + let key = event.keyCode + let filter = true + let refreshIndex = false + switch (key) { + case 8: { // delete + // if search empty and delete is clicked, do not filter nor show overlay + if (search === '' && !overlay.displayed) filter = false + break + } + case 13: { // enter + if (!overlay.displayed || !overlay.selected) return + let { index, parents } = this.getCurrent(overlay.name) + event.delegate = query(overlay.box).find('.w2ui-selected').get(0) + // reset active chain for folders + this.menuClick(overlay, event, parseInt(index), parents) + filter = false + break + } + case 27: { // escape + filter = false + if (overlay.displayed) { + this.hide(overlay.name) + } else { + // clear selected + let el = overlay.anchor + if (['INPUT', 'TEXTAREA'].includes(el.tagName)) { + el.value = '' + delete el.dataset.selected + delete el.dataset.selectedIndex + } + } + break + } + case 37: { // left + if (!overlay.displayed) return + let { item, index, parents } = this.getCurrent(overlay.name) + // collapse parent if any + if (parents) { + item = options.items[parents] + index = parseInt(parents) + parents = '' + refreshIndex = true + } + if (Array.isArray(item?.items) && item.items.length > 0 && item.expanded) { + event.delegate = query(overlay.box).find(`.w2ui-menu-item[index="${index}"]`).get(0) + overlay.selected = index + this.menuClick(overlay, event, parseInt(index), parents) + } + filter = false + break + } + case 39: { // right + if (!overlay.displayed) return + let { item, index, parents } = this.getCurrent(overlay.name) + if (Array.isArray(item?.items) && item.items.length > 0 && !item.expanded) { + event.delegate = query(overlay.box).find('.w2ui-selected').get(0) + this.menuClick(overlay, event, parseInt(index), parents) + } + filter = false + break + } + case 38: { // up + if (!overlay.displayed) { + break + } + let chain = this.getActiveChain(overlay.name) + if (overlay.selected == null || overlay.selected?.length == 0) { + overlay.selected = chain[chain.length-1] + } else { + let ind = chain.indexOf(overlay.selected) + // selected not in chain of items + if (ind == -1) { + overlay.selected = chain[chain.length-1] + } + // not first item + if (ind > 0) { + overlay.selected = chain[ind - 1] + } + } + filter = false + refreshIndex = true + event.preventDefault() + break + } + case 40: { // down + if (!overlay.displayed) { + break + } + let chain = this.getActiveChain(overlay.name) + if (overlay.selected == null || overlay.selected?.length == 0) { + overlay.selected = chain[0] + } else { + let ind = chain.indexOf(overlay.selected) + // selected not in chain of items + if (ind == -1) { + overlay.selected = chain[0] + } + // not the last item + if (ind < chain.length - 1) { + overlay.selected = chain[ind + 1] + } + } + filter = false + refreshIndex = true + event.preventDefault() + break + } + } + // filter + if (filter && overlay.displayed && ((options.filter && event._searchType == 'filter') + || (options.search && event._searchType == 'search'))) { + let count = this.applyFilter(overlay.name, null, search) + overlay.tmp.searchCount = count + overlay.tmp.search = search + // if selected is not in searched items + if (count === 0 || !this.getActiveChain(overlay.name).includes(overlay.selected)) { + overlay.selected = null + } + this.refreshSearch(overlay.name) + } + if (refreshIndex) { + this.refreshIndex(overlay.name) + } + } +} +class DateTooltip extends Tooltip { + constructor() { + super() + let td = new Date() + this.daysCount = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + this.today = td.getFullYear() + '/' + (Number(td.getMonth()) + 1) + '/' + td.getDate() + this.defaults = w2utils.extend({}, this.defaults, { + position : 'top|bottom', + class : 'w2ui-calendar', + type : 'date', // can be date/time/datetime + format : '', + value : '', // initial date (in w2utils.settings format) + start : null, + end : null, + blockDates : [], // array of blocked dates + blockWeekdays : [], // blocked weekdays 0 - sunday, 1 - monday, etc + colored : {}, // ex: { '3/13/2022': 'bg-color|text-color' } + arrowSize : 12, + autoResize : false, + anchorClass : 'w2ui-focus', + autoShowOn : 'focus', + hideOn : ['doc-click', 'focus-change'], + onSelect : null + }) + } + attach(anchor, text) { + let options + if (arguments.length == 1 && anchor.anchor) { + options = anchor + anchor = options.anchor + } else if (arguments.length === 2 && text != null && typeof text === 'object') { + options = text + options.anchor = anchor + } + let prevHideOn = options.hideOn + options = w2utils.extend({}, this.defaults, options || {}) + if (prevHideOn) { + options.hideOn = prevHideOn + } + if (!options.format) { + let df = w2utils.settings.dateFormat + let tf = w2utils.settings.timeFormat + if (options.type == 'date') { + options.format = df + } else if (options.type == 'time') { + options.format = tf + } else { + options.format = df + '|' + tf + } + } + let cal = options.type == 'time' ? this.getHourHTML(options) : this.getMonthHTML(options) + options.style += '; padding: 0;' + options.html = cal.html + let ret = super.attach(options) + let overlay = ret.overlay + Object.assign(overlay.tmp, cal) + overlay.on('show.attach', event => { + let overlay = event.detail.overlay + let anchor = overlay.anchor + let options = overlay.options + if (['INPUT', 'TEXTAREA'].includes(anchor.tagName) && !options.value && anchor.value) { + overlay.tmp.initValue = anchor.value + } + delete overlay.newValue + delete overlay.newDate + }) + overlay.on('show:after.attach', event => { + if (ret.overlay?.box) { + this.initControls(ret.overlay) + } + }) + overlay.on('update:after.attach', event => { + if (ret.overlay?.box) { + this.initControls(ret.overlay) + } + }) + overlay.on('hide.attach', event => { + let overlay = event.detail.overlay + let anchor = overlay.anchor + if (overlay.newValue != null) { + if (overlay.newDate) { + overlay.newValue = overlay.newDate + ' ' + overlay.newValue + } + if (['INPUT', 'TEXTAREA'].includes(anchor.tagName) && anchor.value != overlay.newValue) { + anchor.value = overlay.newValue + } + let edata = this.trigger('select', { date: overlay.newValue, target: overlay.name, overlay }) + if (edata.isCancelled === true) return + // event after + edata.finish() + } + }) + ret.select = (callback) => { + overlay.on('select.attach', (event) => { callback(event) }) + return ret + } + return ret + } + initControls(overlay) { + let options = overlay.options + let moveMonth = (inc) => { + let { month, year } = overlay.tmp + month += inc + if (month > 12) { + month = 1 + year++ + } + if (month < 1 ) { + month = 12 + year-- + } + let cal = this.getMonthHTML(options, month, year) + Object.assign(overlay.tmp, cal) + query(overlay.box).find('.w2ui-overlay-body').html(cal.html) + this.initControls(overlay) + } + let checkJump = (event, dblclick) => { + query(event.target).parent().find('.w2ui-jump-month, .w2ui-jump-year') + .removeClass('w2ui-selected') + query(event.target).addClass('w2ui-selected') + let dt = new Date() + let { jumpMonth, jumpYear } = overlay.tmp + if (dblclick) { + if (jumpYear == null) jumpYear = dt.getFullYear() + if (jumpMonth == null) jumpMonth = dt.getMonth() + 1 + } + if (jumpMonth && jumpYear) { + let cal = this.getMonthHTML(options, jumpMonth, jumpYear) + Object.assign(overlay.tmp, cal) + query(overlay.box).find('.w2ui-overlay-body').html(cal.html) + overlay.tmp.jump = false + this.initControls(overlay) + } + } + // events for next/prev buttons and title + query(overlay.box).find('.w2ui-cal-title') + .off('.calendar') + // click on title + .on('click.calendar', event => { + Object.assign(overlay.tmp, { jumpYear: null, jumpMonth: null }) + if (overlay.tmp.jump) { + let { month, year } = overlay.tmp + let cal = this.getMonthHTML(options, month, year) + query(overlay.box).find('.w2ui-overlay-body').html(cal.html) + overlay.tmp.jump = false + } else { + query(overlay.box).find('.w2ui-overlay-body .w2ui-cal-days') + .replace(this.getYearHTML()) + let el = query(overlay.box).find(`[name="${overlay.tmp.year}"]`).get(0) + if (el) el.scrollIntoView(true) + overlay.tmp.jump = true + } + this.initControls(overlay) + event.stopPropagation() + }) + // prev button + .find('.w2ui-cal-previous') + .off('.calendar') + .on('click.calendar', event => { + moveMonth(-1) + event.stopPropagation() + }) + .parent() + // next button + .find('.w2ui-cal-next') + .off('.calendar') + .on('click.calendar', event => { + moveMonth(1) + event.stopPropagation() + }) + // now button + query(overlay.box).find('.w2ui-cal-now') + .off('.calendar') + .on('click.calendar', event => { + if (options.type == 'datetime') { + if (overlay.newDate) { + overlay.newValue = w2utils.formatTime(new Date(), options.format.split('|')[1]) + } else { + overlay.newValue = w2utils.formatDateTime(new Date(), options.format) + } + } else if (options.type == 'date') { + overlay.newValue = w2utils.formatDate(new Date(), options.format) + } else if (options.type == 'time') { + overlay.newValue = w2utils.formatTime(new Date(), options.format) + } + this.hide(overlay.name) + }) + // events for dates + query(overlay.box) + .off('.calendar') + .on('click.calendar', { delegate: '.w2ui-day.w2ui-date' }, event => { + if (options.type == 'datetime') { + overlay.newDate = query(event.target).attr('date') + query(overlay.box).find('.w2ui-overlay-body').html(this.getHourHTML(overlay.options).html) + this.initControls(overlay) + } else { + overlay.newValue = query(event.target).attr('date') + this.hide(overlay.name) + } + }) + // click on month + .on('click.calendar', { delegate: '.w2ui-jump-month' }, event => { + overlay.tmp.jumpMonth = parseInt(query(event.target).attr('name')) + checkJump(event) + }) + // double click on month + .on('dblclick.calendar', { delegate: '.w2ui-jump-month' }, event => { + overlay.tmp.jumpMonth = parseInt(query(event.target).attr('name')) + checkJump(event, true) + }) + // click on year + .on('click.calendar', { delegate: '.w2ui-jump-year' }, event => { + overlay.tmp.jumpYear = parseInt(query(event.target).attr('name')) + checkJump(event) + }) + // dbl click on year + .on('dblclick.calendar', { delegate: '.w2ui-jump-year' }, event => { + overlay.tmp.jumpYear = parseInt(query(event.target).attr('name')) + checkJump(event, true) + }) + // click on hour + .on('click.calendar', { delegate: '.w2ui-time.hour' }, event => { + let hour = query(event.target).attr('hour') + let min = this.str2min(options.value) % 60 + if (overlay.tmp.initValue && !options.value) { + min = this.str2min(overlay.tmp.initValue) % 60 + } + if (options.noMinutes) { + overlay.newValue = this.min2str(hour * 60, options.format) + this.hide(overlay.name) + } else { + overlay.newValue = hour + ':' + min + let html = this.getMinHTML(hour, options).html + query(overlay.box).find('.w2ui-overlay-body').html(html) + this.initControls(overlay) + } + }) + // click on minute + .on('click.calendar', { delegate: '.w2ui-time.min' }, event => { + let hour = Math.floor(this.str2min(overlay.newValue) / 60) + let time = (hour * 60) + parseInt(query(event.target).attr('min')) + overlay.newValue = this.min2str(time, options.format) + this.hide(overlay.name) + }) + } + getMonthHTML(options, month, year) { + let days = w2utils.settings.fulldays.slice() // creates copy of the array + let sdays = w2utils.settings.shortdays.slice() // creates copy of the array + if (w2utils.settings.weekStarts !== 'M') { + days.unshift(days.pop()) + sdays.unshift(sdays.pop()) + } + let td = new Date() + let dayLengthMil = 1000 * 60 * 60 * 24 + let selected = options.type === 'datetime' + ? w2utils.isDateTime(options.value, options.format, true) + : w2utils.isDate(options.value, options.format, true) + let selected_dsp = w2utils.formatDate(selected) + // normalize date + if (month == null || year == null) { + year = selected ? selected.getFullYear() : td.getFullYear() + month = selected ? selected.getMonth() + 1 : td.getMonth() + 1 + } + if (month > 12) { month -= 12; year++ } + if (month < 1 || month === 0) { month += 12; year-- } + if (year/4 == Math.floor(year/4)) { this.daysCount[1] = 29 } else { this.daysCount[1] = 28 } + options.current = month + '/' + year + // start with the required date + td = new Date(year, month-1, 1) + let weekDay = td.getDay() + let weekDays = '' + let st = w2utils.settings.weekStarts + for (let i = 0; i < sdays.length; i++) { + let isSat = (st == 'M' && i == 5) || (st != 'M' && i == 6) ? true : false + let isSun = (st == 'M' && i == 6) || (st != 'M' && i == 0) ? true : false + weekDays += `
    ${sdays[i]}
    ` + } + let html = ` +
    +
    +
    +
    +
    +
    +
    + ${w2utils.settings.fullmonths[month-1]}, ${year} + +
    +
    + ${weekDays} + ` + let DT = new Date(`${year}/${month}/1`) // first of month + /** + * Move to noon, instead of midnight. If not, then the date when time saving happens + * will be duplicated in the calendar + */ + DT = new Date(DT.getTime() + dayLengthMil * 0.5) + let weekday = DT.getDay() + if (w2utils.settings.weekStarts == 'M') weekDay-- + if (weekday > 0) { + DT = new Date(DT.getTime() - (weekDay * dayLengthMil)) + } + for (let ci = 0; ci < 42; ci++) { + let className = [] + let dt = `${DT.getFullYear()}/${DT.getMonth()+1}/${DT.getDate()}` + if (DT.getDay() === 6) className.push('w2ui-saturday') + if (DT.getDay() === 0) className.push('w2ui-sunday') + if (DT.getMonth() + 1 !== month) className.push('outside') + if (dt == this.today) className.push('w2ui-today') + let dspDay = DT.getDate() + let col = '' + let bgcol = '' + let tmp_dt, tmp_dt_fmt + if (options.type === 'datetime') { + tmp_dt = w2utils.formatDateTime(dt, options.format) + tmp_dt_fmt = w2utils.formatDate(dt, w2utils.settings.dateFormat) + } else { + tmp_dt = w2utils.formatDate(dt, options.format) + tmp_dt_fmt = tmp_dt + } + if (options.colored && options.colored[tmp_dt_fmt] !== undefined) { // if there is predefined colors for dates + let tmp = options.colored[tmp_dt_fmt].split('|') + bgcol = 'background-color: ' + tmp[0] + ';' + col = 'color: ' + tmp[1] + ';' + } + html += `
    + ${dspDay} +
    ` + DT = new Date(DT.getTime() + dayLengthMil) + } + html += '
    ' + if (options.btnNow) { + let label = w2utils.lang('Today' + (options.type == 'datetime' ? ' & Now' : '')) + html += `
    ${label}
    ` + } + return { html, month, year } + } + getYearHTML() { + let mhtml = '' + let yhtml = '' + for (let m = 0; m < w2utils.settings.fullmonths.length; m++) { + mhtml += `
    ${w2utils.settings.shortmonths[m]}
    ` + } + for (let y = w2utils.settings.dateStartYear; y <= w2utils.settings.dateEndYear; y++) { + yhtml += `
    ${y}
    ` + } + return `
    +
    ${mhtml}
    +
    ${yhtml}
    +
    ` + } + getHourHTML(options) { + options = options ?? {} + if (!options.format) options.format = w2utils.settings.timeFormat + let h24 = (options.format.indexOf('h24') > -1) + let value = options.value ? options.value : (options.anchor ? options.anchor.value : '') + let tmp = [] + for (let a = 0; a < 24; a++) { + let time = (a >= 12 && !h24 ? a - 12 : a) + ':00' + (!h24 ? (a < 12 ? ' am' : ' pm') : '') + if (a == 12 && !h24) time = '12:00 pm' + if (!tmp[Math.floor(a/8)]) tmp[Math.floor(a/8)] = '' + let tm1 = this.min2str(this.str2min(time)) + let tm2 = this.min2str(this.str2min(time) + 59) + if (options.type === 'datetime') { + let dt = w2utils.isDateTime(value, options.format, true) + let fm = options.format.split('|')[0].trim() + tm1 = w2utils.formatDate(dt, fm) + ' ' + tm1 + tm2 = w2utils.formatDate(dt, fm) + ' ' + tm2 + } + let valid = this.inRange(tm1, options) || this.inRange(tm2, options) + tmp[Math.floor(a/8)] += `${time}` + } + let html = `
    +
    ${w2utils.lang('Select Hour')}
    +
    +
    ${tmp[0]}
    +
    ${tmp[1]}
    +
    ${tmp[2]}
    +
    + ${options.btnNow ? `
    ${w2utils.lang('Now')}
    ` : '' } +
    ` + return { html } + } + getMinHTML(hour, options) { + if (hour == null) hour = 0 + options = options ?? {} + if (!options.format) options.format = w2utils.settings.timeFormat + let h24 = (options.format.indexOf('h24') > -1) + let value = options.value ? options.value : (options.anchor ? options.anchor.value : '') + let tmp = [] + for (let a = 0; a < 60; a += 5) { + let time = (hour > 12 && !h24 ? hour - 12 : hour) + ':' + (a < 10 ? 0 : '') + a + ' ' + (!h24 ? (hour < 12 ? 'am' : 'pm') : '') + let tm = time + let ind = a < 20 ? 0 : (a < 40 ? 1 : 2) + if (!tmp[ind]) tmp[ind] = '' + if (options.type === 'datetime') { + let dt = w2utils.isDateTime(value, options.format, true) + let fm = options.format.split('|')[0].trim() + tm = w2utils.formatDate(dt, fm) + ' ' + tm + } + tmp[ind] += `${time}` + } + let html = `
    +
    ${w2utils.lang('Select Minute')}
    +
    +
    ${tmp[0]}
    +
    ${tmp[1]}
    +
    ${tmp[2]}
    +
    + ${options.btnNow ? `
    ${w2utils.lang('Now')}
    ` : '' } +
    ` + return { html } + } + // checks if date is in range (loost at start, end, blockDates, blockWeekdays) + inRange(str, options, dateOnly) { + let inRange = false + if (options.type === 'date') { + let dt = w2utils.isDate(str, options.format, true) + if (dt) { + // enable range + if (options.start || options.end) { + let st = (typeof options.start === 'string' ? options.start : query(options.start).val()) + let en = (typeof options.end === 'string' ? options.end : query(options.end).val()) + let start = w2utils.isDate(st, options.format, true) + let end = w2utils.isDate(en, options.format, true) + let current = new Date(dt) + if (!start) start = current + if (!end) end = current + if (current >= start && current <= end) inRange = true + } else { + inRange = true + } + // block predefined dates + if (Array.isArray(options.blockDates) && options.blockDates.includes(str)) inRange = false + // block weekdays + if (Array.isArray(options.blockWeekdays) && options.blockWeekdays.includes(dt.getDay())) inRange = false + } + } else if (options.type === 'time') { + if (options.start || options.end) { + let tm = this.str2min(str) + let tm1 = this.str2min(options.start) + let tm2 = this.str2min(options.end) + if (!tm1) tm1 = tm + if (!tm2) tm2 = tm + if (tm >= tm1 && tm <= tm2) inRange = true + } else { + inRange = true + } + } else if (options.type === 'datetime') { + let dt = w2utils.isDateTime(str, options.format, true) + if (dt) { + let format = options.format.split('|').map(format => format.trim()) + if (dateOnly) { + let date = w2utils.formatDate(dt, format[0]) + let opts = w2utils.extend({}, options, { type: 'date', format: format[0] }) + if (this.inRange(date, opts)) inRange = true + } else { + let time = w2utils.formatTime(dt, format[1]) + let opts = { type: 'time', format: format[1], start: options.startTime, end: options.endTime } + if (this.inRange(time, opts)) inRange = true + } + } + } + return inRange + } + // converts time into number of minutes since midnight -- '11:50am' => 710 + str2min(str) { + if (typeof str !== 'string') return null + let tmp = str.split(':') + if (tmp.length === 2) { + tmp[0] = parseInt(tmp[0]) + tmp[1] = parseInt(tmp[1]) + if (str.indexOf('pm') !== -1 && tmp[0] !== 12) tmp[0] += 12 + if (str.includes('am') && tmp[0] == 12) tmp[0] = 0 // 12:00am - is midnight + } else { + return null + } + return tmp[0] * 60 + tmp[1] + } + // converts minutes since midnight into time str -- 710 => '11:50am' + min2str(time, format) { + let ret = '' + if (time >= 24 * 60) time = time % (24 * 60) + if (time < 0) time = 24 * 60 + time + let hour = Math.floor(time/60) + let min = ((time % 60) < 10 ? '0' : '') + (time % 60) + if (!format) { format = w2utils.settings.timeFormat} + if (format.indexOf('h24') !== -1) { + ret = hour + ':' + min + } else { + ret = (hour <= 12 ? hour : hour - 12) + ':' + min + ' ' + (hour >= 12 ? 'pm' : 'am') + } + return ret + } +} +let w2tooltip = new Tooltip() +let w2menu = new MenuTooltip() +let w2color = new ColorTooltip() +let w2date = new DateTooltip() + +/* +// pull records from remote source for w2menu +clearCache() { + let options = this.options + options.items = [] + this.tmp.xhr_loading = false + this.tmp.xhr_search = '' + this.tmp.xhr_total = -1 +} +request(interval) { + let obj = this + let options = this.options + let search = $(obj.el).val() || '' + // if no url - do nothing + if (!options.url) return + // -- + if (obj.type === 'enum') { + let tmp = $(obj.helpers.multi).find('input') + if (tmp.length === 0) search = ''; else search = tmp.val() + } + if (obj.type === 'list') { + let tmp = $(obj.helpers.focus).find('input') + if (tmp.length === 0) search = ''; else search = tmp.val() + } + if (options.minLength !== 0 && search.length < options.minLength) { + options.items = [] // need to empty the list + this.updateOverlay() + return + } + if (interval == null) interval = options.interval + if (obj.tmp.xhr_search == null) obj.tmp.xhr_search = '' + if (obj.tmp.xhr_total == null) obj.tmp.xhr_total = -1 + // check if need to search + if (options.url && $(obj.el).prop('readonly') !== true && $(obj.el).prop('disabled') !== true && ( + (options.items.length === 0 && obj.tmp.xhr_total !== 0) || + (obj.tmp.xhr_total == options.cacheMax && search.length > obj.tmp.xhr_search.length) || + (search.length >= obj.tmp.xhr_search.length && search.substr(0, obj.tmp.xhr_search.length) !== obj.tmp.xhr_search) || + (search.length < obj.tmp.xhr_search.length) + )) { + // empty list + if (obj.tmp.xhr) try { obj.tmp.xhr.abort() } catch (e) {} + obj.tmp.xhr_loading = true + obj.search() + // timeout + clearTimeout(obj.tmp.timeout) + obj.tmp.timeout = setTimeout(() => { + // trigger event + let url = options.url + let postData = { + search : search, + max : options.cacheMax + } + $.extend(postData, options.postData) + let edata = obj.trigger({ phase: 'before', type: 'request', search: search, target: obj.el, url: url, postData: postData }) + if (edata.isCancelled === true) return + url = edata.url + postData = edata.postData + let ajaxOptions = { + type : 'GET', + url : url, + data : postData, + dataType : 'JSON' // expected from server + } + if (options.method) ajaxOptions.type = options.method + if (w2utils.settings.dataType === 'JSON') { + ajaxOptions.type = 'POST' + ajaxOptions.data = JSON.stringify(ajaxOptions.data) + ajaxOptions.contentType = 'application/json' + } + if (w2utils.settings.dataType === 'HTTPJSON') { + ajaxOptions.data = { request: JSON.stringify(ajaxOptions.data) } + } + if (w2utils.settings.dataType === 'RESTFULLJSON') { + ajaxOptions.data = JSON.stringify(ajaxOptions.data) + ajaxOptions.contentType = 'application/json' + } + if (options.method != null) ajaxOptions.type = options.method + obj.tmp.xhr = $.ajax(ajaxOptions) + .done((data, status, xhr) => { + // trigger event + let edata2 = obj.trigger({ phase: 'before', type: 'load', target: obj.el, search: postData.search, data: data, xhr: xhr }) + if (edata2.isCancelled === true) return + // default behavior + data = edata2.data + if (typeof data === 'string') data = JSON.parse(data) + // if server just returns array + if (Array.isArray(data)) { + data = { records: data } + } + // needed for backward compatibility + if (data.records == null && data.items != null) { + data.records = data.items + delete data.items + } + // handles Golang marshal of empty arrays to null + if (data.status == 'success' && data.records == null) { + data.records = [] + } + if (!Array.isArray(data.records)) { + console.error('ERROR: server did not return proper data structure', '\n', + ' - it should return', { status: 'success', records: [{ id: 1, text: 'item' }] }, '\n', + ' - or just an array ', [{ id: 1, text: 'item' }], '\n', + ' - actual response', typeof data === 'object' ? data : xhr.responseText) + return + } + // remove all extra items if more then needed for cache + if (data.records.length > options.cacheMax) data.records.splice(options.cacheMax, 100000) + // map id and text + if (options.recId == null && options.recid != null) options.recId = options.recid // since lower-case recid is used in grid + if (options.recId || options.recText) { + data.records.forEach((item) => { + if (typeof options.recId === 'string') item.id = item[options.recId] + if (typeof options.recId === 'function') item.id = options.recId(item) + if (typeof options.recText === 'string') item.text = item[options.recText] + if (typeof options.recText === 'function') item.text = options.recText(item) + }) + } + // remember stats + obj.tmp.xhr_loading = false + obj.tmp.xhr_search = search + obj.tmp.xhr_total = data.records.length + obj.tmp.lastError = '' + options.items = w2utils.normMenu(data.records) + if (search === '' && data.records.length === 0) obj.tmp.emptySet = true; else obj.tmp.emptySet = false + // preset item + let find_selected = $(obj.el).data('find_selected') + if (find_selected) { + let sel + if (Array.isArray(find_selected)) { + sel = [] + find_selected.forEach((find) => { + let isFound = false + options.items.forEach((item) => { + if (item.id == find || (find && find.id == item.id)) { + sel.push($.extend(true, {}, item)) + isFound = true + } + }) + if (!isFound) sel.push(find) + }) + } else { + sel = find_selected + options.items.forEach((item) => { + if (item.id == find_selected || (find_selected && find_selected.id == item.id)) { + sel = item + } + }) + } + $(obj.el).data('selected', sel).removeData('find_selected').trigger('input').trigger('change') + } + obj.search() + // event after + obj.trigger($.extend(edata2, { phase: 'after' })) + }) + .fail((xhr, status, error) => { + // trigger event + let errorObj = { status: status, error: error, rawResponseText: xhr.responseText } + let edata2 = obj.trigger({ phase: 'before', type: 'error', target: obj.el, search: search, error: errorObj, xhr: xhr }) + if (edata2.isCancelled === true) return + // default behavior + if (status !== 'abort') { + let data + try { data = JSON.parse(xhr.responseText) } catch (e) {} + console.error('ERROR: server did not return proper data structure', '\n', + ' - it should return', { status: 'success', records: [{ id: 1, text: 'item' }] }, '\n', + ' - or just an array ', [{ id: 1, text: 'item' }], '\n', + ' - actual response', typeof data === 'object' ? data : xhr.responseText) + } + // reset stats + obj.tmp.xhr_loading = false + obj.tmp.xhr_search = search + obj.tmp.xhr_total = 0 + obj.tmp.emptySet = true + obj.tmp.lastError = (edata2.error || 'Server communication failed') + options.items = [] + obj.clearCache() + obj.search() + obj.updateOverlay(false) + // event after + obj.trigger($.extend(edata2, { phase: 'after' })) + }) + // event after + obj.trigger($.extend(edata, { phase: 'after' })) + }, interval) + } +} +search() { + let obj = this + let options = this.options + let search = $(obj.el).val() + let target = obj.el + let ids = [] + let selected = $(obj.el).data('selected') + if (obj.type === 'enum') { + target = $(obj.helpers.multi).find('input') + search = target.val() + for (let s in selected) { if (selected[s]) ids.push(selected[s].id) } + } + else if (obj.type === 'list') { + target = $(obj.helpers.focus).find('input') + search = target.val() + for (let s in selected) { if (selected[s]) ids.push(selected[s].id) } + } + let items = options.items + if (obj.tmp.xhr_loading !== true) { + let shown = 0 + for (let i = 0; i < items.length; i++) { + let item = items[i] + if (options.compare != null) { + if (typeof options.compare === 'function') { + item.hidden = (options.compare.call(this, item, search) === false ? true : false) + } + } else { + let prefix = '' + let suffix = '' + if (['is', 'begins'].indexOf(options.match) !== -1) prefix = '^' + if (['is', 'ends'].indexOf(options.match) !== -1) suffix = '$' + try { + let re = new RegExp(prefix + search + suffix, 'i') + if (re.test(item.text) || item.text === '...') item.hidden = false; else item.hidden = true + } catch (e) {} + } + if (options.filter === false) item.hidden = false + // do not show selected items + if (obj.type === 'enum' && $.inArray(item.id, ids) !== -1) item.hidden = true + if (item.hidden !== true) { shown++; delete item.hidden } + } + // preselect first item + options.index = [] + options.spinner = false + setTimeout(() => { + if (options.markSearch && $('#w2ui-overlay .no-matches').length == 0) { // do not highlight when no items + $('#w2ui-overlay').w2marker(search) + } + }, 1) + } else { + items.splice(0, options.cacheMax) + options.spinner = true + } + // only update overlay when it is displayed already + if ($('#w2ui-overlay').length > 0) { + obj.updateOverlay() + } +} +*/ +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2tooltip, w2color, w2menu + * + * == TODO == + * - tab navigation (index state) + * - vertical toolbar + * - w2menu on second click of tb button should hide + * - button display groups for each show/hide, possibly add state: { single: t/f, multiple: t/f, type: 'font' } + * - item.count - should just support html, so a custom block can be created, such as a colored line + * + * == 2.0 changes + * - CSP - fixed inline events + * - removed jQuery dependency + * - item.icon - can be class or or + * - new w2tooltips and w2menu + * - scroll returns promise + * - added onMouseEntter, onMouseLeave, onMouseDown, onMouseUp events + * - add(..., skipRefresh), insert(..., skipRefresh) + */ + +class w2toolbar extends w2base { + constructor(options) { + super(options.name) + this.box = null // DOM Element that holds the element + this.name = null // unique name for w2ui + this.routeData = {} // data for dynamic routes + this.items = [] + this.right = '' // HTML text on the right of toolbar + this.tooltip = 'top|left'// can be top, bottom, left, right + this.onClick = null + this.onMouseDown = null + this.onMouseUp = null + this.onMouseEnter = null // mouse enter the button event + this.onMouseLeave = null + this.onRender = null + this.onRefresh = null + this.onResize = null + this.onDestroy = null + this.item_template = { + id: null, // command to be sent to all event handlers + type: 'button', // button, check, radio, drop, menu, menu-radio, menu-check, break, html, spacer + text: null, + html: '', + tooltip: null, // w2toolbar.tooltip should be + count: null, + hidden: false, + disabled: false, + checked: false, // used for radio buttons + icon: null, + route: null, // if not null, it is route to go + arrow: null, // arrow down for drop/menu types + style: null, // extra css style for caption + group: null, // used for radio buttons + items: null, // for type menu* it is an array of items in the menu + selected: null, // used for menu-check, menu-radio + color: null, // color value - used in color pickers + overlay: { // additional options for overlay + anchorClass: '' + }, + onClick: null, + onRefresh: null + } + this.last = { + badge: {} + } + // mix in options, w/o items + let items = options.items + delete options.items + Object.assign(this, options) + // add item via method to makes sure item_template is applied + if (Array.isArray(items)) this.add(items, true) + // need to reassign back to keep it in config + options.items = items + // render if box specified + if (typeof this.box == 'string') this.box = query(this.box).get(0) + if (this.box) this.render(this.box) + } + add(items, skipRefresh) { + this.insert(null, items, skipRefresh) + } + insert(id, items, skipRefresh) { + if (!Array.isArray(items)) items = [items] + items.forEach((item, idx, arr) => { + if (typeof item === 'string') { + item = arr[idx] = { id: item, text: item } + } + // checks + let valid = ['button', 'check', 'radio', 'drop', 'menu', 'menu-radio', 'menu-check', 'color', 'text-color', 'html', + 'break', 'spacer', 'new-line'] + if (!valid.includes(String(item.type))) { + console.log('ERROR: The parameter "type" should be one of the following:', valid, `, but ${item.type} is supplied.`, item) + return + } + if (item.id == null && !['break', 'spacer', 'new-line'].includes(item.type)) { + console.log('ERROR: The parameter "id" is required but not supplied.', item) + return + } + if (item.type == null) { + console.log('ERROR: The parameter "type" is required but not supplied.', item) + return + } + if (!w2utils.checkUniqueId(item.id, this.items, 'toolbar', this.name)) return + // add item + let newItem = w2utils.extend({}, this.item_template, item) + if (newItem.type == 'menu-check') { + if (!Array.isArray(newItem.selected)) newItem.selected = [] + if (Array.isArray(newItem.items)) { + newItem.items.forEach(it => { + if (typeof it === 'string') { + it = arr[idx] = { id: it, text: it } + } + if (it.checked && !newItem.selected.includes(it.id)) newItem.selected.push(it.id) + if (!it.checked && newItem.selected.includes(it.id)) it.checked = true + if (it.checked == null) it.checked = false + }) + } + } else if (newItem.type == 'menu-radio') { + if (Array.isArray(newItem.items)) { + newItem.items.forEach((it, idx, arr) => { + if (typeof it === 'string') { + it = arr[idx] = { id: it, text: it } + } + if (it.checked && newItem.selected == null) newItem.selected = it.id; else it.checked = false + if (!it.checked && newItem.selected == it.id) it.checked = true + if (it.checked == null) it.checked = false + }) + } + } + if (id == null) { + this.items.push(newItem) + } else { + let middle = this.get(id, true) + this.items = this.items.slice(0, middle).concat([newItem], this.items.slice(middle)) + } + newItem.line = newItem.line ?? 1 + if (skipRefresh !== true) this.refresh(newItem.id) + }) + if (skipRefresh !== true) this.resize() + } + remove() { + let effected = 0 + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it || String(item).indexOf(':') != -1) return + effected++ + // remove from screen + query(this.box).find('#tb_'+ this.name +'_item_'+ w2utils.escapeId(it.id)).remove() + // remove from array + let ind = this.get(it.id, true) + if (ind != null) this.items.splice(ind, 1) + }) + this.resize() + return effected + } + set(id, newOptions) { + let item = this.get(id) + if (item == null) return false + Object.assign(item, newOptions) + this.refresh(String(id).split(':')[0]) + return true + } + get(id, returnIndex) { + if (arguments.length === 0) { + let all = [] + for (let i1 = 0; i1 < this.items.length; i1++) if (this.items[i1].id != null) all.push(this.items[i1].id) + return all + } + let tmp = String(id).split(':') + for (let i2 = 0; i2 < this.items.length; i2++) { + let it = this.items[i2] + // find a menu item + if (['menu', 'menu-radio', 'menu-check'].includes(it.type) && tmp.length == 2 && it.id == tmp[0]) { + let subItems = it.items + if (typeof subItems == 'function') subItems = subItems(this) + for (let i = 0; i < subItems.length; i++) { + let item = subItems[i] + if (item.id == tmp[1] || (item.id == null && item.text == tmp[1])) { + if (returnIndex == true) return i; else return item + } + if (Array.isArray(item.items)) { + for (let j = 0; j < item.items.length; j++) { + if (item.items[j].id == tmp[1] || (item.items[j].id == null && item.items[j].text == tmp[1])) { + if (returnIndex == true) return i; else return item.items[j] + } + } + } + } + } else if (it.id == tmp[0]) { + if (returnIndex == true) return i2; else return it + } + } + return null + } + setCount(id, count, className, style) { + let btn = query(this.box).find(`#tb_${this.name}_item_${w2utils.escapeId(id)} .w2ui-tb-count > span`) + if (btn.length > 0) { + btn.removeClass() + .addClass(className ?? '') + .text(count) + .get(0).style.cssText = style ?? '' + this.last.badge[id] = { + className: className ?? '', + style: style ?? '' + } + let item = this.get(id) + item.count = count + } else { + this.set(id, { count: count }) + this.setCount(...arguments) // to update styles + } + } + show() { + let effected = [] + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it) return + it.hidden = false + effected.push(String(item).split(':')[0]) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it); this.resize() }) }, 15) // needs timeout + return effected + } + hide() { + let effected = [] + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it) return + it.hidden = true + effected.push(String(item).split(':')[0]) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it); this.tooltipHide(it); this.resize() }) }, 15) // needs timeout + return effected + } + enable() { + let effected = [] + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it) return + it.disabled = false + effected.push(String(item).split(':')[0]) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it) }) }, 15) // needs timeout + return effected + } + disable() { + let effected = [] + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it) return + it.disabled = true + effected.push(String(item).split(':')[0]) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it); this.tooltipHide(it) }) }, 15) // needs timeout + return effected + } + check() { + let effected = [] + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it || String(item).indexOf(':') != -1) return + it.checked = true + effected.push(String(item).split(':')[0]) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it) }) }, 15) // needs timeout + return effected + } + uncheck() { + let effected = [] + Array.from(arguments).forEach(item => { + let it = this.get(item) + if (!it || String(item).indexOf(':') != -1) return + // remove overlay + if (['menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(it.type) && it.checked) { + w2tooltip.hide(this.name + '-drop') + } + it.checked = false + effected.push(String(item).split(':')[0]) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it) }) }, 15) // needs timeout + return effected + } + click(id, event) { + // click on menu items + let tmp = String(id).split(':') + let it = this.get(tmp[0]) + let items = (it && it.items ? w2utils.normMenu.call(this, it.items, it) : []) + if (tmp.length > 1) { + let subItem = this.get(id) + if (subItem && !subItem.disabled) { + this.menuClick({ name: this.name, item: it, subItem: subItem, originalEvent: event }) + } + return + } + if (it && !it.disabled) { + // event before + let edata = this.trigger('click', { + target: (id != null ? id : this.name), + item: it, object: it, originalEvent: event + }) + if (edata.isCancelled === true) return + // read items again, they might have been changed in the click event handler + items = (it && it.items ? w2utils.normMenu.call(this, it.items, it) : []) + let btn = '#tb_'+ this.name +'_item_'+ w2utils.escapeId(it.id) + query(this.box).find(btn).removeClass('down') // need to re-query at the moment -- as well as elsewhere in this function + if (it.type == 'radio') { + for (let i = 0; i < this.items.length; i++) { + let itt = this.items[i] + if (itt == null || itt.id == it.id || itt.type !== 'radio') continue + if (itt.group == it.group && itt.checked) { + itt.checked = false + this.refresh(itt.id) + } + } + it.checked = true + query(this.box).find(btn).addClass('checked') + } + if (['menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(it.type)) { + this.tooltipHide(id) + if (it.checked) { + w2tooltip.hide(this.name + '-drop') + return + } else { + // timeout is needed to make sure previous overlay hides + setTimeout(() => { + let hideDrop = (id, btn) => { + // need a closure to capture id variable + let self = this + return function () { + self.set(id, { checked: false }) + } + } + let el = query(this.box).find('#tb_'+ this.name +'_item_'+ w2utils.escapeId(it.id)) + if (!w2utils.isPlainObject(it.overlay)) it.overlay = {} + if (it.type == 'drop') { + w2tooltip.show(w2utils.extend({ + html: it.html, + class: 'w2ui-white', + hideOn: ['doc-click'] + }, it.overlay, { + anchor: el[0], + name: this.name + '-drop', + data: { item: it, btn } + })) + .hide(hideDrop(it.id, btn)) + } + if (['menu', 'menu-radio', 'menu-check'].includes(it.type)) { + let menuType = 'normal' + if (it.type == 'menu-radio') { + menuType = 'radio' + items.forEach((item) => { + if (it.selected == item.id) item.checked = true; else item.checked = false + }) + } + if (it.type == 'menu-check') { + menuType = 'check' + items.forEach((item) => { + if (Array.isArray(it.selected) && it.selected.includes(item.id)) item.checked = true; else item.checked = false + }) + } + w2menu.show(w2utils.extend({ + items, + }, it.overlay, { + type: menuType, + name : this.name + '-drop', + anchor: el[0], + data: { item: it, btn } + })) + .hide(hideDrop(it.id, btn)) + .remove(event => { + this.menuClick({ name: this.name, remove: true, item: it, subItem: event.detail.item, + originalEvent: event }) + }) + .select(event => { + this.menuClick({ name: this.name, item: it, subItem: event.detail.item, + originalEvent: event }) + }) + } + if (['color', 'text-color'].includes(it.type)) { + w2color.show(w2utils.extend({ + color: it.color + }, it.overlay, { + anchor: el[0], + name: this.name + '-drop', + data: { item: it, btn } + })) + .hide(hideDrop(it.id, btn)) + .select(event => { + if (event.detail.color != null) { + this.colorClick({ name: this.name, item: it, color: event.detail.color }) + } + }) + } + }, 0) + } + } + if (['check', 'menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(it.type)) { + it.checked = !it.checked + if (it.checked) { + query(this.box).find(btn).addClass('checked') + } else { + query(this.box).find(btn).removeClass('checked') + } + } + // route processing + if (it.route) { + let route = String('/'+ it.route).replace(/\/{2,}/g, '/') + let info = w2utils.parseRoute(route) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]) + } + } + setTimeout(() => { window.location.hash = route }, 1) + } + // need to refresh toolbar as it might be dynamic + this.tooltipShow(id) + // event after + edata.finish() + } + } + scroll(direction, line, instant) { + return new Promise((resolve, reject) => { + let scrollBox = query(this.box).find(`.w2ui-tb-line:nth-child(${line}) .w2ui-scroll-wrapper`) + let scrollLeft = scrollBox.get(0).scrollLeft + let right = scrollBox.find('.w2ui-tb-right').get(0) + let width1 = scrollBox.parent().get(0).getBoundingClientRect().width + let width2 = scrollLeft + parseInt(right.offsetLeft) + parseInt(right.clientWidth ) + switch (direction) { + case 'left': { + scroll = scrollLeft - width1 + 50 // 35 is width of both button + if (scroll <= 0) scroll = 0 + scrollBox.get(0).scrollTo({ top: 0, left: scroll, behavior: instant ? 'atuo' : 'smooth' }) + break + } + case 'right': { + scroll = scrollLeft + width1 - 50 // 35 is width of both button + if (scroll >= width2 - width1) scroll = width2 - width1 + scrollBox.get(0).scrollTo({ top: 0, left: scroll, behavior: instant ? 'atuo' : 'smooth' }) + break + } + } + setTimeout(() => { this.resize(); resolve() }, instant ? 0 : 500) + }) + } + render(box) { + let time = Date.now() + if (typeof box == 'string') box = query(box).get(0) + // event before + let edata = this.trigger('render', { target: this.name, box: box ?? this.box }) + if (edata.isCancelled === true) return + // defaul action + if (box != null) { + // clean previous box + if (query(this.box).find('.w2ui-scroll-wrapper .w2ui-tb-right').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-toolbar') + .html('') + } + this.box = box + } + if (!this.box) return + if (!Array.isArray(this.right)) { + this.right = [this.right] + } + // render all buttons + let html = '' + let line = 0 + for (let i = 0; i < this.items.length; i++) { + let it = this.items[i] + if (it == null) continue + if (it.id == null) it.id = 'item_' + i + if (it.caption != null) { + console.log('NOTICE: toolbar item.caption property is deprecated, please use item.text. Item -> ', it) + } + if (it.hint != null) { + console.log('NOTICE: toolbar item.hint property is deprecated, please use item.tooltip. Item -> ', it) + } + if (i === 0 || it.type == 'new-line') { + line++ + html += ` +
    +
    +
    ${this.right[line-1] ?? ''}
    +
    +
    +
    +
    + ` + } + it.line = line + } + query(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-toolbar') + .html(html) + if (query(this.box).length > 0) { + query(this.box)[0].style.cssText += this.style + } + w2utils.bindEvents(query(this.box).find('.w2ui-tb-line .w2ui-eaction'), this) + // observe div resize + this.last.observeResize = new ResizeObserver(() => { this.resize() }) + this.last.observeResize.observe(this.box) + // refresh all + this.refresh() + this.resize() + // event after + edata.finish() + return Date.now() - time + } + refresh(id) { + let time = Date.now() + // event before + let edata = this.trigger('refresh', { target: (id != null ? id : this.name), item: this.get(id) }) + if (edata.isCancelled === true) return + let edata2 + // refresh all + if (id == null) { + for (let i = 0; i < this.items.length; i++) { + let it1 = this.items[i] + if (it1.id == null) it1.id = 'item_' + i + this.refresh(it1.id) + } + return + } + // create or refresh only one item + let it = this.get(id) + if (it == null) return false + if (typeof it.onRefresh == 'function') { + edata2 = this.trigger('refresh', { target: id, item: it, object: it }) + if (edata2.isCancelled === true) return + } + let selector = `#tb_${this.name}_item_${w2utils.escapeId(it.id)}` + let btn = query(this.box).find(selector) + let html = this.getItemHTML(it) + // hide tooltip + this.tooltipHide(id) + // if there is a spacer, then right HTML is not 100% + if (it.type == 'spacer') { + query(this.box).find(`.w2ui-tb-line:nth-child(${it.line}`).find('.w2ui-tb-right').css('width', 'auto') + } + if (btn.length === 0) { + let next = parseInt(this.get(id, true)) + 1 + let $next = query(this.box).find(`#tb_${this.name}_item_${w2utils.escapeId(this.items[next] ? this.items[next].id : '')}`) + if ($next.length == 0) { + $next = query(this.box).find(`.w2ui-tb-line:nth-child(${it.line}`).find('.w2ui-tb-right').before(html) + } else { + $next.after(html) + } + w2utils.bindEvents(query(this.box).find(selector), this) + } else { + // refresh + query(this.box).find(selector).replace(query.html(html)) + let newBtn = query(this.box).find(selector).get(0) + w2utils.bindEvents(newBtn, this) + // update overlay's anchor if changed + let overlays = w2tooltip.get(true) + Object.keys(overlays).forEach(key => { + if (overlays[key].anchor == btn.get(0)) { + overlays[key].anchor = newBtn + } + }) + } + if (['menu', 'menu-radio', 'menu-check'].includes(it.type) && it.checked) { + // check selected items + let selected = Array.isArray(it.selected) ? it.selected : [it.selected] + it.items.forEach((item) => { + if (selected.includes(item.id)) item.checked = true; else item.checked = false + }) + w2menu.update(this.name + '-drop', it.items) + } + // event after + if (typeof it.onRefresh == 'function') { + edata2.finish() + } + edata.finish() + return Date.now() - time + } + resize() { + let time = Date.now() + // event before + let edata = this.trigger('resize', { target: this.name }) + if (edata.isCancelled === true) return + query(this.box).find('.w2ui-tb-line').each(el => { + // show hide overflow buttons + let box = query(el) + box.find('.w2ui-scroll-left, .w2ui-scroll-right').hide() + let scrollBox = box.find('.w2ui-scroll-wrapper').get(0) + let $right = box.find('.w2ui-tb-right') + let boxWidth = box.get(0).getBoundingClientRect().width + let itemsWidth = ($right.length > 0 ? $right[0].offsetLeft + $right[0].clientWidth : 0) + if (boxWidth < itemsWidth) { + // we have overflown content + if (scrollBox.scrollLeft > 0) { + box.find('.w2ui-scroll-left').show() + } + if (boxWidth < itemsWidth - scrollBox.scrollLeft) { + box.find('.w2ui-scroll-right').show() + } + } + }) + // event after + edata.finish() + return Date.now() - time + } + destroy() { + // event before + let edata = this.trigger('destroy', { target: this.name }) + if (edata.isCancelled === true) return + // clean up + if (query(this.box).find('.w2ui-scroll-wrapper .w2ui-tb-right').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-toolbar') + .html('') + } + query(this.box).html('') + this.last.observeResize?.disconnect() + delete w2ui[this.name] + // event after + edata.finish() + } + // ======================================== + // --- Internal Functions + getItemHTML(item) { + let html = '' + if (item.caption != null && item.text == null) item.text = item.caption // for backward compatibility + if (item.text == null) item.text = '' + if (item.tooltip == null && item.hint != null) item.tooltip = item.hint // for backward compatibility + if (item.tooltip == null) item.tooltip = '' + if (typeof item.get !== 'function' && (Array.isArray(item.items) || typeof item.items == 'function')) { + item.get = function get(id) { // need scope, cannot be arrow func + let tmp = item.items + if (typeof tmp == 'function') tmp = item.items(item) + return tmp.find(it => it.id == id ? true : false) + } + } + let icon = '' + let text = (typeof item.text == 'function' ? item.text.call(this, item) : item.text) + if (item.icon) { + icon = item.icon + if (typeof item.icon == 'function') { + icon = item.icon.call(this, item) + } + if (String(icon).slice(0, 1) !== '<') { + icon = `` + } + icon = `
    ${icon}
    ` + } + let classes = ['w2ui-tb-button'] + if (item.checked) classes.push('checked') + if (item.disabled) classes.push('disabled') + if (item.hidden) classes.push('hidden') + if (!icon) classes.push('no-icon') + switch (item.type) { + case 'color': + case 'text-color': + if (typeof item.color == 'string') { + if (item.color.slice(0, 1) == '#') item.color = item.color.slice(1) + if ([3, 6, 8].includes(item.color.length)) item.color = '#' + item.color + } + if (item.type == 'color') { + text = ` + ${(item.text ? `
    ${w2utils.lang(item.text)}
    ` : '')}` + } + if (item.type == 'text-color') { + text = ''+ + (item.text ? w2utils.lang(item.text) : 'Aa') + + '' + } + case 'menu': + case 'menu-check': + case 'menu-radio': + case 'button': + case 'check': + case 'radio': + case 'drop': { + let arrow = (item.arrow === true + || (item.arrow !== false && ['menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(item.type))) + html = ` +
    + ${ icon } + ${ text != '' + ? `
    + ${ w2utils.lang(text) } + ${ item.count != null + ? w2utils.stripSpaces(` + ${item.count} + `) + : '' + } + ${ arrow + ? '' + : '' + } +
    ` + : ''} +
    + ` + break + } + case 'break': + html = `
    +   +
    ` + break + case 'spacer': + html = `
    +
    ` + break + case 'html': + html = `
    + ${(typeof item.html == 'function' ? item.html.call(this, item) : item.html)} +
    ` + break + } + return html + } + tooltipShow(id) { + if (this.tooltip == null) return + let el = query(this.box).find('#tb_'+ this.name + '_item_'+ w2utils.escapeId(id)).get(0) + let item = this.get(id) + let pos = this.tooltip + let txt = item.tooltip + if (typeof txt == 'function') txt = txt.call(this, item) + // not for opened drop downs + if (['menu', 'menu-radio', 'menu-check', 'drop', 'color', 'text-color'].includes(item.type) + && item.checked == true) { + return + } + w2tooltip.show({ + anchor: el, + name: this.name + '-tooltip', + html: txt, + position: pos + }) + return + } + tooltipHide(id) { + if (this.tooltip == null) return + w2tooltip.hide(this.name + '-tooltip') + } + menuClick(event) { + if (event.item && !event.item.disabled) { + // event before + let edata = this.trigger((event.remove !== true ? 'click' : 'remove'), { + target: event.item.id + ':' + event.subItem.id, item: event.item, + subItem: event.subItem, originalEvent: event.originalEvent + }) + if (edata.isCancelled === true) return + // route processing + let it = event.subItem + let item = this.get(event.item.id) + let items = item.items + if (typeof items == 'function') items = item.items() + if (item.type == 'menu') { + item.selected = it.id + } + if (item.type == 'menu-radio') { + item.selected = it.id + if (Array.isArray(items)) { + items.forEach((item) => { + if (item.checked === true) delete item.checked + if (Array.isArray(item.items)) { + item.items.forEach((item) => { + if (item.checked === true) delete item.checked + }) + } + }) + } + it.checked = true + } + if (item.type == 'menu-check') { + if (!Array.isArray(item.selected)) item.selected = [] + if (it.group == null) { + let ind = item.selected.indexOf(it.id) + if (ind == -1) { + item.selected.push(it.id) + it.checked = true + } else { + item.selected.splice(ind, 1) + it.checked = false + } + } else if (it.group === false) { + // if group is false, then it is not part of checkboxes + } else { + let unchecked = [] + let ind = item.selected.indexOf(it.id) + let checkNested = (items) => { + items.forEach((sub) => { + if (sub.group === it.group) { + let ind = item.selected.indexOf(sub.id) + if (ind != -1) { + if (sub.id != it.id) unchecked.push(sub.id) + item.selected.splice(ind, 1) + } + } + if (Array.isArray(sub.items)) checkNested(sub.items) + }) + } + checkNested(items) + if (ind == -1) { + item.selected.push(it.id) + it.checked = true + } + } + } + if (typeof it.route == 'string') { + let route = it.route !== '' ? String('/'+ it.route).replace(/\/{2,}/g, '/') : '' + let info = w2utils.parseRoute(route) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + if (this.routeData[info.keys[k].name] == null) continue + route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]) + } + } + setTimeout(() => { window.location.hash = route }, 1) + } + this.refresh(event.item.id) + // event after + edata.finish() + } + } + colorClick(event) { + let obj = this + if (event.item && !event.item.disabled) { + // event before + let edata = this.trigger('click', { + target: event.item.id, item: event.item, + color: event.color, final: event.final, originalEvent: event.originalEvent + }) + if (edata.isCancelled === true) return + // default behavior + event.item.color = event.color + obj.refresh(event.item.id) + // event after + edata.finish() + } + } + mouseAction(event, target, action, id) { + let btn = this.get(id) + let edata = this.trigger('mouse' + action, { target: id, item: btn, object: btn, originalEvent: event }) + if (edata.isCancelled === true || btn.disabled || btn.hidden) return + switch (action) { + case 'Enter': + query(target).addClass('over') + this.tooltipShow(id) + break + case 'Leave': + query(target).removeClass('over down') + this.tooltipHide(id) + break + case 'Down': + query(target).addClass('down') + break + case 'Up': + query(target).removeClass('down') + break + } + edata.finish() + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2tooltip, w2menu + * + * == TODO == + * - dbl click should be like it is in grid (with timer not HTML dbl click event) + * - node.style is misleading - should be there to apply color for example + * - node.plus - is not working + * + * == 2.0 changes + * - remove jQuery dependency + * - deprecarted obj.img, node.img + * - CSP - fixed inline events + * - observeResize for the box + * - handleTooltip and handle.tooltip - text/function + * - added onMouseEntter, onMouseLeave events + */ + +class w2sidebar extends w2base { + constructor(options) { + super(options.name) + this.name = null + this.box = null + this.sidebar = null + this.parent = null + this.nodes = [] // Sidebar child nodes + this.menu = [] + this.routeData = {} // data for dynamic routes + this.selected = null // current selected node (readonly) + this.icon = null + this.style = '' + this.topHTML = '' + this.bottomHTML = '' + this.flatButton = false + this.keyboard = true + this.flat = false + this.hasFocus = false + this.levelPadding = 12 + this.skipRefresh = false + this.tabIndex = null // will only be set if > 0 and not null + this.handle = { size: 0, style: '', html: '', tooltip: '' }, + this.onClick = null // Fire when user click on Node Text + this.onDblClick = null // Fire when user dbl clicks + this.onMouseEnter = null // mouse enter/leave over an item + this.onMouseLeave = null + this.onContextMenu = null + this.onMenuClick = null // when context menu item selected + this.onExpand = null // Fire when node expands + this.onCollapse = null // Fire when node collapses + this.onKeydown = null + this.onRender = null + this.onRefresh = null + this.onResize = null + this.onDestroy = null + this.onFocus = null + this.onBlur = null + this.onFlat = null + this.node_template = { + id: null, + text: '', + order: null, + count: null, + icon: null, + nodes: [], + style: '', // additional style for subitems + route: null, + selected: false, + expanded: false, + hidden: false, + disabled: false, + group: false, // if true, it will build as a group + groupShowHide: true, + collapsible: false, + plus: false, // if true, plus will be shown even if there is no sub nodes + // events + onClick: null, + onDblClick: null, + onContextMenu: null, + onExpand: null, + onCollapse: null, + // internal + parent: null, // node object + sidebar: null + } + this.last = { + badge: {} + } + let nodes = options.nodes + delete options.nodes + // mix in options + Object.assign(this, options) + // add item via method to makes sure item_template is applied + if (Array.isArray(nodes)) this.add(nodes) + // need to reassign back to keep it in config + options.nodes = nodes + // render if box specified + if (typeof this.box == 'string') this.box = query(this.box).get(0) + if (this.box) this.render(this.box) + } + add(parent, nodes) { + if (arguments.length == 1) { + // need to be in reverse order + nodes = arguments[0] + parent = this + } + if (typeof parent == 'string') parent = this.get(parent) + if (parent == null || parent == '') parent = this + return this.insert(parent, null, nodes) + } + insert(parent, before, nodes) { + let txt, ind, tmp, node, nd + if (arguments.length == 2 && typeof parent == 'string') { + // need to be in reverse order + nodes = arguments[1] + before = arguments[0] + if (before != null) { + ind = this.get(before) + if (ind == null) { + if (!Array.isArray(nodes)) nodes = [nodes] + if (nodes[0].caption != null && nodes[0].text == null) { + console.log('NOTICE: sidebar node.caption property is deprecated, please use node.text. Node -> ', nodes[0]) + nodes[0].text = nodes[0].caption + } + txt = nodes[0].text + console.log('ERROR: Cannot insert node "'+ txt +'" because cannot find node "'+ before +'" to insert before.') + return null + } + parent = this.get(before).parent + } else { + parent = this + } + } + if (typeof parent == 'string') parent = this.get(parent) + if (parent == null || parent == '') parent = this + if (!Array.isArray(nodes)) nodes = [nodes] + for (let o = 0; o < nodes.length; o++) { + node = nodes[o] + if (node.caption != null && node.text == null) { + console.log('NOTICE: sidebar node.caption property is deprecated, please use node.text') + node.text = node.caption + } + if (typeof node.id == null) { + txt = node.text + console.log('ERROR: Cannot insert node "'+ txt +'" because it has no id.') + continue + } + if (this.get(this, node.id) != null) { + console.log('ERROR: Cannot insert node with id='+ node.id +' (text: '+ node.text + ') because another node with the same id already exists.') + continue + } + tmp = Object.assign({}, this.node_template, node) + tmp.sidebar = this + tmp.parent = parent + nd = tmp.nodes || [] + tmp.nodes = [] // very important to re-init empty nodes array + if (before == null) { // append to the end + parent.nodes.push(tmp) + } else { + ind = this.get(parent, before, true) + if (ind == null) { + console.log('ERROR: Cannot insert node "'+ node.text +'" because cannot find node "'+ before +'" to insert before.') + return null + } + parent.nodes.splice(ind, 0, tmp) + } + if (nd.length > 0) { + this.insert(tmp, null, nd) + } + } + if (!this.skipRefresh) this.refresh(parent.id) + return tmp + } + remove() { // multiple arguments + let effected = 0 + let node + Array.from(arguments).forEach(arg => { + node = this.get(arg) + if (node == null) return + if (this.selected != null && this.selected === node.id) { + this.selected = null + } + let ind = this.get(node.parent, arg, true) + if (ind == null) return + if (node.parent.nodes[ind].selected) node.sidebar.unselect(node.id) + node.parent.nodes.splice(ind, 1) + node.parent.collapsible = node.parent.nodes.length > 0 + effected++ + }) + if (!this.skipRefresh) { + if (effected > 0 && arguments.length == 1) this.refresh(node.parent.id); else this.refresh() + } + return effected + } + set(parent, id, node) { + if (arguments.length == 2) { + // need to be in reverse order + node = id + id = parent + parent = this + } + // searches all nested nodes + if (typeof parent == 'string') parent = this.get(parent) + if (parent.nodes == null) return null + for (let i = 0; i < parent.nodes.length; i++) { + if (parent.nodes[i].id === id) { + // see if quick update is possible + let res = this.update(id, node) + if (Object.keys(res).length != 0) { + // make sure nodes inserted correctly + let nodes = node.nodes + w2utils.extend(parent.nodes[i], node, { nodes: [] }) + if (nodes != null) { + this.add(parent.nodes[i], nodes) + } + if (!this.skipRefresh) this.refresh(id) + } + return true + } else { + let rv = this.set(parent.nodes[i], id, node) + if (rv) return true + } + } + return false + } + get(parent, id, returnIndex) { // can be just called get(id) or get(id, true) + if (arguments.length === 0) { + let all = [] + let tmp = this.find({}) + for (let t = 0; t < tmp.length; t++) { + if (tmp[t].id != null) all.push(tmp[t].id) + } + return all + } else { + if (arguments.length == 1 || (arguments.length == 2 && id === true) ) { + // need to be in reverse order + returnIndex = id + id = parent + parent = this + } + // searches all nested nodes + if (typeof parent == 'string') parent = this.get(parent) + if (parent.nodes == null) return null + for (let i = 0; i < parent.nodes.length; i++) { + if (parent.nodes[i].id == id) { + if (returnIndex === true) return i; else return parent.nodes[i] + } else { + let rv = this.get(parent.nodes[i], id, returnIndex) + if (rv || rv === 0) return rv + } + } + return null + } + } + setCount(id, count, className, style) { + let btn = query(this.box).find(`#node_${w2utils.escapeId(id)} .w2ui-node-count`) + if (btn.length > 0) { + btn.removeClass() + .addClass(`w2ui-node-count ${className || ''}`) + .text(count) + .get(0).style.cssText = style || '' + this.last.badge[id] = { + className: className || '', + style: style || '' + } + let item = this.get(id) + item.count = count + } else { + this.set(id, { count: count }) + this.setCount(...arguments) // to update styles + } + } + find(parent, params, results) { // can be just called find({ selected: true }) + // TODO: rewrite with this.each() + if (arguments.length == 1) { + // need to be in reverse order + params = parent + parent = this + } + if (!results) results = [] + // searches all nested nodes + if (typeof parent == 'string') parent = this.get(parent) + if (parent.nodes == null) return results + for (let i = 0; i < parent.nodes.length; i++) { + let match = true + for (let prop in params) { // params is an object + if (parent.nodes[i][prop] != params[prop]) match = false + } + if (match) results.push(parent.nodes[i]) + if (parent.nodes[i].nodes.length > 0) results = this.find(parent.nodes[i], params, results) + } + return results + } + sort(options, nodes) { + // default options + if (!options || typeof options != 'object') options = {} + if (options.foldersFirst == null) options.foldersFirst = true + if (options.caseSensitive == null) options.caseSensitive = false + if (options.reverse == null) options.reverse = false + if (nodes == null) { + nodes = this.nodes + } + nodes.sort((a, b) => { + // folders first + let isAfolder = (a.nodes && a.nodes.length > 0) + let isBfolder = (b.nodes && b.nodes.length > 0) + // both folder or both not folders + if (options.foldersFirst === false || (!isAfolder && !isBfolder) || (isAfolder && isBfolder)) { + let aText = a.text + let bText = b.text + if (!options.caseSensitive) { + aText = aText.toLowerCase() + bText = bText.toLowerCase() + } + if (a.order != null) aText = a.order + if (b.order != null) bText = b.order + let cmp = w2utils.naturalCompare(aText, bText) + return (cmp === 1 || cmp === -1) & options.reverse ? -cmp : cmp + } + if (isAfolder && !isBfolder) { + return !options.reverse ? -1 : 1 + } + if (!isAfolder && isBfolder) { + return !options.reverse ? 1 : -1 + } + }) + nodes.forEach(node => { + if (node.nodes && node.nodes.length > 0) { + this.sort(options, node.nodes) + } + }) + } + each(fn, nodes) { + if (nodes == null) nodes = this.nodes + nodes.forEach((node) => { + fn.call(this, node) + if (node.nodes && node.nodes.length > 0) { + this.each(fn, node.nodes) + } + }) + } + search(str) { + let count = 0 + let str2 = str.toLowerCase() + this.each((node) => { + if (node.text.toLowerCase().indexOf(str2) === -1) { + node.hidden = true + } else { + count++ + showParents(node) + node.hidden = false + } + }) + this.refresh() + return count + function showParents(node) { + if (node.parent) { + node.parent.hidden = false + showParents(node.parent) + } + } + } + show() { // multiple arguments + let effected = [] + Array.from(arguments).forEach(it => { + let node = this.get(it) + if (node == null || node.hidden === false) return + node.hidden = false + effected.push(node.id) + }) + if (effected.length > 0) { + if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh() + } + return effected + } + hide() { // multiple arguments + let effected = [] + Array.from(arguments).forEach(it => { + let node = this.get(it) + if (node == null || node.hidden === true) return + node.hidden = true + effected.push(node.id) + }) + if (effected.length > 0) { + if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh() + } + return effected + } + enable() { // multiple arguments + let effected = [] + Array.from(arguments).forEach(it => { + let node = this.get(it) + if (node == null || node.disabled === false) return + node.disabled = false + effected.push(node.id) + }) + if (effected.length > 0) { + if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh() + } + return effected + } + disable() { // multiple arguments + let effected = [] + Array.from(arguments).forEach(it => { + let node = this.get(it) + if (node == null || node.disabled === true) return + node.disabled = true + if (node.selected) this.unselect(node.id) + effected.push(node.id) + }) + if (effected.length > 0) { + if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh() + } + return effected + } + select(id) { + let new_node = this.get(id) + if (!new_node) return false + if (this.selected == id && new_node.selected) return false + this.unselect(this.selected) + let $el = query(this.box).find('#node_'+ w2utils.escapeId(id)) + $el.addClass('w2ui-selected') + .find('.w2ui-icon') + .addClass('w2ui-icon-selected') + if ($el.length > 0) { + if (!this.inView(id)) this.scrollIntoView(id) + } + new_node.selected = true + this.selected = id + return true + } + unselect(id) { + // if no arguments provided, unselect selected node + if (arguments.length === 0) { + id = this.selected + } + let current = this.get(id) + if (!current) return false + current.selected = false + query(this.box).find('#node_'+ w2utils.escapeId(id)) + .removeClass('w2ui-selected') + .find('.w2ui-icon').removeClass('w2ui-icon-selected') + if (this.selected == id) this.selected = null + return true + } + toggle(id) { + let nd = this.get(id) + if (nd == null) return false + if (nd.plus) { + this.set(id, { plus: false }) + this.expand(id) + this.refresh(id) + return + } + if (nd.nodes.length === 0) return false + if (!nd.collapsible) return false + if (this.get(id).expanded) return this.collapse(id); else return this.expand(id) + } + collapse(id) { + let self = this + let nd = this.get(id) + if (nd == null) return false + // event before + let edata = this.trigger('collapse', { target: id, object: nd }) + if (edata.isCancelled === true) return + // default action + query(this.box).find('#node_'+ w2utils.escapeId(id) +'_sub').hide() + query(this.box).find('#node_'+ w2utils.escapeId(id) +' .w2ui-expanded') + .removeClass('w2ui-expanded') + .addClass('w2ui-collapsed') + nd.expanded = false + // event after + edata.finish() + setTimeout(() => { self.refresh(id) }, 0) + return true + } + expand(id) { + let self = this + let nd = this.get(id) + // event before + let edata = this.trigger('expand', { target: id, object: nd }) + if (edata.isCancelled === true) return + // default action + query(this.box).find('#node_'+ w2utils.escapeId(id) +'_sub') + .show() + query(this.box).find('#node_'+ w2utils.escapeId(id) +' .w2ui-collapsed') + .removeClass('w2ui-collapsed') + .addClass('w2ui-expanded') + nd.expanded = true + // event after + edata.finish() + self.refresh(id) + return true + } + collapseAll(parent) { + if (parent == null) parent = this + if (typeof parent == 'string') parent = this.get(parent) + if (parent.nodes == null) return false + for (let i = 0; i < parent.nodes.length; i++) { + if (parent.nodes[i].expanded === true) parent.nodes[i].expanded = false + if (parent.nodes[i].nodes && parent.nodes[i].nodes.length > 0) this.collapseAll(parent.nodes[i]) + } + this.refresh(parent.id) + return true + } + expandAll(parent) { + if (parent == null) parent = this + if (typeof parent == 'string') parent = this.get(parent) + if (parent.nodes == null) return false + for (let i = 0; i < parent.nodes.length; i++) { + if (parent.nodes[i].expanded === false) parent.nodes[i].expanded = true + if (parent.nodes[i].nodes && parent.nodes[i].nodes.length > 0) this.expandAll(parent.nodes[i]) + } + this.refresh(parent.id) + } + expandParents(id) { + let node = this.get(id) + if (node == null) return false + if (node.parent) { + if (!node.parent.expanded) { + node.parent.expanded = true + this.refresh(node.parent.id) + } + this.expandParents(node.parent.id) + } + return true + } + click(id, event) { + let obj = this + let nd = this.get(id) + if (nd == null) return + if (nd.disabled || nd.group) return // should click event if already selected + // unselect all previously + query(obj.box).find('.w2ui-node.w2ui-selected').each(el => { + let oldID = query(el).attr('id').replace('node_', '') + let oldNode = obj.get(oldID) + if (oldNode != null) oldNode.selected = false + query(el).removeClass('w2ui-selected').find('.w2ui-icon').removeClass('w2ui-icon-selected') + }) + // select new one + let newNode = query(obj.box).find('#node_'+ w2utils.escapeId(id)) + let oldNode = query(obj.box).find('#node_'+ w2utils.escapeId(obj.selected)) + newNode.addClass('w2ui-selected').find('.w2ui-icon').addClass('w2ui-icon-selected') + // need timeout to allow rendering + setTimeout(() => { + // event before + let edata = obj.trigger('click', { target: id, originalEvent: event, node: nd, object: nd }) + if (edata.isCancelled === true) { + // restore selection + newNode.removeClass('w2ui-selected').find('.w2ui-icon').removeClass('w2ui-icon-selected') + oldNode.addClass('w2ui-selected').find('.w2ui-icon').addClass('w2ui-icon-selected') + return + } + // default action + if (oldNode != null) oldNode.selected = false + obj.get(id).selected = true + obj.selected = id + // route processing + if (typeof nd.route == 'string') { + let route = nd.route !== '' ? String('/'+ nd.route).replace(/\/{2,}/g, '/') : '' + let info = w2utils.parseRoute(route) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + if (obj.routeData[info.keys[k].name] == null) continue + route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), obj.routeData[info.keys[k].name]) + } + } + setTimeout(() => { window.location.hash = route }, 1) + } + // event after + edata.finish() + }, 1) + } + focus(event) { + let self = this + // event before + let edata = this.trigger('focus', { target: this.name, originalEvent: event }) + if (edata.isCancelled === true) return false + // default behaviour + this.hasFocus = true + query(this.box).find('.w2ui-sidebar-body').addClass('w2ui-focus') + setTimeout(() => { + let input = query(self.box).find('#sidebar_'+ self.name + '_focus').get(0) + if (document.activeElement != input) input.focus() + }, 10) + // event after + edata.finish() + } + blur(event) { + // event before + let edata = this.trigger('blur', { target: this.name, originalEvent: event }) + if (edata.isCancelled === true) return false + // default behaviour + this.hasFocus = false + query(this.box).find('.w2ui-sidebar-body').removeClass('w2ui-focus') + // event after + edata.finish() + } + keydown(event) { + let obj = this + let nd = obj.get(obj.selected) + if (obj.keyboard !== true) return + if (!nd) nd = obj.nodes[0] + // trigger event + let edata = obj.trigger('keydown', { target: obj.name, originalEvent: event }) + if (edata.isCancelled === true) return + // default behaviour + if (event.keyCode == 13 || event.keyCode == 32) { // enter or space + if (nd.nodes.length > 0) obj.toggle(obj.selected) + } + if (event.keyCode == 37) { // left + if (nd.nodes.length > 0 && nd.expanded) { + obj.collapse(obj.selected) + } else { + selectNode(nd.parent) + if (!nd.parent.group) obj.collapse(nd.parent.id) + } + } + if (event.keyCode == 39) { // right + if ((nd.nodes.length > 0 || nd.plus) && !nd.expanded) obj.expand(obj.selected) + } + if (event.keyCode == 38) { // up + if (obj.get(obj.selected) == null) { + selectNode(this.nodes[0] || null) + } else { + selectNode(neighbor(nd, prev)) + } + } + if (event.keyCode == 40) { // down + if (obj.get(obj.selected) == null) { + selectNode(this.nodes[0] || null) + } else { + selectNode(neighbor(nd, next)) + } + } + // cancel event if needed + if ([13, 32, 37, 38, 39, 40].includes(event.keyCode)) { + if (event.preventDefault) event.preventDefault() + if (event.stopPropagation) event.stopPropagation() + } + // event after + edata.finish() + function selectNode(node, event) { + if (node != null && !node.hidden && !node.disabled && !node.group) { + obj.click(node.id, event) + if (!obj.inView(node.id)) obj.scrollIntoView(node.id) + } + } + function neighbor(node, neighborFunc) { + node = neighborFunc(node) + while (node != null && (node.hidden || node.disabled)) { + if (node.group) break; else node = neighborFunc(node) + } + return node + } + function next(node, noSubs) { + if (node == null) return null + let parent = node.parent + let ind = obj.get(node.id, true) + let nextNode = null + // jump inside + if (node.expanded && node.nodes.length > 0 && noSubs !== true) { + let t = node.nodes[0] + if (t.hidden || t.disabled || t.group) nextNode = next(t); else nextNode = t + } else { + if (parent && ind + 1 < parent.nodes.length) { + nextNode = parent.nodes[ind + 1] + } else { + nextNode = next(parent, true) // jump to the parent + } + } + if (nextNode != null && (nextNode.hidden || nextNode.disabled || nextNode.group)) nextNode = next(nextNode) + return nextNode + } + function prev(node) { + if (node == null) return null + let parent = node.parent + let ind = obj.get(node.id, true) + let prevNode = (ind > 0) ? lastChild(parent.nodes[ind - 1]) : parent + if (prevNode != null && (prevNode.hidden || prevNode.disabled || prevNode.group)) prevNode = prev(prevNode) + return prevNode + } + function lastChild(node) { + if (node.expanded && node.nodes.length > 0) { + let t = node.nodes[node.nodes.length - 1] + if (t.hidden || t.disabled || t.group) return prev(t); else return lastChild(t) + } + return node + } + } + inView(id) { + let item = query(this.box).find('#node_'+ w2utils.escapeId(id)).get(0) + if (!item) { + return false + } + let div = query(this.box).find('.w2ui-sidebar-body').get(0) + if (item.offsetTop < div.scrollTop || (item.offsetTop + item.clientHeight > div.clientHeight + div.scrollTop)) { + return false + } + return true + } + scrollIntoView(id, instant) { + return new Promise((resolve, reject) => { + if (id == null) id = this.selected + let nd = this.get(id) + if (nd == null) return + let item = query(this.box).find('#node_'+ w2utils.escapeId(id)).get(0) + item.scrollIntoView({ block: 'center', inline: 'center', behavior: instant ? 'atuo' : 'smooth' }) + setTimeout(() => { this.resize(); resolve() }, instant ? 0 : 500) + }) + } + dblClick(id, event) { + let nd = this.get(id) + // event before + let edata = this.trigger('dblClick', { target: id, originalEvent: event, object: nd }) + if (edata.isCancelled === true) return + // default action + this.toggle(id) + // event after + edata.finish() + } + contextMenu(id, event) { + let nd = this.get(id) + if (id != this.selected) this.click(id) + // event before + let edata = this.trigger('contextMenu', { target: id, originalEvent: event, object: nd, allowOnDisabled: false }) + if (edata.isCancelled === true) return + // default action + if (nd.disabled && !edata.allowOnDisabled) return + if (this.menu.length > 0) { + w2menu.show({ + name: this.name + '_menu', + anchor: document.body, + items: this.menu, + originalEvent: event + }) + .select(evt => { + this.menuClick(id, parseInt(evt.detail.index), event) + }) + } + // prevent default context menu + if (event.preventDefault) event.preventDefault() + // event after + edata.finish() + } + menuClick(itemId, index, event) { + // event before + let edata = this.trigger('menuClick', { target: itemId, originalEvent: event, menuIndex: index, menuItem: this.menu[index] }) + if (edata.isCancelled === true) return + // default action + // -- empty + // event after + edata.finish() + } + goFlat() { + // event before + let edata = this.trigger('flat', { goFlat: !this.flat }) + if (edata.isCancelled === true) return + // default action + this.flat = !this.flat + this.refresh() + // event after + edata.finish() + } + render(box) { + let time = Date.now() + let obj = this + if (typeof box == 'string') box = query(box).get(0) + // event before + let edata = this.trigger('render', { target: this.name, box: box ?? this.box }) + if (edata.isCancelled === true) return + // default action + if (box != null) { + // clean previous box + if (query(this.box).find('.w2ui-sidebar-body').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-sidebar') + .html('') + } + this.box = box + } + if (!this.box) return + query(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-sidebar') + .html(`
    +
    + +
    +
    +
    `) + let rect = query(this.box).get(0).getBoundingClientRect() + query(this.box).find(':scope > div').css({ + width : rect.width + 'px', + height : rect.height + 'px' + }) + query(this.box).get(0).style.cssText += this.style + // focus + let kbd_timer + query(this.box).find('#sidebar_'+ this.name + '_focus') + .on('focus', function(event) { + clearTimeout(kbd_timer) + if (!obj.hasFocus) obj.focus(event) + }) + .on('blur', function(event) { + kbd_timer = setTimeout(() => { + if (obj.hasFocus) { obj.blur(event) } + }, 100) + }) + .on('keydown', function(event) { + if (event.keyCode != 9) { // not tab + w2ui[obj.name].keydown.call(w2ui[obj.name], event) + } + }) + query(this.box).off('mousedown') + .on('mousedown', function(event) { + // set focus to grid + setTimeout(() => { + // if input then do not focus + if (['INPUT', 'TEXTAREA', 'SELECT'].indexOf(event.target.tagName.toUpperCase()) == -1) { + let $input = query(obj.box).find('#sidebar_'+ obj.name + '_focus') + if (document.activeElement != $input.get(0)) { + $input.get(0).focus() + } + } + }, 1) + }) + // observe div resize + this.last.observeResize = new ResizeObserver(() => { this.resize() }) + this.last.observeResize.observe(this.box) + // event after + edata.finish() + // --- + this.refresh() + return Date.now() - time + } + update(id, options) { + // quick function to refresh just this item (not sub nodes) + // - icon, class, style, text, count + let nd = this.get(id) + let level + if (nd) { + let $el = query(this.box).find('#node_'+ w2utils.escapeId(nd.id)) + if (nd.group) { + if (options.text) { + nd.text = options.text + $el.find('.w2ui-group-text').replace(typeof nd.text == 'function' + ? nd.text.call(this, nd) + : ''+ nd.text +'') + delete options.text + } + if (options.class) { + nd.class = options.class + level = $el.data('level') + $el.get(0).className = 'w2ui-node-group w2ui-level-'+ level +(nd.class ? ' ' + nd.class : '') + delete options.class + } + if (options.style) { + nd.style = options.style + $el.get(0).nextElementSibling.style = nd.style +';'+ (!nd.hidden && nd.expanded ? '' : 'display: none;') + delete options.style + } + } else { + if (options.icon) { + let $icon = $el.find('.w2ui-node-image > span') + if ($icon.length > 0) { + nd.icon = options.icon + $icon[0].className = (typeof nd.icon == 'function' ? nd.icon.call(this, nd) : nd.icon) + delete options.icon + } + } + if (options.count) { + nd.count = options.count + $el.find('.w2ui-node-count').html(nd.count) + if ($el.find('.w2ui-node-count').length > 0) delete options.count + } + if (options.class && $el.length > 0) { + nd.class = options.class + level = $el.data('level') + $el[0].className = 'w2ui-node w2ui-level-'+ level + (nd.selected ? ' w2ui-selected' : '') + (nd.disabled ? ' w2ui-disabled' : '') + (nd.class ? ' ' + nd.class : '') + delete options.class + } + if (options.text) { + nd.text = options.text + $el.find('.w2ui-node-text').html(typeof nd.text == 'function' ? nd.text.call(this, nd) : nd.text) + delete options.text + } + if (options.style && $el.length > 0) { + let $txt = $el.find('.w2ui-node-text') + nd.style = options.style + $txt[0].style = nd.style + delete options.style + } + } + } + // return what was not set + return options + } + refresh(id, noBinding) { + if (this.box == null) return + let time = Date.now() + // event before + let edata = this.trigger('refresh', { + target: (id != null ? id : this.name), + nodeId: (id != null ? id : null), + fullRefresh: (id != null ? false : true) + }) + if (edata.isCancelled === true) return + // adjust top and bottom + let flatHTML = '' + if (this.flatButton == true) { + flatHTML = `
    ` + } + if (id == null && (this.topHTML !== '' || flatHTML !== '')) { + query(this.box).find('.w2ui-sidebar-top').html(this.topHTML + flatHTML) + query(this.box).find('.w2ui-sidebar-body') + .css('top', query(this.box).find('.w2ui-sidebar-top').get(0)?.clientHeight + 'px') + query(this.box).find('.w2ui-flat') + .off('click') + .on('click', event => { this.goFlat() }) + } + if (id != null && this.bottomHTML !== '') { + query(this.box).find('.w2ui-sidebar-bottom').html(this.bottomHTML) + query(this.box).find('.w2ui-sidebar-body') + .css('bottom', query(this.box).find('.w2ui-sidebar-bottom').get(0)?.clientHeight + 'px') + } + // default action + query(this.box).find(':scope > div').removeClass('w2ui-sidebar-flat').addClass(this.flat ? 'w2ui-sidebar-flat' : '').css({ + width : query(this.box).get(0)?.clientWidth + 'px', + height: query(this.box).get(0)?.clientHeight + 'px' + }) + // if no parent - reset nodes + if (this.nodes.length > 0 && this.nodes[0].parent == null) { + let tmp = this.nodes + this.nodes = [] + this.add(this, tmp) + } + let obj = this + let node + let nodeSubId + if (id == null) { + node = this + nodeSubId = '.w2ui-sidebar-body' + } else { + node = this.get(id) + if (node == null) return + nodeSubId = '#node_'+ w2utils.escapeId(node.id) + '_sub' + } + let nodeId = '#node_'+ w2utils.escapeId(node.id) + let nodeHTML + if (node !== this) { + nodeHTML = getNodeHTML(node) + query(this.box).find(nodeId).before('') + query(this.box).find(nodeId).remove() + query(this.box).find(nodeSubId).remove() + query(this.box).find('#sidebar_'+ this.name + '_tmp').before(nodeHTML) + query(this.box).find('#sidebar_'+ this.name + '_tmp').remove() + } + // remember scroll position + let div = query(this.box).find(':scope > div').get(0) + let scroll = { + top: div?.scrollTop, + left: div?.scrollLeft + } + // refresh sub nodes + query(this.box).find(nodeSubId).html('') + for (let i = 0; i < node.nodes.length; i++) { + let subNode = node.nodes[i] + nodeHTML = getNodeHTML(subNode) + query(this.box).find(nodeSubId).append(nodeHTML) + if (subNode.nodes.length !== 0) { + this.refresh(subNode.id, true) + } else { + // trigger event + let edata2 = this.trigger('refresh', { target: subNode.id }) + if (edata2.isCancelled === true) return + // event after + edata2.finish() + } + } + // reset scroll + if (div) { + div.scrollTop = scroll.top + div.scrollLeft = scroll.left + } + // bind events + if (!noBinding) { + let els = query(this.box).find(`${nodeId}.w2ui-eaction, ${nodeSubId} .w2ui-eaction`) + w2utils.bindEvents(els, this) + } + // event after + edata.finish() + return Date.now() - time + function getNodeHTML(nd) { + let html = '' + let icon = nd.icon + if (icon == null) icon = obj.icon + // -- find out level + let tmp = nd.parent + let level = 0 + while (tmp && tmp.parent != null) { + // if (tmp.group) level--; + tmp = tmp.parent + level++ + } + if (nd.caption != null && nd.text == null) nd.text = nd.caption + if (nd.caption != null) { + console.log('NOTICE: sidebar node.caption property is deprecated, please use node.text. Node -> ', nd) + nd.text = nd.caption + } + if (Array.isArray(nd.nodes) && nd.nodes.length > 0) nd.collapsible = true + if (nd.group) { + let text = w2utils.lang(typeof nd.text == 'function' ? nd.text.call(obj, nd) : nd.text) + if (String(text).substr(0, 5) != '${text}` + } + html = ` +
    + ${nd.groupShowHide && nd.collapsible + ? `${!nd.hidden && nd.expanded ? w2utils.lang('Hide') : w2utils.lang('Show')}` + : '' + } ${text} +
    +
    +
    ` + if (obj.flat) { + html = ` +
     
    +
    ` + } + } else { + if (nd.selected && !nd.disabled) obj.selected = nd.id + tmp = '' + if (icon) { + tmp = ` +
    + +
    ` + } + let expand = '' + let counts = (nd.count != null + ? `
    + ${nd.count} +
    ` + : '') + if (nd.collapsible === true) { + expand = `
    ` + } + let text = w2utils.lang(typeof nd.text == 'function' ? nd.text.call(obj, nd) : nd.text) + // array with classes + let classes = ['w2ui-node', `w2ui-level-${level}`, 'w2ui-eaction'] + if (nd.selected) classes.push('w2ui-selected') + if (nd.disabled) classes.push('w2ui-disabled') + if (nd.class) classes.push(nd.class) + html = ` +
    + ${obj.handle.html + ? `
    + ${typeof obj.handle.html == 'function' ? obj.handle.html.call(obj, nd) : obj.handle.html} +
    ` + : '' + } +
    + ${expand} ${tmp} ${counts} +
    ${text}
    +
    +
    +
    ` + if (obj.flat) { + html = ` +
    +
    ${tmp}
    +
    +
    ` + } + } + return html + } + } + mouseAction(action, el, id, event, type) { + let node = this.get(id) + let text = w2utils.lang(typeof node.text == 'function' ? node.text.call(this, node) : node.text) + let tooltip = text + (node.count || node.count === 0 ? ' - '+ node.count +'' : '') + let edata = this.trigger('mouse' + action, { target: id, node, tooltip, originalEvent: event }) + if (type == 'tooltip') { + this.tooltip(el, tooltip, id) + } + if (type == 'handle') { + this.handleTooltip(el, id) + } + edata.finish() + } + tooltip(el, text, id) { + let $el = query(el).find('.w2ui-node-data') + if (text !== '') { + w2tooltip.show({ + anchor: $el.get(0), + name: this.name + '_tooltip', + html: text, + position: 'right|left' + }) + } else { + w2tooltip.hide(this.name + '_tooltip') + } + } + handleTooltip(anchor, id) { + let text = this.handle.tooltip + if (typeof text == 'function') { + text = text(id) + } + if (text !== '' && id != null) { + w2tooltip.show({ + anchor: anchor, + name: this.name + '_tooltip', + html: text, + position: 'top|bottom' + }) + } else { + w2tooltip.hide(this.name + '_tooltip') + } + } + showPlus(el, color) { + query(el).find('span:nth-child(1)').css('color', color) + } + resize() { + let time = Date.now() + // event before + let edata = this.trigger('resize', { target: this.name }) + if (edata.isCancelled === true) return + // default action + let rect = query(this.box).get(0).getBoundingClientRect() + query(this.box).css('overflow', 'hidden') // container should have no overflow + query(this.box).find(':scope > div').css({ + width : rect.width + 'px', + height : rect.height + 'px' + }) + // event after + edata.finish() + return Date.now() - time + } + destroy() { + // event before + let edata = this.trigger('destroy', { target: this.name }) + if (edata.isCancelled === true) return + // clean up + if (query(this.box).find('.w2ui-sidebar-body').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-sidebar') + .html('') + } + this.last.observeResize?.disconnect() + delete w2ui[this.name] + // event after + edata.finish() + } + lock(msg, showSpinner) { + let args = Array.from(arguments) + args.unshift(this.box) + w2utils.lock(...args) + } + unlock(speed) { + w2utils.unlock(this.box, speed) + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2tooltip + * + * == 2.0 changes + * - CSP - fixed inline events + * - removed jQuery dependency + * - observeResize for the box + * - refactored w2events + * - scrollIntoView - removed callback + * - scroll, scrollIntoView return promise + * - animateInsert, animateClose - returns a promise + * - add, insert return a promise + * - onMouseEnter, onMouseLeave, onMouseDown, onMouseUp + */ + +class w2tabs extends w2base { + constructor(options) { + super(options.name) + this.box = null // DOM Element that holds the element + this.name = null // unique name for w2ui + this.active = null + this.reorder = false + this.flow = 'down' // can be down or up + this.tooltip = 'top|left' // can be top, bottom, left, right + this.tabs = [] + this.routeData = {} // data for dynamic routes + this.last = {} // placeholder for internal variables + this.right = '' + this.style = '' + this.onClick = null + this.onMouseEnter = null // mouse enter and lease + this.onMouseLeave = null + this.onMouseDown = null + this.onMouseUp = null + this.onClose = null + this.onRender = null + this.onRefresh = null + this.onResize = null + this.onDestroy = null + this.tab_template = { + id: null, + text: null, + route: null, + hidden: false, + disabled: false, + closable: false, + tooltip: null, + style: '', + onClick: null, + onRefresh: null, + onClose: null + } + let tabs = options.tabs + delete options.tabs + // mix in options + Object.assign(this, options) + // add item via method to makes sure item_template is applied + if (Array.isArray(tabs)) this.add(tabs) + // need to reassign back to keep it in config + options.tabs = tabs + // render if box specified + if (typeof this.box == 'string') this.box = query(this.box).get(0) + if (this.box) this.render(this.box) + } + add(tab) { + return this.insert(null, tab) + } + insert(id, tabs) { + if (!Array.isArray(tabs)) tabs = [tabs] + // assume it is array + let proms = [] + tabs.forEach(tab => { + // checks + if (tab.id == null) { + console.log(`ERROR: The parameter "id" is required but not supplied. (obj: ${this.name})`) + return + } + if (!w2utils.checkUniqueId(tab.id, this.tabs, 'tabs', this.name)) return + // add tab + let it = Object.assign({}, this.tab_template, tab) + if (id == null) { + this.tabs.push(it) + proms.push(this.animateInsert(null, it)) + } else { + let middle = this.get(id, true) + let before = this.tabs[middle].id + this.tabs.splice(middle, 0, it) + proms.push(this.animateInsert(before, it)) + } + }) + return Promise.all(proms) + } + remove() { + let effected = 0 + Array.from(arguments).forEach(it => { + let tab = this.get(it) + if (!tab) return + effected++ + // remove from array + this.tabs.splice(this.get(tab.id, true), 1) + // remove from screen + query(this.box).find(`#tabs_${this.name}_tab_${w2utils.escapeId(tab.id)}`).remove() + }) + this.resize() + return effected + } + select(id) { + if (this.active == id || this.get(id) == null) return false + this.active = id + this.refresh() + return true + } + set(id, tab) { + let index = this.get(id, true) + if (index == null) return false + w2utils.extend(this.tabs[index], tab) + this.refresh(id) + return true + } + get(id, returnIndex) { + if (arguments.length === 0) { + let all = [] + for (let i1 = 0; i1 < this.tabs.length; i1++) { + if (this.tabs[i1].id != null) { + all.push(this.tabs[i1].id) + } + } + return all + } else { + for (let i2 = 0; i2 < this.tabs.length; i2++) { + if (this.tabs[i2].id == id) { // need to be == since id can be numeric + return (returnIndex === true ? i2 : this.tabs[i2]) + } + } + } + return null + } + show() { + let effected = [] + Array.from(arguments).forEach(it => { + let tab = this.get(it) + if (!tab || tab.hidden === false) return + tab.hidden = false + effected.push(tab.id) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it); this.resize() }) }, 15) // needs timeout + return effected + } + hide() { + let effected = [] + Array.from(arguments).forEach(it => { + let tab = this.get(it) + if (!tab || tab.hidden === true) return + tab.hidden = true + effected.push(tab.id) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it); this.resize() }) }, 15) // needs timeout + return effected + } + enable() { + let effected = [] + Array.from(arguments).forEach(it => { + let tab = this.get(it) + if (!tab || tab.disabled === false) return + tab.disabled = false + effected.push(tab.id) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it) }) }, 15) // needs timeout + return effected + } + disable() { + let effected = [] + Array.from(arguments).forEach(it => { + let tab = this.get(it) + if (!tab || tab.disabled === true) return + tab.disabled = true + effected.push(tab.id) + }) + setTimeout(() => { effected.forEach(it => { this.refresh(it) }) }, 15) // needs timeout + return effected + } + dragMove(event) { + if (!this.last.reordering) return + let self = this + let info = this.last.moving + let tab = this.tabs[info.index] + let next = _find(info.index, 1) + let prev = _find(info.index, -1) + let $el = query(this.box).find('#tabs_'+ this.name + '_tab_'+ w2utils.escapeId(tab.id)) + if (info.divX > 0 && next) { + let $nextEl = query(this.box).find('#tabs_'+ this.name + '_tab_'+ w2utils.escapeId(next.id)) + let width1 = parseInt($el.get(0).clientWidth) + let width2 = parseInt($nextEl.get(0).clientWidth) + if (width1 < width2) { + width1 = Math.floor(width1 / 3) + width2 = width2 - width1 + } else { + width1 = Math.floor(width2 / 3) + width2 = width2 - width1 + } + if (info.divX > width2) { + let index = this.tabs.indexOf(next) + this.tabs.splice(info.index, 0, this.tabs.splice(index, 1)[0]) // reorder in the array + info.$tab.before($nextEl.get(0)) + info.$tab.css('opacity', 0) + Object.assign(this.last.moving, { + index: index, + divX: -width1, + x: event.pageX + width1, + left: info.left + info.divX + width1 + }) + return + } + } + if (info.divX < 0 && prev) { + let $prevEl = query(this.box).find('#tabs_'+ this.name + '_tab_'+ w2utils.escapeId(prev.id)) + let width1 = parseInt($el.get(0).clientWidth) + let width2 = parseInt($prevEl.get(0).clientWidth) + if (width1 < width2) { + width1 = Math.floor(width1 / 3) + width2 = width2 - width1 + } else { + width1 = Math.floor(width2 / 3) + width2 = width2 - width1 + } + if (Math.abs(info.divX) > width2) { + let index = this.tabs.indexOf(prev) + this.tabs.splice(info.index, 0, this.tabs.splice(index, 1)[0]) // reorder in the array + $prevEl.before(info.$tab) + info.$tab.css('opacity', 0) + Object.assign(info, { + index: index, + divX: width1, + x: event.pageX - width1, + left: info.left + info.divX - width1 + }) + return + } + } + function _find(ind, inc) { + ind += inc + let tab = self.tabs[ind] + if (tab && tab.hidden) { + tab = _find(ind, inc) + } + return tab + } + } + mouseAction(action, id, event) { + let tab = this.get(id) + let edata = this.trigger('mouse' + action, { target: id, tab, object: tab, originalEvent: event }) + if (edata.isCancelled === true || tab.disabled || tab.hidden) return + switch (action) { + case 'Enter': + this.tooltipShow(id) + break + case 'Leave': + this.tooltipHide(id) + break + case 'Down': + this.initReorder(id, event) + break + case 'Up': + break + } + edata.finish() + } + tooltipShow(id) { + let item = this.get(id) + let el = query(this.box).find('#tabs_'+ this.name + '_tab_'+ w2utils.escapeId(id)).get(0) + if (this.tooltip == null || item.disabled || this.last.reordering) { + return + } + let pos = this.tooltip + let txt = item.tooltip + if (typeof txt == 'function') txt = txt.call(this, item) + w2tooltip.show({ + anchor: el, + name: this.name + '_tooltip', + html: txt, + position: pos + }) + } + tooltipHide(id) { + if (this.tooltip == null) return + w2tooltip.hide(this.name + '_tooltip') + } + getTabHTML(id) { + let index = this.get(id, true) + let tab = this.tabs[index] + if (tab == null) return false + if (tab.text == null && tab.caption != null) tab.text = tab.caption + if (tab.tooltip == null && tab.hint != null) tab.tooltip = tab.hint // for backward compatibility + if (tab.caption != null) { + console.log('NOTICE: tabs tab.caption property is deprecated, please use tab.text. Tab -> ', tab) + } + if (tab.hint != null) { + console.log('NOTICE: tabs tab.hint property is deprecated, please use tab.tooltip. Tab -> ', tab) + } + let text = tab.text + if (typeof text == 'function') text = text.call(this, tab) + if (text == null) text = '' + let closable = '' + let addStyle = '' + if (tab.hidden) { addStyle += 'display: none;' } + if (tab.disabled) { addStyle += 'opacity: 0.2;' } + if (tab.closable && !tab.disabled) { + closable = `
    +
    ` + } + return ` +
    + ${w2utils.lang(text) + closable} +
    ` + } + refresh(id) { + let time = Date.now() + if (this.flow == 'up') { + query(this.box).addClass('w2ui-tabs-up') + } else { + query(this.box).removeClass('w2ui-tabs-up') + } + // event before + let edata = this.trigger('refresh', { target: (id != null ? id : this.name), object: this.get(id) }) + if (edata.isCancelled === true) return + if (id == null) { + // refresh all + for (let i = 0; i < this.tabs.length; i++) { + this.refresh(this.tabs[i].id) + } + } else { + // create or refresh only one item + let selector = '#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(id) + let $tab = query(this.box).find(selector) + let tabHTML = this.getTabHTML(id) + if ($tab.length === 0) { + query(this.box).find('#tabs_'+ this.name +'_right').before(tabHTML) + } else { + if (query(this.box).find('.tab-animate-insert').length == 0) { + $tab.replace(tabHTML) + } + } + w2utils.bindEvents(query(this.box).find(`${selector}, ${selector} .w2ui-eaction`), this) + } + // right html + query(this.box).find('#tabs_'+ this.name +'_right').html(this.right) + // event after + edata.finish() + // this.resize(); + return Date.now() - time + } + render(box) { + let time = Date.now() + if (typeof box == 'string') box = query(box).get(0) + // event before + let edata = this.trigger('render', { target: this.name, box: box ?? this.box }) + if (edata.isCancelled === true) return + // default action + if (box != null) { + // clean previous box + if (query(this.box).find('#tabs_'+ this.name + '_right').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-tabs') + .html('') + } + this.box = box + } + if (!this.box) return false + // render all buttons + let html =` +
    +
    +
    ${this.right}
    +
    +
    +
    ` + query(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-tabs') + .html(html) + if (query(this.box).length > 0) { + query(this.box)[0].style.cssText += this.style + } + w2utils.bindEvents(query(this.box).find('.w2ui-eaction'), this) + // observe div resize + this.last.observeResize = new ResizeObserver(() => { this.resize() }) + this.last.observeResize.observe(this.box) + // event after + edata.finish() + this.refresh() + this.resize() + return Date.now() - time + } + initReorder(id, event) { + if (!this.reorder) return + let self = this + let $tab = query(this.box).find('#tabs_' + this.name + '_tab_' + w2utils.escapeId(id)) + let tabIndex = this.get(id, true) + let $ghost = query($tab.get(0).cloneNode(true)) + let edata + $ghost.attr('id', '#tabs_' + this.name + '_tab_ghost') + this.last.moving = { + index: tabIndex, + indexFrom: tabIndex, + $tab: $tab, + $ghost: $ghost, + divX: 0, + left: $tab.get(0).getBoundingClientRect().left, + parentX: query(this.box).get(0).getBoundingClientRect().left, + x: event.pageX, + opacity: $tab.css('opacity') + } + query(document) + .off('.w2uiTabReorder') + .on('mousemove.w2uiTabReorder', function (event) { + if (!self.last.reordering) { + // event before + edata = self.trigger('reorder', { target: self.tabs[tabIndex].id, indexFrom: tabIndex, tab: self.tabs[tabIndex] }) + if (edata.isCancelled === true) return + w2tooltip.hide(this.name + '_tooltip') + self.last.reordering = true + $ghost.addClass('moving') + $ghost.css({ + 'pointer-events': 'none', + 'position': 'absolute', + 'left': $tab.get(0).getBoundingClientRect().left + }) + $tab.css('opacity', 0) + query(self.box).find('.w2ui-scroll-wrapper').append($ghost.get(0)) + query(self.box).find('.w2ui-tab-close').hide() + } + self.last.moving.divX = event.pageX - self.last.moving.x + $ghost.css('left', (self.last.moving.left - self.last.moving.parentX + self.last.moving.divX) + 'px') + self.dragMove(event) + }) + .on('mouseup.w2uiTabReorder', function () { + query(document).off('.w2uiTabReorder') + $ghost.css({ + 'transition': '0.1s', + 'left': self.last.moving.$tab.get(0).getBoundingClientRect().left - self.last.moving.parentX + }) + query(self.box).find('.w2ui-tab-close').show() + setTimeout(() => { + $ghost.remove() + $tab.css({ opacity: self.last.moving.opacity }) + // self.render() + if (self.last.reordering) { + edata.finish({ indexTo: self.last.moving.index }) + } + self.last.reordering = false + }, 100) + }) + } + scroll(direction, instant) { + return new Promise((resolve, reject) => { + let scrollBox = query(this.box).find('.w2ui-scroll-wrapper') + let scrollLeft = scrollBox.get(0).scrollLeft + let right = scrollBox.find('.w2ui-tabs-right').get(0) + let width1 = scrollBox.parent().get(0).getBoundingClientRect().width + let width2 = scrollLeft + parseInt(right.offsetLeft) + parseInt(right.clientWidth ) + switch (direction) { + case 'left': { + let scroll = scrollLeft - width1 + 50 // 35 is width of both button + if (scroll <= 0) scroll = 0 + scrollBox.get(0).scrollTo({ top: 0, left: scroll, behavior: instant ? 'atuo' : 'smooth' }) + break + } + case 'right': { + let scroll = scrollLeft + width1 - 50 // 35 is width of both button + if (scroll >= width2 - width1) scroll = width2 - width1 + scrollBox.get(0).scrollTo({ top: 0, left: scroll, behavior: instant ? 'atuo' : 'smooth' }) + break + } + } + setTimeout(() => { this.resize(); resolve() }, instant ? 0 : 350) + }) + } + scrollIntoView(id, instant) { + return new Promise((resolve, reject) => { + if (id == null) id = this.active + let tab = this.get(id) + if (tab == null) return + let tabEl = query(this.box).find('#tabs_' + this.name + '_tab_' + w2utils.escapeId(id)).get(0) + tabEl.scrollIntoView({ block: 'start', inline: 'center', behavior: instant ? 'atuo' : 'smooth' }) + setTimeout(() => { this.resize(); resolve() }, instant ? 0 : 500) + }) + } + resize() { + let time = Date.now() + if (this.box == null) return + // event before + let edata = this.trigger('resize', { target: this.name }) + if (edata.isCancelled === true) return + // show hide overflow buttons + let box = query(this.box) + box.find('.w2ui-scroll-left, .w2ui-scroll-right').hide() + let scrollBox = box.find('.w2ui-scroll-wrapper').get(0) + let $right = box.find('.w2ui-tabs-right') + let boxWidth = box.get(0).getBoundingClientRect().width + let itemsWidth = ($right.length > 0 ? $right[0].offsetLeft + $right[0].clientWidth : 0) + if (boxWidth < itemsWidth) { + // we have overflown content + if (scrollBox.scrollLeft > 0) { + box.find('.w2ui-scroll-left').show() + } + if (boxWidth < itemsWidth - scrollBox.scrollLeft) { + box.find('.w2ui-scroll-right').show() + } + } + // event after + edata.finish() + return Date.now() - time + } + destroy() { + // event before + let edata = this.trigger('destroy', { target: this.name }) + if (edata.isCancelled === true) return + // clean up + if (query(this.box).find('#tabs_'+ this.name + '_right').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-tabs') + .html('') + } + this.last.observeResize?.disconnect() + delete w2ui[this.name] + // event after + edata.finish() + } + // =================================================== + // -- Internal Event Handlers + click(id, event) { + let tab = this.get(id) + if (tab == null || tab.disabled || this.last.reordering) return false + // event before + let edata = this.trigger('click', { target: id, tab: tab, object: tab, originalEvent: event }) + if (edata.isCancelled === true) return + // default action + query(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(this.active)).removeClass('active') + this.active = tab.id + query(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(this.active)).addClass('active') + // route processing + if (typeof tab.route == 'string') { + let route = tab.route !== '' ? String('/'+ tab.route).replace(/\/{2,}/g, '/') : '' + let info = w2utils.parseRoute(route) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + if (this.routeData[info.keys[k].name] == null) continue + route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]) + } + } + setTimeout(() => { window.location.hash = route }, 1) + } + // event after + edata.finish() + } + clickClose(id, event) { + let tab = this.get(id) + if (tab == null || tab.disabled) return false + // event before + let edata = this.trigger('close', { target: id, object: tab, tab, originalEvent: event }) + if (edata.isCancelled === true) return + this.animateClose(id).then(() => { + this.remove(id) + edata.finish() + this.refresh() + }) + if (event) event.stopPropagation() + } + animateClose(id) { + return new Promise((resolve, reject) => { + let $tab = query(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(id)) + let width = parseInt($tab.get(0).clientWidth || 0) + let anim = `
    ` + let $anim = $tab.replace(anim) + setTimeout(() => { $anim.css({ width: '0px' }) }, 1) + setTimeout(() => { + $anim.remove() + this.resize() + resolve() + }, 500) + }) + } + animateInsert(id, tab) { + return new Promise((resolve, reject) => { + let $before = query(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(id)) + let $tab = query.html(this.getTabHTML(tab.id)) + if ($before.length == 0) { + $before = query(this.box).find('#tabs_tabs_right') + $before.before($tab) + this.resize() + } else { + $tab.css({ opacity: 0 }) + // first insert tab on the right to get its proper dimentions + query(this.box).find('#tabs_tabs_right').before($tab.get(0)) + let $tmp = query(this.box).find('#' + $tab.attr('id')) + let width = $tmp.get(0).clientWidth ?? 0 + // insert animation div + let $anim = query.html('
    ') + $before.before($anim) + // hide tab and move it in the right position + $tab.hide() + $anim.before($tab[0]) + setTimeout(() => { $anim.css({ width: width + 'px' }) }, 1) + setTimeout(() => { + $anim.remove() + $tab.css({ opacity: 1 }).show() + this.refresh(tab.id) + this.resize() + resolve() + }, 500) + } + }) + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2tabs, w2toolbar + * + * == 2.0 changes + * - CSP - fixed inline events + * - remove jQuery dependency + * - layout.confirm - refactored + * - layout.message - refactored + * - panel.removed + */ + +let w2panels = ['top', 'left', 'main', 'preview', 'right', 'bottom'] +class w2layout extends w2base { + constructor(options) { + super(options.name) + this.box = null // DOM Element that holds the element + this.name = null // unique name for w2ui + this.panels = [] + this.last = {} + this.padding = 1 // panel padding + this.resizer = 4 // resizer width or height + this.style = '' + this.onShow = null + this.onHide = null + this.onResizing = null + this.onResizerClick = null + this.onRender = null + this.onRefresh = null + this.onChange = null + this.onResize = null + this.onDestroy = null + this.panel_template = { + type: null, // left, right, top, bottom + title: '', + size: 100, // width or height depending on panel name + minSize: 20, + maxSize: false, + hidden: false, + resizable: false, + overflow: 'auto', + style: '', + html: '', // can be String or Object with .render(box) method + tabs: null, + toolbar: null, + width: null, // read only + height: null, // read only + show: { + toolbar: false, + tabs: false + }, + removed: null, // function to call when content is overwritten + onRefresh: null, + onShow: null, + onHide: null + } + // mix in options + Object.assign(this, options) + if (!Array.isArray(this.panels)) this.panels = [] + // add defined panels + this.panels.forEach((panel, ind) => { + this.panels[ind] = w2utils.extend({}, this.panel_template, panel) + if (w2utils.isPlainObject(panel.tabs) || Array.isArray(panel.tabs)) initTabs(this, panel.type) + if (w2utils.isPlainObject(panel.toolbar) || Array.isArray(panel.toolbar)) initToolbar(this, panel.type) + }) + // add all other panels + w2panels.forEach(tab => { + if (this.get(tab) != null) return + this.panels.push(w2utils.extend({}, this.panel_template, { type: tab, hidden: (tab !== 'main'), size: 50 })) + }) + // render if box specified + if (typeof this.box == 'string') this.box = query(this.box).get(0) + if (this.box) this.render(this.box) + function initTabs(object, panel, tabs) { + let pan = object.get(panel) + if (pan != null && tabs == null) tabs = pan.tabs + if (pan == null || tabs == null) return false + // instantiate tabs + if (Array.isArray(tabs)) tabs = { tabs: tabs } + let name = object.name + '_' + panel + '_tabs' + if (w2ui[name]) w2ui[name].destroy() // destroy if existed + pan.tabs = new w2tabs(w2utils.extend({}, tabs, { owner: object, name: object.name + '_' + panel + '_tabs' })) + pan.show.tabs = true + return true + } + function initToolbar(object, panel, toolbar) { + let pan = object.get(panel) + if (pan != null && toolbar == null) toolbar = pan.toolbar + if (pan == null || toolbar == null) return false + // instantiate toolbar + if (Array.isArray(toolbar)) toolbar = { items: toolbar } + let name = object.name + '_' + panel + '_toolbar' + if (w2ui[name]) w2ui[name].destroy() // destroy if existed + pan.toolbar = new w2toolbar(w2utils.extend({}, toolbar, { owner: object, name: object.name + '_' + panel + '_toolbar' })) + pan.show.toolbar = true + return true + } + } + html(panel, data, transition) { + let p = this.get(panel) + let promise = { + panel: panel, + html: p.html, + error: false, + cancelled: false, + removed(cb) { + if (typeof cb == 'function') { + p.removed = cb + } + } + } + if (typeof p.removed == 'function') { + p.removed({ panel: panel, html: p.html, html_new: data, transition: transition || 'none' }) + p.removed = null // this is one time call back only + } + // if it is CSS panel + if (panel == 'css') { + query(this.box).find('#layout_'+ this.name +'_panel_css').html('') + promise.status = true + return promise + } + if (p == null) { + console.log('ERROR: incorrect panel name. Panel name can be main, left, right, top, bottom, preview or css') + promise.error = true + return promise + } + if (data == null) { + return promise + } + // event before + let edata = this.trigger('change', { target: panel, panel: p, html_new: data, transition: transition }) + if (edata.isCancelled === true) { + promise.cancelled = true + return promise + } + let pname = '#layout_'+ this.name + '_panel_'+ p.type + let current = query(this.box).find(pname + '> .w2ui-panel-content') + let panelTop = 0 + if (current.length > 0) { + query(this.box).find(pname).get(0).scrollTop = 0 + panelTop = query(current).css('top') + } + if (p.html === '') { + p.html = data + this.refresh(panel) + } else { + p.html = data + if (!p.hidden) { + if (transition != null && transition !== '') { + // apply transition + query(this.box).addClass('animating') + let div1 = query(this.box).find(pname + '> .w2ui-panel-content') + div1.after('
    ') + let div2 = query(this.box).find(pname + '> .w2ui-panel-content.new-panel') + div1.css('top', panelTop) + div2.css('top', panelTop) + if (typeof data == 'object') { + data.box = div2[0] // do not do .render(box); + data.render() + } else { + div2.hide().html(data) + } + w2utils.transition(div1[0], div2[0], transition, () => { + div1.remove() + div2.removeClass('new-panel') + div2.css('overflow', p.overflow) + // make sure only one content left + query(query(this.box).find(pname + '> .w2ui-panel-content').get(1)).remove() + query(this.box).removeClass('animating') + this.refresh(panel) + }) + } else { + this.refresh(panel) + } + } + } + // event after + edata.finish() + return promise + } + message(panel, options) { + let p = this.get(panel) + let box = query(this.box).find('#layout_'+ this.name + '_panel_'+ p.type) + let oldOverflow = box.css('overflow') + box.css('overflow', 'hidden') + let prom = w2utils.message({ + owner: this, + box : box.get(0), + after: '.w2ui-panel-title', + param: panel + }, options) + if (prom) { + prom.self.on('close:after', () => { + box.css('overflow', oldOverflow) + }) + } + return prom + } + confirm(panel, options) { + let p = this.get(panel) + let box = query(this.box).find('#layout_'+ this.name + '_panel_'+ p.type) + let oldOverflow = box.css('overflow') + box.css('overflow', 'hidden') + let prom = w2utils.confirm({ + owner : this, + box : box.get(0), + after : '.w2ui-panel-title', + param : panel + }, options) + if (prom) { + prom.self.on('close:after', () => { + box.css('overflow', oldOverflow) + }) + } + return prom + } + load(panel, url, transition) { + return new Promise((resolve, reject) => { + if ((panel == 'css' || this.get(panel) != null) && url != null) { + fetch(url) + .then(resp => resp.text()) + .then(text => { + this.resize() + resolve(this.html(panel, text, transition)) + }) + } else { + reject() + } + }) + } + sizeTo(panel, size, instant) { + let pan = this.get(panel) + if (pan == null) return false + // resize + query(this.box).find(':scope > div > .w2ui-panel') + .css('transition', (instant !== true ? '.2s' : '0s')) + setTimeout(() => { this.set(panel, { size: size }) }, 1) + // clean + setTimeout(() => { + query(this.box).find(':scope > div > .w2ui-panel').css('transition', '0s') + this.resize() + }, 300) + return true + } + show(panel, immediate) { + // event before + let edata = this.trigger('show', { target: panel, thisect: this.get(panel), immediate: immediate }) + if (edata.isCancelled === true) return + let p = this.get(panel) + if (p == null) return false + p.hidden = false + if (immediate === true) { + query(this.box).find('#layout_'+ this.name +'_panel_'+panel) + .css({ 'opacity': '1' }) + edata.finish() + this.resize() + } else { + // resize + query(this.box).addClass('animating') + query(this.box).find('#layout_'+ this.name +'_panel_'+panel) + .css({ 'opacity': '0' }) + query(this.box).find(':scope > div > .w2ui-panel') + .css('transition', '.2s') + setTimeout(() => { this.resize() }, 1) + // show + setTimeout(() => { + query(this.box).find('#layout_'+ this.name +'_panel_'+ panel).css({ 'opacity': '1' }) + }, 250) + // clean + setTimeout(() => { + query(this.box).find(':scope > div > .w2ui-panel') + .css('transition', '0s') + query(this.box).removeClass('animating') + edata.finish() + this.resize() + }, 300) + } + return true + } + hide(panel, immediate) { + // event before + let edata = this.trigger('hide', { target: panel, object: this.get(panel), immediate: immediate }) + if (edata.isCancelled === true) return + let p = this.get(panel) + if (p == null) return false + p.hidden = true + if (immediate === true) { + query(this.box).find('#layout_'+ this.name +'_panel_'+panel) + .css({ 'opacity': '0' }) + edata.finish() + this.resize() + } else { + // hide + query(this.box).addClass('animating') + query(this.box).find(':scope > div > .w2ui-panel') + .css('transition', '.2s') + query(this.box).find('#layout_'+ this.name +'_panel_'+panel) + .css({ 'opacity': '0' }) + setTimeout(() => { this.resize() }, 1) + // clean + setTimeout(() => { + query(this.box).find(':scope > div > .w2ui-panel') + .css('transition', '0s') + query(this.box).removeClass('animating') + edata.finish() + this.resize() + }, 300) + } + return true + } + toggle(panel, immediate) { + let p = this.get(panel) + if (p == null) return false + if (p.hidden) return this.show(panel, immediate); else return this.hide(panel, immediate) + } + set(panel, options) { + let ind = this.get(panel, true) + if (ind == null) return false + w2utils.extend(this.panels[ind], options) + // refresh only when content changed + if (options.html != null || options.resizable != null) { + this.refresh(panel) + } + // show/hide resizer + this.resize() // resize is needed when panel size is changed + return true + } + get(panel, returnIndex) { + for (let p = 0; p < this.panels.length; p++) { + if (this.panels[p].type == panel) { + if (returnIndex === true) return p; else return this.panels[p] + } + } + return null + } + el(panel) { + let el = query(this.box).find('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-content') + if (el.length != 1) return null + return el[0] + } + hideToolbar(panel) { + let pan = this.get(panel) + if (!pan) return + pan.show.toolbar = false + query(this.box).find('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-toolbar').hide() + this.resize() + } + showToolbar(panel) { + let pan = this.get(panel) + if (!pan) return + pan.show.toolbar = true + query(this.box).find('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-toolbar').show() + this.resize() + } + toggleToolbar(panel) { + let pan = this.get(panel) + if (!pan) return + if (pan.show.toolbar) this.hideToolbar(panel); else this.showToolbar(panel) + } + assignToolbar(panel, toolbar) { + if (typeof toolbar == 'string' && w2ui[toolbar] != null) toolbar = w2ui[toolbar] + let pan = this.get(panel) + pan.toolbar = toolbar + let tmp = query(this.box).find(panel +'> .w2ui-panel-toolbar') + if (pan.toolbar != null) { + if (tmp.find('[name='+ pan.toolbar.name +']').length === 0) { + pan.toolbar.render(tmp.get(0)) + } else if (pan.toolbar != null) { + pan.toolbar.refresh() + } + toolbar.owner = this + this.showToolbar(panel) + this.refresh(panel) + } else { + tmp.html('') + this.hideToolbar(panel) + } + } + hideTabs(panel) { + let pan = this.get(panel) + if (!pan) return + pan.show.tabs = false + query(this.box).find('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-tabs').hide() + this.resize() + } + showTabs(panel) { + let pan = this.get(panel) + if (!pan) return + pan.show.tabs = true + query(this.box).find('#layout_'+ this.name +'_panel_'+ panel +'> .w2ui-panel-tabs').show() + this.resize() + } + toggleTabs(panel) { + let pan = this.get(panel) + if (!pan) return + if (pan.show.tabs) this.hideTabs(panel); else this.showTabs(panel) + } + render(box) { + let time = Date.now() + let self = this + if (typeof box == 'string') box = query(box).get(0) + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + // event before + let edata = this.trigger('render', { target: this.name, box: box ?? this.box }) + if (edata.isCancelled === true) return + // default action + if (box != null) { + // clean previous box + if (query(this.box).find('#layout_'+ this.name +'_panel_main').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-layout') + .html('') + } + this.box = box + } + if (!this.box) return false + // render layout + query(this.box) + .attr('name', this.name) + .addClass('w2ui-layout') + .html('
    ') + if (query(this.box).length > 0) { + query(this.box)[0].style.cssText += this.style + } + // create all panels + for (let p1 = 0; p1 < w2panels.length; p1++) { + let html = '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    ' + query(this.box).find(':scope > div').append(html) + } + query(this.box).find(':scope > div') + .append('
    ') + this.refresh() // if refresh is not called here, the layout will not be available right after initialization + // observe div resize + this.last.observeResize = new ResizeObserver(() => { this.resize() }) + this.last.observeResize.observe(this.box) + // process event + edata.finish() + // re-init events + setTimeout(() => { // needed this timeout to allow browser to render first if there are tabs or toolbar + self.last.events = { resizeStart, mouseMove, mouseUp } + this.resize() + }, 0) + return Date.now() - time + function resizeStart(type, evnt) { + if (!self.box) return + if (!evnt) evnt = window.event + query(document) + .off('mousemove', self.last.events.mouseMove) + .on('mousemove', self.last.events.mouseMove) + query(document) + .off('mouseup', self.last.events.mouseUp) + .on('mouseup', self.last.events.mouseUp) + self.last.resize = { + type : type, + x : evnt.screenX, + y : evnt.screenY, + diff_x : 0, + diff_y : 0, + value : 0 + } + // lock all panels + w2panels.forEach(panel => { + let $tmp = query(self.el(panel)).find('.w2ui-lock') + if ($tmp.length > 0) { + $tmp.data('locked', 'yes') + } else { + self.lock(panel, { opacity: 0 }) + } + }) + let el = query(self.box).find('#layout_'+ self.name +'_resizer_'+ type).get(0) + if (type == 'left' || type == 'right') { + self.last.resize.value = parseInt(el.style.left) + } + if (type == 'top' || type == 'preview' || type == 'bottom') { + self.last.resize.value = parseInt(el.style.top) + } + } + function mouseUp(evnt) { + if (!self.box) return + if (!evnt) evnt = window.event + query(document).off('mousemove', self.last.events.mouseMove) + query(document).off('mouseup', self.last.events.mouseUp) + if (self.last.resize == null) return + // unlock all panels + w2panels.forEach(panel => { + let $tmp = query(self.el(panel)).find('.w2ui-lock') + if ($tmp.data('locked') == 'yes') { + $tmp.removeData('locked') + } else { + self.unlock(panel) + } + }) + // set new size + if (self.last.diff_x !== 0 || self.last.resize.diff_y !== 0) { // only recalculate if changed + let ptop = self.get('top') + let pbottom = self.get('bottom') + let panel = self.get(self.last.resize.type) + let width = w2utils.getSize(query(self.box), 'width') + let height = w2utils.getSize(query(self.box), 'height') + let str = String(panel.size) + let ns, nd + switch (self.last.resize.type) { + case 'top': + ns = parseInt(panel.sizeCalculated) + self.last.resize.diff_y + nd = 0 + break + case 'bottom': + ns = parseInt(panel.sizeCalculated) - self.last.resize.diff_y + nd = 0 + break + case 'preview': + ns = parseInt(panel.sizeCalculated) - self.last.resize.diff_y + nd = (ptop && !ptop.hidden ? ptop.sizeCalculated : 0) + + (pbottom && !pbottom.hidden ? pbottom.sizeCalculated : 0) + break + case 'left': + ns = parseInt(panel.sizeCalculated) + self.last.resize.diff_x + nd = 0 + break + case 'right': + ns = parseInt(panel.sizeCalculated) - self.last.resize.diff_x + nd = 0 + break + } + // set size + if (str.substr(str.length-1) == '%') { + panel.size = Math.floor(ns * 100 / (panel.type == 'left' || panel.type == 'right' ? width : height - nd) * 100) / 100 + '%' + } else { + if (String(panel.size).substr(0, 1) == '-') { + panel.size = parseInt(panel.size) - panel.sizeCalculated + ns + } else { + panel.size = ns + } + } + self.resize() + } + query(self.box) + .find('#layout_'+ self.name + '_resizer_'+ self.last.resize.type) + .removeClass('active') + delete self.last.resize + } + function mouseMove(evnt) { + if (!self.box) return + if (!evnt) evnt = window.event + if (self.last.resize == null) return + let panel = self.get(self.last.resize.type) + // event before + let tmp = self.last.resize + let edata = self.trigger('resizing', { target: self.name, object: panel, originalEvent: evnt, + panel: tmp ? tmp.type : 'all', diff_x: tmp ? tmp.diff_x : 0, diff_y: tmp ? tmp.diff_y : 0 }) + if (edata.isCancelled === true) return + let p = query(self.box).find('#layout_'+ self.name + '_resizer_'+ tmp.type) + let resize_x = (evnt.screenX - tmp.x) + let resize_y = (evnt.screenY - tmp.y) + let mainPanel = self.get('main') + if (!p.hasClass('active')) p.addClass('active') + switch (tmp.type) { + case 'left': + if (panel.minSize - resize_x > panel.width) { + resize_x = panel.minSize - panel.width + } + if (panel.maxSize && (panel.width + resize_x > panel.maxSize)) { + resize_x = panel.maxSize - panel.width + } + if (mainPanel.minSize + resize_x > mainPanel.width) { + resize_x = mainPanel.width - mainPanel.minSize + } + break + case 'right': + if (panel.minSize + resize_x > panel.width) { + resize_x = panel.width - panel.minSize + } + if (panel.maxSize && (panel.width - resize_x > panel.maxSize)) { + resize_x = panel.width - panel.maxSize + } + if (mainPanel.minSize - resize_x > mainPanel.width) { + resize_x = mainPanel.minSize - mainPanel.width + } + break + case 'top': + if (panel.minSize - resize_y > panel.height) { + resize_y = panel.minSize - panel.height + } + if (panel.maxSize && (panel.height + resize_y > panel.maxSize)) { + resize_y = panel.maxSize - panel.height + } + if (mainPanel.minSize + resize_y > mainPanel.height) { + resize_y = mainPanel.height - mainPanel.minSize + } + break + case 'preview': + case 'bottom': + if (panel.minSize + resize_y > panel.height) { + resize_y = panel.height - panel.minSize + } + if (panel.maxSize && (panel.height - resize_y > panel.maxSize)) { + resize_y = panel.height - panel.maxSize + } + if (mainPanel.minSize - resize_y > mainPanel.height) { + resize_y = mainPanel.minSize - mainPanel.height + } + break + } + tmp.diff_x = resize_x + tmp.diff_y = resize_y + switch (tmp.type) { + case 'top': + case 'preview': + case 'bottom': + tmp.diff_x = 0 + if (p.length > 0) p[0].style.top = (tmp.value + tmp.diff_y) + 'px' + break + case 'left': + case 'right': + tmp.diff_y = 0 + if (p.length > 0) p[0].style.left = (tmp.value + tmp.diff_x) + 'px' + break + } + // event after + edata.finish() + } + } + refresh(panel) { + let self = this + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + if (panel == null) panel = null + let time = Date.now() + // event before + let edata = self.trigger('refresh', { target: (panel != null ? panel : self.name), object: self.get(panel) }) + if (edata.isCancelled === true) return + // self.unlock(panel); + if (typeof panel == 'string') { + let p = self.get(panel) + if (p == null) return + let pname = '#layout_'+ self.name + '_panel_'+ p.type + let rname = '#layout_'+ self.name +'_resizer_'+ p.type + // apply properties to the panel + query(self.box).find(pname).css({ display: p.hidden ? 'none' : 'block' }) + if (p.resizable) { + query(self.box).find(rname).show() + } else { + query(self.box).find(rname).hide() + } + // insert content + if (typeof p.html == 'object' && typeof p.html.render === 'function') { + p.html.box = query(self.box).find(pname +'> .w2ui-panel-content')[0] + setTimeout(() => { + // need to remove unnecessary classes + if (query(self.box).find(pname +'> .w2ui-panel-content').length > 0) { + query(self.box).find(pname +'> .w2ui-panel-content') + .removeClass() + .removeAttr('name') + .addClass('w2ui-panel-content') + .css('overflow', p.overflow)[0].style.cssText += ';' + p.style + } + if (p.html && typeof p.html.render == 'function') { + p.html.render() // do not do .render(box); + } + }, 1) + } else { + // need to remove unnecessary classes + if (query(self.box).find(pname +'> .w2ui-panel-content').length > 0) { + query(self.box).find(pname +'> .w2ui-panel-content') + .removeClass() + .removeAttr('name') + .addClass('w2ui-panel-content') + .html(p.html) + .css('overflow', p.overflow)[0].style.cssText += ';' + p.style + } + } + // if there are tabs and/or toolbar - render it + let tmp = query(self.box).find(pname +'> .w2ui-panel-tabs') + if (p.show.tabs) { + if (tmp.find('[name='+ p.tabs.name +']').length === 0 && p.tabs != null) { + p.tabs.render(tmp.get(0)) + } else { + p.tabs.refresh() + } + } else { + tmp.html('').removeClass('w2ui-tabs').hide() + } + tmp = query(self.box).find(pname +'> .w2ui-panel-toolbar') + if (p.show.toolbar) { + if (tmp.find('[name='+ p.toolbar.name +']').length === 0 && p.toolbar != null) { + p.toolbar.render(tmp.get(0)) + } else { + p.toolbar.refresh() + } + } else { + tmp.html('').removeClass('w2ui-toolbar').hide() + } + // show title + tmp = query(self.box).find(pname +'> .w2ui-panel-title') + if (p.title) { + tmp.html(p.title).show() + } else { + tmp.html('').hide() + } + } else { + if (query(self.box).find('#layout_'+ self.name +'_panel_main').length === 0) { + self.render() + return + } + self.resize() + // refresh all of them + for (let p1 = 0; p1 < this.panels.length; p1++) { self.refresh(this.panels[p1].type) } + } + edata.finish() + return Date.now() - time + } + resize() { + // if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection + if (!this.box) return false + let time = Date.now() + // event before + let tmp = this.last.resize + let edata = this.trigger('resize', { target: this.name, + panel: tmp ? tmp.type : 'all', diff_x: tmp ? tmp.diff_x : 0, diff_y: tmp ? tmp.diff_y : 0 }) + if (edata.isCancelled === true) return + if (this.padding < 0) this.padding = 0 + // layout itself + // width includes border and padding, we need to exclude that so panels + // are sized correctly + let width = w2utils.getSize(query(this.box), 'width') + let height = w2utils.getSize(query(this.box), 'height') + let self = this + // panels + let pmain = this.get('main') + let pprev = this.get('preview') + let pleft = this.get('left') + let pright = this.get('right') + let ptop = this.get('top') + let pbottom = this.get('bottom') + let sprev = (pprev != null && pprev.hidden !== true ? true : false) + let sleft = (pleft != null && pleft.hidden !== true ? true : false) + let sright = (pright != null && pright.hidden !== true ? true : false) + let stop = (ptop != null && ptop.hidden !== true ? true : false) + let sbottom = (pbottom != null && pbottom.hidden !== true ? true : false) + let l, t, w, h + // calculate % + for (let p = 0; p < w2panels.length; p++) { + if (w2panels[p] === 'main') continue + tmp = this.get(w2panels[p]) + if (!tmp) continue + let str = String(tmp.size || 0) + if (str.substr(str.length-1) == '%') { + let tmph = height + if (tmp.type == 'preview') { + tmph = tmph - + (ptop && !ptop.hidden ? ptop.sizeCalculated : 0) - + (pbottom && !pbottom.hidden ? pbottom.sizeCalculated : 0) + } + tmp.sizeCalculated = parseInt((tmp.type == 'left' || tmp.type == 'right' ? width : tmph) * parseFloat(tmp.size) / 100) + } else { + tmp.sizeCalculated = parseInt(tmp.size) + } + tmp.sizeCalculated = Math.max(tmp.sizeCalculated, parseInt(tmp.minSize)) + } + // negative size + if (String(pright.size).substr(0, 1) == '-') { + if (sleft && String(pleft.size).substr(0, 1) == '-') { + console.log('ERROR: you cannot have both left panel.size and right panel.size be negative.') + } else { + pright.sizeCalculated = width - (sleft ? pleft.sizeCalculated : 0) + parseInt(pright.size) + } + } + if (String(pleft.size).substr(0, 1) == '-') { + if (sright && String(pright.size).substr(0, 1) == '-') { + console.log('ERROR: you cannot have both left panel.size and right panel.size be negative.') + } else { + pleft.sizeCalculated = width - (sright ? pright.sizeCalculated : 0) + parseInt(pleft.size) + } + } + // top if any + if (ptop != null && ptop.hidden !== true) { + l = 0 + t = 0 + w = width + h = ptop.sizeCalculated + query(this.box).find('#layout_'+ this.name +'_panel_top') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }) + ptop.width = w + ptop.height = h + // resizer + if (ptop.resizable) { + t = ptop.sizeCalculated - (this.padding === 0 ? this.resizer : 0) + h = (this.resizer > this.padding ? this.resizer : this.padding) + query(this.box).find('#layout_'+ this.name +'_resizer_top') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ns-resize' + }) + .off('mousedown') + .on('mousedown', function(event) { + event.preventDefault() + // event before + let edata = self.trigger('resizerClick', { target: 'top', originalEvent: event }) + if (edata.isCancelled === true) return + // default action + w2ui[self.name].last.events.resizeStart('top', event) + // event after + edata.finish() + return false + }) + } + } else { + query(this.box).find('#layout_'+ this.name +'_panel_top').hide() + query(this.box).find('#layout_'+ this.name +'_resizer_top').hide() + } + // left if any + if (pleft != null && pleft.hidden !== true) { + l = 0 + t = 0 + (stop ? ptop.sizeCalculated + this.padding : 0) + w = pleft.sizeCalculated + h = height - (stop ? ptop.sizeCalculated + this.padding : 0) - + (sbottom ? pbottom.sizeCalculated + this.padding : 0) + query(this.box).find('#layout_'+ this.name +'_panel_left') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }) + pleft.width = w + pleft.height = h + // resizer + if (pleft.resizable) { + l = pleft.sizeCalculated - (this.padding === 0 ? this.resizer : 0) + w = (this.resizer > this.padding ? this.resizer : this.padding) + query(this.box).find('#layout_'+ this.name +'_resizer_left') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ew-resize' + }) + .off('mousedown') + .on('mousedown', function(event) { + event.preventDefault() + // event before + let edata = self.trigger('resizerClick', { target: 'left', originalEvent: event }) + if (edata.isCancelled === true) return + // default action + w2ui[self.name].last.events.resizeStart('left', event) + // event after + edata.finish() + return false + }) + } + } else { + query(this.box).find('#layout_'+ this.name +'_panel_left').hide() + query(this.box).find('#layout_'+ this.name +'_resizer_left').hide() + } + // right if any + if (pright != null && pright.hidden !== true) { + l = width - pright.sizeCalculated + t = 0 + (stop ? ptop.sizeCalculated + this.padding : 0) + w = pright.sizeCalculated + h = height - (stop ? ptop.sizeCalculated + this.padding : 0) - + (sbottom ? pbottom.sizeCalculated + this.padding : 0) + query(this.box).find('#layout_'+ this.name +'_panel_right') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }) + pright.width = w + pright.height = h + // resizer + if (pright.resizable) { + l = l - this.padding + w = (this.resizer > this.padding ? this.resizer : this.padding) + query(this.box).find('#layout_'+ this.name +'_resizer_right') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ew-resize' + }) + .off('mousedown') + .on('mousedown', function(event) { + event.preventDefault() + // event before + let edata = self.trigger('resizerClick', { target: 'right', originalEvent: event }) + if (edata.isCancelled === true) return + // default action + w2ui[self.name].last.events.resizeStart('right', event) + // event after + edata.finish() + return false + }) + } + } else { + query(this.box).find('#layout_'+ this.name +'_panel_right').hide() + query(this.box).find('#layout_'+ this.name +'_resizer_right').hide() + } + // bottom if any + if (pbottom != null && pbottom.hidden !== true) { + l = 0 + t = height - pbottom.sizeCalculated + w = width + h = pbottom.sizeCalculated + query(this.box).find('#layout_'+ this.name +'_panel_bottom') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }) + pbottom.width = w + pbottom.height = h + // resizer + if (pbottom.resizable) { + t = t - (this.padding === 0 ? 0 : this.padding) + h = (this.resizer > this.padding ? this.resizer : this.padding) + query(this.box).find('#layout_'+ this.name +'_resizer_bottom') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ns-resize' + }) + .off('mousedown') + .on('mousedown', function(event) { + event.preventDefault() + // event before + let edata = self.trigger('resizerClick', { target: 'bottom', originalEvent: event }) + if (edata.isCancelled === true) return + // default action + w2ui[self.name].last.events.resizeStart('bottom', event) + // event after + edata.finish() + return false + }) + } + } else { + query(this.box).find('#layout_'+ this.name +'_panel_bottom').hide() + query(this.box).find('#layout_'+ this.name +'_resizer_bottom').hide() + } + // main - always there + l = 0 + (sleft ? pleft.sizeCalculated + this.padding : 0) + t = 0 + (stop ? ptop.sizeCalculated + this.padding : 0) + w = width - (sleft ? pleft.sizeCalculated + this.padding : 0) - + (sright ? pright.sizeCalculated + this.padding: 0) + h = height - (stop ? ptop.sizeCalculated + this.padding : 0) - + (sbottom ? pbottom.sizeCalculated + this.padding : 0) - + (sprev ? pprev.sizeCalculated + this.padding : 0) + query(this.box) + .find('#layout_'+ this.name +'_panel_main') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }) + pmain.width = w + pmain.height = h + // preview if any + if (pprev != null && pprev.hidden !== true) { + l = 0 + (sleft ? pleft.sizeCalculated + this.padding : 0) + t = height - (sbottom ? pbottom.sizeCalculated + this.padding : 0) - pprev.sizeCalculated + w = width - (sleft ? pleft.sizeCalculated + this.padding : 0) - + (sright ? pright.sizeCalculated + this.padding : 0) + h = pprev.sizeCalculated + query(this.box).find('#layout_'+ this.name +'_panel_preview') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px' + }) + pprev.width = w + pprev.height = h + // resizer + if (pprev.resizable) { + t = t - (this.padding === 0 ? 0 : this.padding) + h = (this.resizer > this.padding ? this.resizer : this.padding) + query(this.box).find('#layout_'+ this.name +'_resizer_preview') + .css({ + 'display': 'block', + 'left': l + 'px', + 'top': t + 'px', + 'width': w + 'px', + 'height': h + 'px', + 'cursor': 'ns-resize' + }) + .off('mousedown') + .on('mousedown', function(event) { + event.preventDefault() + // event before + let edata = self.trigger('resizerClick', { target: 'preview', originalEvent: event }) + if (edata.isCancelled === true) return + // default action + w2ui[self.name].last.events.resizeStart('preview', event) + // event after + edata.finish() + return false + }) + } + } else { + query(this.box).find('#layout_'+ this.name +'_panel_preview').hide() + query(this.box).find('#layout_'+ this.name +'_resizer_preview').hide() + } + // display tabs and toolbar if needed + for (let p1 = 0; p1 < w2panels.length; p1++) { + let pan = this.get(w2panels[p1]) + let tmp2 = '#layout_'+ this.name +'_panel_'+ w2panels[p1] +' > .w2ui-panel-' + let tabHeight = 0 + if (pan) { + if (pan.title) { + let el = query(this.box).find(tmp2 + 'title').css({ top: tabHeight + 'px', display: 'block' }) + tabHeight += w2utils.getSize(el, 'height') + } + if (pan.show.tabs) { + let el = query(this.box).find(tmp2 + 'tabs').css({ top: tabHeight + 'px', display: 'block' }) + tabHeight += w2utils.getSize(el, 'height') + } + if (pan.show.toolbar) { + let el = query(this.box).find(tmp2 + 'toolbar').css({ top: tabHeight + 'px', display: 'block' }) + tabHeight += w2utils.getSize(el, 'height') + } + } + query(this.box).find(tmp2 + 'content').css({ display: 'block' }).css({ top: tabHeight + 'px' }) + } + edata.finish() + return Date.now() - time + } + destroy() { + // event before + let edata = this.trigger('destroy', { target: this.name }) + if (edata.isCancelled === true) return + if (w2ui[this.name] == null) return false + // clean up + if (query(this.box).find('#layout_'+ this.name +'_panel_main').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-layout') + .html('') + } + this.last.observeResize?.disconnect() + delete w2ui[this.name] + // event after + edata.finish() + if (this.last.events && this.last.events.resize) { + query(window).off('resize', this.last.events.resize) + } + return true + } + lock(panel, msg, showSpinner) { + if (w2panels.indexOf(panel) == -1) { + console.log('ERROR: First parameter needs to be the a valid panel name.') + return + } + let args = Array.from(arguments) + args[0] = '#layout_'+ this.name + '_panel_' + panel + w2utils.lock(...args) + } + unlock(panel, speed) { + if (w2panels.indexOf(panel) == -1) { + console.log('ERROR: First parameter needs to be the a valid panel name.') + return + } + let nm = '#layout_'+ this.name + '_panel_' + panel + w2utils.unlock(nm, speed) + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: jQuery, w2utils, w2base, w2toolbar, w2field + * + * == TODO == + * - problem with .set() and arrays, array get extended too, but should be replaced + * - allow functions in routeData (also add routeData to list/enum) + * - send parsed URL to the event if there is routeData + * - add selectType: 'none' so that no selection can be make but with mouse + * - focus/blur for selectType = cell not display grayed out selection + * - allow enum in inline edit (see https://github.com/vitmalina/w2ui/issues/911#issuecomment-107341193) + * - remote source, but localSort/localSearch + * - promise for request, load, save, etc. + * - onloadmore event (so it will be easy to implement remote data source with local sort) + * - status() - clears on next select, etc. Should not if it is off + * + * == DEMOS To create == + * - batch for disabled buttons + * - natural sort + * - resize on max content + * + * == 2.0 changes + * - toolbarInput - deprecated, toolbarSearch stays + * - searchSuggest + * - searchSave, searchSelected, savedSearches, defaultSearches, useLocalStorage, searchFieldTooltip + * - cache, cacheSave + * - onSearchSave, onSearchRemove, onSearchSelect + * - show.searchLogic + * - show.searchSave + * - refreshSearch + * - initAllFields -> searchInitInput + * - textSearch - deprecated in favor of defaultOperator + * - grid.confirm - refactored + * - grid.message - refactored + * - search.type == 'text' can have 'in' and 'not in' operators, then it will switch to enum + * - grid.find(..., displayedOnly) + * - column.render(..., this) - added + * - observeResize for the box + * - remove edit.type == 'select' + * - editDone(...) + * - liveSearch + * - deprecated onUnselect event + * - requestComplete(data, action, callBack, resolve, reject) - new argument list + * - msgAJAXError -> msgHTTPError + * - aded msgServerError + * - deleted grid.method + * - added grid.prepareParams + * - added mouseEnter/mouseLeave + * - grid.show.columnReorder -> grid.reorderRows + * - updagte docs search.label (not search.text) + */ + +class w2grid extends w2base { + constructor(options) { + super(options.name) + this.name = null + this.box = null // HTML element that hold this element + this.columns = [] // { field, text, size, attr, render, hidden, gridMinWidth, editable } + this.columnGroups = [] // { span: int, text: 'string', main: true/false, style: 'string' } + this.records = [] // { recid: int(required), field1: 'value1', ... fieldN: 'valueN', style: 'string', changes: object } + this.summary = [] // array of summary records, same structure as records array + this.searches = [] // { type, label, field, attr, text, hidden } + this.toolbar = {} // if not empty object; then it is toolbar object + this.ranges = [] + this.contextMenu = [] + this.searchMap = {} // re-map search fields + this.searchData = [] + this.sortMap = {} // re-map sort fields + this.sortData = [] + this.savedSearches = [] + this.defaultSearches = [] + this.total = 0 // server total + this.recid = null // field from records to be used as recid + // internal + this.last = { + field : '', // last search field, e.g. 'all' + label : '', // last search field label, e.g. 'All Fields' + logic : 'AND', // last search logic, e.g. 'AND' or 'OR' + search : '', // last search text + searchIds : [], // last search IDs + selection : { // last selection details + indexes : [], + columns : {} + }, + saved_sel : null, // last result of selectionSave() + multi : false, // last multi flag, true when searching for multiple fields + scrollTop : 0, // last scrollTop position + scrollLeft : 0, // last scrollLeft position + colStart : 0, // for column virtual scrolling + colEnd : 0, // for column virtual scrolling + fetch: { + action : '', // last fetch command, e.g. 'load' + offset : null, // last fetch offset, integer + start : 0, // timestamp of start of last fetch request + response : 0, // time it took to complete the last fetch request in seconds + options : null, + controller: null, + loaded : false, // data is loaded from the server + hasMore : false // flag to indicate if there are more items to pull from the server + }, + pull_more : false, + pull_refresh : true, + range_start : null, // last range start cell + range_end : null, // last range end cell + sel_ind : null, // last selected cell index + sel_col : null, // last selected column + sel_type : null, // last selection type, e.g. 'click' or 'key' + sel_recid : null, // last selected record id + idCache : {}, // object, id cache for get() + move : null, // object, move details + cancelClick : null, // boolean flag to indicate if the click event should be ignored, set during mouseMove() + inEditMode : false, // flag to indicate if we're currently in edit mode during inline editing + _edit : null, // object with details on the last edited cell, { value, index, column, recid } + kbd_timer : null, // last id of blur() timer + marker_timer : null, // last id of markSearch() timer + click_time : null, // timestamp of last click + click_recid : null, // last clicked record id + bubbleEl : null, // last bubble element + colResizing : false, // flag to indicate that a column is currently being resized + tmp : null, // object with last column resizing details + copy_event : null, // last copy event + userSelect : '', // last user select type, e.g. 'text' + columnDrag : false, // false or an object with a remove() method + state : null, // last grid state + show_extra : 0, // last show extra for virtual scrolling + toolbar_height: 0, // height of grid's toolbar + } + this.header = '' + this.url = '' + this.limit = 100 + this.offset = 0 // how many records to skip (for infinite scroll) when pulling from server + this.postData = {} + this.routeData = {} + this.httpHeaders = {} + this.show = { + header : false, + toolbar : false, + footer : false, + columnMenu : true, + columnHeaders : true, + lineNumbers : false, + expandColumn : false, + selectColumn : false, + emptyRecords : true, + toolbarReload : true, + toolbarColumns : false, + toolbarSearch : true, + toolbarAdd : false, + toolbarEdit : false, + toolbarDelete : false, + toolbarSave : false, + searchAll : true, + searchLogic : true, + searchHiddenMsg : false, + searchSave : true, + statusRange : true, + statusBuffered : false, + statusRecordID : true, + statusSelection : true, + statusResponse : true, + statusSort : false, + statusSearch : false, + recordTitles : false, + selectionBorder : true, + skipRecords : true, + saveRestoreState: true + } + this.stateId = null // Custom state name for stateSave, stateRestore and stateReset + this.hasFocus = false + this.autoLoad = true // for infinite scroll + this.fixedBody = true // if false; then grid grows with data + this.recordHeight = 32 + this.lineNumberWidth = 34 + this.keyboard = true + this.selectType = 'row' // can be row|cell + this.liveSearch = false // if true, it will auto search if typed in search_all + this.multiSearch = true + this.multiSelect = true + this.multiSort = true + this.reorderColumns = false + this.reorderRows = false + this.showExtraOnSearch = 0 // show extra records before and after on search + this.markSearch = true + this.columnTooltip = 'top|bottom' // can be top, bottom, left, right + this.disableCVS = false // disable Column Virtual Scroll + this.nestedFields = true // use field name containing dots as separator to look into object + this.vs_start = 150 + this.vs_extra = 5 + this.style = '' + this.tabIndex = null + this.dataType = null // if defined, then overwrites w2utils.settings.dataType + this.parser = null + this.advanceOnEdit = true // automatically begin editing the next cell after submitting an inline edit? + this.useLocalStorage = true + // default values for the column + this.colTemplate = { + text : '', // column text (can be a function) + field : '', // field name to map the column to a record + size : null, // size of column in px or % + min : 20, // minimum width of column in px + max : null, // maximum width of column in px + gridMinWidth : null, // minimum width of the grid when column is visible + sizeCorrected : null, // read only, corrected size (see explanation below) + sizeCalculated : null, // read only, size in px (see explanation below) + sizeOriginal : null, // size as defined + sizeType : null, // px or % + hidden : false, // indicates if column is hidden + sortable : false, // indicates if column is sortable + sortMode : null, // sort mode ('default'|'natural'|'i18n') or custom compare function + searchable : false, // bool/string: int,float,date,... or an object to create search field + resizable : true, // indicates if column is resizable + hideable : true, // indicates if column can be hidden + autoResize : null, // indicates if column can be auto-resized by double clicking on the resizer + attr : '', // string that will be inside the tag + style : '', // additional style for the td tag + render : null, // string or render function + title : null, // string or function for the title property for the column cells + tooltip : null, // string for the title property for the column header + editable : {}, // editable object (see explanation below) + frozen : false, // indicates if the column is fixed to the left + info : null, // info bubble, can be bool/object + clipboardCopy : false, // if true (or string or function), it will display clipboard copy icon + } + // these column properties will be saved in stateSave() + this.stateColProps = { + text : false, + field : true, + size : true, + min : false, + max : false, + gridMinWidth : false, + sizeCorrected : false, + sizeCalculated : true, + sizeOriginal : true, + sizeType : true, + hidden : true, + sortable : false, + sortMode : true, + searchable : false, + resizable : false, + hideable : false, + autoResize : false, + attr : false, + style : false, + render : false, + title : false, + tooltip : false, + editable : false, + frozen : true, + info : false, + clipboardCopy : false + } + this.msgDelete = 'Are you sure you want to delete ${count} ${records}?' + this.msgNotJSON = 'Returned data is not in valid JSON format.' + this.msgHTTPError = 'HTTP error. See console for more details.' + this.msgServerError= 'Server error' + this.msgRefresh = 'Refreshing...' + this.msgNeedReload = 'Your remote data source record count has changed, reloading from the first record.' + this.msgEmpty = '' // if not blank, then it is message when server returns no records + this.buttons = { + 'reload' : { type: 'button', id: 'w2ui-reload', icon: 'w2ui-icon-reload', tooltip: 'Reload data in the list' }, + 'columns' : { type: 'menu-check', id: 'w2ui-column-on-off', icon: 'w2ui-icon-columns', tooltip: 'Show/hide columns', + overlay: { align: 'none' } + }, + 'search' : { type: 'html', id: 'w2ui-search', + html: '' + }, + 'add' : { type: 'button', id: 'w2ui-add', text: 'Add New', tooltip: 'Add new record', icon: 'w2ui-icon-plus' }, + 'edit' : { type: 'button', id: 'w2ui-edit', text: 'Edit', tooltip: 'Edit selected record', icon: 'w2ui-icon-pencil', batch: 1, disabled: true }, + 'delete' : { type: 'button', id: 'w2ui-delete', text: 'Delete', tooltip: 'Delete selected records', icon: 'w2ui-icon-cross', batch: true, disabled: true }, + 'save' : { type: 'button', id: 'w2ui-save', text: 'Save', tooltip: 'Save changed records', icon: 'w2ui-icon-check' } + } + this.operators = { // for search fields + 'text' : ['is', 'begins', 'contains', 'ends'], // could have "in" and "not in" + 'number' : ['=', 'between', '>', '<', '>=', '<='], + 'date' : ['is', { oper: 'less', text: 'before'}, { oper: 'more', text: 'since' }, 'between'], + 'list' : ['is'], + 'hex' : ['is', 'between'], + 'color' : ['is', 'begins', 'contains', 'ends'], + 'enum' : ['in', 'not in'] + // -- all possible + // "text" : ['is', 'begins', 'contains', 'ends'], + // "number" : ['is', 'between', 'less:less than', 'more:more than', 'null:is null', 'not null:is not null'], + // "list" : ['is', 'null:is null', 'not null:is not null'], + // "enum" : ['in', 'not in', 'null:is null', 'not null:is not null'] + } + this.defaultOperator = { + 'text' : 'begins', + 'number' : '=', + 'date' : 'is', + 'list' : 'is', + 'enum' : 'in', + 'hex' : 'begins', + 'color' : 'begins' + } + // map search field type to operator + this.operatorsMap = { + 'text' : 'text', + 'int' : 'number', + 'float' : 'number', + 'money' : 'number', + 'currency' : 'number', + 'percent' : 'number', + 'hex' : 'hex', + 'alphanumeric' : 'text', + 'color' : 'color', + 'date' : 'date', + 'time' : 'date', + 'datetime' : 'date', + 'list' : 'list', + 'combo' : 'text', + 'enum' : 'enum', + 'file' : 'enum', + 'select' : 'list', + 'radio' : 'list', + 'checkbox' : 'list', + 'toggle' : 'list' + } + // events + this.onAdd = null + this.onEdit = null + this.onRequest = null // called on any server event + this.onLoad = null + this.onDelete = null + this.onSave = null + this.onSelect = null + this.onClick = null + this.onDblClick = null + this.onContextMenu = null + this.onContextMenuClick = null // when context menu item selected + this.onColumnClick = null + this.onColumnDblClick = null + this.onColumnResize = null + this.onColumnAutoResize = null + this.onSort = null + this.onSearch = null + this.onSearchOpen = null + this.onChange = null // called when editable record is changed + this.onRestore = null // called when editable record is restored + this.onExpand = null + this.onCollapse = null + this.onError = null + this.onKeydown = null + this.onToolbar = null // all events from toolbar + this.onColumnOnOff = null + this.onCopy = null + this.onPaste = null + this.onSelectionExtend = null + this.onEditField = null + this.onRender = null + this.onRefresh = null + this.onReload = null + this.onResize = null + this.onDestroy = null + this.onStateSave = null + this.onStateRestore = null + this.onFocus = null + this.onBlur = null + this.onReorderRow = null + this.onSearchSave = null + this.onSearchRemove = null + this.onSearchSelect = null + this.onColumnSelect = null + this.onColumnDragStart = null + this.onColumnDragEnd = null + this.onResizerDblClick = null + this.onMouseEnter = null // mouse enter over record event + this.onMouseLeave = null + // need deep merge, should be extend, not objectAssign + w2utils.extend(this, options) + // check if there are records without recid + if (Array.isArray(this.records)) { + let remove = [] // remove from records as they are summary + this.records.forEach((rec, ind) => { + if (rec[this.recid] != null) { + rec.recid = rec[this.recid] + } + if (rec.recid == null) { + console.log('ERROR: Cannot add records without recid. (obj: '+ this.name +')') + } + if (rec.w2ui?.summary === true) { + this.summary.push(rec) + remove.push(ind) // cannot remove here as it will mess up array walk thru + } + }) + remove.sort() + for (let t = remove.length-1; t >= 0; t--) { + this.records.splice(remove[t], 1) + } + } + // add searches + if (Array.isArray(this.columns)) { + this.columns.forEach((col, ind) => { + col = w2utils.extend({}, this.colTemplate, col) + this.columns[ind] = col + let search = col.searchable + if (search == null || search === false || this.getSearch(col.field) != null) return + if (w2utils.isPlainObject(search)) { + this.addSearch(w2utils.extend({ field: col.field, label: col.text, type: 'text' }, search)) + } else { + let stype = col.searchable + let attr = '' + if (col.searchable === true) { + stype = 'text' + attr = 'size="20"' + } + this.addSearch({ field: col.field, label: col.text, type: stype, attr: attr }) + } + }) + } + // add icon to default searches if not defined + if (Array.isArray(this.defaultSearches)) { + this.defaultSearches.forEach((search, ind) => { + search.id = 'default-'+ ind + search.icon ??= 'w2ui-icon-search' + }) + } + // check if there are saved searches in localStorage + let data = this.cache('searches') + if (Array.isArray(data)) { + data.forEach(search => { + this.savedSearches.push({ + id: search.id ?? 'none', + text: search.text ?? 'none', + icon: 'w2ui-icon-search', + remove: true, + logic: search.logic ?? 'AND', + data: search.data ?? [] + }) + }) + } + // render if box specified + if (typeof this.box == 'string') this.box = query(this.box).get(0) + if (this.box) this.render(this.box) + } + add(record, first) { + if (!Array.isArray(record)) record = [record] + let added = 0 + for (let i = 0; i < record.length; i++) { + let rec = record[i] + if (rec[this.recid] != null) { + rec.recid = rec[this.recid] + } + if (rec.recid == null) { + console.log('ERROR: Cannot add record without recid. (obj: '+ this.name +')') + continue + } + if (rec.w2ui?.summary === true) { + if (first) this.summary.unshift(rec); else this.summary.push(rec) + } else { + if (first) this.records.unshift(rec); else this.records.push(rec) + } + added++ + } + let url = this.url?.get ?? this.url + if (!url) { + this.total = this.records.length + this.localSort(false, true) + this.localSearch() + // do not call this.refresh(), this is unnecessary, heavy, and messes with the toolbar. + // this.refreshBody() + // this.resizeRecords() + this.refresh() + } else { + this.refresh() // ?? should it be reload? + } + return added + } + find(obj, returnIndex, displayedOnly) { + if (obj == null) obj = {} + let recs = [] + let hasDots = false + // check if property is nested - needed for speed + for (let o in obj) if (String(o).indexOf('.') != -1) hasDots = true + // look for an item + let start = displayedOnly ? this.last.range_start : 0 + let end = displayedOnly ? this.last.range_end + 1: this.records.length + if (end > this.records.length) end = this.records.length + for (let i = start; i < end; i++) { + let match = true + for (let o in obj) { + let val = this.records[i][o] + if (hasDots && String(o).indexOf('.') != -1) val = this.parseField(this.records[i], o) + if (obj[o] == 'not-null') { + if (val == null || val === '') match = false + } else { + if (obj[o] != val) match = false + } + } + if (match && returnIndex !== true) recs.push(this.records[i].recid) + if (match && returnIndex === true) recs.push(i) + } + return recs + } + set(recid, record, noRefresh) { // does not delete existing, but overrides on top of it + if ((typeof recid == 'object') && (recid !== null)) { + noRefresh = record + record = recid + recid = null + } + // update all records + if (recid == null) { + for (let i = 0; i < this.records.length; i++) { + w2utils.extend(this.records[i], record) // recid is the whole record + } + if (noRefresh !== true) this.refresh() + } else { // find record to update + let ind = this.get(recid, true) + if (ind == null) return false + let isSummary = (this.records[ind] && this.records[ind].recid == recid ? false : true) + if (isSummary) { + w2utils.extend(this.summary[ind], record) + } else { + w2utils.extend(this.records[ind], record) + } + if (noRefresh !== true) this.refreshRow(recid, ind) // refresh only that record + } + return true + } + get(recid, returnIndex) { + // search records + if (Array.isArray(recid)) { + let recs = [] + for (let i = 0; i < recid.length; i++) { + let v = this.get(recid[i], returnIndex) + if (v !== null) + recs.push(v) + } + return recs + } else { + // get() must be fast, implements a cache to bypass loop over all records + // most of the time. + let idCache = this.last.idCache + if (!idCache) { + this.last.idCache = idCache = {} + } + let i = idCache[recid] + if (typeof(i) === 'number') { + if (i >= 0 && i < this.records.length && this.records[i].recid == recid) { + if (returnIndex === true) return i; else return this.records[i] + } + // summary indexes are stored as negative numbers, try them now. + i = ~i + if (i >= 0 && i < this.summary.length && this.summary[i].recid == recid) { + if (returnIndex === true) return i; else return this.summary[i] + } + // wrong index returned, clear cache + this.last.idCache = idCache = {} + } + for (let i = 0; i < this.records.length; i++) { + if (this.records[i].recid == recid) { + idCache[recid] = i + if (returnIndex === true) return i; else return this.records[i] + } + } + // search summary + for (let i = 0; i < this.summary.length; i++) { + if (this.summary[i].recid == recid) { + idCache[recid] = ~i + if (returnIndex === true) return i; else return this.summary[i] + } + } + return null + } + } + getFirst(offset) { + if (this.records.length == 0) return null + let rec = this.records[0] + let tmp = this.last.searchIds + if (this.searchData.length > 0) { + if (Array.isArray(tmp) && tmp.length > 0) { + rec = this.records[tmp[offset || 0]] + } else { + rec = null + } + } + return rec + } + remove() { + let removed = 0 + for (let a = 0; a < arguments.length; a++) { + for (let r = this.records.length-1; r >= 0; r--) { + if (this.records[r].recid == arguments[a]) { this.records.splice(r, 1); removed++ } + } + for (let r = this.summary.length-1; r >= 0; r--) { + if (this.summary[r].recid == arguments[a]) { this.summary.splice(r, 1); removed++ } + } + } + let url = this.url?.get ?? this.url + if (!url) { + this.localSort(false, true) + this.localSearch() + } + this.refresh() + return removed + } + addColumn(before, columns) { + let added = 0 + if (arguments.length == 1) { + columns = before + before = this.columns.length + } else { + if (typeof before == 'string') before = this.getColumn(before, true) + if (before == null) before = this.columns.length + } + if (!Array.isArray(columns)) columns = [columns] + for (let i = 0; i < columns.length; i++) { + let col = w2utils.extend({}, this.colTemplate, columns[i]) + this.columns.splice(before, 0, col) + // if column is searchable, add search field + if (columns[i].searchable) { + let stype = columns[i].searchable + let attr = '' + if (columns[i].searchable === true) { stype = 'text'; attr = 'size="20"' } + this.addSearch({ field: columns[i].field, label: columns[i].text, type: stype, attr: attr }) + } + before++ + added++ + } + this.refresh() + return added + } + removeColumn() { + let removed = 0 + for (let a = 0; a < arguments.length; a++) { + for (let r = this.columns.length-1; r >= 0; r--) { + if (this.columns[r].field == arguments[a]) { + if (this.columns[r].searchable) this.removeSearch(arguments[a]) + this.columns.splice(r, 1) + removed++ + } + } + } + this.refresh() + return removed + } + getColumn(field, returnIndex) { + // no arguments - return fields of all columns + if (arguments.length === 0) { + let ret = [] + for (let i = 0; i < this.columns.length; i++) ret.push(this.columns[i].field) + return ret + } + // find column + for (let i = 0; i < this.columns.length; i++) { + if (this.columns[i].field == field) { + if (returnIndex === true) return i; else return this.columns[i] + } + } + return null + } + updateColumn(fields, updates) { + let effected = 0 + fields = (Array.isArray(fields) ? fields : [fields]) + fields.forEach((colName) => { + this.columns.forEach((col) => { + if (col.field == colName) { + let _updates = w2utils.clone(updates) + Object.keys(_updates).forEach((key) => { + // if it is a function + if (typeof _updates[key] == 'function') { + _updates[key] = _updates[key](col) + } + if (col[key] != _updates[key]) effected++ + }) + w2utils.extend(col, _updates) + } + }) + }) + if (effected > 0) { + this.refresh() // need full refresh due to colgroups not reassigning properly + } + return effected + } + toggleColumn() { + return this.updateColumn(Array.from(arguments), { hidden(col) { return !col.hidden } }) + } + showColumn() { + return this.updateColumn(Array.from(arguments), { hidden: false }) + } + hideColumn() { + return this.updateColumn(Array.from(arguments), { hidden: true }) + } + addSearch(before, search) { + let added = 0 + if (arguments.length == 1) { + search = before + before = this.searches.length + } else { + if (typeof before == 'string') before = this.getSearch(before, true) + if (before == null) before = this.searches.length + } + if (!Array.isArray(search)) search = [search] + for (let i = 0; i < search.length; i++) { + this.searches.splice(before, 0, search[i]) + before++ + added++ + } + this.searchClose() + return added + } + removeSearch() { + let removed = 0 + for (let a = 0; a < arguments.length; a++) { + for (let r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a]) { this.searches.splice(r, 1); removed++ } + } + } + this.searchClose() + return removed + } + getSearch(field, returnIndex) { + // no arguments - return fields of all searches + if (arguments.length === 0) { + let ret = [] + for (let i = 0; i < this.searches.length; i++) ret.push(this.searches[i].field) + return ret + } + // find search + for (let i = 0; i < this.searches.length; i++) { + if (this.searches[i].field == field) { + if (returnIndex === true) return i; else return this.searches[i] + } + } + return null + } + toggleSearch() { + let effected = 0 + for (let a = 0; a < arguments.length; a++) { + for (let r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a]) { + this.searches[r].hidden = !this.searches[r].hidden + effected++ + } + } + } + this.searchClose() + return effected + } + showSearch() { + let shown = 0 + for (let a = 0; a < arguments.length; a++) { + for (let r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a] && this.searches[r].hidden !== false) { + this.searches[r].hidden = false + shown++ + } + } + } + this.searchClose() + return shown + } + hideSearch() { + let hidden = 0 + for (let a = 0; a < arguments.length; a++) { + for (let r = this.searches.length-1; r >= 0; r--) { + if (this.searches[r].field == arguments[a] && this.searches[r].hidden !== true) { + this.searches[r].hidden = true + hidden++ + } + } + } + this.searchClose() + return hidden + } + getSearchData(field) { + for (let i = 0; i < this.searchData.length; i++) { + if (this.searchData[i].field == field) return this.searchData[i] + } + return null + } + localSort(silent, noResetRefresh) { + let obj = this + let url = this.url?.get ?? this.url + if (url) { + console.log('ERROR: grid.localSort can only be used on local data source, grid.url should be empty.') + return + } + if (Object.keys(this.sortData).length === 0) return + let time = Date.now() + // process date fields + this.selectionSave() + this.prepareData() + if (!noResetRefresh) { + this.reset() + } + // process sortData + for (let i = 0; i < this.sortData.length; i++) { + let column = this.getColumn(this.sortData[i].field) + if (!column) return // TODO: ability to sort columns when they are not part of colums array + if (typeof column.render == 'string') { + if (['date', 'age'].indexOf(column.render.split(':')[0]) != -1) { + this.sortData[i].field_ = column.field + '_' + } + if (['time'].indexOf(column.render.split(':')[0]) != -1) { + this.sortData[i].field_ = column.field + '_' + } + } + } + // prepare paths and process sort + preparePaths() + this.records.sort((a, b) => { + return compareRecordPaths(a, b) + }) + cleanupPaths() + this.selectionRestore(noResetRefresh) + time = Date.now() - time + if (silent !== true && this.show.statusSort) { + setTimeout(() => { + this.status(w2utils.lang('Sorting took ${count} seconds', { count: time/1000 })) + }, 10) + } + return time + // grab paths before sorting for efficiency and because calling obj.get() + // while sorting 'obj.records' is unsafe, at least on webkit + function preparePaths() { + for (let i = 0; i < obj.records.length; i++) { + let rec = obj.records[i] + if (rec.w2ui?.parent_recid != null) { + rec.w2ui._path = getRecordPath(rec) + } + } + } + // cleanup and release memory allocated by preparePaths() + function cleanupPaths() { + for (let i = 0; i < obj.records.length; i++) { + let rec = obj.records[i] + if (rec.w2ui?.parent_recid != null) { + rec.w2ui._path = null + } + } + } + // compare two paths, from root of tree to given records + function compareRecordPaths(a, b) { + if ((!a.w2ui || a.w2ui.parent_recid == null) && (!b.w2ui || b.w2ui.parent_recid == null)) { + return compareRecords(a, b) // no tree, fast path + } + let pa = getRecordPath(a) + let pb = getRecordPath(b) + for (let i = 0; i < Math.min(pa.length, pb.length); i++) { + let diff = compareRecords(pa[i], pb[i]) + if (diff !== 0) return diff // different subpath + } + if (pa.length > pb.length) return 1 + if (pa.length < pb.length) return -1 + console.log('ERROR: two paths should not be equal.') + return 0 + } + // return an array of all records from root to and including 'rec' + function getRecordPath(rec) { + if (!rec.w2ui || rec.w2ui.parent_recid == null) return [rec] + if (rec.w2ui._path) + return rec.w2ui._path + // during actual sort, we should never reach this point + let subrec = obj.get(rec.w2ui.parent_recid) + if (!subrec) { + console.log('ERROR: no parent record: ' + rec.w2ui.parent_recid) + return [rec] + } + return (getRecordPath(subrec).concat(rec)) + } + // compare two records according to sortData and finally recid + function compareRecords(a, b) { + if (a === b) return 0 // optimize, same object + for (let i = 0; i < obj.sortData.length; i++) { + let fld = obj.sortData[i].field + let sortFld = (obj.sortData[i].field_) ? obj.sortData[i].field_ : fld + let aa = a[sortFld] + let bb = b[sortFld] + if (String(fld).indexOf('.') != -1) { + aa = obj.parseField(a, sortFld) + bb = obj.parseField(b, sortFld) + } + let col = obj.getColumn(fld) + if (col && Object.keys(col.editable).length > 0) { // for drop editable fields and drop downs + if (w2utils.isPlainObject(aa) && aa.text) aa = aa.text + if (w2utils.isPlainObject(bb) && bb.text) bb = bb.text + } + let ret = compareCells(aa, bb, i, obj.sortData[i].direction, col.sortMode || 'default') + if (ret !== 0) return ret + } + // break tie for similar records, + // required to have consistent ordering for tree paths + let ret = compareCells(a.recid, b.recid, -1, 'asc') + return ret + } + // compare two values, aa and bb, producing consistent ordering + function compareCells(aa, bb, i, direction, sortMode) { + // if both objects are strictly equal, we're done + if (aa === bb) + return 0 + // all nulls, empty and undefined on bottom + if ((aa == null || aa === '') && (bb != null && bb !== '')) + return 1 + if ((aa != null && aa !== '') && (bb == null || bb === '')) + return -1 + let dir = (direction.toLowerCase() === 'asc') ? 1 : -1 + // for different kind of objects, sort by object type + if (typeof aa != typeof bb) + return (typeof aa > typeof bb) ? dir : -dir + // for different kind of classes, sort by classes + if (aa.constructor.name != bb.constructor.name) + return (aa.constructor.name > bb.constructor.name) ? dir : -dir + // if we're dealing with non-null objects, call valueOf(). + // this mean that Date() or custom objects will compare properly. + if (aa && typeof aa == 'object') + aa = aa.valueOf() + if (bb && typeof bb == 'object') + bb = bb.valueOf() + // if we're still dealing with non-null objects that have + // a useful Object => String conversion, convert to string. + let defaultToString = {}.toString + if (aa && typeof aa == 'object' && aa.toString != defaultToString) + aa = String(aa) + if (bb && typeof bb == 'object' && bb.toString != defaultToString) + bb = String(bb) + // do case-insensitive string comparison + if (typeof aa == 'string') + aa = aa.toLowerCase().trim() + if (typeof bb == 'string') + bb = bb.toLowerCase().trim() + switch (sortMode) { + case 'natural': + sortMode = w2utils.naturalCompare + break + case 'i18n': + sortMode = w2utils.i18nCompare + break + } + if (typeof sortMode == 'function') { + return sortMode(aa,bb) * dir + } + // compare both objects + if (aa > bb) + return dir + if (aa < bb) + return -dir + return 0 + } + } + localSearch(silent) { + let obj = this + let url = this.url?.get ?? this.url + if (url) { + console.log('ERROR: grid.localSearch can only be used on local data source, grid.url should be empty.') + return + } + let time = Date.now() + let defaultToString = {}.toString + let duplicateMap = {} + this.total = this.records.length + // mark all records as shown + this.last.searchIds = [] + // prepare date/time fields + this.prepareData() + // hide records that did not match + if (this.searchData.length > 0 && !url) { + this.total = 0 + for (let i = 0; i < this.records.length; i++) { + let rec = this.records[i] + let match = searchRecord(rec) + if (match) { + if (rec?.w2ui) addParent(rec.w2ui.parent_recid) + if (this.showExtraOnSearch > 0) { + let before = this.showExtraOnSearch + let after = this.showExtraOnSearch + if (i < before) before = i + if (i + after > this.records.length) after = this.records.length - i + if (before > 0) { + for (let j = i - before; j < i; j++) { + if (this.last.searchIds.indexOf(j) < 0) + this.last.searchIds.push(j) + } + } + if (this.last.searchIds.indexOf(i) < 0) this.last.searchIds.push(i) + if (after > 0) { + for (let j = (i + 1) ; j <= (i + after) ; j++) { + if (this.last.searchIds.indexOf(j) < 0) this.last.searchIds.push(j) + } + } + } else { + this.last.searchIds.push(i) + } + } + } + this.total = this.last.searchIds.length + } + time = Date.now() - time + if (silent !== true && this.show.statusSearch) { + setTimeout(() => { + this.status(w2utils.lang('Search took ${count} seconds', { count: time/1000 })) + }, 10) + } + return time + // check if a record (or one of its closed children) matches the search data + function searchRecord(rec) { + let fl = 0, val1, val2, val3, tmp + let orEqual = false + for (let j = 0; j < obj.searchData.length; j++) { + let sdata = obj.searchData[j] + let search = obj.getSearch(sdata.field) + if (sdata == null) continue + if (search == null) search = { field: sdata.field, type: sdata.type } + let val1b = obj.parseField(rec, search.field) + val1 = (val1b !== null && val1b !== undefined && + (typeof val1b != 'object' || val1b.toString != defaultToString)) ? + String(val1b).toLowerCase() : '' // do not match a bogus string + if (sdata.value != null) { + if (!Array.isArray(sdata.value)) { + val2 = String(sdata.value).toLowerCase() + } else { + val2 = sdata.value[0] + val3 = sdata.value[1] + } + } + switch (sdata.operator) { + case '=': + case 'is': + if (obj.parseField(rec, search.field) == sdata.value) fl++ // do not hide record + else if (search.type == 'date') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatDate(tmp, 'yyyy-mm-dd') + val2 = w2utils.formatDate(w2utils.isDate(val2, w2utils.settings.dateFormat, true), 'yyyy-mm-dd') + if (val1 == val2) fl++ + } + else if (search.type == 'time') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatTime(tmp, 'hh24:mi') + val2 = w2utils.formatTime(val2, 'hh24:mi') + if (val1 == val2) fl++ + } + else if (search.type == 'datetime') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatDateTime(tmp, 'yyyy-mm-dd|hh24:mm:ss') + val2 = w2utils.formatDateTime(w2utils.isDateTime(val2, w2utils.settings.datetimeFormat, true), 'yyyy-mm-dd|hh24:mm:ss') + if (val1 == val2) fl++ + } + break + case 'between': + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + if (parseFloat(obj.parseField(rec, search.field)) >= parseFloat(val2) && parseFloat(obj.parseField(rec, search.field)) <= parseFloat(val3)) fl++ + } + else if (search.type == 'date') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.isDate(tmp, w2utils.settings.dateFormat, true) + val2 = w2utils.isDate(val2, w2utils.settings.dateFormat, true) + val3 = w2utils.isDate(val3, w2utils.settings.dateFormat, true) + if (val3 != null) val3 = new Date(val3.getTime() + 86400000) // 1 day + if (val1 >= val2 && val1 < val3) fl++ + } + else if (search.type == 'time') { + val1 = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val2 = w2utils.isTime(val2, true) + val3 = w2utils.isTime(val3, true) + val2 = (new Date()).setHours(val2.hours, val2.minutes, val2.seconds ? val2.seconds : 0, 0) + val3 = (new Date()).setHours(val3.hours, val3.minutes, val3.seconds ? val3.seconds : 0, 0) + if (val1 >= val2 && val1 < val3) fl++ + } + else if (search.type == 'datetime') { + val1 = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val2 = w2utils.isDateTime(val2, w2utils.settings.datetimeFormat, true) + val3 = w2utils.isDateTime(val3, w2utils.settings.datetimeFormat, true) + if (val3) val3 = new Date(val3.getTime() + 86400000) // 1 day + if (val1 >= val2 && val1 < val3) fl++ + } + break + case '<=': + orEqual = true + case '<': + case 'less': + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + val1 = parseFloat(obj.parseField(rec, search.field)) + val2 = parseFloat(sdata.value) + if (val1 < val2 || (orEqual && val1 === val2)) fl++ + } + else if (search.type == 'date') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.isDate(tmp, w2utils.settings.dateFormat, true) + val2 = w2utils.isDate(val2, w2utils.settings.dateFormat, true) + if (val1 < val2 || (orEqual && val1 === val2)) fl++ + } + else if (search.type == 'time') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatTime(tmp, 'hh24:mi') + val2 = w2utils.formatTime(val2, 'hh24:mi') + if (val1 < val2 || (orEqual && val1 === val2)) fl++ + } + else if (search.type == 'datetime') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatDateTime(tmp, 'yyyy-mm-dd|hh24:mm:ss') + val2 = w2utils.formatDateTime(w2utils.isDateTime(val2, w2utils.settings.datetimeFormat, true), 'yyyy-mm-dd|hh24:mm:ss') + if (val1.length == val2.length && (val1 < val2 || (orEqual && val1 === val2))) fl++ + } + break + case '>=': + orEqual = true + case '>': + case 'more': + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + val1 = parseFloat(obj.parseField(rec, search.field)) + val2 = parseFloat(sdata.value) + if (val1 > val2 || (orEqual && val1 === val2)) fl++ + } + else if (search.type == 'date') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.isDate(tmp, w2utils.settings.dateFormat, true) + val2 = w2utils.isDate(val2, w2utils.settings.dateFormat, true) + if (val1 > val2 || (orEqual && val1 === val2)) fl++ + } + else if (search.type == 'time') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatTime(tmp, 'hh24:mi') + val2 = w2utils.formatTime(val2, 'hh24:mi') + if (val1 > val2 || (orEqual && val1 === val2)) fl++ + } + else if (search.type == 'datetime') { + tmp = (obj.parseField(rec, search.field + '_') instanceof Date ? obj.parseField(rec, search.field + '_') : obj.parseField(rec, search.field)) + val1 = w2utils.formatDateTime(tmp, 'yyyy-mm-dd|hh24:mm:ss') + val2 = w2utils.formatDateTime(w2utils.isDateTime(val2, w2utils.settings.datetimeFormat, true), 'yyyy-mm-dd|hh24:mm:ss') + if (val1.length == val2.length && (val1 > val2 || (orEqual && val1 === val2))) fl++ + } + break + case 'in': + tmp = sdata.value + if (sdata.svalue) tmp = sdata.svalue + if ((tmp.indexOf(w2utils.isFloat(val1b) ? parseFloat(val1b) : val1b) !== -1) || tmp.indexOf(val1) !== -1) fl++ + break + case 'not in': + tmp = sdata.value + if (sdata.svalue) tmp = sdata.svalue + if (!((tmp.indexOf(w2utils.isFloat(val1b) ? parseFloat(val1b) : val1b) !== -1) || tmp.indexOf(val1) !== -1)) fl++ + break + case 'begins': + case 'begins with': // need for back compatibility + if (val1.indexOf(val2) === 0) fl++ // do not hide record + break + case 'contains': + if (val1.indexOf(val2) >= 0) fl++ // do not hide record + break + case 'null': + if (obj.parseField(rec, search.field) == null) fl++ // do not hide record + break + case 'not null': + if (obj.parseField(rec, search.field) != null) fl++ // do not hide record + break + case 'ends': + case 'ends with': // need for back compatibility + let lastIndex = val1.lastIndexOf(val2) + if (lastIndex !== -1 && lastIndex == val1.length - val2.length) fl++ // do not hide record + break + } + } + if ((obj.last.logic == 'OR' && fl !== 0) || + (obj.last.logic == 'AND' && fl == obj.searchData.length)) + return true + if (rec.w2ui?.children && rec.w2ui?.expanded !== true) { + // there are closed children, search them too. + for (let r = 0; r < rec.w2ui.children.length; r++) { + let subRec = rec.w2ui.children[r] + if (searchRecord(subRec)) + return true + } + } + return false + } + // add parents nodes recursively + function addParent(recid) { + let i = obj.get(recid, true) + if (i == null || recid == null || duplicateMap[recid] || obj.last.searchIds.includes(i)) { + return + } + duplicateMap[recid] = true + let rec = obj.records[i] + if (rec?.w2ui) { + addParent(rec.w2ui.parent_recid) + } + obj.last.searchIds.push(i) + } + } + getRangeData(range, extra) { + let rec1 = this.get(range[0].recid, true) + let rec2 = this.get(range[1].recid, true) + let col1 = range[0].column + let col2 = range[1].column + let res = [] + if (col1 == col2) { // one row + for (let r = rec1; r <= rec2; r++) { + let record = this.records[r] + let dt = record[this.columns[col1].field] || null + if (extra !== true) { + res.push(dt) + } else { + res.push({ data: dt, column: col1, index: r, record: record }) + } + } + } else if (rec1 == rec2) { // one line + let record = this.records[rec1] + for (let i = col1; i <= col2; i++) { + let dt = record[this.columns[i].field] || null + if (extra !== true) { + res.push(dt) + } else { + res.push({ data: dt, column: i, index: rec1, record: record }) + } + } + } else { + for (let r = rec1; r <= rec2; r++) { + let record = this.records[r] + res.push([]) + for (let i = col1; i <= col2; i++) { + let dt = record[this.columns[i].field] + if (extra !== true) { + res[res.length-1].push(dt) + } else { + res[res.length-1].push({ data: dt, column: i, index: r, record: record }) + } + } + } + } + return res + } + addRange(ranges) { + let added = 0, first, last + if (this.selectType == 'row') return added + if (!Array.isArray(ranges)) ranges = [ranges] + // if it is selection + for (let i = 0; i < ranges.length; i++) { + if (typeof ranges[i] != 'object') ranges[i] = { name: 'selection' } + if (ranges[i].name == 'selection') { + if (this.show.selectionBorder === false) continue + let sel = this.getSelection() + if (sel.length === 0) { + this.removeRange('selection') + continue + } else { + first = sel[0] + last = sel[sel.length-1] + } + } else { // other range + first = ranges[i].range[0] + last = ranges[i].range[1] + } + if (first) { + let rg = { + name: ranges[i].name, + range: [{ recid: first.recid, column: first.column }, { recid: last.recid, column: last.column }], + style: ranges[i].style || '' + } + // add range + let ind = false + for (let j = 0; j < this.ranges.length; j++) if (this.ranges[j].name == ranges[i].name) { ind = j; break } + if (ind !== false) { + this.ranges[ind] = rg + } else { + this.ranges.push(rg) + } + added++ + } + } + this.refreshRanges() + return added + } + removeRange() { + let removed = 0 + for (let a = 0; a < arguments.length; a++) { + let name = arguments[a] + query(this.box).find('#grid_'+ this.name +'_'+ name).remove() + query(this.box).find('#grid_'+ this.name +'_f'+ name).remove() + for (let r = this.ranges.length-1; r >= 0; r--) { + if (this.ranges[r].name == name) { + this.ranges.splice(r, 1) + removed++ + } + } + } + return removed + } + refreshRanges() { + if (this.ranges.length === 0) return + let self = this + let range + let time = Date.now() + let rec1 = query(this.box).find(`#grid_${this.name}_frecords`) + let rec2 = query(this.box).find(`#grid_${this.name}_records`) + for (let i = 0; i < this.ranges.length; i++) { + let rg = this.ranges[i] + let first = rg.range[0] + let last = rg.range[1] + if (first.index == null) first.index = this.get(first.recid, true) + if (last.index == null) last.index = this.get(last.recid, true) + let td1 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(first.recid) + ' td[col="'+ first.column +'"]') + let td2 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(last.recid) + ' td[col="'+ last.column +'"]') + let td1f = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(first.recid) + ' td[col="'+ first.column +'"]') + let td2f = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(last.recid) + ' td[col="'+ last.column +'"]') + let _lastColumn = last.column + // adjustment due to column virtual scroll + if (first.column < this.last.colStart && last.column > this.last.colStart) { + td1 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(first.recid) + ' td[col="start"]') + } + if (first.column < this.last.colEnd && last.column > this.last.colEnd) { + td2 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(last.recid) + ' td[col="end"]') + _lastColumn = '"end"' + } + // if virtual scrolling kicked in + let index_top = parseInt(query(this.box).find('#grid_'+ this.name +'_rec_top').next().attr('index')) + let index_bottom = parseInt(query(this.box).find('#grid_'+ this.name +'_rec_bottom').prev().attr('index')) + let index_ftop = parseInt(query(this.box).find('#grid_'+ this.name +'_frec_top').next().attr('index')) + let index_fbottom = parseInt(query(this.box).find('#grid_'+ this.name +'_frec_bottom').prev().attr('index')) + if (td1.length === 0 && first.index < index_top && last.index > index_top) { + td1 = query(this.box).find('#grid_'+ this.name +'_rec_top').next().find('td[col="'+ first.column +'"]') + } + if (td2.length === 0 && last.index > index_bottom && first.index < index_bottom) { + td2 = query(this.box).find('#grid_'+ this.name +'_rec_bottom').prev().find('td[col="'+ _lastColumn +'"]') + } + if (td1f.length === 0 && first.index < index_ftop && last.index > index_ftop) { // frozen + td1f = query(this.box).find('#grid_'+ this.name +'_frec_top').next().find('td[col="'+ first.column +'"]') + } + if (td2f.length === 0 && last.index > index_fbottom && first.index < index_fbottom) { // frozen + td2f = query(this.box).find('#grid_'+ this.name +'_frec_bottom').prev().find('td[col="'+ last.column +'"]') + } + // do not show selection cell if it is editable + let edit = query(this.box).find('#grid_'+ this.name + '_editable') + let tmp = edit.find('.w2ui-input') + let tmp1 = tmp.attr('recid') + let tmp2 = tmp.attr('column') + if (rg.name == 'selection' && rg.range[0].recid == tmp1 && rg.range[0].column == tmp2) continue + // frozen regular columns range + range = query(this.box).find('#grid_'+ this.name +'_f'+ rg.name) + if (td1f.length > 0 || td2f.length > 0) { + if (range.length === 0) { + rec1.append('
    '+ + (rg.name == 'selection' ? '
    ' : '')+ + '
    ') + range = query(this.box).find('#grid_'+ this.name +'_f'+ rg.name) + } else { + range.attr('style', rg.style) + range.find('.w2ui-selection-resizer').show() + } + if (td2f.length === 0) { + td2f = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(last.recid) +' td:last-child') + if (td2f.length === 0) td2f = query(this.box).find('#grid_'+ this.name +'_frec_bottom td:first-child') + range.css('border-right', '0px') + range.find('.w2ui-selection-resizer').hide() + } + if (first.recid != null && last.recid != null && td1f.length > 0 && td2f.length > 0) { + let style = getComputedStyle(td2f[0]) + let top1 = (td1f.prop('offsetTop') - td1f.prop('scrollTop')) + let left1 = (td1f.prop('offsetLeft') + td1f.prop('scrollLeft')) + let top2 = (td2f.prop('offsetTop') - td2f.prop('scrollTop')) + let left2 = (td2f.prop('offsetLeft') + td2f.prop('scrollLeft')) + range.show().css({ + top : (top1 > 0 ? top1 : 0) + 'px', + left : (left1 > 0 ? left1 : 0) + 'px', + width : (left2 - left1 + parseFloat(style.width) + 2) + 'px', + height : (top2 - top1 + parseFloat(style.height) + 1) + 'px' + }) + } else { + range.hide() + } + } else { + range.hide() + } + // regular columns range + range = query(this.box).find('#grid_'+ this.name +'_'+ rg.name) + if (td1.length > 0 || td2.length > 0) { + if (range.length === 0) { + rec2.append('
    '+ + (rg.name == 'selection' ? '
    ' : '')+ + '
    ') + range = query(this.box).find('#grid_'+ this.name +'_'+ rg.name) + } else { + range.attr('style', rg.style) + } + if (td1.length === 0) { + td1 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(first.recid) +' td:first-child') + if (td1.length === 0) td1 = query(this.box).find('#grid_'+ this.name +'_rec_top td:first-child') + } + if (td2f.length !== 0) { + range.css('border-left', '0px') + } + if (first.recid != null && last.recid != null && td1.length > 0 && td2.length > 0) { + let style = getComputedStyle(td2[0]) + let top1 = (td1.prop('offsetTop') - td1.prop('scrollTop')) + let left1 = (td1.prop('offsetLeft') + td1.prop('scrollLeft')) + let top2 = (td2.prop('offsetTop') - td2.prop('scrollTop')) + let left2 = (td2.prop('offsetLeft') + td2.prop('scrollLeft')) + range.show().css({ + top : (top1 > 0 ? top1 : 0) + 'px', + left : (left1 > 0 ? left1 : 0) + 'px', + width : (left2 - left1 + parseFloat(style.width) + 2) + 'px', + height : (top2 - top1 + parseFloat(style.height) + 1) + 'px' + }) + } else { + range.hide() + } + } else { + range.hide() + } + } + // add resizer events + query(this.box).find('.w2ui-selection-resizer') + .off('.resizer') + .on('mousedown.resizer', mouseStart) + .on('dblclick.resizer', (event) => { + let edata = this.trigger('resizerDblClick', { target: this.name, originalEvent: event }) + if (edata.isCancelled === true) return + edata.finish() + }) + let edata = { target: this.name, originalRange: null, newRange: null } + return Date.now() - time + function mouseStart(event) { + let sel = self.getSelection() + self.last.move = { + type : 'expand', + x : event.screenX, + y : event.screenY, + divX : 0, + divY : 0, + recid : sel[0].recid, + column : sel[0].column, + originalRange : [w2utils.clone(sel[0]), w2utils.clone(sel[sel.length-1]) ], + newRange : [w2utils.clone(sel[0]), w2utils.clone(sel[sel.length-1]) ] + } + query('body') + .off('.w2ui-' + self.name) + .on('mousemove.w2ui-' + self.name, mouseMove) + .on('mouseup.w2ui-' + self.name, mouseStop) + // do not blur grid + event.preventDefault() + } + function mouseMove(event) { + let mv = self.last.move + if (!mv || mv.type != 'expand') return + mv.divX = (event.screenX - mv.x) + mv.divY = (event.screenY - mv.y) + // find new cell + let recid, column + let tmp = event.target + if (tmp.tagName.toUpperCase() != 'TD') tmp = query(tmp).closest('td')[0] + if (query(tmp).attr('col') != null) column = parseInt(query(tmp).attr('col')) + if (column == null) { + return + } + tmp = query(tmp).closest('tr')[0] + recid = self.records[query(tmp).attr('index')].recid + // new range + if (mv.newRange[1].recid == recid && mv.newRange[1].column == column) return + let prevNewRange = w2utils.clone(mv.newRange) + mv.newRange = [{ recid: mv.recid, column: mv.column }, { recid: recid, column: column }] + // event before + if (edata.detail) { + edata.detail.newRange = w2utils.clone(mv.newRange) + edata.detail.originalRange = w2utils.clone(mv.originalRange) + } + edata = self.trigger('selectionExtend', edata) + if (edata.isCancelled === true) { + mv.newRange = prevNewRange + edata.detail.newRange = prevNewRange + return + } else { + // default behavior + self.removeRange('grid-selection-expand') + self.addRange({ + name : 'grid-selection-expand', + range : mv.newRange, + style : 'background-color: rgba(100,100,100,0.1); border: 2px dotted rgba(100,100,100,0.5);' + }) + } + } + function mouseStop(event) { + // default behavior + self.removeRange('grid-selection-expand') + delete self.last.move + query('body').off('.w2ui-' + self.name) + // event after + if (edata.finish) edata.finish() + } + } + select() { + if (arguments.length === 0) return 0 + let selected = 0 + let sel = this.last.selection + if (!this.multiSelect) this.selectNone(true) + // if too many arguments > 150k, then it errors off + let args = Array.from(arguments) + if (Array.isArray(args[0])) args = args[0] + // event before + let tmp = { target: this.name } + if (args.length == 1) { + tmp.multiple = false + if (w2utils.isPlainObject(args[0])) { + tmp.clicked = { + recid: args[0].recid, + column: args[0].column + } + } else { + tmp.recid = args[0] + } + } else { + tmp.multiple = true + tmp.clicked = { recids: args } + } + let edata = this.trigger('select', tmp) + if (edata.isCancelled === true) return 0 + // default action + if (this.selectType == 'row') { + for (let a = 0; a < args.length; a++) { + let recid = typeof args[a] == 'object' ? args[a].recid : args[a] + let index = this.get(recid, true) + if (index == null) continue + let recEl1 = null + let recEl2 = null + if (this.searchData.length !== 0 || (index + 1 >= this.last.range_start && index + 1 <= this.last.range_end)) { + recEl1 = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)) + recEl2 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)) + } + if (this.selectType == 'row') { + if (sel.indexes.indexOf(index) != -1) continue + sel.indexes.push(index) + if (recEl1 && recEl2) { + recEl1.addClass('w2ui-selected').find('.w2ui-col-number').addClass('w2ui-row-selected') + recEl2.addClass('w2ui-selected').find('.w2ui-col-number').addClass('w2ui-row-selected') + recEl1.find('.w2ui-grid-select-check').prop('checked', true) + } + selected++ + } + } + } else { + // normalize for performance + let new_sel = {} + for (let a = 0; a < args.length; a++) { + let recid = typeof args[a] == 'object' ? args[a].recid : args[a] + let column = typeof args[a] == 'object' ? args[a].column : null + new_sel[recid] = new_sel[recid] || [] + if (Array.isArray(column)) { + new_sel[recid] = column + } else if (w2utils.isInt(column)) { + new_sel[recid].push(column) + } else { + for (let i = 0; i < this.columns.length; i++) { if (this.columns[i].hidden) continue; new_sel[recid].push(parseInt(i)) } + } + } + // add all + let col_sel = [] + for (let recid in new_sel) { + let index = this.get(recid, true) + if (index == null) continue + let recEl1 = null + let recEl2 = null + if (index + 1 >= this.last.range_start && index + 1 <= this.last.range_end) { + recEl1 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)) + recEl2 = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)) + } + let s = sel.columns[index] || [] + // default action + if (sel.indexes.indexOf(index) == -1) { + sel.indexes.push(index) + } + // only only those that are new + for (let t = 0; t < new_sel[recid].length; t++) { + if (s.indexOf(new_sel[recid][t]) == -1) s.push(new_sel[recid][t]) + } + s.sort((a, b) => { return a-b }) // sort function must be for numerical sort + for (let t = 0; t < new_sel[recid].length; t++) { + let col = new_sel[recid][t] + if (col_sel.indexOf(col) == -1) col_sel.push(col) + if (recEl1) { + recEl1.find('#grid_'+ this.name +'_data_'+ index +'_'+ col).addClass('w2ui-selected') + recEl1.find('.w2ui-col-number').addClass('w2ui-row-selected') + recEl1.find('.w2ui-grid-select-check').prop('checked', true) + } + if (recEl2) { + recEl2.find('#grid_'+ this.name +'_data_'+ index +'_'+ col).addClass('w2ui-selected') + recEl2.find('.w2ui-col-number').addClass('w2ui-row-selected') + recEl2.find('.w2ui-grid-select-check').prop('checked', true) + } + selected++ + } + // save back to selection object + sel.columns[index] = s + } + // select columns (need here for speed) + for (let c = 0; c < col_sel.length; c++) { + query(this.box).find('#grid_'+ this.name +'_column_'+ col_sel[c] +' .w2ui-col-header').addClass('w2ui-col-selected') + } + } + // need to sort new selection for speed + sel.indexes.sort((a, b) => { return a-b }) + // all selected? + let areAllSelected = (this.records.length > 0 && sel.indexes.length == this.records.length), + areAllSearchedSelected = (sel.indexes.length > 0 && this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length) + if (areAllSelected || areAllSearchedSelected) { + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', true) + } else { + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', false) + } + this.status() + this.addRange('selection') + this.updateToolbar(sel, areAllSelected) + // event after + edata.finish() + return selected + } + unselect() { + let unselected = 0 + let sel = this.last.selection + // if too many arguments > 150k, then it errors off + let args = Array.from(arguments) + if (Array.isArray(args[0])) args = args[0] + // event before + let tmp = { target: this.name } + if (args.length == 1) { + tmp.multiple = false + if (w2utils.isPlainObject(args[0])) { + tmp.clicked = { + recid: args[0].recid, + column: args[0].column + } + } else { + tmp.clicked = { recid: args[0] } + } + } else { + tmp.multiple = true + tmp.recids = args + } + let edata = this.trigger('select', tmp) + if (edata.isCancelled === true) return 0 + for (let a = 0; a < args.length; a++) { + let recid = typeof args[a] == 'object' ? args[a].recid : args[a] + let record = this.get(recid) + if (record == null) continue + let index = this.get(record.recid, true) + let recEl1 = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)) + let recEl2 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)) + if (this.selectType == 'row') { + if (sel.indexes.indexOf(index) == -1) continue + // default action + sel.indexes.splice(sel.indexes.indexOf(index), 1) + recEl1.removeClass('w2ui-selected w2ui-inactive').find('.w2ui-col-number').removeClass('w2ui-row-selected') + recEl2.removeClass('w2ui-selected w2ui-inactive').find('.w2ui-col-number').removeClass('w2ui-row-selected') + if (recEl1.length != 0) { + recEl1[0].style.cssText = 'height: '+ this.recordHeight +'px; ' + recEl1.attr('custom_style') + recEl2[0].style.cssText = 'height: '+ this.recordHeight +'px; ' + recEl2.attr('custom_style') + } + recEl1.find('.w2ui-grid-select-check').prop('checked', false) + unselected++ + } else { + let col = args[a].column + if (!w2utils.isInt(col)) { // unselect all columns + let cols = [] + for (let i = 0; i < this.columns.length; i++) { if (this.columns[i].hidden) continue; cols.push({ recid: recid, column: i }) } + return this.unselect(cols) + } + let s = sel.columns[index] + if (!Array.isArray(s) || s.indexOf(col) == -1) continue + // default action + s.splice(s.indexOf(col), 1) + query(this.box).find(`#grid_${this.name}_rec_${w2utils.escapeId(recid)} > td[col="${col}"]`).removeClass('w2ui-selected w2ui-inactive') + query(this.box).find(`#grid_${this.name}_frec_${w2utils.escapeId(recid)} > td[col="${col}"]`).removeClass('w2ui-selected w2ui-inactive') + // check if any row/column still selected + let isColSelected = false + let isRowSelected = false + let tmp = this.getSelection() + for (let i = 0; i < tmp.length; i++) { + if (tmp[i].column == col) isColSelected = true + if (tmp[i].recid == recid) isRowSelected = true + } + if (!isColSelected) { + query(this.box).find(`.w2ui-grid-columns td[col="${col}"] .w2ui-col-header, .w2ui-grid-fcolumns td[col="${col}"] .w2ui-col-header`).removeClass('w2ui-col-selected') + } + if (!isRowSelected) { + query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)).find('.w2ui-col-number').removeClass('w2ui-row-selected') + } + unselected++ + if (s.length === 0) { + delete sel.columns[index] + sel.indexes.splice(sel.indexes.indexOf(index), 1) + recEl1.find('.w2ui-grid-select-check').prop('checked', false) + } + } + } + // all selected? + let areAllSelected = (this.records.length > 0 && sel.indexes.length == this.records.length), + areAllSearchedSelected = (sel.indexes.length > 0 && this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length) + if (areAllSelected || areAllSearchedSelected) { + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', true) + } else { + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', false) + } + // show number of selected + this.status() + this.addRange('selection') + this.updateToolbar(sel, areAllSelected) + // event after + edata.finish() + return unselected + } + selectAll() { + let time = Date.now() + if (this.multiSelect === false) return + // default action + let url = this.url?.get ?? this.url + let sel = w2utils.clone(this.last.selection) + let cols = [] + for (let i = 0; i < this.columns.length; i++) cols.push(i) + // if local data source and searched + sel.indexes = [] + if (!url && this.searchData.length !== 0) { + // local search applied + for (let i = 0; i < this.last.searchIds.length; i++) { + sel.indexes.push(this.last.searchIds[i]) + if (this.selectType != 'row') sel.columns[this.last.searchIds[i]] = cols.slice() // .slice makes copy of the array + } + } else { + let buffered = this.records.length + if (this.searchData.length != 0 && !url) buffered = this.last.searchIds.length + for (let i = 0; i < buffered; i++) { + sel.indexes.push(i) + if (this.selectType != 'row') sel.columns[i] = cols.slice() // .slice makes copy of the array + } + } + // event before + let edata = this.trigger('select', { target: this.name, multiple: true, all: true, clicked: sel }) + if (edata.isCancelled === true) return + this.last.selection = sel + // add selected class + if (this.selectType == 'row') { + query(this.box).find('.w2ui-grid-records tr:not(.w2ui-empty-record)') + .addClass('w2ui-selected').find('.w2ui-col-number').addClass('w2ui-row-selected') + query(this.box).find('.w2ui-grid-frecords tr:not(.w2ui-empty-record)') + .addClass('w2ui-selected').find('.w2ui-col-number').addClass('w2ui-row-selected') + query(this.box).find('input.w2ui-grid-select-check').prop('checked', true) + } else { + query(this.box).find('.w2ui-grid-columns td .w2ui-col-header, .w2ui-grid-fcolumns td .w2ui-col-header').addClass('w2ui-col-selected') + query(this.box).find('.w2ui-grid-records tr .w2ui-col-number').addClass('w2ui-row-selected') + query(this.box).find('.w2ui-grid-records tr:not(.w2ui-empty-record)') + .find('.w2ui-grid-data:not(.w2ui-col-select)').addClass('w2ui-selected') + query(this.box).find('.w2ui-grid-frecords tr .w2ui-col-number').addClass('w2ui-row-selected') + query(this.box).find('.w2ui-grid-frecords tr:not(.w2ui-empty-record)') + .find('.w2ui-grid-data:not(.w2ui-col-select)').addClass('w2ui-selected') + query(this.box).find('input.w2ui-grid-select-check').prop('checked', true) + } + // enable/disable toolbar buttons + sel = this.getSelection(true) + this.addRange('selection') + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', true) + this.status() + this.updateToolbar({ indexes: sel }, true) + // event after + edata.finish() + return Date.now() - time + } + selectNone(skipEvent) { + let time = Date.now() + // event before + let edata + if (!skipEvent) { + edata = this.trigger('select', { target: this.name, clicked: [] }) + if (edata.isCancelled === true) return + } + // default action + let sel = this.last.selection + // remove selected class + if (this.selectType == 'row') { + query(this.box).find('.w2ui-grid-records tr.w2ui-selected').removeClass('w2ui-selected w2ui-inactive') + .find('.w2ui-col-number').removeClass('w2ui-row-selected') + query(this.box).find('.w2ui-grid-frecords tr.w2ui-selected').removeClass('w2ui-selected w2ui-inactive') + .find('.w2ui-col-number').removeClass('w2ui-row-selected') + query(this.box).find('input.w2ui-grid-select-check').prop('checked', false) + } else { + query(this.box).find('.w2ui-grid-columns td .w2ui-col-header, .w2ui-grid-fcolumns td .w2ui-col-header').removeClass('w2ui-col-selected') + query(this.box).find('.w2ui-grid-records tr .w2ui-col-number').removeClass('w2ui-row-selected') + query(this.box).find('.w2ui-grid-frecords tr .w2ui-col-number').removeClass('w2ui-row-selected') + query(this.box).find('.w2ui-grid-data.w2ui-selected').removeClass('w2ui-selected w2ui-inactive') + query(this.box).find('input.w2ui-grid-select-check').prop('checked', false) + } + sel.indexes = [] + sel.columns = {} + this.removeRange('selection') + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', false) + this.status() + this.updateToolbar(sel, false) + // event after + if (!skipEvent) { + edata.finish() + } + return Date.now() - time + } + updateToolbar(sel) { + let obj = this + let cnt = sel && sel.indexes ? sel.indexes.length : 0 + this.toolbar.items.forEach((item) => { + _checkItem(item, '') + if (Array.isArray(item.items)) { + item.items.forEach((it) => { + _checkItem(it, item.id + ':') + }) + } + }) + // enable/disable toolbar search button + if (this.show.toolbarSave) { + if (this.getChanges().length > 0) { + this.toolbar.enable('w2ui-save') + } else { + this.toolbar.disable('w2ui-save') + } + } + function _checkItem(item, prefix) { + if (item.batch != null) { + let enabled = false + if (item.batch === true) { + if (cnt > 0) enabled = true + } else if (typeof item.batch == 'number') { + if (cnt === item.batch) enabled = true + } else if (typeof item.batch == 'function') { + enabled = item.batch({ cnt, sel }) + } + if (enabled) { + obj.toolbar.enable(prefix + item.id) + } else { + obj.toolbar.disable(prefix + item.id) + } + } + } + } + getSelection(returnIndex) { + let ret = [] + let sel = this.last.selection + if (this.selectType == 'row') { + for (let i = 0; i < sel.indexes.length; i++) { + if (!this.records[sel.indexes[i]]) continue + if (returnIndex === true) ret.push(sel.indexes[i]); else ret.push(this.records[sel.indexes[i]].recid) + } + return ret + } else { + for (let i = 0; i < sel.indexes.length; i++) { + let cols = sel.columns[sel.indexes[i]] + if (!this.records[sel.indexes[i]]) continue + for (let j = 0; j < cols.length; j++) { + ret.push({ recid: this.records[sel.indexes[i]].recid, index: parseInt(sel.indexes[i]), column: cols[j] }) + } + } + return ret + } + } + search(field, value) { + let url = this.url?.get ?? this.url + let searchData = [] + let last_multi = this.last.multi + let last_logic = this.last.logic + let last_field = this.last.field + let last_search = this.last.search + let hasHiddenSearches = false + let overlay = query(`#w2overlay-${this.name}-search-overlay`) + // add hidden searches + for (let i = 0; i < this.searches.length; i++) { + if (!this.searches[i].hidden || this.searches[i].value == null) continue + searchData.push({ + field : this.searches[i].field, + operator : this.searches[i].operator || 'is', + type : this.searches[i].type, + value : this.searches[i].value || '' + }) + hasHiddenSearches = true + } + if (arguments.length === 0 && overlay.length === 0) { + if (this.multiSearch) { + field = this.searchData + value = this.last.logic + } else { + field = this.last.field + value = this.last.search + } + } + // 1: search() - advanced search (reads from popup) + if (arguments.length === 0 && overlay.length !== 0) { + this.focus() // otherwise search drop down covers searches + last_logic = overlay.find(`#grid_${this.name}_logic`).val() + last_search = '' + // advanced search + for (let i = 0; i < this.searches.length; i++) { + let search = this.searches[i] + let operator = overlay.find('#grid_'+ this.name + '_operator_'+ i).val() + let field1 = overlay.find('#grid_'+ this.name + '_field_'+ i) + let field2 = overlay.find('#grid_'+ this.name + '_field2_'+ i) + let value1 = field1.val() + let value2 = field2.val() + let svalue = null + let text = null + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) { + let fld1 = field1[0]._w2field + let fld2 = field2[0]._w2field + if (fld1) value1 = fld1.clean(value1) + if (fld2) value2 = fld2.clean(value2) + } + if (['list', 'enum'].indexOf(search.type) != -1 || ['in', 'not in'].indexOf(operator) != -1) { + value1 = field1[0]._w2field.selected || {} + if (Array.isArray(value1)) { + svalue = [] + for (let j = 0; j < value1.length; j++) { + svalue.push(w2utils.isFloat(value1[j].id) ? parseFloat(value1[j].id) : String(value1[j].id).toLowerCase()) + delete value1[j].hidden + } + if (Object.keys(value1).length === 0) value1 = '' + } else { + text = value1.text || '' + value1 = value1.id || '' + } + } + if ((value1 !== '' && value1 != null) || (value2 != null && value2 !== '')) { + let tmp = { + field : search.field, + type : search.type, + operator : operator + } + if (operator == 'between') { + w2utils.extend(tmp, { value: [value1, value2] }) + } else if (operator == 'in' && typeof value1 == 'string') { + w2utils.extend(tmp, { value: value1.split(',') }) + } else if (operator == 'not in' && typeof value1 == 'string') { + w2utils.extend(tmp, { value: value1.split(',') }) + } else { + w2utils.extend(tmp, { value: value1 }) + } + if (svalue) w2utils.extend(tmp, { svalue: svalue }) + if (text) w2utils.extend(tmp, { text: text }) + // convert date to unix time + try { + if (search.type == 'date' && operator == 'between') { + tmp.value[0] = value1 // w2utils.isDate(value1, w2utils.settings.dateFormat, true).getTime(); + tmp.value[1] = value2 // w2utils.isDate(value2, w2utils.settings.dateFormat, true).getTime(); + } + if (search.type == 'date' && operator == 'is') { + tmp.value = value1 // w2utils.isDate(value1, w2utils.settings.dateFormat, true).getTime(); + } + } catch (e) { + } + searchData.push(tmp) + last_multi = true // if only hidden searches, then do not set + } + } + } + // 2: search(field, value) - regular search + if (typeof field == 'string') { + // if only one argument - search all + if (arguments.length == 1) { + value = field + field = 'all' + } + last_field = field + last_search = value + last_multi = false + last_logic = (hasHiddenSearches ? 'AND' : 'OR') + // loop through all searches and see if it applies + if (value != null) { + if (field.toLowerCase() == 'all') { + // if there are search fields loop thru them + if (this.searches.length > 0) { + for (let i = 0; i < this.searches.length; i++) { + let search = this.searches[i] + if (search.type == 'text' || (search.type == 'alphanumeric' && w2utils.isAlphaNumeric(value)) + || (search.type == 'int' && w2utils.isInt(value)) || (search.type == 'float' && w2utils.isFloat(value)) + || (search.type == 'percent' && w2utils.isFloat(value)) || ((search.type == 'hex' || search.type == 'color') && w2utils.isHex(value)) + || (search.type == 'currency' && w2utils.isMoney(value)) || (search.type == 'money' && w2utils.isMoney(value)) + || (search.type == 'date' && w2utils.isDate(value)) || (search.type == 'time' && w2utils.isTime(value)) + || (search.type == 'datetime' && w2utils.isDateTime(value)) || (search.type == 'datetime' && w2utils.isDate(value)) + || (search.type == 'enum' && w2utils.isAlphaNumeric(value)) || (search.type == 'list' && w2utils.isAlphaNumeric(value)) + ) { + let def = this.defaultOperator[this.operatorsMap[search.type]] + let tmp = { + field : search.field, + type : search.type, + operator : (search.operator != null ? search.operator : def), + value : value + } + if (String(value).trim() != '') searchData.push(tmp) + } + // range in global search box + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1 && String(value).trim().split('-').length == 2) { + let t = String(value).trim().split('-') + let tmp = { + field : search.field, + type : search.type, + operator : (search.operator != null ? search.operator : 'between'), + value : [t[0], t[1]] + } + searchData.push(tmp) + } + // lists fields + if (['list', 'enum'].indexOf(search.type) != -1) { + let new_values = [] + if (search.options == null) search.options = {} + if (!Array.isArray(search.options.items)) search.options.items = [] + for (let j = 0; j < search.options.items; j++) { + let tmp = search.options.items[j] + try { + let re = new RegExp(value, 'i') + if (re.test(tmp)) new_values.push(j) + if (tmp.text && re.test(tmp.text)) new_values.push(tmp.id) + } catch (e) {} + } + if (new_values.length > 0) { + let tmp = { + field : search.field, + type : search.type, + operator : (search.operator != null ? search.operator : 'in'), + value : new_values + } + searchData.push(tmp) + } + } + } + } else { + // no search fields, loop thru columns + for (let i = 0; i < this.columns.length; i++) { + let tmp = { + field : this.columns[i].field, + type : 'text', + operator : this.defaultOperator.text, + value : value + } + searchData.push(tmp) + } + } + } else { + let el = overlay.find('#grid_'+ this.name +'_search_all') + let search = this.getSearch(field) + if (search == null) search = { field: field, type: 'text' } + if (search.field == field) this.last.label = search.label + if (value !== '') { + let op = this.defaultOperator[this.operatorsMap[search.type]] + let val = value + if (['date', 'time', 'datetime'].indexOf(search.type) != -1) op = 'is' + if (['list', 'enum'].indexOf(search.type) != -1) { + op = 'is' + let tmp = el._w2field.get() + if (tmp && Object.keys(tmp).length > 0) val = tmp.id; else val = '' + } + if (search.type == 'int' && value !== '') { + op = 'is' + if (String(value).indexOf('-') != -1) { + let tmp = value.split('-') + if (tmp.length == 2) { + op = 'between' + val = [parseInt(tmp[0]), parseInt(tmp[1])] + } + } + if (String(value).indexOf(',') != -1) { + let tmp = value.split(',') + op = 'in' + val = [] + for (let i = 0; i < tmp.length; i++) val.push(tmp[i]) + } + } + if (search.operator != null) op = search.operator + let tmp = { + field : search.field, + type : search.type, + operator : op, + value : val + } + searchData.push(tmp) + } + } + } + } + // 3: search([{ field, value, [operator,] [type] }, { field, value, [operator,] [type] } ], logic) - submit whole structure + if (Array.isArray(field)) { + let logic = 'AND' + if (typeof value == 'string') { + logic = value.toUpperCase() + if (logic != 'OR' && logic != 'AND') logic = 'AND' + } + last_search = '' + last_multi = true + last_logic = logic + for (let i = 0; i < field.length; i++) { + let data = field[i] + if (typeof data.value == 'number' && data.operator == null) data.operator = this.defaultOperator.number + if (typeof data.value == 'string' && data.operator == null) data.operator = this.defaultOperator.text + if (Array.isArray(data.value) && data.operator == null) data.operator = this.defaultOperator.enum + if (w2utils.isDate(data.value) && data.operator == null) data.operator = this.defaultOperator.date + // merge current field and search if any + searchData.push(data) + } + } + // event before + let edata = this.trigger('search', { + target: this.name, + multi: (arguments.length === 0 ? true : false), + searchField: (field ? field : 'multi'), + searchValue: (field ? value : 'multi'), + searchData: searchData, + searchLogic: last_logic + }) + if (edata.isCancelled === true) return + // default action + this.searchData = edata.detail.searchData + this.last.field = last_field + this.last.search = last_search + this.last.multi = last_multi + this.last.logic = edata.detail.searchLogic + this.last.scrollTop = 0 + this.last.scrollLeft = 0 + this.last.selection.indexes = [] + this.last.selection.columns = {} + // -- clear all search field + this.searchClose() + // apply search + if (url) { + this.last.fetch.offset = 0 + this.reload() + } else { + // local search + this.localSearch() + this.refresh() + } + // event after + edata.finish() + } + // open advanced search popover + searchOpen() { + if (!this.box) return + if (this.searches.length === 0) return + // event before + let edata = this.trigger('searchOpen', { target: this.name }) + if (edata.isCancelled === true) { + return + } + let $btn = query(this.toolbar.box).find('.w2ui-grid-search-input .w2ui-search-drop') + $btn.addClass('checked') + // show search + w2tooltip.show({ + name: this.name + '-search-overlay', + anchor: query(this.box).find('#grid_'+ this.name +'_search_all').get(0), + position: 'bottom|top', + html: this.getSearchesHTML(), + align: 'left', + arrowSize: 12, + class: 'w2ui-grid-search-advanced', + hideOn: ['doc-click'] + }) + .then(event => { + this.initSearches() + this.last.search_opened = true + let overlay = query(`#w2overlay-${this.name}-search-overlay`) + overlay + .data('gridName', this.name) + .off('.grid-search') + .on('click.grid-search', () => { + // hide any tooltip opened by searches + overlay.find('input, select').each(el => { + let names = query(el).data('tooltipName') + if (names) names.forEach(name => { + w2tooltip.hide(name) + }) + }) + }) + w2utils.bindEvents(overlay.find('select, input, button'), this) + // init first field + let sfields = query(`#w2overlay-${this.name}-search-overlay *[rel=search]`) + if (sfields.length > 0) sfields[0].focus() + // event after + edata.finish() + }) + .hide(event => { + $btn.removeClass('checked') + this.last.search_opened = false + }) + } + searchClose() { + w2tooltip.hide(this.name + '-search-overlay') + } + // if clicked on a field in the search strip + searchFieldTooltip(ind, sd_ind, el) { + let sf = this.searches[ind] + let sd = this.searchData[sd_ind] + let oper = sd.operator + if (oper == 'more' && sd.type == 'date') oper = 'since' + if (oper == 'less' && sd.type == 'date') oper = 'before' + let options = '' + let val = sd.value + if (Array.isArray(sd.value)) { // && Array.isArray(sf.options.items)) { + sd.value.forEach(opt => { + options += `${opt.text || opt}` + }) + if (sd.type == 'date') { + options = '' + sd.value.forEach(opt => { + options += `${w2utils.formatDate(opt)}` + }) + } + } else { + if (sd.type == 'date') { + val = w2utils.formatDateTime(val) + } + } + w2tooltip.hide(this.name + '-search-props') + w2tooltip.show({ + name: this.name + '-search-props', + anchor: el, + class: 'w2ui-white', + hideOn: 'doc-click', + html: ` +
    + ${sf.label} + ${w2utils.lang(oper)} + ${Array.isArray(sd.value) + ? `${options}` + : `${val}` + } +
    + +
    +
    ` + }).then(event => { + query(event.detail.overlay.box).find('#remove').on('click', () => { + this.searchData.splice(`${sd_ind}`, 1) + this.reload() + this.localSearch() + w2tooltip.hide(this.name + '-search-props') + }) + }) + } + // drop down with save searches + searchSuggest(imediate, forceHide, input) { + clearTimeout(this.last.kbd_timer) + clearTimeout(this.last.overlay_timer) + this.searchShowFields(true) + this.searchClose() + if (forceHide === true) { + w2tooltip.hide(this.name + '-search-suggest') + return + } + if (query(`#w2overlay-${this.name}-search-suggest`).length > 0) { + // already shown + return + } + if (!imediate) { + this.last.overlay_timer = setTimeout(() => { this.searchSuggest(true) }, 100) + return + } + let el = query(this.box).find(`#grid_${this.name}_search_all`).get(0) + let searches = [ + ...this.defaultSearches ?? [], + ...this.defaultSearches?.length > 0 && this.savedSearches?.length > 0 ? ['--'] : [], + ...this.savedSearches ?? [] + ] + if (Array.isArray(searches) && searches.length > 0) { + w2menu.show({ + name: this.name + '-search-suggest', + anchor: el, + align: 'both', + items: searches, + hideOn: ['doc-click', 'sleect', 'remove'], + render(item) { + let ret = item.text + if (item.isDefault) ret = `${ret}` + return ret + } + }) + .select(event => { + let edata = this.trigger('searchSelect', { + target: this.name, + index: event.detail.index, + item: event.detail.item + }) + if (edata.isCancelled === true) { + event.preventDefault() + return + } + event.detail.overlay.hide() + this.last.logic = event.detail.item.logic || 'AND' + this.last.search = '' + this.last.label = '[Multiple Fields]' + this.searchData = w2utils.clone(event.detail.item.data) + this.searchSelected = w2utils.clone(event.detail.item, { exclude: ['icon', 'remove'] }) + this.reload() + edata.finish() + }) + .remove(event => { + let item = event.detail.item + let edata = this.trigger('searchRemove', { target: this.name, index: event.detail.index, item }) + if (edata.isCancelled === true) { + event.preventDefault() + return + } + event.detail.overlay.hide() + this.confirm(w2utils.lang('Do you want to delete search "${item}"?', { item: item.text })) + .yes(evt => { + // remove from searches + let search = this.savedSearches.findIndex((s) => s.id == item.id ? true : false) + if (search !== -1) { + this.savedSearches.splice(search, 1) + } + this.cacheSave('searches', this.savedSearches.map(s => w2utils.clone(s, { exclude: ['remove', 'icon'] }))) + evt.detail.self.close() + // evt after + edata.finish() + }) + .no(evt => { + evt.detail.self.close() + }) + }) + } + } + searchSave() { + let value = '' + if (this.searchSelected) { + value = this.searchSelected.text + } + let ind = this.savedSearches.findIndex(s => { return s.id == this.searchSelected?.id ? true : false }) + // event before + let edata = this.trigger('searchSave', { target: this.name, saveLocalStorage: true }) + if (edata.isCancelled === true) return + this.message({ + width: 350, + height: 150, + body: ``, + buttons: ` + + + ` + }).open(async (event) => { + query(event.detail.box).find('input, button').eq(0).val(value) + await event.complete + query(event.detail.box).find('#grid-search-cancel').on('click', () => { + this.message() + }) + query(event.detail.box).find('#grid-search-save').on('click', () => { + let name = query(event.detail.box).find('.w2ui-message .search-name').val() + // save in savedSearches + if (this.searchSelected && ind != -1) { + Object.assign(this.savedSearches[ind], { + id: name, + text: name, + logic: this.last.logic, + data: w2utils.clone(this.searchData) + }) + } else { + this.savedSearches.push({ + id: name, + text: name, + icon: 'w2ui-icon-search', + remove: true, + logic: this.last.logic, + data: this.searchData + }) + } + // save local storage + this.cacheSave('searches', this.savedSearches.map(s => w2utils.clone(s, { exclude: ['remove', 'icon'] }))) + this.message() + // update on screen + if (this.searchSelected) { + this.searchSelected.text = name + query(this.box).find(`#grid_${this.name}_search_name .name-text`).html(name) + } else { + this.searchSelected = { + text: name, + logic: this.last.logic, + data: w2utils.clone(this.searchData) + } + query(event.detail.box).find(`#grid_${this.name}_search_all`).val(' ').prop('readOnly', true) + query(event.detail.box).find(`#grid_${this.name}_search_name`).show().find('.name-text').html(name) + } + edata.finish({ name }) + }) + query(event.detail.box).find('input, button') + .off('.message') + .on('keydown.message', evt => { + let val = String(query(event.detail.box).find('.w2ui-message-body input').val()).trim() + if (evt.keyCode == 13 && val != '') { + query(event.detail.box).find('#grid-search-save').trigger('click') // enter + } + if (evt.keyCode == 27) { // escape + this.message() + } + }) + .eq(0) + .on('input.message', evt => { + let $save = query(event.detail.box).closest('.w2ui-message').find('#grid-search-save') + if (String(query(event.detail.box).val()).trim() === '') { + $save.prop('disabled', true) + } else { + $save.prop('disabled', false) + } + }) + .get(0) + .focus() + }) + } + cache(type) { + if (w2utils.hasLocalStorage && this.useLocalStorage) { + try { + let data = JSON.parse(localStorage.w2ui || '{}') + data[(this.stateId || this.name)] ??= {} + return data[(this.stateId || this.name)][type] + } catch (e) { + } + } + return null + } + cacheSave(type, value) { + if (w2utils.hasLocalStorage && this.useLocalStorage) { + try { + let data = JSON.parse(localStorage.w2ui || '{}') + data[(this.stateId || this.name)] ??= {} + data[(this.stateId || this.name)][type] = value + localStorage.w2ui = JSON.stringify(data) + return true + } catch (e) { + delete localStorage.w2ui + } + } + return false + } + searchReset(noReload) { + let searchData = [] + let hasHiddenSearches = false + // add hidden searches + for (let i = 0; i < this.searches.length; i++) { + if (!this.searches[i].hidden || this.searches[i].value == null) continue + searchData.push({ + field : this.searches[i].field, + operator : this.searches[i].operator || 'is', + type : this.searches[i].type, + value : this.searches[i].value || '' + }) + hasHiddenSearches = true + } + // event before + let edata = this.trigger('search', { reset: true, target: this.name, searchData: searchData }) + if (edata.isCancelled === true) return + // default action + let input = query(this.box).find('#grid_'+ this.name +'_search_all') + this.searchData = edata.detail.searchData + this.searchSelected = null + this.last.search = '' + this.last.logic = (hasHiddenSearches ? 'AND' : 'OR') + // --- do not reset to All Fields (I think) + input.next().hide() // advanced search button + if (this.searches.length > 0) { + if (!this.multiSearch || !this.show.searchAll) { + let tmp = 0 + while (tmp < this.searches.length && (this.searches[tmp].hidden || this.searches[tmp].simple === false)) tmp++ + if (tmp >= this.searches.length) { + // all searches are hidden + this.last.field = '' + this.last.label = '' + } else { + this.last.field = this.searches[tmp].field + this.last.label = this.searches[tmp].label + } + } else { + this.last.field = 'all' + this.last.label = 'All Fields' + input.next().show() // advanced search button + } + } + this.last.multi = false + this.last.fetch.offset = 0 + // reset scrolling position + this.last.scrollTop = 0 + this.last.scrollLeft = 0 + this.last.selection.indexes = [] + this.last.selection.columns = {} + // -- clear all search field + this.searchClose() + let all = input.val('').get(0) + if (all?._w2field) { all._w2field.reset() } + // apply search + if (!noReload) this.reload() + // event after + edata.finish() + } + searchShowFields(forceHide) { + if (forceHide === true) { + w2tooltip.hide(this.name + '-search-fields') + return + } + let items = [] + for (let s = -1; s < this.searches.length; s++) { + let search = this.searches[s] + let sField = (search ? search.field : null) + let column = this.getColumn(sField) + let disabled = false + let tooltip = null + if (this.show.searchHiddenMsg == true && s != -1 + && (column == null || (column.hidden === true && column.hideable !== false))) { + disabled = true + tooltip = w2utils.lang(`This column ${column == null ? 'does not exist' : 'is hidden'}`) + } + if (s == -1) { // -1 is All Fields search + if (!this.multiSearch || !this.show.searchAll) continue + search = { field: 'all', label: 'All Fields' } + } else { + if (column != null && column.hideable === false) continue + if (search.hidden === true) { + tooltip = w2utils.lang('This column is hidden') + // don't show hidden (not simple) searches + if (search.simple === false) continue + } + } + if (search.label == null && search.caption != null) { + console.log('NOTICE: grid search.caption property is deprecated, please use search.label. Search ->', search) + search.label = search.caption + } + items.push({ + id: search.field, + text: w2utils.lang(search.label), + search, + tooltip, + disabled, + checked: (search.field == this.last.field) + }) + } + w2menu.show({ + type: 'radio', + name: this.name + '-search-fields', + anchor: query(this.box).find('#grid_'+ this.name +'_search_name').parent().find('.w2ui-search-down').get(0), + items, + align: 'none', + hideOn: ['doc-click', 'select'] + }) + .select(event => { + this.searchInitInput(event.detail.item.search.field) + }) + } + searchInitInput(field, value) { + let search + let el = query(this.box).find('#grid_'+ this.name +'_search_all') + if (field == 'all') { + search = { field: 'all', label: w2utils.lang('All Fields') } + } else { + search = this.getSearch(field) + if (search == null) return + } + // update field + if (this.last.search != '') { + this.last.label = search.label + this.search(search.field, this.last.search) + } else { + this.last.field = search.field + this.last.label = search.label + } + el.attr('placeholder', w2utils.lang('Search') + ' ' + w2utils.lang(search.label || search.caption || search.field, true)) + } + // clears records and related params + clear(noRefresh) { + this.total = 0 + this.records = [] + this.summary = [] + this.last.fetch.offset = 0 // need this for reload button to work on remote data set + this.last.idCache = {} // optimization to free memory + this.last.selection = { indexes: [], columns: {} } + this.reset(true) + // refresh + if (!noRefresh) this.refresh() + } + // clears scroll position, selection, ranges + reset(noRefresh) { + // position + this.last.scrollTop = 0 + this.last.scrollLeft = 0 + this.last.range_start = null + this.last.range_end = null + // additional + query(this.box).find(`#grid_${this.name}_records`).prop('scrollTop', 0) + // refresh + if (!noRefresh) this.refresh() + } + skip(offset, callBack) { + let url = this.url?.get ?? this.url + if (url) { + this.offset = parseInt(offset) + if (this.offset > this.total) this.offset = this.total - this.limit + if (this.offset < 0 || !w2utils.isInt(this.offset)) this.offset = 0 + this.clear(true) + this.reload(callBack) + } else { + console.log('ERROR: grid.skip() can only be called when you have remote data source.') + } + } + load(url, callBack) { + if (url == null) { + console.log('ERROR: You need to provide url argument when calling .load() method of "'+ this.name +'" object.') + return new Promise((resolve, reject) => { reject() }) + } + // default action + this.clear(true) + return this.request('load', {}, url, callBack) + } + reload(callBack) { + let grid = this + let url = this.url?.get ?? this.url + grid.selectionSave() + if (url) { + // need to remember selection (not just last.selection object) + return this.load(url, () => { + grid.selectionRestore() + if (typeof callBack == 'function') callBack() + }) + } else { + this.reset(true) + this.localSearch() + this.selectionRestore() + if (typeof callBack == 'function') callBack({ status: 'success' }) + return new Promise(resolve => { resolve() }) + } + } + prepareParams(url, fetchOptions) { + let dataType = this.dataType ?? w2utils.settings.dataType + let postParams = fetchOptions.body + switch (dataType) { + case 'HTTPJSON': + postParams = { request: postParams } + if (['PUT', 'DELETE'].includes(fetchOptions.method)) { + fetchOptions.method = 'POST' + } + body2params() + break + case 'HTTP': + if (['PUT', 'DELETE'].includes(fetchOptions.method)) { + fetchOptions.method = 'POST' + } + body2params() + break + case 'RESTFULL': + if (['PUT', 'DELETE'].includes(fetchOptions.method)) { + fetchOptions.headers['Content-Type'] = 'application/json' + } else { + body2params() + } + break + case 'JSON': + if (fetchOptions.method == 'GET') { + postParams = { request: postParams } + body2params() + } else { + fetchOptions.headers['Content-Type'] = 'application/json' + fetchOptions.method = 'POST' + } + break + } + fetchOptions.body = typeof fetchOptions.body == 'string' ? fetchOptions.body : JSON.stringify(fetchOptions.body) + return fetchOptions + function body2params() { + Object.keys(postParams).forEach(key => { + let param = postParams[key] + if (typeof param == 'object') param = JSON.stringify(param) + url.searchParams.append(key, param) + }) + delete fetchOptions.body + } + } + request(action, postData, url, callBack) { + let self = this + let resolve, reject + let requestProm = new Promise((res, rej) => { resolve = res; reject = rej }) + if (postData == null) postData = {} + if (!url) url = this.url + if (!url) return new Promise((resolve, reject) => { reject() }) + // build parameters list + if (!w2utils.isInt(this.offset)) this.offset = 0 + if (!w2utils.isInt(this.last.fetch.offset)) this.last.fetch.offset = 0 + // add list params + let edata + let params = { + limit: this.limit, + offset: parseInt(this.offset) + parseInt(this.last.fetch.offset), + searchLogic: this.last.logic, + search: this.searchData.map((search) => { + let _search = w2utils.clone(search) + if (this.searchMap && this.searchMap[_search.field]) _search.field = this.searchMap[_search.field] + return _search + }), + sort: this.sortData.map((sort) => { + let _sort = w2utils.clone(sort) + if (this.sortMap && this.sortMap[_sort.field]) _sort.field = this.sortMap[_sort.field] + return _sort + }) + } + if (this.searchData.length === 0) { + delete params.search + delete params.searchLogic + } + if (this.sortData.length === 0) { + delete params.sort + } + // append other params + w2utils.extend(params, this.postData) + w2utils.extend(params, postData) + // other actions + if (action == 'delete' || action == 'save') { + delete params.limit + delete params.offset + params.action = action + if (action == 'delete') { + params[this.recid || 'recid'] = this.getSelection() + } + } + // event before + if (action == 'load') { + edata = this.trigger('request', { target: this.name, url, postData: params, httpMethod: 'GET', + httpHeaders: this.httpHeaders }) + if (edata.isCancelled === true) return new Promise((resolve, reject) => { reject() }) + } else { + edata = { detail: { + url, + postData: params, + httpMethod: action == 'save' ? 'PUT' : 'DELETE', + httpHeaders: this.httpHeaders + }} + } + // call server to get data + if (this.last.fetch.offset === 0) { + this.lock(w2utils.lang(this.msgRefresh), true) + } + if (this.last.fetch.controller) try { this.last.fetch.controller.abort() } catch (e) {} + // URL + url = edata.detail.url + switch (action) { + case 'save': + if (url?.save) url = url.save + break + case 'delete': + if (url?.remove) url = url.remove + break + default: + url = url?.get ?? url + } + // process url with routeData + if (Object.keys(this.routeData).length > 0) { + let info = w2utils.parseRoute(url) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + if (this.routeData[info.keys[k].name] == null) continue + url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]) + } + } + } + url = new URL(url, location) + // ajax options + let fetchOptions = this.prepareParams(url, { + method: edata.detail.httpMethod, + headers: edata.detail.httpHeaders, + body: edata.detail.postData + }) + Object.assign(this.last.fetch, { + action: action, + options: fetchOptions, + controller: new AbortController(), + start: Date.now(), + loaded: false + }) + fetchOptions.signal = this.last.fetch.controller.signal + fetch(url, fetchOptions) + .catch(processError) + .then(resp => { + if (resp == null) return // request aborted + if (resp?.status != 200) { + processError(resp ?? {}) + return + } + self.unlock() + resp.json() + .catch(processError) + .then(data => { + this.requestComplete(data, action, callBack, resolve, reject) + }) + }) + if (action == 'load') { + // event after + edata.finish() + } + return requestProm + function processError(response) { + if (response?.name === 'AbortError') { + // request was aborted by the grid + return + } + self.unlock() + // trigger event + let edata2 = self.trigger('error', { response, lastFetch: self.last.fetch }) + if (edata2.isCancelled === true) return + // default behavior + if (response.status && response.status != 200) { + self.error(response.status + ': ' + response.statusText) + } else { + console.log('ERROR: Server communication failed.', + '\n EXPECTED:', { total: 5, records: [{ recid: 1, field: 'value' }] }, + '\n OR:', { error: true, message: 'error message' }) + self.requestComplete({ error: true, message: w2utils.lang(this.msgHTTPError), response }, action, callBack, resolve, reject) + } + // event after + edata2.finish() + } + } + requestComplete(data, action, callBack, resolve, reject) { + let error = data.error ?? false + if (data.error == null && data.status === 'error') error = true + this.last.fetch.response = (Date.now() - this.last.fetch.start) / 1000 + setTimeout(() => { + if (this.show.statusResponse) { + this.status(w2utils.lang('Server Response ${count} seconds', { count: this.last.fetch.response })) + } + }, 10) + this.last.pull_more = false + this.last.pull_refresh = true + // event before + let event_name = 'load' + if (this.last.fetch.action == 'save') event_name = 'save' + if (this.last.fetch.action == 'delete') event_name = 'delete' + let edata = this.trigger(event_name, { target: this.name, error, data, lastFetch: this.last.fetch }) + if (edata.isCancelled === true) { + reject() + return + } + // parse server response + if (!error) { + // default action + if (typeof this.parser == 'function') { + data = this.parser(data) + if (typeof data != 'object') { + console.log('ERROR: Your parser did not return proper object') + } + } else { + if (data == null) { + data = { + error: true, + message: w2utils.lang(this.msgNotJSON), + } + } else if (Array.isArray(data)) { + // if it is plain array, assume these are records + data = { + error, + records: data, + total: data.length + } + } + } + if (action == 'load') { + if (data.total == null) data.total = -1 + if (data.records == null) { + data.records = [] + } + if (data.records.length == this.limit) { + let loaded = this.records.length + data.records.length + this.last.fetch.hasMore = (loaded == this.total ? false : true) + } else { + this.last.fetch.hasMore = false + this.total = this.offset + this.last.fetch.offset + data.records.length + } + if (!this.last.fetch.hasMore) { + // if no more records, then hide spinner + query(this.box).find('#grid_'+ this.name +'_rec_more, #grid_'+ this.name +'_frec_more').hide() + } + if (this.last.fetch.offset === 0) { + this.records = [] + this.summary = [] + } else { + if (data.total != -1 && parseInt(data.total) != parseInt(this.total)) { + let grid = this + this.message(w2utils.lang(this.msgNeedReload)) + .ok(() => { + delete grid.last.fetch.offset + grid.reload() + }) + return new Promise(resolve => { resolve() }) + } + } + if (w2utils.isInt(data.total)) this.total = parseInt(data.total) + // records + if (data.records) { + data.records.forEach(rec => { + if (this.recid) { + rec.recid = this.parseField(rec, this.recid) + } + if (rec.recid == null) { + rec.recid = 'recid-' + this.records.length + } + if (rec.w2ui?.summary === true) { + this.summary.push(rec) + } else { + this.records.push(rec) + } + }) + } + // summary records (if any) + if (data.summary) { + this.summary = [] // reset summary with each call + data.summary.forEach(rec => { + if (this.recid) { + rec.recid = this.parseField(rec, this.recid) + } + if (rec.recid == null) { + rec.recid = 'recid-' + this.summary.length + } + this.summary.push(rec) + }) + } + } else if (action == 'delete') { + this.reset() // unselect old selections + return this.reload() + } + } else { + this.error(w2utils.lang(data.message ?? this.msgServerError)) + reject(data) + } + // event after + let url = this.url?.get ?? this.url + if (!url) { + this.localSort() + this.localSearch() + } + this.total = parseInt(this.total) + // do not refresh if loading on infinite scroll + if (this.last.fetch.offset === 0) { + this.refresh() + } else { + this.scroll() + this.resize() + } + // call back + if (typeof callBack == 'function') callBack(data) // need to be before event:after + resolve(data) + // after event + edata.finish() + this.last.fetch.loaded = true + } + error(msg) { + // let the management of the error outside of the grid + let edata = this.trigger('error', { target: this.name, message: msg }) + if (edata.isCancelled === true) { + return + } + this.message(msg) + // event after + edata.finish() + } + getChanges(recordsBase) { + let changes = [] + if (typeof recordsBase == 'undefined') { + recordsBase = this.records + } + for (let r = 0; r < recordsBase.length; r++) { + let rec = recordsBase[r] + if (rec?.w2ui) { + if (rec.w2ui.changes != null) { + let obj = {} + obj[this.recid || 'recid'] = rec.recid + changes.push(w2utils.extend(obj, rec.w2ui.changes)) + } + // recursively look for changes in non-expanded children + if (rec.w2ui.expanded !== true && rec.w2ui.children && rec.w2ui.children.length) { + changes.push(...this.getChanges(rec.w2ui.children)) + } + } + } + return changes + } + mergeChanges() { + let changes = this.getChanges() + for (let c = 0; c < changes.length; c++) { + let record = this.get(changes[c][this.recid || 'recid']) + for (let s in changes[c]) { + if (s == 'recid' || (this.recid && s == this.recid)) continue // do not allow to change recid + if (typeof changes[c][s] === 'object') changes[c][s] = changes[c][s].text + try { + _setValue(record, s, changes[c][s]) + } catch (e) { + console.log('ERROR: Cannot merge. ', e.message || '', e) + } + if (record.w2ui) delete record.w2ui.changes + } + } + this.refresh() + function _setValue(obj, field, value) { + let fld = field.split('.') + if (fld.length == 1) { + obj[field] = value + } else { + obj = obj[fld[0]] + fld.shift() + _setValue(obj, fld.join('.'), value) + } + } + } + save(callBack) { + let changes = this.getChanges() + let url = this.url?.save ?? this.url + // event before + let edata = this.trigger('save', { target: this.name, changes: changes }) + if (edata.isCancelled === true) return + if (url) { + this.request('save', { 'changes' : edata.detail.changes }, null, + (data) => { + if (!data.error) { + // only merge changes, if save was successful + this.mergeChanges() + } + // event after + edata.finish() + // call back + if (typeof callBack == 'function') callBack(data) + } + ) + } else { + this.mergeChanges() + // event after + edata.finish() + } + } + editField(recid, column, value, event) { + let self = this + if (this.last.inEditMode === true) { + // This is triggerign when user types fast + if (event && event.keyCode == 13) { + let { index, column, value } = this.last._edit + this.editChange({ type: 'custom', value }, index, column, event) + this.editDone(index, column, event) + } else { + // when 2 chars entered fast (spreadsheet) + let input = query(this.box).find('div.w2ui-edit-box .w2ui-input') + if (input.length > 0) { + if (input.get(0).tagName == 'DIV') { + input.text(input.text() + value) + w2utils.setCursorPosition(input.get(0), input.text().length) + } else { + input.val(input.val() + value) + w2utils.setCursorPosition(input.get(0), input.val().length) + } + } + } + return + } + let index = this.get(recid, true) + let edit = this.getCellEditable(index, column) + if (!edit || ['checkbox', 'check'].includes(edit.type)) return + let rec = this.records[index] + let col = this.columns[column] + let prefix = (col.frozen === true ? '_f' : '_') + if (['list', 'enum', 'file'].indexOf(edit.type) != -1) { + console.log('ERROR: input types "list", "enum" and "file" are not supported in inline editing.') + return + } + // event before + let edata = this.trigger('editField', { target: this.name, recid, column, value, index, originalEvent: event }) + if (edata.isCancelled === true) return + value = edata.detail.value + // default behaviour + this.last.inEditMode = true + this.last.editColumn = column + this.last._edit = { value: value, index: index, column: column, recid: recid } + this.selectNone(true) // no need to trigger select event + this.select({ recid: recid, column: column }) + // create input element + let tr = query(this.box).find('#grid_'+ this.name + prefix +'rec_' + w2utils.escapeId(recid)) + let div = tr.find('[col="'+ column +'"] > div') // TD -> DIV + this.last._edit.tr = tr + this.last._edit.div = div + // clear previous if any (spreadsheet) + query(this.box).find('div.w2ui-edit-box').remove() + // for spreadsheet - insert into selection + if (this.selectType != 'row') { + query(this.box).find('#grid_'+ this.name + prefix + 'selection') + .attr('id', 'grid_'+ this.name + '_editable') + .removeClass('w2ui-selection') + .addClass('w2ui-edit-box') + .prepend('
    ') + .find('.w2ui-selection-resizer') + .remove() + div = query(this.box).find('#grid_'+ this.name + '_editable > div:first-child') + } + edit.attr = edit.attr ?? '' + edit.text = edit.text ?? '' + edit.style = edit.style ?? '' + edit.items = edit.items ?? [] + let val = (rec.w2ui?.changes?.[col.field] != null + ? w2utils.stripTags(rec.w2ui.changes[col.field]) + : w2utils.stripTags(self.parseField(rec, col.field))) + if (val == null) val = '' + let prevValue = (typeof val != 'object' ? val : '') + if (edata.detail.prevValue != null) prevValue = edata.detail.prevValue + if (value != null) val = value + let addStyle = (col.style != null ? col.style + ';' : '') + if (typeof col.render == 'string' + && ['number', 'int', 'float', 'money', 'percent', 'size'].includes(col.render.split(':')[0])) { + addStyle += 'text-align: right;' + } + // normalize items, if not yet normlized + if (edit.items.length > 0 && !w2utils.isPlainObject(edit.items[0])) { + edit.items = w2utils.normMenu(edit.items) + } + let input + let dropTypes = ['date', 'time', 'datetime', 'color', 'list', 'combo'] + let styles = getComputedStyle(tr.find('[col="'+ column +'"] > div').get(0)) + let font = `font-family: ${styles['font-family']}; font-size: ${styles['font-size']};` + switch (edit.type) { + case 'div': { + div.addClass('w2ui-editable') + .html(w2utils.stripSpaces(`
    +
    ${edit.text}`)) + input = div.find('div.w2ui-input').get(0) + input.innerText = (typeof val != 'object' ? val : '') + if (value != null) { + w2utils.setCursorPosition(input, input.innerText.length) + } else { + w2utils.setCursorPosition(input, 0, input.innerText.length) + } + break + } + default: { + div.addClass('w2ui-editable') + .html(w2utils.stripSpaces(`${edit.text}`)) + input = div.find('input').get(0) + // issue #499 + if (edit.type == 'number') { + val = w2utils.formatNumber(val) + } + if (edit.type == 'date') { + val = w2utils.formatDate(w2utils.isDate(val, edit.format, true) || new Date(), edit.format) + } + input.value = (typeof val != 'object' ? val : '') + // init w2field, attached to input._w2field + let doHide = (event) => { + let escKey = this.last._edit?.escKey + // check if any element is selected in drop down + let selected = false + let name = query(input).data('tooltipName') + if (name && w2tooltip.get(name[0])?.selected != null) { + selected = true + } + // trigger change on new value if selected from overlay + if (this.last.inEditMode && !escKey && dropTypes.includes(edit.type) // drop down types + && (event.detail.overlay.anchor?.id == this.last._edit.input?.id || edit.type == 'list')) { + this.editChange() + this.editDone(undefined, undefined, { keyCode: selected ? 13 : 0 }) // advance on select + } + } + new w2field(w2utils.extend({}, edit, { + el: input, + selected: val, + onSelect: doHide, + onHide: doHide + })) + if (value == null && input) { + // if no new value, then select content + input.select() + } + } + } + Object.assign(this.last._edit, { input, edit }) + query(input) + .off('.w2ui-editable') + .on('blur.w2ui-editable', (event) => { + if (this.last.inEditMode) { + let type = this.last._edit.edit.type + let name = query(input).data('tooltipName') // if popup is open + if (dropTypes.includes(type) && name) { + // drop downs finish edit when popover is closed + return + } + this.editChange(input, index, column, event) + this.editDone() + } + }) + .on('mousedown.w2ui-editable', (event) => { + event.stopPropagation() + }) + .on('click.w2ui-editable', (event) => { + expand.call(input, event) + }) + .on('paste.w2ui-editable', (event) => { + // clean paste to be plain text + event.preventDefault() + let text = event.clipboardData.getData('text/plain') + document.execCommand('insertHTML', false, text) + }) + .on('keyup.w2ui-editable', (event) => { + expand.call(input, event) + }) + .on('keydown.w2ui-editable', (event) => { + switch (event.keyCode) { + case 8: // backspace; + if (edit.type == 'list' && !input._w2field) { // cancel backspace when deleting element + event.preventDefault() + } + break + case 9: + case 13: + event.preventDefault() + break + case 27: // esc button exits edit mode, but if in a popup, it will also close the popup, hence + // if tooltip is open - hide it + let name = query(input).data('tooltipName') + if (name && name.length > 0) { + this.last._edit.escKey = true + w2tooltip.hide(name[0]) + event.preventDefault() + } + event.stopPropagation() + break + } + // need timeout so, this handler is executed after key is processed by browser + setTimeout(() => { + switch (event.keyCode) { + case 9: { // tab + let next = event.shiftKey + ? self.prevCell(index, column, true) + : self.nextCell(index, column, true) + if (next != null) { + let recid = self.records[next.index].recid + this.editChange(input, index, column, event) + this.editDone(index, column, event) + if (self.selectType != 'row') { + self.selectNone(true) // no need to trigger select event + self.select({ recid, column: next.colIndex }) + } else { + self.editField(recid, next.colIndex, null, event) + } + if (event.preventDefault) event.preventDefault() + } + break + } + case 13: { // enter + // check if any element is selected in drop down + let selected = false + let name = query(input).data('tooltipName') + if (name && w2tooltip.get(name[0]).selected != null) { + selected = true + } + // if tooltip is not open or no element is selected + if (!name || !selected) { + this.editChange(input, index, column, event) + this.editDone(index, column, event) + } + break + } + case 27: { // escape + this.last._edit.escKey = false + let old = self.parseField(rec, col.field) + if (rec.w2ui?.changes?.[col.field] != null) old = rec.w2ui.changes[col.field] + if (input._prevValue != null) old = input._prevValue + if (input.tagName == 'DIV') { + input.innerText = old != null ? old : '' + } else { + input.value = old != null ? old : '' + } + this.editDone(index, column, event) + setTimeout(() => { self.select({ recid: recid, column: column }) }, 1) + break + } + } + // if input too small - expand + expand(input) + }, 1) + }) + // save previous value + if (input) input._prevValue = prevValue + // focus and select + setTimeout(() => { + if (!this.last.inEditMode) return + if (input) { + input.focus() + clearTimeout(this.last.kbd_timer) // keep focus + input.resize = expand + expand(input) + } + }, 50) + // event after + edata.finish({ input }) + return + function expand(input) { + try { + let styles = getComputedStyle(input) + let val = (input.tagName.toUpperCase() == 'DIV' ? input.innerText : input.value) + let editBox = query(self.box).find('#grid_'+ self.name + '_editable').get(0) + let style = `font-family: ${styles['font-family']}; font-size: ${styles['font-size']}; white-space: no-wrap;` + let width = w2utils.getStrWidth(val, style) + if (width + 20 > editBox.clientWidth) { + query(editBox).css('width', width + 20 + 'px') + } + } catch (e) { + } + } + } + editChange(input, index, column, event) { + // if params are not specified + input = input ?? this.last._edit.input + index = index ?? this.last._edit.index + column = column ?? this.last._edit.column + event = event ?? {} + // all other fields + let summary = index < 0 + index = index < 0 ? -index - 1 : index + let records = summary ? this.summary : this.records + let rec = records[index] + let col = this.columns[column] + let new_val = (input?.tagName == 'DIV' ? input.innerText : input.value) + let fld = input._w2field + if (fld) { + if (fld.type == 'list') { + new_val = fld.selected + } + if (Object.keys(new_val).length === 0 || new_val == null) new_val = '' + if (!w2utils.isPlainObject(new_val)) new_val = fld.clean(new_val) + } + if (input.type == 'checkbox') { + if (rec.w2ui?.editable === false) input.checked = !input.checked + new_val = input.checked + } + let old_val = this.parseField(rec, col.field) + let prev_val = (rec.w2ui?.changes && rec.w2ui.changes.hasOwnProperty(col.field) ? rec.w2ui.changes[col.field]: old_val) + // change/restore event + let edata = { + target: this.name, input, + recid: rec.recid, index, column, + originalEvent: event, + value: { + new: new_val, + previous: prev_val, + original: old_val, + } + } + if (event.target?._prevValue != null) edata.value.previous = event.target._prevValue + let count = 0 // just in case to avoid infinite loop + while (count < 20) { + count++ + new_val = edata.value.new + if ((typeof new_val != 'object' && String(old_val) != String(new_val)) || + (typeof new_val == 'object' && new_val && new_val.id != old_val + && (typeof old_val != 'object' || old_val == null || new_val.id != old_val.id))) { + // change event + edata = this.trigger('change', edata) + if (edata.isCancelled !== true) { + if (new_val !== edata.detail.value.new) { + // re-evaluate the type of change to be made + continue + } + // default action + if ((edata.detail.value.new === '' || edata.detail.value.new == null) && (prev_val === '' || prev_val == null)) { + // value did not change, was empty is empty + } else { + rec.w2ui = rec.w2ui ?? {} + rec.w2ui.changes = rec.w2ui.changes ?? {} + rec.w2ui.changes[col.field] = edata.detail.value.new + } + // event after + edata.finish() + } + } else { + // restore event + edata = this.trigger('restore', edata) + if (edata.isCancelled !== true) { + if (new_val !== edata.detail.value.new) { + // re-evaluate the type of change to be made + continue + } + // default action + if (rec.w2ui?.changes) { + delete rec.w2ui.changes[col.field] + if (Object.keys(rec.w2ui.changes).length === 0) { + delete rec.w2ui.changes + } + } + // event after + edata.finish() + } + } + break + } + } + editDone(index, column, event) { + // if params are not specified + index = index ?? this.last._edit.index + column = column ?? this.last._edit.column + event = event ?? {} + // removal of input happens when TR is redrawn + if (this.advanceOnEdit && event.keyCode == 13) { + let next = event.shiftKey ? this.prevRow(index, column, 1) : this.nextRow(index, column, 1) + if (next == null) next = index // keep the same + setTimeout(() => { + if (this.selectType != 'row') { + this.selectNone(true) // no need to trigger select event + this.select({ recid: this.records[next].recid, column: column }) + } else { + this.editField(this.records[next].recid, column, null, event) + } + }, 1) + } + let summary = index < 0 + let cell = query(this.last._edit.tr).find('[col="'+ column +'"]') + let rec = this.records[index] + let col = this.columns[column] + // need to set before remove, as remove will trigger blur + this.last.inEditMode = false + this.last._edit = null + // remove - by updating cell data + if (!summary) { + if (rec.w2ui?.changes?.[col.field] != null) { + cell.addClass('w2ui-changed') + } else { + cell.removeClass('w2ui-changed') + } + cell.replace(this.getCellHTML(index, column, summary)) + } + // remove - spreadsheet + query(this.box).find('div.w2ui-edit-box').remove() + // update toolbar buttons + this.updateToolbar() + // keep grid in focus if needed + setTimeout(() => { + let input = query(this.box).find(`#grid_${this.name}_focus`).get(0) + if (document.activeElement !== input && !this.last.inEditMode) { + input.focus() + } + }, 10) + } + 'delete'(force) { + // event before + let edata = this.trigger('delete', { target: this.name, force: force }) + if (force) this.message() // close message + if (edata.isCancelled === true) return + force = edata.detail.force + // default action + let recs = this.getSelection() + if (recs.length === 0) return + if (this.msgDelete != '' && !force) { + this.confirm({ + text: w2utils.lang(this.msgDelete, { + count: recs.length, + records: w2utils.lang( recs.length == 1 ? 'record' : 'records') + }), + width: 380, + height: 170, + yes_text: 'Delete', + yes_class: 'w2ui-btn-red', + no_text: 'Cancel', + }) + .yes(event => { + event.detail.self.close() + this.delete(true) + }) + .no(event => { + event.detail.self.close() + }) + return + } + // call delete script + let url = (typeof this.url != 'object' ? this.url : this.url.remove) + if (url) { + this.request('delete') + } else { + if (typeof recs[0] != 'object') { + this.selectNone() + this.remove.apply(this, recs) + } else { + // clear cells + for (let r = 0; r < recs.length; r++) { + let fld = this.columns[recs[r].column].field + let ind = this.get(recs[r].recid, true) + let rec = this.records[ind] + if (ind != null && fld != 'recid') { + this.records[ind][fld] = '' + if (rec.w2ui?.changes) delete rec.w2ui.changes[fld] + // -- style should not be deleted + // if (rec.style != null && w2utils.isPlainObject(rec.style) && rec.style[recs[r].column]) { + // delete rec.style[recs[r].column]; + // } + } + } + this.update() + } + } + // event after + edata.finish() + } + click(recid, event) { + let time = Date.now() + let column = null + if (this.last.cancelClick == true || (event && event.altKey)) return + if ((typeof recid == 'object') && (recid !== null)) { + column = recid.column + recid = recid.recid + } + if (event == null) event = {} + // check for double click + if (time - parseInt(this.last.click_time) < 350 && this.last.click_recid == recid && event.type == 'click') { + this.dblClick(recid, event) + return + } + // hide bubble + if (this.last.bubbleEl) { + this.last.bubbleEl = null + } + this.last.click_time = time + let last_recid = this.last.click_recid + this.last.click_recid = recid + // column user clicked on + if (column == null && event.target) { + let trg = event.target + if (trg.tagName != 'TD') trg = query(trg).closest('td')[0] + if (query(trg).attr('col') != null) column = parseInt(query(trg).attr('col')) + } + // event before + let edata = this.trigger('click', { target: this.name, recid, column, originalEvent: event }) + if (edata.isCancelled === true) return + // default action + let sel = this.getSelection() + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', false) + let ind = this.get(recid, true) + let selectColumns = [] + this.last.sel_ind = ind + this.last.sel_col = column + this.last.sel_recid = recid + this.last.sel_type = 'click' + // multi select with shift key + let start, end, t1, t2 + if (event.shiftKey && sel.length > 0 && this.multiSelect) { + if (sel[0].recid) { + start = this.get(sel[0].recid, true) + end = this.get(recid, true) + if (column > sel[0].column) { + t1 = sel[0].column + t2 = column + } else { + t1 = column + t2 = sel[0].column + } + for (let c = t1; c <= t2; c++) selectColumns.push(c) + } else { + start = this.get(last_recid, true) + end = this.get(recid, true) + } + let sel_add = [] + if (start > end) { let tmp = start; start = end; end = tmp } + let url = this.url?.get ? this.url.get : this.url + for (let i = start; i <= end; i++) { + if (this.searchData.length > 0 && !url && !this.last.searchIds.includes(i)) continue + if (this.selectType == 'row') { + sel_add.push(this.records[i].recid) + } else { + for (let sc = 0; sc < selectColumns.length; sc++) { + sel_add.push({ recid: this.records[i].recid, column: selectColumns[sc] }) + } + } + //sel.push(this.records[i].recid); + } + this.select(sel_add) + } else { + let last = this.last.selection + let flag = (last.indexes.indexOf(ind) != -1 ? true : false) + let fselect = false + // if clicked on the checkbox + if (query(event.target).closest('td').hasClass('w2ui-col-select')) fselect = true + // clear other if necessary + if (((!event.ctrlKey && !event.shiftKey && !event.metaKey && !fselect) || !this.multiSelect) && !this.showSelectColumn) { + if (this.selectType != 'row' && !last.columns[ind]?.includes(column)) flag = false + this.selectNone(true) // no need to trigger select event + if (flag === true && sel.length == 1) { + this.unselect({ recid: recid, column: column }) + } else { + this.select({ recid: recid, column: column }) + } + } else { + if (this.selectType != 'row' && !last.columns[ind]?.includes(column)) flag = false + if (flag === true) { + this.unselect({ recid: recid, column: column }) + } else { + this.select({ recid: recid, column: column }) + } + } + } + this.status() + this.initResize() + // event after + edata.finish() + } + columnClick(field, event) { + // ignore click if column was resized + if (this.last.colResizing === true) { + return + } + // event before + let edata = this.trigger('columnClick', { target: this.name, field: field, originalEvent: event }) + if (edata.isCancelled === true) return + // default behaviour + if (this.selectType == 'row') { + let column = this.getColumn(field) + if (column && column.sortable) this.sort(field, null, (event && (event.ctrlKey || event.metaKey) ? true : false)) + if (edata.detail.field == 'line-number') { + if (this.getSelection().length >= this.records.length) { + this.selectNone() + } else { + this.selectAll() + } + } + } else { + if (event.altKey){ + let column = this.getColumn(field) + if (column && column.sortable) this.sort(field, null, (event && (event.ctrlKey || event.metaKey) ? true : false)) + } + // select entire column + if (edata.detail.field == 'line-number') { + if (this.getSelection().length >= this.records.length) { + this.selectNone() + } else { + this.selectAll() + } + } else { + if (!event.shiftKey && !event.metaKey && !event.ctrlKey) { + this.selectNone(true) + } + let tmp = this.getSelection() + let column = this.getColumn(edata.detail.field, true) + let sel = [] + let cols = [] + // check if there was a selection before + if (tmp.length != 0 && event.shiftKey) { + let start = column + let end = tmp[0].column + if (start > end) { + start = tmp[0].column + end = column + } + for (let i = start; i<=end; i++) cols.push(i) + } else { + cols.push(column) + } + edata = this.trigger('columnSelect', { target: this.name, columns: cols }) + if (edata.isCancelled !== true) { + for (let i = 0; i < this.records.length; i++) { + sel.push({ recid: this.records[i].recid, column: cols }) + } + this.select(sel) + } + edata.finish() + } + } + // event after + edata.finish() + } + columnDblClick(field, event) { + // event before + let edata = this.trigger('columnDblClick', { target: this.name, field: field, originalEvent: event }) + if (edata.isCancelled === true) return + // event after + edata.finish() + } + focus(event) { + // event before + let edata = this.trigger('focus', { target: this.name, originalEvent: event }) + if (edata.isCancelled === true) return false + // default behaviour + this.hasFocus = true + query(this.box).removeClass('w2ui-inactive').find('.w2ui-inactive').removeClass('w2ui-inactive') + setTimeout(() => { + let txt = query(this.box).find(`#grid_${this.name}_focus`).get(0) + if (txt && document.activeElement != txt) { + txt.focus() + } + }, 10) + // event after + edata.finish() + } + blur(event) { + // event before + let edata = this.trigger('blur', { target: this.name, originalEvent: event }) + if (edata.isCancelled === true) return false + // default behaviour + this.hasFocus = false + query(this.box).addClass('w2ui-inactive').find('.w2ui-selected').addClass('w2ui-inactive') + query(this.box).find('.w2ui-selection').addClass('w2ui-inactive') + // event after + edata.finish() + } + keydown(event) { + // this method is called from w2utils + let obj = this + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (obj.keyboard !== true) return + // trigger event + let edata = obj.trigger('keydown', { target: obj.name, originalEvent: event }) + if (edata.isCancelled === true) return + // default behavior + if (query(this.box).find('.w2ui-message').length > 0) { + // if there are messages + if (event.keyCode == 27) this.message() + return + } + let empty = false + let records = query(obj.box).find('#grid_'+ obj.name +'_records') + let sel = obj.getSelection() + if (sel.length === 0) empty = true + let recid = sel[0] || null + let columns = [] + let recid2 = sel[sel.length-1] + if (typeof recid == 'object' && recid != null) { + recid = sel[0].recid + columns = [] + let ii = 0 + while (true) { + if (!sel[ii] || sel[ii].recid != recid) break + columns.push(sel[ii].column) + ii++ + } + recid2 = sel[sel.length-1].recid + } + let ind = obj.get(recid, true) + let ind2 = obj.get(recid2, true) + let recEL = query(obj.box).find(`#grid_${obj.name}_rec_${(ind != null ? w2utils.escapeId(obj.records[ind].recid) : 'none')}`) + let pageSize = Math.floor(records[0].clientHeight / obj.recordHeight) + let cancel = false + let key = event.keyCode + let shiftKey = event.shiftKey + switch (key) { + case 8: // backspace + case 46: // delete + // delete if button is visible + obj.delete() + cancel = true + event.stopPropagation() + break + case 27: // escape + obj.selectNone() + cancel = true + break + case 65: // cmd + A + if (!event.metaKey && !event.ctrlKey) break + obj.selectAll() + cancel = true + break + case 13: // enter + // if expandable columns - expand it + if (this.selectType == 'row' && obj.show.expandColumn === true) { + if (recEL.length <= 0) break + obj.toggle(recid, event) + cancel = true + } else { // or enter edit + for (let c = 0; c < this.columns.length; c++) { + let edit = this.getCellEditable(ind, c) + if (edit) { + columns.push(parseInt(c)) + break + } + } + // edit last column that was edited + if (this.selectType == 'row' && this.last._edit && this.last._edit.column) { + columns = [this.last._edit.column] + } + if (columns.length > 0) { + obj.editField(recid, this.last.editColumn || columns[0], null, event) + cancel = true + } + } + break + case 37: // left + moveLeft() + break + case 39: // right + moveRight() + break + case 33: // + moveUp(pageSize) + break + case 34: // + moveDown(pageSize) + break + case 35: // + moveDown(-1) + break + case 36: // + moveUp(-1) + break + case 38: // up + // ctrl (or cmd) + up -> same as home + moveUp(event.metaKey || event.ctrlKey ? -1 : 1) + break + case 40: // down + // ctrl (or cmd) + up -> same as end + moveDown(event.metaKey || event.ctrlKey ? -1 : 1) + break + // copy & paste + case 17: // ctrl key + case 91: // cmd key + // SLOW: 10k records take 7.0 + if (empty) break + // in Safari need to copy to buffer on cmd or ctrl key (otherwise does not work) + if (w2utils.isSafari) { + obj.last.copy_event = obj.copy(false, event) + let focus = query(obj.box).find('#grid_'+ obj.name + '_focus') + focus.val(obj.last.copy_event.detail.text) + focus[0].select() + } + break + case 67: // - c + // this fill trigger event.onComplete + if (event.metaKey || event.ctrlKey) { + if (w2utils.isSafari) { + obj.copy(obj.last.copy_event, event) + } else { + obj.last.copy_event = obj.copy(false, event) + let focus = query(obj.box).find('#grid_'+ obj.name + '_focus') + focus.val(obj.last.copy_event.detail.text) + focus[0].select() + obj.copy(obj.last.copy_event, event) + } + } + break + case 88: // x - cut + if (empty) break + if (event.ctrlKey || event.metaKey) { + if (w2utils.isSafari) { + obj.copy(obj.last.copy_event, event) + } else { + obj.last.copy_event = obj.copy(false, event) + let focus = query(obj.box).find('#grid_'+ obj.name + '_focus') + focus.val(obj.last.copy_event.detail.text) + focus[0].select() + obj.copy(obj.last.copy_event, event) + } + } + break + } + let tmp = [32, 187, 189, 192, 219, 220, 221, 186, 222, 188, 190, 191] // other typeable chars + for (let i = 48; i <= 111; i++) tmp.push(i) // 0-9,a-z,A-Z,numpad + if (tmp.indexOf(key) != -1 && !event.ctrlKey && !event.metaKey && !cancel) { + if (columns.length === 0) columns.push(0) + cancel = false + // move typed key into edit + setTimeout(() => { + let focus = query(obj.box).find('#grid_'+ obj.name + '_focus') + let key = focus.val() + focus.val('') + obj.editField(recid, columns[0], key, event) + }, 1) + } + if (cancel) { // cancel default behaviour + if (event.preventDefault) event.preventDefault() + } + // event after + edata.finish() + function moveLeft() { + if (empty) { // no selection + selectTopRecord() + return + } + if (obj.selectType == 'row') { + if (recEL.length <= 0) return + let tmp = obj.records[ind].w2ui || {} + if (tmp && tmp.parent_recid != null && (!Array.isArray(tmp.children) || tmp.children.length === 0 || !tmp.expanded)) { + obj.unselect(recid) + obj.collapse(tmp.parent_recid, event) + obj.select(tmp.parent_recid) + } else { + obj.collapse(recid, event) + } + } else { + let prev = obj.prevCell(ind, columns[0]) + if (prev?.index != ind) { + prev = null + } else { + prev = prev?.colIndex + } + if (!shiftKey && prev == null) { + obj.selectNone(true) + prev = 0 + } + if (prev != null) { + if (shiftKey && obj.multiSelect) { + if (tmpUnselect()) return + let tmp = [] + let newSel = [] + let unSel = [] + if (columns.indexOf(obj.last.sel_col) === 0 && columns.length > 1) { + for (let i = 0; i < sel.length; i++) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid) + unSel.push({ recid: sel[i].recid, column: columns[columns.length-1] }) + } + obj.unselect(unSel) + obj.scrollIntoView(ind, columns[columns.length-1], true) + } else { + for (let i = 0; i < sel.length; i++) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid) + newSel.push({ recid: sel[i].recid, column: prev }) + } + obj.select(newSel) + obj.scrollIntoView(ind, prev, true) + } + } else { + obj.click({ recid: recid, column: prev }, event) + obj.scrollIntoView(ind, prev, true) + } + } else { + // if selected more then one, then select first + if (!shiftKey) { + obj.selectNone(true) + } + } + } + cancel = true + } + function moveRight() { + if (empty) { + selectTopRecord() + return + } + if (obj.selectType == 'row') { + if (recEL.length <= 0) return + obj.expand(recid, event) + } else { + let next = obj.nextCell(ind, columns[columns.length-1]) // columns is an array of selected columns + if (next.index != ind) { + next = null + } else { + next = next.colIndex + } + if (!shiftKey && next == null) { + obj.selectNone(true) + next = obj.columns.length-1 + } + if (next != null) { + if (shiftKey && key == 39 && obj.multiSelect) { + if (tmpUnselect()) return + let tmp = [] + let newSel = [] + let unSel = [] + if (columns.indexOf(obj.last.sel_col) == columns.length-1 && columns.length > 1) { + for (let i = 0; i < sel.length; i++) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid) + unSel.push({ recid: sel[i].recid, column: columns[0] }) + } + obj.unselect(unSel) + obj.scrollIntoView(ind, columns[0], true) + } else { + for (let i = 0; i < sel.length; i++) { + if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid) + newSel.push({ recid: sel[i].recid, column: next }) + } + obj.select(newSel) + obj.scrollIntoView(ind, next, true) + } + } else { + obj.click({ recid: recid, column: next }, event) + obj.scrollIntoView(ind, next, true) + } + } else { + // if selected more then one, then select first + if (!shiftKey) { + obj.selectNone(true) + } + } + } + cancel = true + } + function moveUp(numRows) { + if (empty) selectTopRecord() + if (recEL.length <= 0) return + // move to the previous record + let prev = obj.prevRow(ind, obj.selectType == 'row' ? 0 : sel[0].column, numRows) + if (!shiftKey && prev == null) { + if (obj.searchData.length != 0 && !url) { + prev = obj.last.searchIds[0] + } else { + prev = 0 + } + } + if (prev != null) { + if (shiftKey && obj.multiSelect) { // expand selection + if (tmpUnselect()) return + if (obj.selectType == 'row') { + if (obj.last.sel_ind > prev && obj.last.sel_ind != ind2) { + obj.unselect(obj.records[ind2].recid) + } else { + obj.select(obj.records[prev].recid) + } + } else { + if (obj.last.sel_ind > prev && obj.last.sel_ind != ind2) { + prev = ind2 + let tmp = [] + for (let c = 0; c < columns.length; c++) tmp.push({ recid: obj.records[prev].recid, column: columns[c] }) + obj.unselect(tmp) + } else { + let tmp = [] + for (let c = 0; c < columns.length; c++) tmp.push({ recid: obj.records[prev].recid, column: columns[c] }) + obj.select(tmp) + } + } + } else { // move selected record + obj.selectNone(true) // no need to trigger select event + obj.click({ recid: obj.records[prev].recid, column: columns[0] }, event) + } + obj.scrollIntoView(prev, null, true, numRows != 1) // top align record + if (event.preventDefault) event.preventDefault() + } else { + // if selected more then one, then select first + if (!shiftKey) { + obj.selectNone(true) + } + } + } + function moveDown(numRows) { + if (empty) selectTopRecord() + if (recEL.length <= 0) return + // move to the next record + let next = obj.nextRow(ind2, obj.selectType == 'row' ? 0 : sel[0].column, numRows) + if (!shiftKey && next == null) { + if (obj.searchData.length != 0 && !url) { + next = obj.last.searchIds[obj.last.searchIds.length - 1] + } else { + next = obj.records.length - 1 + } + } + if (next != null) { + if (shiftKey && obj.multiSelect) { // expand selection + if (tmpUnselect()) return + if (obj.selectType == 'row') { + if (obj.last.sel_ind < next && obj.last.sel_ind != ind) { + obj.unselect(obj.records[ind].recid) + } else { + obj.select(obj.records[next].recid) + } + } else { + if (obj.last.sel_ind < next && obj.last.sel_ind != ind) { + next = ind + let tmp = [] + for (let c = 0; c < columns.length; c++) tmp.push({ recid: obj.records[next].recid, column: columns[c] }) + obj.unselect(tmp) + } else { + let tmp = [] + for (let c = 0; c < columns.length; c++) tmp.push({ recid: obj.records[next].recid, column: columns[c] }) + obj.select(tmp) + } + } + } else { // move selected record + obj.selectNone(true) // no need to trigger select event + obj.click({ recid: obj.records[next].recid, column: columns[0] }, event) + } + obj.scrollIntoView(next, null, true, numRows != 1) // top align record + cancel = true + } else { + // if selected more then one, then select first + if (!shiftKey) { + obj.selectNone(true) // no need to trigger select event + } + } + } + function selectTopRecord() { + if (!obj.records || obj.records.length === 0) return + let ind = Math.floor(records[0].scrollTop / obj.recordHeight) + 1 + if (!obj.records[ind] || ind < 2) ind = 0 + if (typeof obj.records[ind] === 'undefined') return + obj.select({ recid: obj.records[ind].recid, column: 0}) + } + function tmpUnselect () { + if (obj.last.sel_type != 'click') return false + if (obj.selectType != 'row') { + obj.last.sel_type = 'key' + if (sel.length > 1) { + for (let s = 0; s < sel.length; s++) { + if (sel[s].recid == obj.last.sel_recid && sel[s].column == obj.last.sel_col) { + sel.splice(s, 1) + break + } + } + obj.unselect(sel) + return true + } + return false + } else { + obj.last.sel_type = 'key' + if (sel.length > 1) { + sel.splice(sel.indexOf(obj.records[obj.last.sel_ind].recid), 1) + obj.unselect(sel) + return true + } + return false + } + } + } + scrollIntoView(ind, column, instant, recTop) { + let buffered = this.records.length + if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length + if (buffered === 0) return + if (ind == null) { + let sel = this.getSelection() + if (sel.length === 0) return + if (w2utils.isPlainObject(sel[0])) { + ind = sel[0].index + column = sel[0].column + } else { + ind = this.get(sel[0], true) + } + } + let records = query(this.box).find(`#grid_${this.name}_records`) + let recWidth = records[0].clientWidth + let recHeight = records[0].clientHeight + let recSTop = records[0].scrollTop + let recSLeft = records[0].scrollLeft + // if all records in view + let len = this.last.searchIds.length + if (len > 0) ind = this.last.searchIds.indexOf(ind) // if search is applied + // smooth or instant + records.css({ 'scroll-behavior': instant ? 'auto' : 'smooth' }) + // vertical + if (recHeight < this.recordHeight * (len > 0 ? len : buffered) && records.length > 0) { + // scroll to correct one + let t1 = Math.floor(recSTop / this.recordHeight) + let t2 = t1 + Math.floor(recHeight / this.recordHeight) + if (ind == t1) { + records.prop('scrollTop', recSTop - recHeight / 1.3) + } + if (ind == t2) { + records.prop('scrollTop', recSTop + recHeight / 1.3) + } + if (ind < t1 || ind > t2) { + records.prop('scrollTop', (ind - 1) * this.recordHeight) + } + if (recTop === true) { + records.prop('scrollTop', ind * this.recordHeight) + } + } + // horizontal + if (column != null) { + let x1 = 0 + let x2 = 0 + let sb = w2utils.scrollBarSize() + for (let i = 0; i <= column; i++) { + let col = this.columns[i] + if (col.frozen || col.hidden) continue + x1 = x2 + x2 += parseInt(col.sizeCalculated) + } + if (recWidth < x2 - recSLeft) { // right + records.prop('scrollLeft', x1 - sb) + } else if (x1 < recSLeft) { // left + records.prop('scrollLeft', x2 - recWidth + sb * 2) + } + } + } + scrollToColumn(field) { + if (field == null) + return + let sWidth = 0 + let found = false + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + if (col.field == field) { + found = true + break + } + if (col.frozen || col.hidden) + continue + let cSize = parseInt(col.sizeCalculated ? col.sizeCalculated : col.size) + sWidth += cSize + } + if (!found) + return + this.last.scrollLeft = sWidth+1 + this.scroll() + } + + dblClick(recid, event) { + // find columns + let column = null + if ((typeof recid == 'object') && (recid !== null)) { + column = recid.column + recid = recid.recid + } + if (event == null) event = {} + // column user clicked on + if (column == null && event.target) { + let tmp = event.target + if (tmp.tagName.toUpperCase() != 'TD') tmp = query(tmp).closest('td')[0] + column = parseInt(query(tmp).attr('col')) + } + let index = this.get(recid, true) + let rec = this.records[index] + // event before + let edata = this.trigger('dblClick', { target: this.name, recid: recid, column: column, originalEvent: event }) + if (edata.isCancelled === true) return + // default action + this.selectNone(true) // no need to trigger select event + let edit = this.getCellEditable(index, column) + if (edit) { + this.editField(recid, column, null, event) + } else { + this.select({ recid: recid, column: column }) + if (this.show.expandColumn || (rec && rec.w2ui && Array.isArray(rec.w2ui.children))) this.toggle(recid) + } + // event after + edata.finish() + } + showContextMenu(recid, column, event) { + if (this.last.userSelect == 'text') return + if (event == null) { + event = { offsetX: 0, offsetY: 0, target: query(this.box).find(`#grid_${this.name}_rec_${recid}`)[0] } + } + if (event.offsetX == null) { + event.offsetX = event.layerX - event.target.offsetLeft + event.offsetY = event.layerY - event.target.offsetTop + } + if (w2utils.isFloat(recid)) recid = parseFloat(recid) + let sel = this.getSelection() + if (this.selectType == 'row') { + if (sel.indexOf(recid) == -1) this.click(recid) + } else { + let selected = false + // check if any selected sel in the right row/column + for (let i = 0; i < sel.length; i++) { + if (sel[i].recid == recid || sel[i].column == column) selected = true + } + if (!selected && recid != null) this.click({ recid: recid, column: column }) + if (!selected && column != null) this.columnClick(this.columns[column].field, event) + } + // event before + let edata = this.trigger('contextMenu', { target: this.name, originalEvent: event, recid, column }) + if (edata.isCancelled === true) return + // default action + if (this.contextMenu.length > 0) { + w2menu.show({ + anchor: document.body, + originalEvent: event, + items: this.contextMenu + }) + .select((event) => { + clearTimeout(this.last.kbd_timer) // keep grid in focus + this.contextMenuClick(recid, event) + }) + clearTimeout(this.last.kbd_timer) // keep grid in focus + } + // cancel browser context menu + event.preventDefault() + // event after + edata.finish() + } + contextMenuClick(recid, event) { + // event before + let edata = this.trigger('contextMenuClick', { target: this.name, recid, originalEvent: event.detail.originalEvent, + menuEvent: event, menuIndex: event.detail.index, menuItem: event.detail.item + }) + if (edata.isCancelled === true) return + // no default action + edata.finish() + } + toggle(recid) { + let rec = this.get(recid) + if (rec == null) return + rec.w2ui = rec.w2ui ?? {} + if (rec.w2ui.expanded === true) return this.collapse(recid); else return this.expand(recid) + } + expand(recid, noRefresh) { + let ind = this.get(recid, true) + let rec = this.records[ind] + rec.w2ui = rec.w2ui ?? {} + let id = w2utils.escapeId(recid) + let children = rec.w2ui.children + let edata + if (Array.isArray(children)) { + if (rec.w2ui.expanded === true || children.length === 0) return false // already shown + edata = this.trigger('expand', { target: this.name, recid: recid }) + if (edata.isCancelled === true) return false + rec.w2ui.expanded = true + children.forEach((child) => { + child.w2ui = child.w2ui ?? {} + child.w2ui.parent_recid = rec.recid + if (child.w2ui.children == null) child.w2ui.children = [] + }) + this.records.splice.apply(this.records, [ind + 1, 0].concat(children)) + if (this.total !== -1) { + this.total += children.length + } + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (!url) { + this.localSort(true, true) + if (this.searchData.length > 0) { + this.localSearch(true) + } + } + if (noRefresh !== true) this.refresh() + edata.finish() + } else { + if (query(this.box).find('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').length > 0 || this.show.expandColumn !== true) return false + if (rec.w2ui.expanded == 'none') return false + // insert expand row + query(this.box).find('#grid_'+ this.name +'_rec_'+ id).after( + ` + +
    + + + `) + query(this.box).find('#grid_'+ this.name +'_frec_'+ id).after( + ` + ${this.show.lineNumbers ? '' : ''} + +
    + + `) + // event before + edata = this.trigger('expand', { target: this.name, recid: recid, + box_id: 'grid_'+ this.name +'_rec_'+ recid +'_expanded', fbox_id: 'grid_'+ this.name +'_frec_'+ recid +'_expanded' }) + if (edata.isCancelled === true) { + query(this.box).find('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').remove() + query(this.box).find('#grid_'+ this.name +'_frec_'+ id +'_expanded_row').remove() + return false + } + // expand column + let row1 = query(this.box).find('#grid_'+ this.name +'_rec_'+ recid +'_expanded') + let row2 = query(this.box).find('#grid_'+ this.name +'_frec_'+ recid +'_expanded') + let innerHeight = row1.find(':scope div:first-child')[0]?.clientHeight ?? 50 + if (row1[0].clientHeight < innerHeight) { + row1.css({ height: innerHeight + 'px' }) + } + if (row2[0].clientHeight < innerHeight) { + row2.css({ height: innerHeight + 'px' }) + } + // default action + query(this.box).find('#grid_'+ this.name +'_rec_'+ id).attr('expanded', 'yes').addClass('w2ui-expanded') + query(this.box).find('#grid_'+ this.name +'_frec_'+ id).attr('expanded', 'yes').addClass('w2ui-expanded') + query(this.box).find('#grid_'+ this.name +'_cell_'+ this.get(recid, true) +'_expand div').html('-') + rec.w2ui.expanded = true + // event after + edata.finish() + this.resizeRecords() + } + return true + } + collapse(recid, noRefresh) { + let ind = this.get(recid, true) + let rec = this.records[ind] + rec.w2ui = rec.w2ui || {} + let id = w2utils.escapeId(recid) + let children = rec.w2ui.children + let edata + if (Array.isArray(children)) { + if (rec.w2ui.expanded !== true) return false // already hidden + edata = this.trigger('collapse', { target: this.name, recid: recid }) + if (edata.isCancelled === true) return false + clearExpanded(rec) + let stops = [] + for (let r = rec; r != null; r = this.get(r.w2ui.parent_recid)) + stops.push(r.w2ui.parent_recid) + // stops contains 'undefined' plus the ID of all nodes in the path from 'rec' to the tree root + let start = ind + 1 + let end = start + while (true) { + if (this.records.length <= end + 1 || this.records[end+1].w2ui == null || + stops.indexOf(this.records[end+1].w2ui.parent_recid) >= 0) { + break + } + end++ + } + this.records.splice(start, end - start + 1) + if (this.total !== -1) { + this.total -= end - start + 1 + } + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (!url) { + if (this.searchData.length > 0) { + this.localSearch(true) + } + } + if (noRefresh !== true) this.refresh() + edata.finish() + } else { + if (query(this.box).find('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').length === 0 || this.show.expandColumn !== true) return false + // event before + edata = this.trigger('collapse', { target: this.name, recid: recid, + box_id: 'grid_'+ this.name +'_rec_'+ recid +'_expanded', fbox_id: 'grid_'+ this.name +'_frec_'+ recid +'_expanded' }) + if (edata.isCancelled === true) return false + // default action + query(this.box).find('#grid_'+ this.name +'_rec_'+ id).removeAttr('expanded').removeClass('w2ui-expanded') + query(this.box).find('#grid_'+ this.name +'_frec_'+ id).removeAttr('expanded').removeClass('w2ui-expanded') + query(this.box).find('#grid_'+ this.name +'_cell_'+ this.get(recid, true) +'_expand div').html('+') + query(this.box).find('#grid_'+ this.name +'_rec_'+ id +'_expanded').css('height', '0px') + query(this.box).find('#grid_'+ this.name +'_frec_'+ id +'_expanded').css('height', '0px') + setTimeout(() => { + query(this.box).find('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').remove() + query(this.box).find('#grid_'+ this.name +'_frec_'+ id +'_expanded_row').remove() + rec.w2ui.expanded = false + // event after + edata.finish() + this.resizeRecords() + }, 300) + } + return true + function clearExpanded(rec) { + rec.w2ui.expanded = false + for (let i = 0; i < rec.w2ui.children.length; i++) { + let subRec = rec.w2ui.children[i] + if (subRec.w2ui.expanded) { + clearExpanded(subRec) + } + } + } + } + sort(field, direction, multiField) { // if no params - clears sort + // event before + let edata = this.trigger('sort', { target: this.name, field: field, direction: direction, multiField: multiField }) + if (edata.isCancelled === true) return + // check if needed to quit + if (field != null) { + // default action + let sortIndex = this.sortData.length + for (let s = 0; s < this.sortData.length; s++) { + if (this.sortData[s].field == field) { sortIndex = s; break } + } + if (direction == null) { + if (this.sortData[sortIndex] == null) { + direction = 'asc' + } else { + if (this.sortData[sortIndex].direction == null) { + this.sortData[sortIndex].direction = '' + } + switch (this.sortData[sortIndex].direction.toLowerCase()) { + case 'asc' : direction = 'desc'; break + case 'desc' : direction = 'asc'; break + default : direction = 'asc'; break + } + } + } + if (this.multiSort === false) { this.sortData = []; sortIndex = 0 } + if (multiField != true) { this.sortData = []; sortIndex = 0 } + // set new sort + if (this.sortData[sortIndex] == null) this.sortData[sortIndex] = {} + this.sortData[sortIndex].field = field + this.sortData[sortIndex].direction = direction + } else { + this.sortData = [] + } + // if local + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (!url) { + this.localSort(false, true) + if (this.searchData.length > 0) this.localSearch(true) + // reset vertical scroll + this.last.scrollTop = 0 + query(this.box).find(`#grid_${this.name}_records`).prop('scrollTop', 0) + // event after + edata.finish({ direction }) + this.refresh() + } else { + // event after + edata.finish({ direction }) + this.last.fetch.offset = 0 + this.reload() + } + } + copy(flag, oEvent) { + if (w2utils.isPlainObject(flag)) { + // event after + flag.finish() + return flag.text + } + // generate text to copy + let sel = this.getSelection() + if (sel.length === 0) return '' + let text = '' + if (typeof sel[0] == 'object') { // cell copy + // find min/max column + let minCol = sel[0].column + let maxCol = sel[0].column + let recs = [] + for (let s = 0; s < sel.length; s++) { + if (sel[s].column < minCol) minCol = sel[s].column + if (sel[s].column > maxCol) maxCol = sel[s].column + if (recs.indexOf(sel[s].index) == -1) recs.push(sel[s].index) + } + recs.sort((a, b) => { return a-b }) // sort function must be for numerical sort + for (let r = 0 ; r < recs.length; r++) { + let ind = recs[r] + for (let c = minCol; c <= maxCol; c++) { + let col = this.columns[c] + if (col.hidden === true) continue + text += this.getCellCopy(ind, c) + '\t' + } + text = text.substr(0, text.length-1) // remove last \t + text += '\n' + } + } else { // row copy + // copy headers + for (let c = 0; c < this.columns.length; c++) { + let col = this.columns[c] + if (col.hidden === true) continue + let colName = (col.text ? col.text : col.field) + if (col.text && col.text.length < 3 && col.tooltip) colName = col.tooltip // if column name is less then 3 char and there is tooltip - use it + text += '"' + w2utils.stripTags(colName) + '"\t' + } + text = text.substr(0, text.length-1) // remove last \t + text += '\n' + // copy selected text + for (let s = 0; s < sel.length; s++) { + let ind = this.get(sel[s], true) + for (let c = 0; c < this.columns.length; c++) { + let col = this.columns[c] + if (col.hidden === true) continue + text += '"' + this.getCellCopy(ind, c) + '"\t' + } + text = text.substr(0, text.length-1) // remove last \t + text += '\n' + } + } + text = text.substr(0, text.length - 1) + // if called without params + let edata + if (flag == null) { + // before event + edata = this.trigger('copy', { target: this.name, text: text, + cut: (oEvent.keyCode == 88 ? true : false), originalEvent: oEvent }) + if (edata.isCancelled === true) return '' + text = edata.detail.text + // event after + edata.finish() + return text + } else if (flag === false) { // only before event + // before event + edata = this.trigger('copy', { target: this.name, text: text, + cut: (oEvent.keyCode == 88 ? true : false), originalEvent: oEvent }) + if (edata.isCancelled === true) return '' + text = edata.detail.text + return edata + } + } + /** + * Gets value to be copied to the clipboard + * @param ind index of the record + * @param col_ind index of the column + * @returns the displayed value of the field's record associated with the cell + */ + getCellCopy(ind, col_ind) { + return w2utils.stripTags(this.getCellHTML(ind, col_ind)) + } + paste(text, event) { + let sel = this.getSelection() + let ind = this.get(sel[0].recid, true) + let col = sel[0].column + // before event + let edata = this.trigger('paste', { target: this.name, text: text, index: ind, column: col, originalEvent: event }) + if (edata.isCancelled === true) return + text = edata.detail.text + // default action + if (this.selectType == 'row' || sel.length === 0) { + console.log('ERROR: You can paste only if grid.selectType = \'cell\' and when at least one cell selected.') + // event after + edata.finish() + return + } + if (typeof text !== 'object') { + let newSel = [] + text = text.split('\n') + for (let t = 0; t < text.length; t++) { + let tmp = text[t].split('\t') + let cnt = 0 + let rec = this.records[ind] + let cols = [] + if (rec == null) continue + for (let dt = 0; dt < tmp.length; dt++) { + if (!this.columns[col + cnt]) continue + setCellPaste(rec, this.columns[col + cnt].field, tmp[dt]) + cols.push(col + cnt) + cnt++ + } + for (let c = 0; c < cols.length; c++) newSel.push({ recid: rec.recid, column: cols[c] }) + ind++ + } + this.selectNone(true) // no need to trigger select event + this.select(newSel) + } else { + this.selectNone(true) // no need to trigger select event + this.select([{ recid: this.records[ind], column: col }]) + } + this.refresh() + // event after + edata.finish() + function setCellPaste(rec, field, paste) { + rec.w2ui = rec.w2ui ?? {} + rec.w2ui.changes = rec.w2ui.changes || {} + rec.w2ui.changes[field] = paste + } + } + // ================================================== + // --- Common functions + resize() { + let time = Date.now() + // make sure the box is right + if (!this.box || query(this.box).attr('name') != this.name) return + // event before + let edata = this.trigger('resize', { target: this.name }) + if (edata.isCancelled === true) return + // resize + this.resizeBoxes() + this.resizeRecords() + // event after + edata.finish() + return Date.now() - time + } + update({ cells, fullCellRefresh, ignoreColumns } = {}) { + let time = Date.now() + let self = this + if (this.box == null) return 0 + if (Array.isArray(cells)) { + for (let i = 0; i < cells.length; i++) { + let index = cells[i].index + let column = cells[i].column + if (index < 0) continue + if (index == null || column == null) { + console.log('ERROR: Wrong argument for grid.update({ cells }), cells should be [{ index: X, column: Y }, ...]') + continue + } + let rec = this.records[index] ?? {} + rec.w2ui = rec.w2ui ?? {} + rec.w2ui._update = rec.w2ui._update ?? { cells: [] } + let row1 = rec.w2ui._update.row1 + let row2 = rec.w2ui._update.row2 + if (row1 == null || !row1.isConnected || row2 == null || !row2.isColSelected) { + row1 = this.box.querySelector(`#grid_${this.name}_rec_${w2utils.escapeId(rec.recid)}`) + row2 = this.box.querySelector(`#grid_${this.name}_frec_${w2utils.escapeId(rec.recid)}`) + rec.w2ui._update.row1 = row1 + rec.w2ui._update.row2 = row2 + } + _update(rec, row1, row2, index, column) + } + } else { + for (let i = this.last.range_start-1; i <= this.last.range_end; i++) { + let index = i + if (this.last.searchIds.length > 0) { // if search is applied + index = this.last.searchIds[i] + } else { + index = i + } + let rec = this.records[index] + if (index < 0 || rec == null) continue + rec.w2ui = rec.w2ui ?? {} + rec.w2ui._update = rec.w2ui._update ?? { cells: [] } + let row1 = rec.w2ui._update.row1 + let row2 = rec.w2ui._update.row2 + if (row1 == null || !row1.isConnected || row2 == null || !row2.isColSelected) { + row1 = this.box.querySelector(`#grid_${this.name}_rec_${w2utils.escapeId(rec.recid)}`) + row2 = this.box.querySelector(`#grid_${this.name}_frec_${w2utils.escapeId(rec.recid)}`) + rec.w2ui._update.row1 = row1 + rec.w2ui._update.row2 = row2 + } + for (let column = 0; column < this.columns.length; column++) { + _update(rec, row1, row2, index, column) + } + } + } + return Date.now() - time + function _update(rec, row1, row2, index, column) { + let pcol = self.columns[column] + if (Array.isArray(ignoreColumns) && (ignoreColumns.includes(column) || ignoreColumns.includes(pcol.field))) { + return + } + let cell = rec.w2ui._update.cells[column] + if (cell == null || !cell.isConnected) { + cell = self.box.querySelector(`#grid_${self.name}_data_${index}_${column}`) + rec.w2ui._update.cells[column] = cell + } + if (cell == null) return + if (fullCellRefresh) { + query(cell).replace(self.getCellHTML(index, column, false)) + // need to reselect as it was replaced + cell = self.box.querySelector(`#grid_${self.name}_data_${index}_${column}`) + rec.w2ui._update.cells[column] = cell + } else { + let div = cell.children[0] // there is always a div inside a cell + // value, attr, style, className, divAttr -- all on TD level except divAttr + let { value, style, className } = self.getCellValue(index, column, false, true) + if (div.innerHTML != value) { + div.innerHTML = value + } + if (style != '' && cell.style.cssText != style) { + cell.style.cssText = style + } + if (className != '') { + let ignore = ['w2ui-grid-data'] + let remove = [] + let add = className.split(' ').filter(cl => !!cl) // remove empty + cell.classList.forEach(cl => { if (!ignore.includes(cl)) remove.push(cl)}) + cell.classList.remove(...remove) + cell.classList.add(...add) + } + } + // column styles if any (lower priority) + if (self.columns[column].style && self.columns[column].style != cell.style.cssText) { + cell.style.cssText = self.columns[column].style ?? '' + } + // record class if any + if (rec.w2ui.class != null) { + if (typeof rec.w2ui.class == 'string') { + let ignore = ['w2ui-odd', 'w2ui-even', 'w2ui-record'] + let remove = [] + let add = rec.w2ui.class.split(' ').filter(cl => !!cl) // remove empty + if (row1 && row2) { + row1.classList.forEach(cl => { if (!ignore.includes(cl)) remove.push(cl)}) + row1.classList.remove(...remove) + row1.classList.add(...add) + row2.classList.remove(...remove) + row2.classList.add(...add) + } + } + if (w2utils.isPlainObject(rec.w2ui.class) && typeof rec.w2ui.class[pcol.field] == 'string') { + let ignore = ['w2ui-grid-data'] + let remove = [] + let add = rec.w2ui.class[pcol.field].split(' ').filter(cl => !!cl) + cell.classList.forEach(cl => { if (!ignore.includes(cl)) remove.push(cl)}) + cell.classList.remove(...remove) + cell.classList.add(...add) + } + } + // record styles if any + if (rec.w2ui.style != null) { + if (row1 && row2 && typeof rec.w2ui.style == 'string' && row1.style.cssText !== rec.w2ui.style) { + row1.style.cssText = 'height: '+ self.recordHeight + 'px;' + rec.w2ui.style + row1.setAttribute('custom_style', rec.w2ui.style) + row2.style.cssText = 'height: '+ self.recordHeight + 'px;' + rec.w2ui.style + row2.setAttribute('custom_style', rec.w2ui.style) + } + if (w2utils.isPlainObject(rec.w2ui.style) && typeof rec.w2ui.style[pcol.field] == 'string' + && cell.style.cssText !== rec.w2ui.style[pcol.field]) { + cell.style.cssText = rec.w2ui.style[pcol.field] + } + } + } + } + refreshCell(recid, field) { + let index = this.get(recid, true) + let col_ind = this.getColumn(field, true) + let isSummary = (this.records[index] && this.records[index].recid == recid ? false : true) + let cell = query(this.box).find(`${isSummary ? '.w2ui-grid-summary ' : ''}#grid_${this.name}_data_${index}_${col_ind}`) + if (cell.length == 0) return false + // set cell html and changed flag + cell.replace(this.getCellHTML(index, col_ind, isSummary)) + return true + } + refreshRow(recid, ind = null) { + let tr1 = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)) + let tr2 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)) + if (tr1.length > 0) { + if (ind == null) ind = this.get(recid, true) + let line = tr1.attr('line') + let isSummary = (this.records[ind] && this.records[ind].recid == recid ? false : true) + // if it is searched, find index in search array + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (this.searchData.length > 0 && !url) for (let s = 0; s < this.last.searchIds.length; s++) if (this.last.searchIds[s] == ind) ind = s + let rec_html = this.getRecordHTML(ind, line, isSummary) + tr1.replace(rec_html[0]) + tr2.replace(rec_html[1]) + // apply style to row if it was changed in render functions + let st = (this.records[ind].w2ui ? this.records[ind].w2ui.style : '') + if (typeof st == 'string') { + tr1 = query(this.box).find('#grid_'+ this.name +'_frec_'+ w2utils.escapeId(recid)) + tr2 = query(this.box).find('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)) + tr1.attr('custom_style', st) + tr2.attr('custom_style', st) + if (tr1.hasClass('w2ui-selected')) { + st = st.replace('background-color', 'none') + } + tr1[0].style.cssText = 'height: '+ this.recordHeight + 'px;' + st + tr2[0].style.cssText = 'height: '+ this.recordHeight + 'px;' + st + } + if (isSummary) { + this.resize() + } + return true + } + return false + } + refresh() { + let time = Date.now() + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (this.total <= 0 && !url && this.searchData.length === 0) { + this.total = this.records.length + } + if (!this.box) return + // event before + let edata = this.trigger('refresh', { target: this.name }) + if (edata.isCancelled === true) return + // -- header + if (this.show.header) { + query(this.box).find(`#grid_${this.name}_header`).html(w2utils.lang(this.header) +' ').show() + } else { + query(this.box).find(`#grid_${this.name}_header`).hide() + } + // -- toolbar + if (this.show.toolbar) { + query(this.box).find('#grid_'+ this.name +'_toolbar').show() + } else { + query(this.box).find('#grid_'+ this.name +'_toolbar').hide() + } + // -- make sure search is closed + this.searchClose() + // search placeholder + let sInput = query(this.box).find('#grid_'+ this.name +'_search_all') + if (!this.multiSearch && this.last.field == 'all' && this.searches.length > 0) { + this.last.field = this.searches[0].field + this.last.label = this.searches[0].label + } + for (let s = 0; s < this.searches.length; s++) { + if (this.searches[s].field == this.last.field) this.last.label = this.searches[s].label + } + if (this.last.multi) { + sInput.attr('placeholder', '[' + w2utils.lang('Multiple Fields') + ']') + } else { + sInput.attr('placeholder', w2utils.lang('Search') + ' ' + w2utils.lang(this.last.label, true)) + } + if (sInput.val() != this.last.search) { + let val = this.last.search + let tmp = sInput._w2field + if (tmp) val = tmp.format(val) + sInput.val(val) + } + this.refreshSearch() + this.refreshBody() + // -- footer + if (this.show.footer) { + query(this.box).find(`#grid_${this.name}_footer`).html(this.getFooterHTML()).show() + } else { + query(this.box).find(`#grid_${this.name}_footer`).hide() + } + // all selected? + let sel = this.last.selection, + areAllSelected = (this.records.length > 0 && sel.indexes.length == this.records.length), + areAllSearchedSelected = (sel.indexes.length > 0 && this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length) + if (areAllSelected || areAllSearchedSelected) { + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', true) + } else { + query(this.box).find('#grid_'+ this.name +'_check_all').prop('checked', false) + } + // show number of selected + this.status() + // collapse all records + let rows = this.find({ 'w2ui.expanded': true }, true, true) + for (let r = 0; r < rows.length; r++) { + let tmp = this.records[rows[r]].w2ui + if (tmp && !Array.isArray(tmp.children)) { + tmp.expanded = false + } + } + // mark selection + if (this.markSearch) { + setTimeout(() => { + // mark all search strings + let search = [] + for (let s = 0; s < this.searchData.length; s++) { + let sdata = this.searchData[s] + let fld = this.getSearch(sdata.field) + if (!fld || fld.hidden) continue + let ind = this.getColumn(sdata.field, true) + search.push({ field: sdata.field, search: sdata.value, col: ind }) + } + if (search.length > 0) { + search.forEach((item) => { + let el = query(this.box).find('td[col="'+ item.col +'"]:not(.w2ui-head)') + w2utils.marker(el, item.search) + }) + } + }, 50) + } + this.updateToolbar() + // event after + edata.finish() + this.resize() + this.addRange('selection') + setTimeout(() => { // allow to render first + this.resize() // needed for horizontal scroll to show (do not remove) + this.scroll() + }, 1) + if (this.reorderColumns && !this.last.columnDrag) { + this.last.columnDrag = this.initColumnDrag() + } else if (!this.reorderColumns && this.last.columnDrag) { + this.last.columnDrag.remove() + } + return Date.now() - time + } + refreshSearch() { + if (this.multiSearch && this.searchData.length > 0) { + if (query(this.box).find('.w2ui-grid-searches').length == 0) { + query(this.box).find('.w2ui-grid-toolbar') + .css('height', (this.last.toolbar_height + 35) + 'px') + .append(`
    `) + } + let searches = ` + +
    ` + this.searchData.forEach((sd, sd_ind) => { + let ind = this.getSearch(sd.field, true) + let sf = this.searches[ind] + let display + if (Array.isArray(sd.value)) { + display = `${sd.value.length}` + } else { + display = `: ${sd.value}` + } + if (sf && sf.type == 'date') { + if (sd.operator == 'between') { + let dsp1 = sd.value[0] + let dsp2 = sd.value[1] + if (Number(dsp1) === dsp1) { + dsp1 = w2utils.formatDate(dsp1) + } + if (Number(dsp2) === dsp2) { + dsp2 = w2utils.formatDate(dsp2) + } + display = `: ${dsp1} - ${dsp2}` + } else { + let dsp = sd.value + if (Number(dsp) == dsp) { + dsp = w2utils.formatDate(dsp) + } + let oper = sd.operator + if (oper == 'more') oper = 'since' + if (oper == 'less') oper = 'before' + if (oper.substr(0, 5) == 'more:') { + oper = 'since' + } + display = `: ${oper} ${dsp}` + } + } + searches += ` + ${sf ? sf.label : ''} + ${display} + + ` + }) + // clear and save + searches += ` + ${this.show.searchSave + ? `
    + + ` + : '' +} + + ` + query(this.box).find(`#grid_${this.name}_searches`).html(searches) + query(this.box).find(`#grid_${this.name}_search_logic`).html(w2utils.lang(this.last.logic == 'AND' ? 'All' : 'Any')) + } else { + query(this.box).find('.w2ui-grid-toolbar') + .css('height', this.last.toolbar_height + 'px') + .find('.w2ui-grid-searches') + .remove() + } + if (this.searchSelected) { + query(this.box).find(`#grid_${this.name}_search_all`).val(' ').prop('readOnly', true) + query(this.box).find(`#grid_${this.name}_search_name`).show().find('.name-text').html(this.searchSelected.text) + } else { + query(this.box).find(`#grid_${this.name}_search_all`).prop('readOnly', false) + query(this.box).find(`#grid_${this.name}_search_name`).hide().find('.name-text').html('') + } + w2utils.bindEvents(query(this.box).find(`#grid_${this.name}_searches .w2ui-action, #grid_${this.name}_searches button`), this) + } + refreshBody() { + this.scroll() // need to calculate virtual scrolling for columns + let recHTML = this.getRecordsHTML() + let colHTML = this.getColumnsHTML() + let bodyHTML = + '
    '+ + recHTML[0] + + '
    '+ + '
    ' + + recHTML[1] + + '
    '+ + '
    '+ + // Columns need to be after to be able to overlap + '
    '+ + ' '+ colHTML[0] +'
    '+ + '
    '+ + '
    '+ + ' '+ colHTML[1] +'
    '+ + '
    '+ + `` + let gridBody = query(this.box).find(`#grid_${this.name}_body`, this.box).html(bodyHTML) + let records = query(this.box).find(`#grid_${this.name}_records`, this.box) + let frecords = query(this.box).find(`#grid_${this.name}_frecords`, this.box) + if (this.selectType == 'row') { + records.on('mouseover mouseout', { delegate: 'tr' }, (event) => { + let recid = query(event.delegate).attr('recid') + query(this.box).find(`#grid_${this.name}_frec_${w2utils.escapeId(recid)}`) + .toggleClass('w2ui-record-hover', event.type == 'mouseover') + }) + frecords.on('mouseover mouseout', { delegate: 'tr' }, (event) => { + let recid = query(event.delegate).attr('recid') + query(this.box).find(`#grid_${this.name}_rec_${w2utils.escapeId(recid)}`) + .toggleClass('w2ui-record-hover', event.type == 'mouseover') + }) + } + if (w2utils.isIOS) { + records.append(frecords) + .on('click', { delegate: 'tr' }, (event) => { + let recid = query(event.delegate).attr('recid') + this.dblClick(recid, event) + }) + } else { + records.add(frecords) + .on('click', { delegate: 'tr' }, (event) => { + let recid = query(event.delegate).attr('recid') + // do not generate click if empty record is clicked + if (recid != '-none-') { + this.click(recid, event) + } + }) + .on('contextmenu', { delegate: 'tr' }, (event) => { + let recid = query(event.delegate).attr('recid') + let td = query(event.target).closest('td') + let column = parseInt(td.attr('col') ?? -1) + this.showContextMenu(recid, column, event) + }) + .on('mouseover', { delegate: 'tr' }, (event) => { + this.last.rec_out = false + let index = query(event.delegate).attr('index') + let recid = query(event.delegate).attr('recid') + if (index !== this.last.rec_over) { + this.last.rec_over = index + // setTimeout is needed for correct event order enter/leave + setTimeout(() => { + delete this.last.rec_out + let edata = this.trigger('mouseEnter', { target: this.name, originalEvent: event, index, recid }) + edata.finish() + }) + } + }) + .on('mouseout', { delegate: 'tr' }, (event) => { + let index = query(event.delegate).attr('index') + let recid = query(event.delegate).attr('recid') + this.last.rec_out = true + // setTimeouts are needed for correct event order enter/leave + setTimeout(() => { + let recLeave = () => { + let edata = this.trigger('mouseLeave', { target: this.name, originalEvent: event, index, recid }) + edata.finish() + } + if (index !== this.last.rec_over) { + recLeave() + } + setTimeout(() => { + if (this.last.rec_out) { + delete this.last.rec_out + delete this.last.rec_over + recLeave() + } + }) + }) + }) + } + // enable scrolling on frozen records, + gridBody + .data('scroll', { lastDelta: 0, lastTime: 0 }) + .find('.w2ui-grid-frecords') + .on('mousewheel DOMMouseScroll ', (event) => { + event.preventDefault() + // TODO: improve, scroll is not smooth, if scrolled to the end, it takes a while to return + let scroll = gridBody.data('scroll') + let container = gridBody.find('.w2ui-grid-records') + let amount = typeof event.wheelDelta != null ? -event.wheelDelta : (event.detail || event.deltaY) + let newScrollTop = container.prop('scrollTop') + scroll.lastDelta += amount + amount = Math.round(scroll.lastDelta) + gridBody.data('scroll', scroll) + // make scroll amount dependent on visible rows + // amount *= (Math.round(records.prop('clientHeight') / self.recordHeight) - 1) * self.recordHeight / 4 + container.get(0).scroll({ top: newScrollTop + amount, behavior: 'smooth' }) + }) + // scroll on records (and frozen records) + records.off('.body-global') + .on('scroll.body-global', { delegate: '.w2ui-grid-records' }, event => { + this.scroll(event) + }) + query(this.box).find('.w2ui-grid-body') // gridBody + .off('.body-global') + // header column click + .on('click.body-global dblclick.body-global contextmenu.body-global', { delegate: 'td.w2ui-head' }, event => { + let col_ind = query(event.delegate).attr('col') + let col = this.columns[col_ind] ?? { field: col_ind } // it could be line number + switch (event.type) { + case 'click': + this.columnClick(col.field, event) + break + case 'dblclick': + this.columnDblClick(col.field, event) + break + case 'contextmenu': + if (this.show.columnMenu) { + w2menu.show({ + type: 'check', + anchor: document.body, + originalEvent: event, + items: this.initColumnOnOff() + }) + .then(() => { + query('#w2overlay-context-menu .w2ui-grid-skip') + .off('.w2ui-grid') + .on('click.w2ui-grid', evt => { + evt.stopPropagation() + }) + .on('keypress', evt => { + if (evt.keyCode == 13) { + this.skip(evt.target.value) + this.toolbar.click('w2ui-column-on-off') // close menu + } + }) + }) + .select((event) => { + let id = event.detail.item.id + if (['w2ui-stateSave', 'w2ui-stateReset'].includes(id)) { + this[id.substring(5)]() + } else if (id == 'w2ui-skip') { + // empty + } else { + this.columnOnOff(event, event.detail.item.id) + } + clearTimeout(this.last.kbd_timer) // keep grid in focus + }) + clearTimeout(this.last.kbd_timer) // keep grid in focus + } + event.preventDefault() + break + } + }) + .on('mouseover.body-global', { delegate: '.w2ui-col-header' }, event => { + let col = query(event.delegate).parent().attr('col') + this.columnTooltipShow(col, event) + query(event.delegate) + .off('.tooltip') + .on('mouseleave.tooltip', () => { + this.columnTooltipHide(col, event) + }) + }) + // select all + .on('click.body-global', { delegate: 'input.w2ui-select-all' }, event => { + if (event.delegate.checked) { this.selectAll() } else { this.selectNone() } + event.stopPropagation() + clearTimeout(this.last.kbd_timer) // keep grid in focus + }) + // tree-like grid (or expandable column) expand/collapse + .on('click.body-global', { delegate: '.w2ui-show-children, .w2ui-col-expand' }, event => { + event.stopPropagation() + this.toggle(query(event.target).parents('tr').attr('recid')) + }) + // info bubbles + .on('click.body-global mouseover.body-global', { delegate: '.w2ui-info' }, event => { + let td = query(event.delegate).closest('td') + let tr = td.parent() + let col = this.columns[td.attr('col')] + let isSummary = tr.parents('.w2ui-grid-body').hasClass('w2ui-grid-summary') + if (['mouseenter', 'mouseover'].includes(col.info?.showOn?.toLowerCase()) && event.type == 'mouseover') { + this.showBubble(tr.attr('index'), td.attr('col'), isSummary) + .then(() => { + query(event.delegate) + .off('.tooltip') + .on('mouseleave.tooltip', () => { w2tooltip.hide(this.name + '-bubble') }) + }) + } else if (event.type == 'click') { + w2tooltip.hide(this.name + '-bubble') + this.showBubble(tr.attr('index'), td.attr('col'), isSummary) + } + }) + // clipborad copy icon + .on('mouseover.body-global', { delegate: '.w2ui-clipboard-copy' }, event => { + if (event.delegate._tooltipShow) return + let td = query(event.delegate).parent() + let tr = td.parent() + let col = this.columns[td.attr('col')] + let isSummary = tr.parents('.w2ui-grid-body').hasClass('w2ui-grid-summary') + w2tooltip.show({ + name: this.name + '-bubble', + anchor: event.delegate, + html: w2utils.lang(typeof col.clipboardCopy == 'string' ? col.clipboardCopy : 'Copy to clipboard'), + position: 'top|bottom', + offsetY: -2 + }) + .hide(evt => { + event.delegate._tooltipShow = false + query(event.delegate).off('.tooltip') + }) + query(event.delegate) + .off('.tooltip') + .on('mouseleave.tooltip', evt => { + w2tooltip.hide(this.name + '-bubble') + }) + .on('click.tooltip', evt => { + evt.stopPropagation() + w2tooltip.update(this.name + '-bubble', w2utils.lang('Copied')) + this.clipboardCopy(tr.attr('index'), td.attr('col'), isSummary) + }) + event.delegate._tooltipShow = true + }) + .on('click.body-global', { delegate: '.w2ui-editable-checkbox' }, event => { + let dt = query(event.delegate).data() + this.editChange.call(this, event.delegate, dt.changeind, dt.colind, event) + this.updateToolbar() + }) + // show empty message + if (this.records.length === 0 && this.msgEmpty) { + query(this.box).find(`#grid_${this.name}_body`) + .append(`
    ${w2utils.lang(this.msgEmpty)}
    `) + } else if (query(this.box).find(`#grid_${this.name}_empty_msg`).length > 0) { + query(this.box).find(`#grid_${this.name}_empty_msg`).remove() + } + // show summary records + if (this.summary.length > 0) { + let sumHTML = this.getSummaryHTML() + query(this.box).find(`#grid_${this.name}_fsummary`).html(sumHTML[0]).show() + query(this.box).find(`#grid_${this.name}_summary`).html(sumHTML[1]).show() + } else { + query(this.box).find(`#grid_${this.name}_fsummary`).hide() + query(this.box).find(`#grid_${this.name}_summary`).hide() + } + } + render(box) { + let time = Date.now() + let obj = this + if (typeof box == 'string') box = query(box).get(0) + // event before + let edata = this.trigger('render', { target: this.name, box: box ?? this.box }) + if (edata.isCancelled === true) return + // default action + if (box != null) { + // clean previous box + if (query(this.box).find(`#grid_${this.name}_body`).length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-grid w2ui-inactive') + .html('') + } + this.box = box + } + if (!this.box) return + let url = (typeof this.url != 'object' ? this.url : this.url.get) + // reset needed if grid existed + this.reset(true) + // --- default search field + if (!this.last.field) { + if (!this.multiSearch || !this.show.searchAll) { + let tmp = 0 + while (tmp < this.searches.length && (this.searches[tmp].hidden || this.searches[tmp].simple === false)) tmp++ + if (tmp >= this.searches.length) { + // all searches are hidden + this.last.field = '' + this.last.label = '' + } else { + this.last.field = this.searches[tmp].field + this.last.label = this.searches[tmp].label + } + } else { + this.last.field = 'all' + this.last.label = 'All Fields' + } + } + // insert elements + query(this.box) + .attr('name', this.name) + .addClass('w2ui-reset w2ui-grid w2ui-inactive') + .html('
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ' '+ + ' '+ // readonly needed on android not to open keyboard + '
    ') + if (this.selectType != 'row') query(this.box).addClass('w2ui-ss') + if (query(this.box).length > 0) query(this.box)[0].style.cssText += this.style + // init toolbar + this.initToolbar() + if (this.toolbar != null) this.toolbar.render(query(this.box).find('#grid_'+ this.name +'_toolbar')[0]) + this.last.toolbar_height = query(this.box).find(`#grid_${this.name}_toolbar`).prop('offsetHeight') + // re-init search_all + if (this.last.field && this.last.field != 'all') { + let sd = this.searchData + setTimeout(() => { this.searchInitInput(this.last.field, (sd.length == 1 ? sd[0].value : null)) }, 1) + } + // init footer + query(this.box).find(`#grid_${this.name}_footer`).html(this.getFooterHTML()) + // refresh + if (!this.last.state) this.last.state = this.stateSave(true) // initial default state + this.stateRestore() + if (url) { this.clear(); this.refresh() } // show empty grid (need it) - should it be only for remote data source + // if hidden searches - apply it + let hasHiddenSearches = false + for (let i = 0; i < this.searches.length; i++) { + if (this.searches[i].hidden) { hasHiddenSearches = true; break } + } + if (hasHiddenSearches) { + this.searchReset(false) // will call reload + if (!url) setTimeout(() => { this.searchReset() }, 1) + } else { + this.reload() + } + // focus + query(this.box).find(`#grid_${this.name}_focus`) + .on('focus', (event) => { + clearTimeout(this.last.kbd_timer) + if (!this.hasFocus) this.focus() + }) + .on('blur', (event) => { + clearTimeout(this.last.kbd_timer) + this.last.kbd_timer = setTimeout(() => { + if (this.hasFocus) { this.blur() } + }, 100) // need this timer to be 100 ms + }) + .on('paste', (event) => { + let cd = (event.clipboardData ? event.clipboardData : null) + if (cd) { + let items = cd.items + if (items.length == 2) { + if (items.length == 2 && items[1].kind == 'file') { + items = [items[1]] + } + if (items.length == 2 && items[0].type == 'text/plain' && items[1].type == 'text/html') { + items = [items[1]] + } + } + let items2send = [] + // might contain data in different formats, but it is a single paste + for (let index in items) { + let item = items[index] + if (item.kind === 'file') { + let file = item.getAsFile() + items2send.push({ kind: 'file', data: file }) + } else if (item.kind === 'string' && (item.type === 'text/plain' || item.type === 'text/html')) { + event.preventDefault() + let text = cd.getData('text/plain') + if (text.indexOf('\r') != -1 && text.indexOf('\n') == -1) { + text = text.replace(/\r/g, '\n') + } + items2send.push({ kind: (item.type == 'text/html' ? 'html' : 'text'), data: text }) + } + } + if (items2send.length === 1 && items2send[0].kind != 'file') { + items2send = items2send[0].data + } + w2ui[this.name].paste(items2send, event) + event.preventDefault() + } + }) + .on('keydown', function (event) { + w2ui[obj.name].keydown.call(w2ui[obj.name], event) + }) + // init mouse events for mouse selection + let edataCol // event for column select + query(this.box).off('mousedown.mouseStart').on('mousedown.mouseStart', mouseStart) + this.updateToolbar() + // event after + edata.finish() + // observe div resize + this.last.observeResize = new ResizeObserver(() => { this.resize() }) + this.last.observeResize.observe(this.box) + return Date.now() - time + function mouseStart (event) { + if (event.which != 1) return // if not left mouse button + // restore css user-select + if (obj.last.userSelect == 'text') { + obj.last.userSelect = '' + query(obj.box).find('.w2ui-grid-body').css('user-select', 'none') + } + // regular record select + if (obj.selectType == 'row' && (query(event.target).parents().hasClass('w2ui-head') || query(event.target).hasClass('w2ui-head'))) return + if (obj.last.move && obj.last.move.type == 'expand') return + // if altKey - alow text selection + if (event.altKey) { + query(obj.box).find('.w2ui-grid-body').css('user-select', 'text') + obj.selectNone() + obj.last.move = { type: 'text-select' } + obj.last.userSelect = 'text' + } else { + let tmp = event.target + let pos = { + x: event.offsetX - 10, + y: event.offsetY - 10 + } + let tmps = false + while (tmp) { + if (tmp.classList && tmp.classList.contains('w2ui-grid')) break + if (tmp.tagName && tmp.tagName.toUpperCase() == 'TD') tmps = true + if (tmp.tagName && tmp.tagName.toUpperCase() != 'TR' && tmps == true) { + pos.x += tmp.offsetLeft + pos.y += tmp.offsetTop + } + tmp = tmp.parentNode + } + obj.last.move = { + x : event.screenX, + y : event.screenY, + divX : 0, + divY : 0, + focusX : pos.x, + focusY : pos.y, + recid : query(event.target).parents('tr').attr('recid'), + column : parseInt(event.target.tagName.toUpperCase() == 'TD' ? query(event.target).attr('col') : query(event.target).parents('td').attr('col')), + type : 'select', + ghost : false, + start : true + } + if (obj.last.move.recid == null) obj.last.move.type = 'select-column' + // set focus to grid + let target = event.target + let $input = query(obj.box).find('#grid_'+ obj.name + '_focus') + // move input next to cursor so screen does not jump + if (obj.last.move) { + let sLeft = obj.last.move.focusX + let sTop = obj.last.move.focusY + let $owner = query(target).parents('table').parent() + if ($owner.hasClass('w2ui-grid-records') || $owner.hasClass('w2ui-grid-frecords') + || $owner.hasClass('w2ui-grid-columns') || $owner.hasClass('w2ui-grid-fcolumns') + || $owner.hasClass('w2ui-grid-summary')) { + sLeft = obj.last.move.focusX - query(obj.box).find('#grid_'+ obj.name +'_records').prop('scrollLeft') + sTop = obj.last.move.focusY - query(obj.box).find('#grid_'+ obj.name +'_records').prop('scrollTop') + } + if (query(target).hasClass('w2ui-grid-footer') || query(target).parents('div.w2ui-grid-footer').length > 0) { + sTop = query(obj.box).find('#grid_'+ obj.name +'_footer').get(0).offsetTop + } + // if clicked on toolbar + if ($owner.hasClass('w2ui-scroll-wrapper') && $owner.parent().hasClass('w2ui-toolbar')) { + sLeft = obj.last.move.focusX - $owner.prop('scrollLeft') + } + $input.css({ + left: sLeft - 10, + top : sTop + }) + } + // if toolbar input is clicked + setTimeout(() => { + if (!obj.last.inEditMode) { + if (['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName)) { + target.focus() + } else { + if ($input.get(0) !== document.active) $input.get(0)?.focus({ preventScroll: true }) + } + } + }, 50) + // disable click select for this condition + if (!obj.multiSelect && !obj.reorderRows && obj.last.move.type == 'drag') { + delete obj.last.move + } + } + if (obj.reorderRows == true) { + let el = event.target + if (el.tagName.toUpperCase() != 'TD') el = query(el).parents('td')[0] + if (query(el).hasClass('w2ui-col-number') || query(el).hasClass('w2ui-col-order')) { + obj.selectNone() + obj.last.move.reorder = true + // suppress hover + let eColor = query(obj.box).find('.w2ui-even.w2ui-empty-record').css('background-color') + let oColor = query(obj.box).find('.w2ui-odd.w2ui-empty-record').css('background-color') + query(obj.box).find('.w2ui-even td').filter(':not(.w2ui-col-number)').css('background-color', eColor) + query(obj.box).find('.w2ui-odd td').filter(':not(.w2ui-col-number)').css('background-color', oColor) + // display empty record and ghost record + let mv = obj.last.move + let recs = query(obj.box).find('.w2ui-grid-records') + if (!mv.ghost) { + let row = query(obj.box).find(`#grid_${obj.name}_rec_${mv.recid}`) + let tmp = row.parents('table').find('tr:first-child').get(0).cloneNode(true) + mv.offsetY = event.offsetY + mv.from = mv.recid + mv.pos = { top: row.get(0).offsetTop-1, left: row.get(0).offsetLeft } + mv.ghost = query(row.get(0).cloneNode(true)) + mv.ghost.removeAttr('id') + mv.ghost.find('td').css({ + 'border-top': '1px solid silver', + 'border-bottom': '1px solid silver' + }) + row.find('td').remove() + row.append(`
    `) + recs.append('
    ') + recs.append('
    ') + query(obj.box).find('#grid_'+ obj.name + '_ghost').append(tmp).append(mv.ghost) + } + let ghost = query(obj.box).find('#grid_'+ obj.name + '_ghost') + ghost.css({ + top : mv.pos.top + 'px', + left : mv.pos.left + 'px' + }) + } else { + obj.last.move.reorder = false + } + } + query(document) + .on('mousemove.w2ui-' + obj.name, mouseMove) + .on('mouseup.w2ui-' + obj.name, mouseStop) + // needed when grid grids are nested, see issue #1275 + event.stopPropagation() + } + function mouseMove(event) { + if (!event.target.tagName) { + // element has no tagName - most likely the target is the #document itself + // this can happen is you click+drag and move the mouse out of the DOM area, + // e.g. into the browser's toolbar area + return + } + let mv = obj.last.move + if (!mv || ['select', 'select-column'].indexOf(mv.type) == -1) return + mv.divX = (event.screenX - mv.x) + mv.divY = (event.screenY - mv.y) + if (Math.abs(mv.divX) <= 1 && Math.abs(mv.divY) <= 1) return // only if moved more then 1px + obj.last.cancelClick = true + if (obj.reorderRows == true && obj.last.move.reorder) { + let tmp = query(event.target).parents('tr') + let recid = tmp.attr('recid') + if (recid == '-none-') recid = 'bottom' + if (recid != mv.from) { + // let row1 = query(obj.box).find('#grid_'+ obj.name + '_rec_'+ mv.recid) + let row2 = query(obj.box).find('#grid_'+ obj.name + '_rec_'+ recid) + query(obj.box).find('.insert-before') + row2.addClass('insert-before') + // MOVABLE GHOST + // if (event.screenY - mv.lastY < 0) row1.after(row2); else row2.after(row1); + mv.lastY = event.screenY + mv.to = recid + // line to insert before + let pos = { top: row2.get(0)?.offsetTop, left: row2.get(0)?.offsetLeft } + let ghost_line = query(obj.box).find('#grid_'+ obj.name + '_ghost_line') + if (pos) { + ghost_line.css({ + top : pos.top + 'px', + left : mv.pos.left + 'px', + 'border-top': '2px solid #769EFC' + }) + } else { + ghost_line.css({ + 'border-top': '2px solid transparent' + }) + } + } + let ghost = query(obj.box).find('#grid_'+ obj.name + '_ghost') + ghost.css({ + top : (mv.pos.top + mv.divY) + 'px', + left : mv.pos.left + 'px' + }) + return + } + if (mv.start && mv.recid) { + obj.selectNone() + mv.start = false + } + let newSel = [] + let recid = (event.target.tagName.toUpperCase() == 'TR' ? query(event.target).attr('recid') : query(event.target).parents('tr').attr('recid')) + if (recid == null) { + // select by dragging columns + if (obj.selectType == 'row') return + if (obj.last.move && obj.last.move.type == 'select') return + let col = parseInt(query(event.target).parents('td').attr('col')) + if (isNaN(col)) { + obj.removeRange('column-selection') + query(obj.box).find('.w2ui-grid-columns .w2ui-col-header, .w2ui-grid-fcolumns .w2ui-col-header').removeClass('w2ui-col-selected') + query(obj.box).find('.w2ui-col-number').removeClass('w2ui-row-selected') + delete mv.colRange + } else { + // add all columns in between + let newRange = col + '-' + col + if (mv.column < col) newRange = mv.column + '-' + col + if (mv.column > col) newRange = col + '-' + mv.column + // array of selected columns + let cols = [] + let tmp = newRange.split('-') + for (let ii = parseInt(tmp[0]); ii <= parseInt(tmp[1]); ii++) { + cols.push(ii) + } + if (mv.colRange != newRange) { + edataCol = obj.trigger('columnSelect', { target: obj.name, columns: cols }) + if (edataCol.isCancelled !== true) { + if (mv.colRange == null) obj.selectNone() + // highlight columns + let tmp = newRange.split('-') + query(obj.box).find('.w2ui-grid-columns .w2ui-col-header, .w2ui-grid-fcolumns .w2ui-col-header').removeClass('w2ui-col-selected') + for (let j = parseInt(tmp[0]); j <= parseInt(tmp[1]); j++) { + query(obj.box).find('#grid_'+ obj.name +'_column_' + j + ' .w2ui-col-header').addClass('w2ui-col-selected') + } + query(obj.box).find('.w2ui-col-number').not('.w2ui-head').addClass('w2ui-row-selected') + // show new range + mv.colRange = newRange + obj.removeRange('column-selection') + obj.addRange({ + name : 'column-selection', + range : [{ recid: obj.records[0].recid, column: tmp[0] }, { recid: obj.records[obj.records.length-1].recid, column: tmp[1] }], + style : 'background-color: rgba(90, 145, 234, 0.1)' + }) + } + } + } + } else { // regular selection + let ind1 = obj.get(mv.recid, true) + // this happens when selection is started on summary row + if (ind1 == null || (obj.records[ind1] && obj.records[ind1].recid != mv.recid)) return + let ind2 = obj.get(recid, true) + // this happens when selection is extended into summary row (a good place to implement scrolling) + if (ind2 == null) return + let col1 = parseInt(mv.column) + let col2 = parseInt(event.target.tagName.toUpperCase() == 'TD' ? query(event.target).attr('col') : query(event.target).parents('td').attr('col')) + if (isNaN(col1) && isNaN(col2)) { // line number select entire record + col1 = 0 + col2 = obj.columns.length-1 + } + if (ind1 > ind2) { let tmp = ind1; ind1 = ind2; ind2 = tmp } + // check if need to refresh + let tmp = 'ind1:'+ ind1 +',ind2;'+ ind2 +',col1:'+ col1 +',col2:'+ col2 + if (mv.range == tmp) return + mv.range = tmp + for (let i = ind1; i <= ind2; i++) { + if (obj.last.searchIds.length > 0 && obj.last.searchIds.indexOf(i) == -1) continue + if (obj.selectType != 'row') { + if (col1 > col2) { let tmp = col1; col1 = col2; col2 = tmp } + for (let c = col1; c <= col2; c++) { + if (obj.columns[c].hidden) continue + newSel.push({ recid: obj.records[i].recid, column: parseInt(c) }) + } + } else { + newSel.push(obj.records[i].recid) + } + } + if (obj.selectType != 'row') { + let sel = obj.getSelection() + // add more items + let tmp = [] + for (let ns = 0; ns < newSel.length; ns++) { + let flag = false + for (let s = 0; s < sel.length; s++) if (newSel[ns].recid == sel[s].recid && newSel[ns].column == sel[s].column) flag = true + if (!flag) tmp.push({ recid: newSel[ns].recid, column: newSel[ns].column }) + } + obj.select(tmp) + // remove items + tmp = [] + for (let s = 0; s < sel.length; s++) { + let flag = false + for (let ns = 0; ns < newSel.length; ns++) if (newSel[ns].recid == sel[s].recid && newSel[ns].column == sel[s].column) flag = true + if (!flag) tmp.push({ recid: sel[s].recid, column: sel[s].column }) + } + obj.unselect(tmp) + } else { + if (obj.multiSelect) { + let sel = obj.getSelection() + for (let ns = 0; ns < newSel.length; ns++) { + if (sel.indexOf(newSel[ns]) == -1) obj.select(newSel[ns]) // add more items + } + for (let s = 0; s < sel.length; s++) { + if (newSel.indexOf(sel[s]) == -1) obj.unselect(sel[s]) // remove items + } + } + } + } + } + function mouseStop (event) { + let mv = obj.last.move + setTimeout(() => { delete obj.last.cancelClick }, 1) + if (query(event.target).parents().hasClass('.w2ui-head') || query(event.target).hasClass('.w2ui-head')) return + if (mv && ['select', 'select-column'].indexOf(mv.type) != -1) { + if (mv.colRange != null && edataCol.isCancelled !== true) { + let tmp = mv.colRange.split('-') + let sel = [] + for (let i = 0; i < obj.records.length; i++) { + let cols = [] + for (let j = parseInt(tmp[0]); j <= parseInt(tmp[1]); j++) cols.push(j) + sel.push({ recid: obj.records[i].recid, column: cols }) + } + obj.removeRange('column-selection') + edataCol.finish() + obj.select(sel) + } + if (obj.reorderRows == true && obj.last.move.reorder) { + if (mv.to != null) { + // event + let edata = obj.trigger('reorderRow', { target: obj.name, recid: mv.from, moveBefore: mv.to }) + if (edata.isCancelled === true) { + resetRowReorder() + delete obj.last.move + return + } + // default behavior + let ind1 = obj.get(mv.from, true) + let ind2 = obj.get(mv.to, true) + if (mv.to == 'bottom') ind2 = obj.records.length // end of list + let tmp = obj.records[ind1] + // swap records + if (ind1 != null && ind2 != null) { + obj.records.splice(ind1, 1) + if (ind1 > ind2) { + obj.records.splice(ind2, 0, tmp) + } else { + obj.records.splice(ind2 - 1, 0, tmp) + } + } + // clear sortData + obj.sortData = [] + query(obj.box) + .find(`#grid_${obj.name}_columns .w2ui-col-header`) + .removeClass('w2ui-col-sorted') + resetRowReorder() + // event after + edata.finish() + } else { + resetRowReorder() + } + } + } + delete obj.last.move + query(document).off('.w2ui-' + obj.name) + } + function resetRowReorder() { + query(obj.box).find(`#grid_${obj.name}_ghost`).remove() + query(obj.box).find(`#grid_${obj.name}_ghost_line`).remove() + obj.refresh() + delete obj.last.move + } + } + destroy() { + // event before + let edata = this.trigger('destroy', { target: this.name }) + if (edata.isCancelled === true) return + // remove all events + query(this.box).off() + // clean up + if (typeof this.toolbar == 'object' && this.toolbar.destroy) this.toolbar.destroy() + if (query(this.box).find(`#grid_${this.name}_body`).length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-grid w2ui-inactive') + .html('') + } + this.last.observeResize?.disconnect() + delete w2ui[this.name] + // event after + edata.finish() + } + // =========================================== + // --- Internal Functions + initColumnOnOff() { + let items = [ + { id: 'line-numbers', text: 'Line #', checked: this.show.lineNumbers } + ] + // columns + for (let c = 0; c < this.columns.length; c++) { + let col = this.columns[c] + let text = this.columns[c].text + if (col.hideable === false) continue + if (!text && this.columns[c].tooltip) text = this.columns[c].tooltip + if (!text) text = '- column '+ (parseInt(c) + 1) +' -' + items.push({ id: col.field, text: w2utils.stripTags(text), checked: !col.hidden }) + } + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if ((url && this.show.skipRecords) || this.show.saveRestoreState) { + items.push({ text: '--' }) + } + // skip records + if (this.show.skipRecords) { + let skip = w2utils.lang('Skip') + + `` + + w2utils.lang('records') + items.push({ id: 'w2ui-skip', text: skip, group: false, icon: 'w2ui-icon-empty' }) + } + // save/restore state + if (this.show.saveRestoreState) { + items.push( + { id: 'w2ui-stateSave', text: w2utils.lang('Save Grid State'), icon: 'w2ui-icon-empty', group: false }, + { id: 'w2ui-stateReset', text: w2utils.lang('Restore Default State'), icon: 'w2ui-icon-empty', group: false } + ) + } + let selected = [] + items.forEach(item => { + item.text = w2utils.lang(item.text) // translate + if (item.checked) selected.push(item.id) + }) + this.toolbar.set('w2ui-column-on-off', { selected, items }) + return items + } + initColumnDrag(box) { + // throw error if using column groups + if (this.columnGroups && this.columnGroups.length) { + throw 'Draggable columns are not currently supported with column groups.' + } + let self = this + let dragData = { + pressed: false, + targetPos: null, + columnHead: null + } + let hasInvalidClass = (target, lastColumn) => { + let iClass = ['w2ui-col-number', 'w2ui-col-expand', 'w2ui-col-select'] + if (lastColumn !== true) iClass.push('w2ui-head-last') + for (let i = 0; i < iClass.length; i++) { + if (query(target).closest('.w2ui-head').hasClass(iClass[i])) { + return true + } + } + return false + } + // attach original event listener + query(self.box) + .off('.colDrag') + .on('mousedown.colDrag', dragColStart) + function dragColStart(event) { + if (dragData.pressed || dragData.numberPreColumnsPresent === 0 || event.button !== 0) return + let edata, columns, origColumn, origColumnNumber + let preColHeadersSelector = '.w2ui-head.w2ui-col-number, .w2ui-head.w2ui-col-expand, .w2ui-head.w2ui-col-select' + // do nothing if it is not a header + if (!query(event.target).parents().hasClass('w2ui-head') || hasInvalidClass(event.target)) return + dragData.pressed = true + dragData.initialX = event.pageX + dragData.initialY = event.pageY + dragData.numberPreColumnsPresent = query(self.box).find(preColHeadersSelector).length + // start event for drag start + dragData.columnHead = origColumn = query(event.target).closest('.w2ui-head') + dragData.originalPos = origColumnNumber = parseInt(origColumn.attr('col'), 10) + edata = self.trigger('columnDragStart', { originalEvent: event, origColumnNumber, target: origColumn[0] }) + if (edata.isCancelled === true) return false + columns = dragData.columns = query(self.box).find('.w2ui-head:not(.w2ui-head-last)') + // add events + query(document).on('mouseup.colDrag', dragColEnd) + query(document).on('mousemove.colDrag', dragColOver) + let col = self.columns[dragData.originalPos] + let colText = w2utils.lang(typeof col.text == 'function' ? col.text(col) : col.text) + dragData.ghost = query.html(`${colText}`)[0] + query(document.body).append(dragData.ghost) + query(dragData.ghost) + .css({ + display: 'none', + left: event.pageX, + top: event.pageY, + opacity: 1, + margin: '3px 0 0 20px', + padding: '3px', + 'background-color': 'white', + position: 'fixed', + 'z-index': 999999, + }) + .addClass('.w2ui-grid-ghost') + + // establish current offsets + dragData.offsets = [] + for (let i = 0, l = columns.length; i < l; i++) { + let rect = columns[i].getBoundingClientRect() + dragData.offsets.push(rect.left) + } + // conclude event + edata.finish() + } + function dragColOver(event) { + if (!dragData.pressed || !dragData.columnHead) return + let cursorX = event.pageX + let cursorY = event.pageY + if (!hasInvalidClass(event.target, true)) { + markIntersection(event) + } + trackGhost(cursorX, cursorY) + } + function dragColEnd(event) { + if (!dragData.pressed || !dragData.columnHead) return + dragData.pressed = false + let edata, target, selected, columnConfig + let finish = () => { + let ghosts = query(self.box).find('.w2ui-grid-ghost') + query(self.box).find('.w2ui-intersection-marker').hide() + query(dragData.ghost).remove() + ghosts.remove() + // dragData.columns.css({ overflow: '' }).children('div').css({ overflow: '' }); + query(document).off('.colDrag') + dragData = {} + } + // if no move, then click event for sorting + if (event.pageX == dragData.initialX && event.pageY == dragData.initialY) { + self.columnClick(self.columns[dragData.originalPos].field, event) + finish() + return + } + // start event for drag start + edata = self.trigger('columnDragEnd', { originalEvent: event, target: dragData.columnHead[0], dragData }) + if (edata.isCancelled === true) return false + selected = self.columns[dragData.originalPos] + columnConfig = self.columns + if (dragData.originalPos != dragData.targetPos && dragData.targetPos != null) { + columnConfig.splice(dragData.targetPos, 0, w2utils.clone(selected)) + columnConfig.splice(columnConfig.indexOf(selected), 1) + } + finish() + self.refresh() + edata.finish({ targetColumn: target - 1 }) + } + function markIntersection(event) { + // if mouse over is not over table + if (query(event.target).closest('td').length == 0) { + return + } + // if mouse over invalid column + let rect1 = query(self.box).find('.w2ui-grid-body').get(0).getBoundingClientRect() + let rect2 = query(event.target).closest('td').get(0).getBoundingClientRect() + query(self.box).find('.w2ui-intersection-marker') + .show() + .css({ + left: (rect2.left - rect1.left) + 'px' + }) + let td = query(event.target).closest('td') + dragData.targetPos = td.hasClass('w2ui-head-last') ? self.columns.length : parseInt(td.attr('col')) + return + } + function trackGhost(cursorX, cursorY){ + query(dragData.ghost) + .css({ + left : (cursorX - 10) + 'px', + top : (cursorY - 10) + 'px' + }) + .show() + } + // return an object to remove drag if it has ever been enabled + return { + remove() { + query(self.box).off('.colDrag') + self.last.columnDrag = false + } + } + } + columnOnOff(event, field) { + // event before + let edata = this.trigger('columnOnOff', { target: this.name, field: field, originalEvent: event }) + if (edata.isCancelled === true) return + // collapse expanded rows + let rows = this.find({ 'w2ui.expanded': true }, true) + for (let r = 0; r < rows.length; r++) { + let tmp = this.records[r].w2ui + if (tmp && !Array.isArray(tmp.children)) { + this.records[r].w2ui.expanded = false + } + } + // show/hide + if (field == 'line-numbers') { + this.show.lineNumbers = !this.show.lineNumbers + this.refresh() + } else { + let col = this.getColumn(field) + if (col.hidden) { + this.showColumn(col.field) + } else { + this.hideColumn(col.field) + } + } + // event after + edata.finish() + } + initToolbar() { + // if it is already initiazlied + if (this.toolbar.render != null) { + return + } + let tb_items = this.toolbar.items || [] + this.toolbar.items = [] + this.toolbar = new w2toolbar(w2utils.extend({}, this.toolbar, { name: this.name +'_toolbar', owner: this })) + if (this.show.toolbarReload) { + this.toolbar.items.push(w2utils.extend({}, this.buttons.reload)) + } + if (this.show.toolbarColumns) { + this.toolbar.items.push(w2utils.extend({}, this.buttons.columns)) + } + if (this.show.toolbarSearch) { + let html =` +
    + ${this.buttons.search.html} +
    + + + x +
    + +
    + +
    +
    ` + this.toolbar.items.push({ + id: 'w2ui-search', + type: 'html', + html, + onRefresh: async (event) => { + await event.complete + let input = query(this.box).find(`#grid_${this.name}_search_all`) + w2utils.bindEvents(query(this.box).find(`#grid_${this.name}_search_all, .w2ui-action`), this) + // slow down live search calls + let slowSearch = w2utils.debounce((event) => { + let val = event.target.value + if (this.liveSearch && this.last.liveText != val) { + this.last.liveText = val + this.search(this.last.field, val) + } + if (event.keyCode == 40) { // arrow down + this.searchSuggest(true) + } + }, 250) + input + .on('change', event => { + if (!this.liveSearch) { + this.search(this.last.field, event.target.value) + this.searchSuggest(true, true, this) + } + }) + .on('blur', () => { this.last.liveText = '' }) + .on('keyup', slowSearch) + } + }) + } + if (Array.isArray(tb_items)) { + let ids = tb_items.map(item => item.id) + if (this.show.toolbarAdd && !ids.includes(this.buttons.add.id)) { + this.toolbar.items.push(w2utils.extend({}, this.buttons.add)) + } + if (this.show.toolbarEdit && !ids.includes(this.buttons.edit.id)) { + this.toolbar.items.push(w2utils.extend({}, this.buttons.edit)) + } + if (this.show.toolbarDelete && !ids.includes(this.buttons.delete.id)) { + this.toolbar.items.push(w2utils.extend({}, this.buttons.delete)) + } + if (this.show.toolbarSave && !ids.includes(this.buttons.save.id)) { + if (this.show.toolbarAdd || this.show.toolbarDelete || this.show.toolbarEdit) { + this.toolbar.items.push({ type: 'break', id: 'w2ui-break2' }) + } + this.toolbar.items.push(w2utils.extend({}, this.buttons.save)) + } + // fill in overwritten items with default buttons + // ids are w2ui-* but in this.buttons the map is just [add, edit, delete] + // must specify at least {id, name} in this.toolbar.items if you want to keep order + tb_items = tb_items.map(item => this.buttons[item.name] + ? w2utils.extend({}, this.buttons[item.name], item) : item) + } + // add original buttons + this.toolbar.items.push(...tb_items) + // ============================================= + // ------ Toolbar onClick processing + this.toolbar.on('click', (event) => { + let edata = this.trigger('toolbar', { target: event.target, originalEvent: event }) + if (edata.isCancelled === true) return + let edata2 + switch (event.detail.item.id) { + case 'w2ui-reload': + edata2 = this.trigger('reload', { target: this.name }) + if (edata2.isCancelled === true) return false + this.reload() + edata2.finish() + break + case 'w2ui-column-on-off': + // TODO: tap on columns will hide menu before opening, only in grid not in toolbar + if (event.detail.subItem) { + let id = event.detail.subItem.id + if (['w2ui-stateSave', 'w2ui-stateReset'].includes(id)) { + this[id.substring(5)]() + } else if (id == 'w2ui-skip') { + // empty + } else { + this.columnOnOff(event, event.detail.subItem.id) + } + } else { + this.initColumnOnOff() + // init input control with records to skip + setTimeout(() => { + query(`#w2overlay-${this.name}_toolbar-drop .w2ui-grid-skip`) + .off('.w2ui-grid') + .on('click.w2ui-grid', evt => { + evt.stopPropagation() + }) + .on('keypress', evt => { + if (evt.keyCode == 13) { + this.skip(evt.target.value) + this.toolbar.click('w2ui-column-on-off') // close menu + } + }) + }, 100) + } + break + case 'w2ui-add': + // events + edata2 = this.trigger('add', { target: this.name, recid: null }) + if (edata2.isCancelled === true) return false + edata2.finish() + break + case 'w2ui-edit': { + let sel = this.getSelection() + let recid = null + if (sel.length == 1) recid = sel[0] + // events + edata2 = this.trigger('edit', { target: this.name, recid: recid }) + if (edata2.isCancelled === true) return false + edata2.finish() + break + } + case 'w2ui-delete': + this.delete() + break + case 'w2ui-save': + this.save() + break + } + // no default action + edata.finish() + }) + this.toolbar.on('refresh', (event) => { + if (event.target == 'w2ui-search') { + let sd = this.searchData + setTimeout(() => { + this.searchInitInput(this.last.field, (sd.length == 1 ? sd[0].value : null)) + }, 1) + } + }) + } + initResize() { + let obj = this + query(this.box).find('.w2ui-resizer') + .off('.grid-col-resize') + .on('click.grid-col-resize', function(event) { + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true + if (event.preventDefault) event.preventDefault() + }) + .on('mousedown.grid-col-resize', function(event) { + if (!event) event = window.event + obj.last.colResizing = true + obj.last.tmp = { + x : event.screenX, + y : event.screenY, + gx : event.screenX, + gy : event.screenY, + col : parseInt(query(this).attr('name')) + } + // find tds that will be resized + obj.last.tmp.tds = query(obj.box).find('#grid_'+ obj.name +'_body table tr:first-child td[col="'+ obj.last.tmp.col +'"]') + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true + if (event.preventDefault) event.preventDefault() + // fix sizes + for (let c = 0; c < obj.columns.length; c++) { + if (obj.columns[c].hidden) continue + if (obj.columns[c].sizeOriginal == null) obj.columns[c].sizeOriginal = obj.columns[c].size + obj.columns[c].size = obj.columns[c].sizeCalculated + } + let edata = { phase: 'before', type: 'columnResize', target: obj.name, column: obj.last.tmp.col, field: obj.columns[obj.last.tmp.col].field } + edata = obj.trigger(w2utils.extend(edata, { resizeBy: 0, originalEvent: event })) + // set move event + let timer + let mouseMove = function(event) { + if (obj.last.colResizing != true) return + if (!event) event = window.event + // event before + edata = obj.trigger(w2utils.extend(edata, { resizeBy: (event.screenX - obj.last.tmp.gx), originalEvent: event })) + if (edata.isCancelled === true) { edata.isCancelled = false; return } + // default action + obj.last.tmp.x = (event.screenX - obj.last.tmp.x) + obj.last.tmp.y = (event.screenY - obj.last.tmp.y) + let newWidth = (parseInt(obj.columns[obj.last.tmp.col].size) + obj.last.tmp.x) + 'px' + obj.columns[obj.last.tmp.col].size = newWidth + if (timer) clearTimeout(timer) + timer = setTimeout(() => { + obj.resizeRecords() + obj.scroll() + }, 100) + // quick resize + obj.last.tmp.tds.css({ width: newWidth }) + // reset + obj.last.tmp.x = event.screenX + obj.last.tmp.y = event.screenY + } + let mouseUp = function(event) { + query(document).off('.grid-col-resize') + obj.resizeRecords() + obj.scroll() + // event after + edata.finish({ originalEvent: event }) + // need timeout to finish processing events + setTimeout(() => { obj.last.colResizing = false }, 1) + } + query(document) + .off('.grid-col-resize') + .on('mousemove.grid-col-resize', mouseMove) + .on('mouseup.grid-col-resize', mouseUp) + }) + .on('dblclick.grid-col-resize', function(event) { + let colId = parseInt(query(this).attr('name')), + col = obj.columns[colId], + maxDiff = 0 + if (col.autoResize === false) { + return true + } + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true + if (event.preventDefault) event.preventDefault() + query(obj.box).find('.w2ui-grid-records td[col="' + colId + '"] > div', obj.box).each(() => { + let thisDiff = this.offsetWidth - this.scrollWidth + if (thisDiff < maxDiff) { + maxDiff = thisDiff - 3 // 3px buffer needed for Firefox + } + }) + // event before + let edata = { phase: 'before', type: 'columnAutoResize', target: obj.name, column: col, field: col.field } + edata = obj.trigger(w2utils.extend(edata, { resizeBy: Math.abs(maxDiff), originalEvent: event })) + if (edata.isCancelled === true) { edata.isCancelled = false; return } + if (maxDiff < 0) { + col.size = Math.min(parseInt(col.size) + Math.abs(maxDiff), col.max || Infinity) + 'px' + obj.resizeRecords() + obj.resizeRecords() // Why do we have to call it twice in order to show the scrollbar? + obj.scroll() + } + // event after + edata.finish({ originalEvent: event }) + }) + .each(el => { + let td = query(el).get(0).parentNode + query(el).css({ + 'height' : td.clientHeight + 'px', + 'margin-left' : (td.clientWidth - 3) + 'px' + }) + }) + } + resizeBoxes() { + // elements + let header = query(this.box).find(`#grid_${this.name}_header`) + let toolbar = query(this.box).find(`#grid_${this.name}_toolbar`) + let fsummary = query(this.box).find(`#grid_${this.name}_fsummary`) + let summary = query(this.box).find(`#grid_${this.name}_summary`) + let footer = query(this.box).find(`#grid_${this.name}_footer`) + let body = query(this.box).find(`#grid_${this.name}_body`) + if (this.show.header) { + header.css({ + top: '0px', + left: '0px', + right: '0px' + }) + } + if (this.show.toolbar) { + toolbar.css({ + top: (0 + (this.show.header ? w2utils.getSize(header, 'height') : 0)) + 'px', + left: '0px', + right: '0px' + }) + } + if (this.summary.length > 0) { + fsummary.css({ + bottom: (0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0)) + 'px' + }) + summary.css({ + bottom: (0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0)) + 'px', + right: '0px' + }) + } + if (this.show.footer) { + footer.css({ + bottom: '0px', + left: '0px', + right: '0px' + }) + } + body.css({ + top: (0 + (this.show.header ? w2utils.getSize(header, 'height') : 0) + (this.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0)) + 'px', + bottom: (0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0) + (this.summary.length > 0 ? w2utils.getSize(summary, 'height') : 0)) + 'px', + left: '0px', + right: '0px' + }) + } + resizeRecords() { + let obj = this + // remove empty records + query(this.box).find('.w2ui-empty-record').remove() + // -- Calculate Column size in PX + let box = query(this.box) + let grid = query(this.box).find(':scope > div.w2ui-grid-box') + let header = query(this.box).find(`#grid_${this.name}_header`) + let toolbar = query(this.box).find(`#grid_${this.name}_toolbar`) + let summary = query(this.box).find(`#grid_${this.name}_summary`) + let fsummary = query(this.box).find(`#grid_${this.name}_fsummary`) + let footer = query(this.box).find(`#grid_${this.name}_footer`) + let body = query(this.box).find(`#grid_${this.name}_body`) + let columns = query(this.box).find(`#grid_${this.name}_columns`) + let fcolumns = query(this.box).find(`#grid_${this.name}_fcolumns`) + let records = query(this.box).find(`#grid_${this.name}_records`) + let frecords = query(this.box).find(`#grid_${this.name}_frecords`) + let scroll1 = query(this.box).find(`#grid_${this.name}_scroll1`) + let lineNumberWidth = String(this.total).length * 8 + 10 + if (lineNumberWidth < 34) lineNumberWidth = 34 // 3 digit width + if (this.lineNumberWidth != null) lineNumberWidth = this.lineNumberWidth + let bodyOverflowX = false + let bodyOverflowY = false + let sWidth = 0 + for (let i = 0; i < this.columns.length; i++) { + if (this.columns[i].frozen || this.columns[i].hidden) continue + let cSize = parseInt(this.columns[i].sizeCalculated ? this.columns[i].sizeCalculated : this.columns[i].size) + sWidth += cSize + } + if (records[0]?.clientWidth < sWidth) bodyOverflowX = true + if (body[0]?.clientHeight - (columns[0]?.clientHeight ?? 0) + < (query(records).find(':scope > table')[0]?.clientHeight ?? 0) + (bodyOverflowX ? w2utils.scrollBarSize() : 0)) { + bodyOverflowY = true + } + // body might be expanded by data + if (!this.fixedBody) { + // allow it to render records, then resize + let bodyHeight = w2utils.getSize(columns, 'height') + + w2utils.getSize(query(this.box).find('#grid_'+ this.name +'_records table'), 'height') + + (bodyOverflowX ? w2utils.scrollBarSize() : 0) + let calculatedHeight = bodyHeight + + (this.show.header ? w2utils.getSize(header, 'height') : 0) + + (this.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0) + + (summary.css('display') != 'none' ? w2utils.getSize(summary, 'height') : 0) + + (this.show.footer ? w2utils.getSize(footer, 'height') : 0) + grid.css('height', calculatedHeight + 'px') + body.css('height', bodyHeight + 'px') + box.css('height', w2utils.getSize(grid, 'height') + 'px') + } else { + // fixed body height + let calculatedHeight = grid[0]?.clientHeight + - (this.show.header ? w2utils.getSize(header, 'height') : 0) + - (this.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0) + - (summary.css('display') != 'none' ? w2utils.getSize(summary, 'height') : 0) + - (this.show.footer ? w2utils.getSize(footer, 'height') : 0) + body.css('height', calculatedHeight + 'px') + } + let buffered = this.records.length + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (this.searchData.length != 0 && !url) buffered = this.last.searchIds.length + // apply overflow + if (!this.fixedBody) { bodyOverflowY = false } + if (bodyOverflowX || bodyOverflowY) { + columns.find(':scope > table > tbody > tr:nth-child(1) td.w2ui-head-last') + .css('width', w2utils.scrollBarSize() + 'px') + .show() + records.css({ + top: ((this.columnGroups.length > 0 && this.show.columns ? 1 : 0) + w2utils.getSize(columns, 'height')) +'px', + '-webkit-overflow-scrolling': 'touch', + 'overflow-x': (bodyOverflowX ? 'auto' : 'hidden'), + 'overflow-y': (bodyOverflowY ? 'auto' : 'hidden') + }) + } else { + columns.find(':scope > table > tbody > tr:nth-child(1) td.w2ui-head-last').hide() + records.css({ + top: ((this.columnGroups.length > 0 && this.show.columns ? 1 : 0) + w2utils.getSize(columns, 'height')) +'px', + overflow: 'hidden' + }) + if (records.length > 0) { this.last.scrollTop = 0; this.last.scrollLeft = 0 } // if no scrollbars, always show top + } + if (bodyOverflowX) { + frecords.css('margin-bottom', w2utils.scrollBarSize() + 'px') + scroll1.show() + } else { + frecords.css('margin-bottom', 0) + scroll1.hide() + } + frecords.css({ overflow: 'hidden', top: records.css('top') }) + if (this.show.emptyRecords && !bodyOverflowY) { + let max = Math.floor((records[0]?.clientHeight ?? 0) / this.recordHeight) - 1 + let leftover = 0 + if (records[0]) leftover = records[0].scrollHeight - max * this.recordHeight + if (leftover >= this.recordHeight) { + leftover -= this.recordHeight + max++ + } + if (this.fixedBody) { + for (let di = buffered; di < max; di++) { + addEmptyRow(di, this.recordHeight, this) + } + addEmptyRow(max, leftover, this) + } + } + function addEmptyRow(row, height, grid) { + let html1 = '' + let html2 = '' + let htmlp = '' + html1 += '' + html2 += '' + if (grid.show.lineNumbers) html1 += '' + if (grid.show.selectColumn) html1 += '' + if (grid.show.expandColumn) html1 += '' + html2 += '' + if (grid.reorderRows) html2 += '' + for (let j = 0; j < grid.columns.length; j++) { + let col = grid.columns[j] + if ((col.hidden || j < grid.last.colStart || j > grid.last.colEnd) && !col.frozen) continue + htmlp = '' + if (col.frozen) html1 += htmlp; else html2 += htmlp + } + html1 += ' ' + html2 += ' ' + query(grid.box).find('#grid_'+ grid.name +'_frecords > table').append(html1) + query(grid.box).find('#grid_'+ grid.name +'_records > table').append(html2) + } + let width_box, percent + if (body.length > 0) { + let width_max = parseInt(body[0].clientWidth) + - (bodyOverflowY ? w2utils.scrollBarSize() : 0) + - (this.show.lineNumbers ? lineNumberWidth : 0) + - (this.reorderRows ? 26 : 0) + - (this.show.selectColumn ? 26 : 0) + - (this.show.expandColumn ? 26 : 0) + - 1 // left is 1px due to border width + width_box = width_max + percent = 0 + // gridMinWidth processing + let restart = false + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + if (col.gridMinWidth > 0) { + if (col.gridMinWidth > width_box && col.hidden !== true) { + col.hidden = true + restart = true + } + if (col.gridMinWidth < width_box && col.hidden === true) { + col.hidden = false + restart = true + } + } + } + if (restart === true) { + this.refresh() + return + } + // assign PX column s + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + if (col.hidden) continue + if (String(col.size).substr(String(col.size).length-2).toLowerCase() == 'px') { + width_max -= parseFloat(col.size) + this.columns[i].sizeCalculated = col.size + this.columns[i].sizeType = 'px' + } else { + percent += parseFloat(col.size) + this.columns[i].sizeType = '%' + delete col.sizeCorrected + } + } + // if sum != 100% -- reassign proportionally + if (percent != 100 && percent > 0) { + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + if (col.hidden) continue + if (col.sizeType == '%') { + col.sizeCorrected = Math.round(parseFloat(col.size) * 100 * 100 / percent) / 100 + '%' + } + } + } + // calculate % columns + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + if (col.hidden) continue + if (col.sizeType == '%') { + if (this.columns[i].sizeCorrected != null) { + // make it 1px smaller, so margin of error can be calculated correctly + this.columns[i].sizeCalculated = Math.floor(width_max * parseFloat(col.sizeCorrected) / 100) - 1 + 'px' + } else { + // make it 1px smaller, so margin of error can be calculated correctly + this.columns[i].sizeCalculated = Math.floor(width_max * parseFloat(col.size) / 100) - 1 + 'px' + } + } + } + } + // fix margin of error that is due percentage calculations + let width_cols = 0 + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + if (col.hidden) continue + if (col.min == null) col.min = 20 + if (parseInt(col.sizeCalculated) < parseInt(col.min)) col.sizeCalculated = col.min + 'px' + if (parseInt(col.sizeCalculated) > parseInt(col.max)) col.sizeCalculated = col.max + 'px' + width_cols += parseInt(col.sizeCalculated) + } + let width_diff = parseInt(width_box) - parseInt(width_cols) + if (width_diff > 0 && percent > 0) { + let i = 0 + while (true) { + let col = this.columns[i] + if (col == null) { i = 0; continue } + if (col.hidden || col.sizeType == 'px') { i++; continue } + col.sizeCalculated = (parseInt(col.sizeCalculated) + 1) + 'px' + width_diff-- + if (width_diff === 0) break + i++ + } + } else if (width_diff > 0) { + columns.find(':scope > table > tbody > tr:nth-child(1) td.w2ui-head-last') + .css('width', w2utils.scrollBarSize() + 'px') + .show() + } + // find width of frozen columns + let fwidth = 1 + if (this.show.lineNumbers) fwidth += lineNumberWidth + if (this.show.selectColumn) fwidth += 26 + // if (this.reorderRows) fwidth += 26; + if (this.show.expandColumn) fwidth += 26 + for (let i = 0; i < this.columns.length; i++) { + if (this.columns[i].hidden) continue + if (this.columns[i].frozen) fwidth += parseInt(this.columns[i].sizeCalculated) + } + fcolumns.css('width', fwidth + 'px') + frecords.css('width', fwidth + 'px') + fsummary.css('width', fwidth + 'px') + scroll1.css('width', fwidth + 'px') + columns.css('left', fwidth + 'px') + records.css('left', fwidth + 'px') + summary.css('left', fwidth + 'px') + // resize columns + columns.find(':scope > table > tbody > tr:nth-child(1) td') + .add(fcolumns.find(':scope > table > tbody > tr:nth-child(1) td')) + .each(el => { + // line numbers + if (query(el).hasClass('w2ui-col-number')) { + query(el).css('width', lineNumberWidth + 'px') + } + // records + let ind = query(el).attr('col') + if (ind != null) { + if (ind == 'start') { + let width = 0 + for (let i = 0; i < obj.last.colStart; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue + width += parseInt(obj.columns[i].sizeCalculated) + } + query(el).css('width', width + 'px') + } + if (obj.columns[ind]) query(el).css('width', obj.columns[ind].sizeCalculated) // already has px + } + // last column + if (query(el).hasClass('w2ui-head-last')) { + if (obj.last.colEnd + 1 < obj.columns.length) { + let width = 0 + for (let i = obj.last.colEnd + 1; i < obj.columns.length; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue + width += parseInt(obj.columns[i].sizeCalculated) + } + query(el).css('width', width + 'px') + } else { + query(el).css('width', w2utils.scrollBarSize() + (width_diff > 0 && percent === 0 ? width_diff : 0) + 'px') + } + } + }) + // if there are column groups - hide first row (needed for sizing) + if (columns.find(':scope > table > tbody > tr').length == 3) { + columns.find(':scope > table > tbody > tr:nth-child(1) td') + .add(fcolumns.find(':scope > table > tbody > tr:nth-child(1) td')) + .html('').css({ + 'height' : '0', + 'border' : '0', + 'padding': '0', + 'margin' : '0' + }) + } + // resize records + records.find(':scope > table > tbody > tr:nth-child(1) td') + .add(frecords.find(':scope > table > tbody > tr:nth-child(1) td')) + .each(el => { + // line numbers + if (query(el).hasClass('w2ui-col-number')) { + query(el).css('width', lineNumberWidth + 'px') + } + // records + let ind = query(el).attr('col') + if (ind != null) { + if (ind == 'start') { + let width = 0 + for (let i = 0; i < obj.last.colStart; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue + width += parseInt(obj.columns[i].sizeCalculated) + } + query(el).css('width', width + 'px') + } + if (obj.columns[ind]) query(el).css('width', obj.columns[ind].sizeCalculated) + } + // last column + if (query(el).hasClass('w2ui-grid-data-last') && query(el).parents('.w2ui-grid-frecords').length === 0) { // not in frecords + if (obj.last.colEnd + 1 < obj.columns.length) { + let width = 0 + for (let i = obj.last.colEnd + 1; i < obj.columns.length; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue + width += parseInt(obj.columns[i].sizeCalculated) + } + query(el).css('width', width + 'px') + } else { + query(el).css('width', (width_diff > 0 && percent === 0 ? width_diff : 0) + 'px') + } + } + }) + // resize summary + summary.find(':scope > table > tbody > tr:nth-child(1) td') + .add(fsummary.find(':scope > table > tbody > tr:nth-child(1) td')) + .each(el => { + // line numbers + if (query(el).hasClass('w2ui-col-number')) { + query(el).css('width', lineNumberWidth + 'px') + } + // records + let ind = query(el).attr('col') + if (ind != null) { + if (ind == 'start') { + let width = 0 + for (let i = 0; i < obj.last.colStart; i++) { + if (!obj.columns[i] || obj.columns[i].frozen || obj.columns[i].hidden) continue + width += parseInt(obj.columns[i].sizeCalculated) + } + query(el).css('width', width + 'px') + } + if (obj.columns[ind]) query(el).css('width', obj.columns[ind].sizeCalculated) + } + // last column + if (query(el).hasClass('w2ui-grid-data-last') && query(el).parents('.w2ui-grid-frecords').length === 0) { // not in frecords + query(el).css('width', w2utils.scrollBarSize() + (width_diff > 0 && percent === 0 ? width_diff : 0) + 'px') + } + }) + this.initResize() + this.refreshRanges() + // apply last scroll if any + if ((this.last.scrollTop || this.last.scrollLeft) && records.length > 0) { + columns.prop('scrollLeft', this.last.scrollLeft) + records.prop('scrollTop', this.last.scrollTop) + records.prop('scrollLeft', this.last.scrollLeft) + } + // Improved performance when scrolling through tables + columns.css('will-change', 'scroll-position') + } + getSearchesHTML() { + let html = ` +
    + ${w2utils.lang('Advanced Search')} + + + +
    + + ` + for (let i = 0; i < this.searches.length; i++) { + let s = this.searches[i] + s.type = String(s.type).toLowerCase() + if (s.hidden) continue + if (s.attr == null) s.attr = '' + if (s.text == null) s.text = '' + if (s.style == null) s.style = '' + if (s.type == null) s.type = 'text' + if (s.label == null && s.caption != null) { + console.log('NOTICE: grid search.caption property is deprecated, please use search.label. Search ->', s) + s.label = s.caption + } + let operator =`` + html += ` + + + ' + + '' + } + html += ` + + +
    ${(w2utils.lang(s.label) || '')}${operator}` + let tmpStyle + switch (s.type) { + case 'text': + case 'alphanumeric': + case 'hex': + case 'color': + case 'list': + case 'combo': + case 'enum': + tmpStyle = 'width: 250px;' + if (['hex', 'color'].indexOf(s.type) != -1) tmpStyle = 'width: 90px;' + html += `` + break + case 'int': + case 'float': + case 'money': + case 'currency': + case 'percent': + case 'date': + case 'time': + case 'datetime': + tmpStyle = 'width: 90px;' + if (s.type == 'datetime') tmpStyle = 'width: 140px;' + html += ` + ` + break + case 'select': + html += `` + break + } + html += s.text + + '
    + + + + +
    ` + return html + } + getOperators(type, opers) { + let operators = this.operators[this.operatorsMap[type]] || [] + if (opers != null && Array.isArray(opers)) { + operators = opers + } + let html = '' + operators.forEach(oper => { + let displayText = oper + let operValue = oper + if (Array.isArray(oper)) { + displayText = oper[1] + operValue = oper[0] + } else if (w2utils.isPlainObject(oper)) { + displayText = oper.text + operValue = oper.oper + } + if (displayText == null) displayText = oper + html += `\n` + }) + return html + } + initOperator(ind) { + let options + let search = this.searches[ind] + let sdata = this.getSearchData(search.field) + let overlay = query(`#w2overlay-${this.name}-search-overlay`) + let $rng = overlay.find(`#grid_${this.name}_range_${ind}`) + let $fld1 = overlay.find(`#grid_${this.name}_field_${ind}`) + let $fld2 = overlay.find(`#grid_${this.name}_field2_${ind}`) + let $oper = overlay.find(`#grid_${this.name}_operator_${ind}`) + let oper = $oper.val() + $fld1.show() + $rng.hide() + // init based on operator value + switch (oper) { + case 'between': + $rng.show() + break + case 'null': + case 'not null': + $fld1.hide() + $fld1.val(oper) // need to insert something for search to activate + $fld1.trigger('change') + break + } + // init based on search type + switch (search.type) { + case 'text': + case 'alphanumeric': + let fld = $fld1[0]._w2field + if (fld) { fld.reset() } + break + case 'int': + case 'float': + case 'hex': + case 'color': + case 'money': + case 'currency': + case 'percent': + case 'date': + case 'time': + case 'datetime': + if (!$fld1[0]._w2field) { + // init fields + new w2field(search.type, { el: $fld1[0], ...search.options }) + new w2field(search.type, { el: $fld2[0], ...search.options }) + setTimeout(() => { // convert to date if it is number + $fld1.trigger('keydown') + $fld2.trigger('keydown') + }, 1) + } + break + case 'list': + case 'combo': + case 'enum': + options = search.options + if (search.type == 'list') options.selected = {} + if (search.type == 'enum') options.selected = [] + if (sdata) options.selected = sdata.value + if (!$fld1[0]._w2field) { + let fld = new w2field(search.type, { el: $fld1[0], ...options }) + if (sdata && sdata.text != null) { + fld.set({ id: sdata.value, text: sdata.text }) + } + } + break + case 'select': + // build options + options = '' + for (let i = 0; i < search.options.items.length; i++) { + let si = search.options.items[i] + if (w2utils.isPlainObject(search.options.items[i])) { + let val = si.id + let txt = si.text + if (val == null && si.value != null) val = si.value + if (txt == null && si.text != null) txt = si.text + if (val == null) val = '' + options += '' + } else { + options += '' + } + } + $fld1.html(options) + break + } + } + initSearches() { + let overlay = query(`#w2overlay-${this.name}-search-overlay`) + // init searches + for (let ind = 0; ind < this.searches.length; ind++) { + let search = this.searches[ind] + let sdata = this.getSearchData(search.field) + search.type = String(search.type).toLowerCase() + if (typeof search.options != 'object') search.options = {} + // operators + let operator = search.operator + let operators = [...this.operators[this.operatorsMap[search.type]]] || [] // need a copy + if (search.operators) operators = search.operators + // normalize + if (w2utils.isPlainObject(operator)) operator = operator.oper + operators.forEach((oper, ind) => { + if (w2utils.isPlainObject(oper)) operators[ind] = oper.oper + }) + if (sdata && sdata.operator) { + operator = sdata.operator + } + // default operator + let def = this.defaultOperator[this.operatorsMap[search.type]] + if (operators.indexOf(operator) == -1) { + operator = def + } + overlay.find(`#grid_${this.name}_operator_${ind}`).val(operator) + this.initOperator(ind) + // populate field value + let $fld1 = overlay.find(`#grid_${this.name}_field_${ind}`) + let $fld2 = overlay.find(`#grid_${this.name}_field2_${ind}`) + if (sdata != null) { + if (!Array.isArray(sdata.value)) { + if (sdata.value != null) $fld1.val(sdata.value).trigger('change') + } else { + if (['in', 'not in'].includes(sdata.operator)) { + $fld1[0]._w2field.set(sdata.value) + } else { + $fld1.val(sdata.value[0]).trigger('change') + $fld2.val(sdata.value[1]).trigger('change') + } + } + } + } + // add on change event + overlay.find('.w2ui-grid-search-advanced *[rel=search]') + .on('keypress', evnt => { + if (evnt.keyCode == 13) { + this.search() + w2tooltip.hide(this.name + '-search-overlay') + } + }) + } + getColumnsHTML() { + let self = this + let html1 = '' + let html2 = '' + if (this.show.columnHeaders) { + if (this.columnGroups.length > 0) { + let tmp1 = getColumns(true) + let tmp2 = getGroups() + let tmp3 = getColumns(false) + html1 = tmp1[0] + tmp2[0] + tmp3[0] + html2 = tmp1[1] + tmp2[1] + tmp3[1] + } else { + let tmp = getColumns(true) + html1 = tmp[0] + html2 = tmp[1] + } + } + return [html1, html2] + function getGroups() { + let html1 = '' + let html2 = '' + let tmpf = '' + // add empty group at the end + let tmp = self.columnGroups.length - 1 + if (self.columnGroups[tmp].text == null && self.columnGroups[tmp].caption != null) { + console.log('NOTICE: grid columnGroup.caption property is deprecated, please use columnGroup.text. Group -> ', self.columnGroups[tmp]) + self.columnGroups[tmp].text = self.columnGroups[tmp].caption + } + if (self.columnGroups[self.columnGroups.length-1].text != '') self.columnGroups.push({ text: '' }) + if (self.show.lineNumbers) { + html1 += '' + + '
     
    ' + + '' + } + if (self.show.selectColumn) { + html1 += '' + + '
     
    ' + + '' + } + if (self.show.expandColumn) { + html1 += '' + + '
     
    ' + + '' + } + let ii = 0 + html2 += `` + if (self.reorderRows) { + html2 += '' + + '
     
    ' + + '' + } + for (let i = 0; i < self.columnGroups.length; i++) { + let colg = self.columnGroups[i] + let col = self.columns[ii] || {} + if (colg.colspan != null) colg.span = colg.colspan + if (colg.span == null || colg.span != parseInt(colg.span)) colg.span = 1 + if (col.text == null && col.caption != null) { + console.log('NOTICE: grid column.caption property is deprecated, please use column.text. Column ->', col) + col.text = col.caption + } + let colspan = 0 + for (let jj = ii; jj < ii + colg.span; jj++) { + if (self.columns[jj] && !self.columns[jj].hidden) { + colspan++ + } + } + if (i == self.columnGroups.length-1) { + colspan = 100 // last column + } + if (colspan <= 0) { + // do nothing here, all columns in the group are hidden. + } else if (colg.main === true) { + let sortStyle = '' + for (let si = 0; si < self.sortData.length; si++) { + if (self.sortData[si].field == col.field) { + if ((self.sortData[si].direction || '').toLowerCase() === 'asc') sortStyle = 'w2ui-sort-up' + if ((self.sortData[si].direction || '').toLowerCase() === 'desc') sortStyle = 'w2ui-sort-down' + } + } + let resizer = '' + if (col.resizable !== false) { + resizer = `
    ` + } + let text = w2utils.lang(typeof col.text == 'function' ? col.text(col) : col.text) + tmpf = ``+ resizer + + `
    ` + + `
    ` + (!text ? ' ' : text) + + '
    '+ + '' + if (col && col.frozen) html1 += tmpf; else html2 += tmpf + } else { + let gText = w2utils.lang(typeof colg.text == 'function' ? colg.text(colg) : colg.text) + tmpf = `` + + `
    ${!gText ? ' ' : gText}
    ` + + '' + if (col && col.frozen) html1 += tmpf; else html2 += tmpf + } + ii += colg.span + } + html1 += '' // need empty column for border-right + html2 += `` + return [html1, html2] + } + function getColumns(main) { + let html1 = '' + let html2 = '' + if (self.show.lineNumbers) { + html1 += '' + + '
    #
    ' + + '' + } + if (self.show.selectColumn) { + html1 += '' + + '
    ' + + ` ' + + '
    ' + + '' + } + if (self.show.expandColumn) { + html1 += '' + + '
     
    ' + + '' + } + let ii = 0 + let id = 0 + let colg + html2 += `` + if (self.reorderRows) { + html2 += ''+ + '
     
    '+ + '' + } + for (let i = 0; i < self.columns.length; i++) { + let col = self.columns[i] + if (col.text == null && col.caption != null) { + console.log('NOTICE: grid column.caption property is deprecated, please use column.text. Column -> ', col) + col.text = col.caption + } + if (col.size == null) col.size = '100%' + if (i == id) { // always true on first iteration + colg = self.columnGroups[ii++] || {} + id = id + colg.span + } + if ((i < self.last.colStart || i > self.last.colEnd) && !col.frozen) + continue + if (col.hidden) + continue + if (colg.main !== true || main) { // grouping of columns + let colCellHTML = self.getColumnCellHTML(i) + if (col && col.frozen) html1 += colCellHTML; else html2 += colCellHTML + } + } + html1 += '
     
    ' + html2 += '
     
    ' + html1 += '' + html2 += '' + return [html1, html2] + } + } + getColumnCellHTML(i) { + let col = this.columns[i] + if (col == null) return '' + // reorder style + let reorderCols = (this.reorderColumns && (!this.columnGroups || !this.columnGroups.length)) ? ' w2ui-col-reorderable ' : '' + // sort style + let sortStyle = '' + for (let si = 0; si < this.sortData.length; si++) { + if (this.sortData[si].field == col.field) { + if ((this.sortData[si].direction || '').toLowerCase() === 'asc') sortStyle = 'w2ui-sort-up' + if ((this.sortData[si].direction || '').toLowerCase() === 'desc') sortStyle = 'w2ui-sort-down' + } + } + // col selected + let tmp = this.last.selection.columns + let selected = false + for (let t in tmp) { + for (let si = 0; si < tmp[t].length; si++) { + if (tmp[t][si] == i) selected = true + } + } + let text = w2utils.lang(typeof col.text == 'function' ? col.text(col) : col.text) + let html = '' + + (col.resizable !== false ? '
    ' : '') + + '
    '+ + '
    '+ + (!text ? ' ' : text) + + '
    '+ + '' + return html + } + columnTooltipShow(ind, event) { + let $el = query(this.box).find('#grid_'+ this.name + '_column_'+ ind) + let item = this.columns[ind] + let pos = this.columnTooltip + w2tooltip.show({ + name: this.name + '-column-tooltip', + anchor: $el.get(0), + html: item.tooltip, + position: pos, + }) + } + columnTooltipHide(ind, event) { + w2tooltip.hide(this.name + '-column-tooltip') + } + getRecordsHTML() { + let buffered = this.records.length + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (this.searchData.length != 0 && !url) buffered = this.last.searchIds.length + // larger number works better with chrome, smaller with FF. + if (buffered > this.vs_start) this.last.show_extra = this.vs_extra; else this.last.show_extra = this.vs_start + let records = query(this.box).find(`#grid_${this.name}_records`) + let limit = Math.floor((records.get(0)?.clientHeight || 0) / this.recordHeight) + this.last.show_extra + 1 + if (!this.fixedBody || limit > buffered) limit = buffered + // always need first record for resizing purposes + let rec_html = this.getRecordHTML(-1, 0) + let html1 = '' + rec_html[0] + let html2 = '
    ' + rec_html[1] + // first empty row with height + html1 += ''+ + ' '+ + '' + html2 += ''+ + ' '+ + '' + for (let i = 0; i < limit; i++) { + rec_html = this.getRecordHTML(i, i+1) + html1 += rec_html[0] + html2 += rec_html[1] + } + let h2 = (buffered - limit) * this.recordHeight + html1 += '' + + ' '+ + ''+ + ''+ + ' '+ + ''+ + '
    ' + html2 += '' + + ' '+ + ''+ + ''+ + ' '+ + ''+ + '' + this.last.range_start = 0 + this.last.range_end = limit + return [html1, html2] + } + getSummaryHTML() { + if (this.summary.length === 0) return + let rec_html = this.getRecordHTML(-1, 0) // need this in summary too for colspan to work properly + let html1 = '' + rec_html[0] + let html2 = '
    ' + rec_html[1] + for (let i = 0; i < this.summary.length; i++) { + rec_html = this.getRecordHTML(i, i+1, true) + html1 += rec_html[0] + html2 += rec_html[1] + } + html1 += '
    ' + html2 += '' + return [html1, html2] + } + scroll(event) { + let obj = this + let url = (typeof this.url != 'object' ? this.url : this.url.get) + let records = query(this.box).find(`#grid_${this.name}_records`) + let frecords = query(this.box).find(`#grid_${this.name}_frecords`) + // sync scroll positions + if (event) { + let sTop = event.target.scrollTop + let sLeft = event.target.scrollLeft + this.last.scrollTop = sTop + this.last.scrollLeft = sLeft + let cols = query(this.box).find(`#grid_${this.name}_columns`)[0] + let summary = query(this.box).find(`#grid_${this.name}_summary`)[0] + if (cols) cols.scrollLeft = sLeft + if (summary) summary.scrollLeft = sLeft + if (frecords[0]) frecords[0].scrollTop = sTop + } + // hide bubble + if (this.last.bubbleEl) { + w2tooltip.hide(this.name + '-bubble') + this.last.bubbleEl = null + } + // column virtual scroll + let colStart = null + let colEnd = null + if (this.disableCVS || this.columnGroups.length > 0) { + // disable virtual scroll + colStart = 0 + colEnd = this.columns.length - 1 + } else { + let sWidth = records.prop('clientWidth') + let cLeft = 0 + for (let i = 0; i < this.columns.length; i++) { + if (this.columns[i].frozen || this.columns[i].hidden) continue + let cSize = parseInt(this.columns[i].sizeCalculated ? this.columns[i].sizeCalculated : this.columns[i].size) + if (cLeft + cSize + 30 > this.last.scrollLeft && colStart == null) colStart = i + if (cLeft + cSize - 30 > this.last.scrollLeft + sWidth && colEnd == null) colEnd = i + cLeft += cSize + } + if (colEnd == null) colEnd = this.columns.length - 1 + } + if (colStart != null) { + if (colStart < 0) colStart = 0 + if (colEnd < 0) colEnd = 0 + if (colStart == colEnd) { + if (colStart > 0) colStart--; else colEnd++ // show at least one column + } + // --------- + if (colStart != this.last.colStart || colEnd != this.last.colEnd) { + let $box = query(this.box) + let deltaStart = Math.abs(colStart - this.last.colStart) + let deltaEnd = Math.abs(colEnd - this.last.colEnd) + // add/remove columns for small jumps + if (deltaStart < 5 && deltaEnd < 5) { + let $cfirst = $box.find(`.w2ui-grid-columns #grid_${this.name}_column_start`) + let $clast = $box.find('.w2ui-grid-columns .w2ui-head-last') + let $rfirst = $box.find(`#grid_${this.name}_records .w2ui-grid-data-spacer`) + let $rlast = $box.find(`#grid_${this.name}_records .w2ui-grid-data-last`) + let $sfirst = $box.find(`#grid_${this.name}_summary .w2ui-grid-data-spacer`) + let $slast = $box.find(`#grid_${this.name}_summary .w2ui-grid-data-last`) + // remove on left + if (colStart > this.last.colStart) { + for (let i = this.last.colStart; i < colStart; i++) { + $box.find('#grid_'+ this.name +'_columns #grid_'+ this.name +'_column_'+ i).remove() // column + $box.find('#grid_'+ this.name +'_records td[col="'+ i +'"]').remove() // record + $box.find('#grid_'+ this.name +'_summary td[col="'+ i +'"]').remove() // summary + } + } + // remove on right + if (colEnd < this.last.colEnd) { + for (let i = this.last.colEnd; i > colEnd; i--) { + $box.find('#grid_'+ this.name +'_columns #grid_'+ this.name +'_column_'+ i).remove() // column + $box.find('#grid_'+ this.name +'_records td[col="'+ i +'"]').remove() // record + $box.find('#grid_'+ this.name +'_summary td[col="'+ i +'"]').remove() // summary + } + } + // add on left + if (colStart < this.last.colStart) { + for (let i = this.last.colStart - 1; i >= colStart; i--) { + if (this.columns[i] && (this.columns[i].frozen || this.columns[i].hidden)) continue + $cfirst.after(this.getColumnCellHTML(i)) // column + // record + $rfirst.each(el => { + let index = query(el).parent().attr('index') + let td = '' // width column + if (index != null) td = this.getCellHTML(parseInt(index), i, false) + query(el).after(td) + }) + // summary + $sfirst.each(el => { + let index = query(el).parent().attr('index') + let td = '' // width column + if (index != null) td = this.getCellHTML(parseInt(index), i, true) + query(el).after(td) + }) + } + } + // add on right + if (colEnd > this.last.colEnd) { + for (let i = this.last.colEnd + 1; i <= colEnd; i++) { + if (this.columns[i] && (this.columns[i].frozen || this.columns[i].hidden)) continue + $clast.before(this.getColumnCellHTML(i)) // column + // record + $rlast.each(el => { + let index = query(el).parent().attr('index') + let td = '' // width column + if (index != null) td = this.getCellHTML(parseInt(index), i, false) + query(el).before(td) + }) + // summary + $slast.each(el => { + let index = query(el).parent().attr('index') || -1 + let td = this.getCellHTML(parseInt(index), i, true) + query(el).before(td) + }) + } + } + this.last.colStart = colStart + this.last.colEnd = colEnd + this.resizeRecords() + } else { + this.last.colStart = colStart + this.last.colEnd = colEnd + // dot not just call this.refresh(); + let colHTML = this.getColumnsHTML() + let recHTML = this.getRecordsHTML() + let sumHTML = this.getSummaryHTML() + let $columns = $box.find(`#grid_${this.name}_columns`) + let $records = $box.find(`#grid_${this.name}_records`) + let $frecords = $box.find(`#grid_${this.name}_frecords`) + let $summary = $box.find(`#grid_${this.name}_summary`) + $columns.find('tbody').html(colHTML[1]) + $frecords.html(recHTML[0]) + $records.prepend(recHTML[1]) + if (sumHTML != null) $summary.html(sumHTML[1]) + // need timeout to clean up (otherwise scroll problem) + setTimeout(() => { + $records.find(':scope > table').filter(':not(table:first-child)').remove() + if ($summary[0]) $summary[0].scrollLeft = this.last.scrollLeft + }, 1) + this.resizeRecords() + } + } + } + // perform virtual scroll + let buffered = this.records.length + if (buffered > this.total && this.total !== -1) buffered = this.total + if (this.searchData.length != 0 && !url) buffered = this.last.searchIds.length + if (buffered === 0 || records.length === 0 || records.prop('clientHeight') === 0) return + if (buffered > this.vs_start) this.last.show_extra = this.vs_extra; else this.last.show_extra = this.vs_start + // update footer + let t1 = Math.round(records.prop('scrollTop') / this.recordHeight + 1) + let t2 = t1 + (Math.round(records.prop('clientHeight') / this.recordHeight) - 1) + if (t1 > buffered) t1 = buffered + if (t2 >= buffered - 1) t2 = buffered + query(this.box).find('#grid_'+ this.name + '_footer .w2ui-footer-right').html( + (this.show.statusRange + ? w2utils.formatNumber(this.offset + t1) + '-' + w2utils.formatNumber(this.offset + t2) + + (this.total != -1 ? ' ' + w2utils.lang('of') + ' ' + w2utils.formatNumber(this.total) : '') + : '') + + (url && this.show.statusBuffered ? ' ('+ w2utils.lang('buffered') + ' '+ w2utils.formatNumber(buffered) + + (this.offset > 0 ? ', skip ' + w2utils.formatNumber(this.offset) : '') + ')' : '') + ) + // only for local data source, else no extra records loaded + if (!url && (!this.fixedBody || (this.total != -1 && this.total <= this.vs_start))) return + // regular processing + let start = Math.floor(records.prop('scrollTop') / this.recordHeight) - this.last.show_extra + let end = start + Math.floor(records.prop('clientHeight') / this.recordHeight) + this.last.show_extra * 2 + 1 + // let div = start - this.last.range_start; + if (start < 1) start = 1 + if (end > this.total && this.total != -1) end = this.total + let tr1 = records.find('#grid_'+ this.name +'_rec_top') + let tr2 = records.find('#grid_'+ this.name +'_rec_bottom') + let tr1f = frecords.find('#grid_'+ this.name +'_frec_top') + let tr2f = frecords.find('#grid_'+ this.name +'_frec_bottom') + // if row is expanded + if (String(tr1.next().prop('id')).indexOf('_expanded_row') != -1) { + tr1.next().remove() + tr1f.next().remove() + } + if (this.total > end && String(tr2.prev().prop('id')).indexOf('_expanded_row') != -1) { + tr2.prev().remove() + tr2f.prev().remove() + } + let first = parseInt(tr1.next().attr('line')) + let last = parseInt(tr2.prev().attr('line')) + let tmp, tmp1, tmp2, rec_start, rec_html + if (first < start || first == 1 || this.last.pull_refresh) { // scroll down + if (end <= last + this.last.show_extra - 2 && end != this.total) return + this.last.pull_refresh = false + // remove from top + while (true) { + tmp1 = frecords.find('#grid_'+ this.name +'_frec_top').next() + tmp2 = records.find('#grid_'+ this.name +'_rec_top').next() + if (tmp2.attr('line') == 'bottom') break + if (parseInt(tmp2.attr('line')) < start) { + tmp1.remove() + tmp2.remove() + } else { + break + } + } + // add at bottom + tmp = records.find('#grid_'+ this.name +'_rec_bottom').prev() + rec_start = tmp.attr('line') + if (rec_start == 'top') rec_start = start + for (let i = parseInt(rec_start) + 1; i <= end; i++) { + if (!this.records[i-1]) continue + tmp2 = this.records[i-1].w2ui + if (tmp2 && !Array.isArray(tmp2.children)) { + tmp2.expanded = false + } + rec_html = this.getRecordHTML(i-1, i) + tr2.before(rec_html[1]) + tr2f.before(rec_html[0]) + } + markSearch() + setTimeout(() => { this.refreshRanges() }, 0) + } else { // scroll up + if (start >= first - this.last.show_extra + 2 && start > 1) return + // remove from bottom + while (true) { + tmp1 = frecords.find('#grid_'+ this.name +'_frec_bottom').prev() + tmp2 = records.find('#grid_'+ this.name +'_rec_bottom').prev() + if (tmp2.attr('line') == 'top') break + if (parseInt(tmp2.attr('line')) > end) { + tmp1.remove() + tmp2.remove() + } else { + break + } + } + // add at top + tmp = records.find('#grid_'+ this.name +'_rec_top').next() + rec_start = tmp.attr('line') + if (rec_start == 'bottom') rec_start = end + for (let i = parseInt(rec_start) - 1; i >= start; i--) { + if (!this.records[i-1]) continue + tmp2 = this.records[i-1].w2ui + if (tmp2 && !Array.isArray(tmp2.children)) { + tmp2.expanded = false + } + rec_html = this.getRecordHTML(i-1, i) + tr1.after(rec_html[1]) + tr1f.after(rec_html[0]) + } + markSearch() + setTimeout(() => { this.refreshRanges() }, 0) + } + // first/last row size + let h1 = (start - 1) * this.recordHeight + let h2 = (buffered - end) * this.recordHeight + if (h2 < 0) h2 = 0 + tr1.css('height', h1 + 'px') + tr1f.css('height', h1 + 'px') + tr2.css('height', h2 + 'px') + tr2f.css('height', h2 + 'px') + this.last.range_start = start + this.last.range_end = end + // load more if needed + let s = Math.floor(records.prop('scrollTop') / this.recordHeight) + let e = s + Math.floor(records.prop('clientHeight') / this.recordHeight) + if (e + 10 > buffered && this.last.pull_more !== true && (buffered < this.total - this.offset || (this.total == -1 && this.last.fetch.hasMore))) { + if (this.autoLoad === true) { + this.last.pull_more = true + this.last.fetch.offset += this.limit + this.request('load') + } + // scroll function + let more = query(this.box).find('#grid_'+ this.name +'_rec_more, #grid_'+ this.name +'_frec_more') + more.show() + .eq(1) // only main table + .off('.load-more') + .on('click.load-more', function() { + // show spinner + query(this).find('td').html('
    ') + // load more + obj.last.pull_more = true + obj.last.fetch.offset += obj.limit + obj.request('load') + }) + .find('td') + .html(obj.autoLoad + ? '
    ' + : '
    '+ w2utils.lang('Load ${count} more...', { count: obj.limit }) + '
    ' + ) + } + function markSearch() { + // mark search + if (!obj.markSearch) return + clearTimeout(obj.last.marker_timer) + obj.last.marker_timer = setTimeout(() => { + // mark all search strings + let search = [] + for (let s = 0; s < obj.searchData.length; s++) { + let sdata = obj.searchData[s] + let fld = obj.getSearch(sdata.field) + if (!fld || fld.hidden) continue + let ind = obj.getColumn(sdata.field, true) + search.push({ field: sdata.field, search: sdata.value, col: ind }) + } + if (search.length > 0) { + search.forEach((item) => { + let el = query(obj.box).find('td[col="'+ item.col +'"]:not(.w2ui-head)') + w2utils.marker(el, item.search) + }) + } + }, 50) + } + } + getRecordHTML(ind, lineNum, summary) { + let tmph = '' + let rec_html1 = '' + let rec_html2 = '' + let sel = this.last.selection + let record + // first record needs for resize purposes + if (ind == -1) { + rec_html1 += '' + rec_html2 += '' + if (this.show.lineNumbers) rec_html1 += '' + if (this.show.selectColumn) rec_html1 += '' + if (this.show.expandColumn) rec_html1 += '' + rec_html2 += '' + if (this.reorderRows) rec_html2 += '' + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + tmph = '' + if (col.frozen && !col.hidden) { + rec_html1 += tmph + } else { + if (col.hidden || i < this.last.colStart || i > this.last.colEnd) continue + rec_html2 += tmph + } + } + rec_html1 += '' + rec_html2 += '' + rec_html1 += '' + rec_html2 += '' + return [rec_html1, rec_html2] + } + // regular record + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (summary !== true) { + if (this.searchData.length > 0 && !url) { + if (ind >= this.last.searchIds.length) return '' + ind = this.last.searchIds[ind] + record = this.records[ind] + } else { + if (ind >= this.records.length) return '' + record = this.records[ind] + } + } else { + if (ind >= this.summary.length) return '' + record = this.summary[ind] + } + if (!record) return '' + if (record.recid == null && this.recid != null) { + let rid = this.parseField(record, this.recid) + if (rid != null) record.recid = rid + } + let isRowSelected = false + if (sel.indexes.indexOf(ind) != -1) isRowSelected = true + let rec_style = (record.w2ui ? record.w2ui.style : '') + if (rec_style == null || typeof rec_style != 'string') rec_style = '' + let rec_class = (record.w2ui ? record.w2ui.class : '') + if (rec_class == null || typeof rec_class != 'string') rec_class = '' + // render TR + rec_html1 += '' + rec_html2 += '' + if (this.show.lineNumbers) { + rec_html1 += ''+ + (summary !== true ? this.getLineHTML(lineNum, record) : '') + + '' + } + if (this.show.selectColumn) { + rec_html1 += + ''+ + (summary !== true && !(record.w2ui && record.w2ui.hideCheckBox === true) ? + '
    '+ + ' '+ + '
    ' + : + '' ) + + '' + } + if (this.show.expandColumn) { + let tmp_img = '' + if (record.w2ui?.expanded === true) tmp_img = '-'; else tmp_img = '+' + if ((record.w2ui?.expanded == 'none' || !Array.isArray(record.w2ui.children) || !record.w2ui.children.length)) tmp_img = '+' + if (record.w2ui?.expanded == 'spinner') tmp_img = '
    ' + rec_html1 += + ''+ + (summary !== true ? `
    ${tmp_img}
    ` : '' ) + + '' + } + // insert empty first column + rec_html2 += '' + if (this.reorderRows) { + rec_html2 += + ''+ + (summary !== true ? '
     
    ' : '' ) + + '' + } + let col_ind = 0 + let col_skip = 0 + while (true) { + let col_span = 1 + let col = this.columns[col_ind] + if (col == null) break + if (col.hidden) { + col_ind++ + if (col_skip > 0) col_skip-- + continue + } + if (col_skip > 0) { + col_ind++ + if (this.columns[col_ind] == null) break + record.w2ui.colspan[this.columns[col_ind-1].field] = 0 // need it for other methods + col_skip-- + continue + } else if (record.w2ui) { + let tmp1 = record.w2ui.colspan + let tmp2 = this.columns[col_ind].field + if (tmp1 && tmp1[tmp2] === 0) { + delete tmp1[tmp2] // if no longer colspan then remove 0 + } + } + // column virtual scroll + if ((col_ind < this.last.colStart || col_ind > this.last.colEnd) && !col.frozen) { + col_ind++ + continue + } + if (record.w2ui) { + if (typeof record.w2ui.colspan == 'object') { + let span = parseInt(record.w2ui.colspan[col.field]) || null + if (span > 1) { + // if there are hidden columns, then no colspan on them + let hcnt = 0 + for (let i = col_ind; i < col_ind + span; i++) { + if (i >= this.columns.length) break + if (this.columns[i].hidden) hcnt++ + } + col_span = span - hcnt + col_skip = span - 1 + } + } + } + let rec_cell = this.getCellHTML(ind, col_ind, summary, col_span) + if (col.frozen) rec_html1 += rec_cell; else rec_html2 += rec_cell + col_ind++ + } + rec_html1 += '' + rec_html2 += '' + rec_html1 += '' + rec_html2 += '' + return [rec_html1, rec_html2] + } + getLineHTML(lineNum) { + return '
    ' + lineNum + '
    ' + } + getCellHTML(ind, col_ind, summary, col_span) { + let obj = this + let col = this.columns[col_ind] + if (col == null) return '' + let record = (summary !== true ? this.records[ind] : this.summary[ind]) + // value, attr, style, className, divAttr + let { value, style, className, attr, divAttr } = this.getCellValue(ind, col_ind, summary, true) + let edit = (ind !== -1 ? this.getCellEditable(ind, col_ind) : '') + //let divStyle = 'max-height: '+ parseInt(this.recordHeight) +'px;' + (col.clipboardCopy ? 'margin-right: 20px' : '') + let divStyle = '' //no need to constraint height + let isChanged = !summary && record?.w2ui?.changes && record.w2ui.changes[col.field] != null + let sel = this.last.selection + let isRowSelected = false + let infoBubble = '' + if (sel.indexes.indexOf(ind) != -1) isRowSelected = true + if (col_span == null) { + if (record?.w2ui?.colspan && record.w2ui.colspan[col.field]) { + col_span = record.w2ui.colspan[col.field] + } else { + col_span = 1 + } + } + // expand icon + if (col_ind === 0 && Array.isArray(record?.w2ui?.children)) { + let level = 0 + let subrec = this.get(record.w2ui.parent_recid, true) + while (true) { + if (subrec != null) { + level++ + let tmp = this.records[subrec].w2ui + if (tmp != null && tmp.parent_recid != null) { + subrec = this.get(tmp.parent_recid, true) + } else { + break + } + } else { + break + } + } + if (record.w2ui.parent_recid) { + for (let i = 0; i < level; i++) { + infoBubble += '' + } + } + let className = record.w2ui.children.length > 0 + ? (record.w2ui.expanded ? 'w2ui-icon-collapse' : 'w2ui-icon-expand') + : 'w2ui-icon-empty' + infoBubble += `` + } + // info bubble + if (col.info === true) col.info = {} + if (col.info != null) { + let infoIcon = 'w2ui-icon-info' + if (typeof col.info.icon == 'function') { + infoIcon = col.info.icon(record, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) + } else if (typeof col.info.icon == 'object') { + infoIcon = col.info.icon[this.parseField(record, col.field)] || '' + } else if (typeof col.info.icon == 'string') { + infoIcon = col.info.icon + } + let infoStyle = col.info.style || '' + if (typeof col.info.style == 'function') { + infoStyle = col.info.style(record, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) + } else if (typeof col.info.style == 'object') { + infoStyle = col.info.style[this.parseField(record, col.field)] || '' + } else if (typeof col.info.style == 'string') { + infoStyle = col.info.style + } + infoBubble += `` + } + let data = value + // if editable checkbox + if (edit && ['checkbox', 'check'].indexOf(edit.type) != -1) { + let changeInd = summary ? -(ind + 1) : ind + divStyle += 'text-align: center;' + data = `` + infoBubble = '' + } + data = `
    ${infoBubble}${String(data)}
    ` + if (data == null) data = '' + // --> cell TD + if (typeof col.render == 'string') { + let tmp = col.render.toLowerCase().split(':') + if (['number', 'int', 'float', 'money', 'currency', 'percent', 'size'].indexOf(tmp[0]) != -1) { + style += 'text-align: right;' + } + } + if (record?.w2ui) { + if (typeof record.w2ui.style == 'object') { + if (typeof record.w2ui.style[col_ind] == 'string') style += record.w2ui.style[col_ind] + ';' + if (typeof record.w2ui.style[col.field] == 'string') style += record.w2ui.style[col.field] + ';' + } + if (typeof record.w2ui.class == 'object') { + if (typeof record.w2ui.class[col_ind] == 'string') className += record.w2ui.class[col_ind] + ' ' + if (typeof record.w2ui.class[col.field] == 'string') className += record.w2ui.class[col.field] + ' ' + } + } + let isCellSelected = false + if (isRowSelected && sel.columns[ind]?.includes(col_ind)) isCellSelected = true + // clipboardCopy + let clipboardIcon + if (col.clipboardCopy){ + clipboardIcon = '' + } + // data + data = ' 1 ? 'colspan="'+ col_span + '"' : '') + + '>' + data + (clipboardIcon && w2utils.stripTags(data) ? clipboardIcon : '') +'' + // summary top row + if (ind === -1 && summary === true) { + data = ' 1 ? 'colspan="'+ col_span + '"' : '') + + '>' + } + return data + function getTitle(cellData){ + let title + if (obj.show.recordTitles) { + if (col.title != null) { + if (typeof col.title == 'function') { + title = col.title.call(obj, record, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) + } + if (typeof col.title == 'string') title = col.title + } else { + title = w2utils.stripTags(String(cellData).replace(/"/g, '\'\'')) + } + } + return (title != null) ? 'title="' + String(title) + '"' : '' + } + } + clipboardCopy(ind, col_ind, summary) { + let rec = summary ? this.summary[ind] : this.records[ind] + let col = this.columns[col_ind] + let txt = (col ? this.parseField(rec, col.field) : '') + if (typeof col.clipboardCopy == 'function') { + txt = col.clipboardCopy(rec, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) + } + query(this.box).find('#grid_' + this.name + '_focus').text(txt).get(0).select() + document.execCommand('copy') + } + showBubble(ind, col_ind, summary) { + let info = this.columns[col_ind].info + if (!info) return + let html = '' + let rec = this.records[ind] + let el = query(this.box).find(`${summary ? '.w2ui-grid-summary' : ''} #grid_${this.name}_data_${ind}_${col_ind} .w2ui-info`) + if (this.last.bubbleEl) { + w2tooltip.hide(this.name + '-bubble') + } + this.last.bubbleEl = el + // if no fields defined - show all + if (info.fields == null) { + info.fields = [] + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + info.fields.push(col.field + (typeof col.render == 'string' ? ':' + col.render : '')) + } + } + let fields = info.fields + if (typeof fields == 'function') { + fields = fields(rec, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) // custom renderer + } + // generate html + if (typeof info.render == 'function') { + html = info.render(rec, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) + } else if (Array.isArray(fields)) { + // display mentioned fields + html = '' + for (let i = 0; i < fields.length; i++) { + let tmp = String(fields[i]).split(':') + if (tmp[0] == '' || tmp[0] == '-' || tmp[0] == '--' || tmp[0] == '---') { + html += '' + continue + } + let col = this.getColumn(tmp[0]) + if (col == null) col = { field: tmp[0], caption: tmp[0] } // if not found in columns + let val = (col ? this.parseField(rec, col.field) : '') + if (tmp.length > 1) { + if (w2utils.formatters[tmp[1]]) { + val = w2utils.formatters[tmp[1]](val, tmp[2] || null, rec) + } else { + console.log('ERROR: w2utils.formatters["'+ tmp[1] + '"] does not exists.') + } + } + if (info.showEmpty !== true && (val == null || val == '')) continue + if (info.maxLength != null && typeof val == 'string' && val.length > info.maxLength) val = val.substr(0, info.maxLength) + '...' + html += '' + } + html += '
    ' + col.text + '' + ((val === 0 ? '0' : val) || '') + '
    ' + } else if (w2utils.isPlainObject(fields)) { + // display some fields + html = '' + for (let caption in fields) { + let fld = fields[caption] + if (fld == '' || fld == '-' || fld == '--' || fld == '---') { + html += '' + continue + } + let tmp = String(fld).split(':') + let col = this.getColumn(tmp[0]) + if (col == null) col = { field: tmp[0], caption: tmp[0] } // if not found in columns + let val = (col ? this.parseField(rec, col.field) : '') + if (tmp.length > 1) { + if (w2utils.formatters[tmp[1]]) { + val = w2utils.formatters[tmp[1]](val, tmp[2] || null, rec) + } else { + console.log('ERROR: w2utils.formatters["'+ tmp[1] + '"] does not exists.') + } + } + if (typeof fld == 'function') { + val = fld(rec, { self: this, index: ind, colIndex: col_ind, summary: !!summary }) + } + if (info.showEmpty !== true && (val == null || val == '')) continue + if (info.maxLength != null && typeof val == 'string' && val.length > info.maxLength) val = val.substr(0, info.maxLength) + '...' + html += '' + } + html += '
    ' + caption + '' + ((val === 0 ? '0' : val) || '') + '
    ' + } + return w2tooltip.show(w2utils.extend({ + name: this.name + '-bubble', + html, + anchor: el.get(0), + position: 'top|bottom', + class: 'w2ui-info-bubble', + style: '', + hideOn: ['doc-click'] + }, info.options ?? {})) + .hide(() => [ + this.last.bubbleEl = null + ]) + } + // return null or the editable object if the given cell is editable + getCellEditable(ind, col_ind) { + let col = this.columns[col_ind] + let rec = this.records[ind] + if (!rec || !col) return null + let edit = (rec.w2ui ? rec.w2ui.editable : null) + if (edit === false) return null + if (edit == null || edit === true) { + edit = (Object.keys(col.editable ?? {}).length > 0 ? col.editable : null) + if (typeof edit === 'function') { + let value = this.getCellValue(ind, col_ind, false) + // same arguments as col.render() + edit = edit.call(this, rec, { self: this, value, index: ind, colIndex: col_ind }) + } + } + return edit + } + getCellValue(ind, col_ind, summary, extra) { + let col = this.columns[col_ind] + let record = (summary !== true ? this.records[ind] : this.summary[ind]) + let value = this.parseField(record, col.field) + let className = '', style = '', attr = '', divAttr = '' + // if change by inline editing + if (record?.w2ui?.changes?.[col.field] != null) { + value = record.w2ui.changes[col.field] + } + // if there is a cell renderer + if (col.render != null && ind !== -1) { + if (typeof col.render == 'function' && record != null) { + let html + try { + html = col.render(record, { self: this, value, index: ind, colIndex: col_ind, summary: !!summary }) + } catch (e) { + throw new Error(`Render function for column "${col.field}" in grid "${this.name}": -- ` + e.message) + } + if (html != null && typeof html == 'object' && typeof html != 'function') { + if (html.id != null && html.text != null) { + // normalized menu kind of return + value = html.text + } else if (typeof html.html == 'string') { + value = (html.html || '').trim() + } else { + value = '' + console.log('ERROR: render function should return a primitive or an object of the following structure.', + { html: '', attr: '', style: '', class: '', divAttr: '' }) + } + attr = html.attr ?? '' + style = html.style ?? '' + className = html.class ?? '' + divAttr = html.divAttr ?? '' + } else { + value = String(html || '').trim() + } + } + // if it is an object + if (typeof col.render == 'object') { + let tmp = col.render[value] + if (tmp != null && tmp !== '') { + value = tmp + } + } + // formatters + if (typeof col.render == 'string') { + let strInd = col.render.toLowerCase().indexOf(':') + let tmp = [] + if (strInd == -1) { + tmp[0] = col.render.toLowerCase() + tmp[1] = '' + } else { + tmp[0] = col.render.toLowerCase().substr(0, strInd) + tmp[1] = col.render.toLowerCase().substr(strInd + 1) + } + // formatters + let func = w2utils.formatters[tmp[0]] + if (col.options && col.options.autoFormat === false) { + func = null + } + value = (typeof func == 'function' ? func(value, tmp[1], record) : '') + } + } + if (value == null) value = '' + return !extra ? value : { value, attr, style, className, divAttr } + } + getFooterHTML() { + return '
    '+ + ' '+ + ' '+ + ' '+ + '
    ' + } + status(msg) { + if (msg != null) { + query(this.box).find(`#grid_${this.name}_footer`).find('.w2ui-footer-left').html(msg) + } else { + // show number of selected + let msgLeft = '' + let sel = this.getSelection() + if (sel.length > 0) { + if (this.show.statusSelection && sel.length > 1) { + msgLeft = String(sel.length).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + w2utils.settings.groupSymbol) + ' ' + w2utils.lang('selected') + } + if (this.show.statusRecordID && sel.length == 1) { + let tmp = sel[0] + if (typeof tmp == 'object') tmp = tmp.recid + ', '+ w2utils.lang('Column') +': '+ tmp.column + msgLeft = w2utils.lang('Record ID') + ': '+ tmp + ' ' + } + } + query(this.box).find('#grid_'+ this.name +'_footer .w2ui-footer-left').html(msgLeft) + } + } + lock(msg, showSpinner) { + let args = Array.from(arguments) + args.unshift(this.box) + setTimeout(() => { + // hide empty msg if any + query(this.box).find('#grid_'+ this.name +'_empty_msg').remove() + w2utils.lock(...args) + }, 10) + } + unlock(speed) { + setTimeout(() => { + // do not unlock if there is a message + if (query(this.box).find('.w2ui-message').hasClass('w2ui-closing')) return + w2utils.unlock(this.box, speed) + }, 25) // needed timer so if server fast, it will not flash + } + stateSave(returnOnly) { + let state = { + columns: [], + show: w2utils.clone(this.show), + last: { + search: this.last.search, + multi : this.last.multi, + logic : this.last.logic, + label : this.last.label, + field : this.last.field, + scrollTop : this.last.scrollTop, + scrollLeft: this.last.scrollLeft + }, + sortData : [], + searchData: [] + } + let prop_val + for (let i = 0; i < this.columns.length; i++) { + let col = this.columns[i] + let col_save_obj = {} + // iterate properties to save + Object.keys(this.stateColProps).forEach((prop, idx) => { + if (this.stateColProps[prop]){ + // check if the property is defined on the column + if (col[prop] !== undefined){ + prop_val = col[prop] + } else { + // use fallback or null + prop_val = this.colTemplate[prop] || null + } + col_save_obj[prop] = prop_val + } + }) + state.columns.push(col_save_obj) + } + for (let i = 0; i < this.sortData.length; i++) state.sortData.push(w2utils.clone(this.sortData[i])) + for (let i = 0; i < this.searchData.length; i++) state.searchData.push(w2utils.clone(this.searchData[i])) + // event before + let edata = this.trigger('stateSave', { target: this.name, state: state }) + if (edata.isCancelled === true) { + return + } + // save into local storage + if (returnOnly !== true) { + this.cacheSave('state', state) + } + // event after + edata.finish() + return state + } + stateRestore(newState) { + let url = (typeof this.url != 'object' ? this.url : this.url.get) + if (!newState) { + newState = this.cache('state') + } + // event before + let edata = this.trigger('stateRestore', { target: this.name, state: newState }) + if (edata.isCancelled === true) { + return + } + // default behavior + if (w2utils.isPlainObject(newState)) { + w2utils.extend(this.show, newState.show ?? {}) + w2utils.extend(this.last, newState.last ?? {}) + let sTop = this.last.scrollTop + let sLeft = this.last.scrollLeft + for (let c = 0; c < newState.columns?.length; c++) { + let tmp = newState.columns[c] + let col_index = this.getColumn(tmp.field, true) + if (col_index !== null) { + w2utils.extend(this.columns[col_index], tmp) + // restore column order from saved state + if (c !== col_index) this.columns.splice(c, 0, this.columns.splice(col_index, 1)[0]) + } + } + this.sortData.splice(0, this.sortData.length) + for (let c = 0; c < newState.sortData?.length; c++) { + this.sortData.push(newState.sortData[c]) + } + this.searchData.splice(0, this.searchData.length) + for (let c = 0; c < newState.searchData?.length; c++) { + this.searchData.push(newState.searchData[c]) + } + // apply sort and search + setTimeout(() => { + // needs timeout as records need to be populated + // ez 10.09.2014 this --> + if (!url) { + if (this.sortData.length > 0) this.localSort() + if (this.searchData.length > 0) this.localSearch() + } + this.last.scrollTop = sTop + this.last.scrollLeft = sLeft + this.refresh() + }, 1) + console.log(`INFO (w2ui): state restored for "${this.name}"`) + } + // event after + edata.finish() + return true + } + stateReset() { + this.stateRestore(this.last.state) + this.cacheSave('state', null) + } + parseField(obj, field) { + if (this.nestedFields) { + let val = '' + try { // need this to make sure no error in fields + val = obj + let tmp = String(field).split('.') + for (let i = 0; i < tmp.length; i++) { + val = val[tmp[i]] + } + } catch (event) { + val = '' + } + return val + } else { + return obj ? obj[field] : '' + } + } + prepareData() { + let obj = this + // loops thru records and prepares date and time objects + for (let r = 0; r < this.records.length; r++) { + let rec = this.records[r] + prepareRecord(rec) + } + // prepare date and time objects for the 'rec' record and its closed children + function prepareRecord(rec) { + for (let c = 0; c < obj.columns.length; c++) { + let column = obj.columns[c] + if (rec[column.field] == null || typeof column.render != 'string') continue + // number + if (['number', 'int', 'float', 'money', 'currency', 'percent'].indexOf(column.render.split(':')[0]) != -1) { + if (typeof rec[column.field] != 'number') rec[column.field] = parseFloat(rec[column.field]) + } + // date + if (['date', 'age'].indexOf(column.render.split(':')[0]) != -1) { + if (!rec[column.field + '_']) { + let dt = rec[column.field] + if (w2utils.isInt(dt)) dt = parseInt(dt) + rec[column.field + '_'] = new Date(dt) + } + } + // time + if (['time'].indexOf(column.render) != -1) { + if (w2utils.isTime(rec[column.field])) { // if string + let tmp = w2utils.isTime(rec[column.field], true) + let dt = new Date() + dt.setHours(tmp.hours, tmp.minutes, (tmp.seconds ? tmp.seconds : 0), 0) // sets hours, min, sec, mills + if (!rec[column.field + '_']) rec[column.field + '_'] = dt + } else { // if date object + let tmp = rec[column.field] + if (w2utils.isInt(tmp)) tmp = parseInt(tmp) + tmp = (tmp != null ? new Date(tmp) : new Date()) + let dt = new Date() + dt.setHours(tmp.getHours(), tmp.getMinutes(), tmp.getSeconds(), 0) // sets hours, min, sec, mills + if (!rec[column.field + '_']) rec[column.field + '_'] = dt + } + } + } + if (rec.w2ui?.children && rec.w2ui?.expanded !== true) { + // there are closed children, prepare them too. + for (let r = 0; r < rec.w2ui.children.length; r++) { + let subRec = rec.w2ui.children[r] + prepareRecord(subRec) + } + } + } + } + nextCell(index, col_ind, editable) { + let check = col_ind + 1 + if (check >= this.columns.length) { + index = this.nextRow(index) + return index == null ? index : this.nextCell(index, -1, editable) + } + let tmp = this.records[index].w2ui + let col = this.columns[check] + let span = (tmp && tmp.colspan && !isNaN(tmp.colspan[col.field]) ? parseInt(tmp.colspan[col.field]) : 1) + if (col == null) return null + if (col && col.hidden || span === 0) return this.nextCell(index, check, editable) + if (editable) { + let edit = this.getCellEditable(index, check) + if (edit == null || ['checkbox', 'check'].indexOf(edit.type) != -1) { + return this.nextCell(index, check, editable) + } + } + return { index, colIndex: check } + } + prevCell(index, col_ind, editable) { + let check = col_ind - 1 + if (check < 0) { + index = this.prevRow(index) + return index == null ? index : this.prevCell(index, this.columns.length, editable) + } + if (check < 0) return null + let tmp = this.records[index].w2ui + let col = this.columns[check] + let span = (tmp && tmp.colspan && !isNaN(tmp.colspan[col.field]) ? parseInt(tmp.colspan[col.field]) : 1) + if (col == null) return null + if (col && col.hidden || span === 0) return this.prevCell(index, check, editable) + if (editable) { + let edit = this.getCellEditable(index, check) + if (edit == null || ['checkbox', 'check'].indexOf(edit.type) != -1) { + return this.prevCell(index, check, editable) + } + } + return { index, colIndex: check } + } + nextRow(ind, col_ind, numRows) { + let sids = this.last.searchIds + let ret = null + if (numRows == null) numRows = 1 + if (numRows == -1) { + return this.records.length-1 + } + if ((ind + numRows < this.records.length && sids.length === 0) // if there are more records + || (sids.length > 0 && ind < sids[sids.length-numRows])) { + ind += numRows + if (sids.length > 0) while (true) { + if (sids.includes(ind) || ind > this.records.length) break + ind += numRows + } + // colspan + let tmp = this.records[ind].w2ui + let col = this.columns[col_ind] + let span = (tmp && tmp.colspan && col != null && !isNaN(tmp.colspan[col.field]) ? parseInt(tmp.colspan[col.field]) : 1) + if (span === 0) { + ret = this.nextRow(ind, col_ind, numRows) + } else { + ret = ind + } + } + return ret + } + prevRow(ind, col_ind, numRows) { + let sids = this.last.searchIds + let ret = null + if (numRows == null) numRows = 1 + if (numRows == -1) { + return 0 + } + if ((ind - numRows >= 0 && sids.length === 0) // if there are more records + || (sids.length > 0 && ind > sids[0])) { + ind -= numRows + if (sids.length > 0) while (true) { + if (sids.includes(ind) || ind < 0) break + ind -= numRows + } + // colspan + let tmp = this.records[ind].w2ui + let col = this.columns[col_ind] + let span = (tmp && tmp.colspan && col != null && !isNaN(tmp.colspan[col.field]) ? parseInt(tmp.colspan[col.field]) : 1) + if (span === 0) { + ret = this.prevRow(ind, col_ind, numRows) + } else { + ret = ind + } + } + return ret + } + selectionSave() { + this.last.saved_sel = this.getSelection() + return this.last.saved_sel + } + selectionRestore(noRefresh) { + let time = Date.now() + this.last.selection = { indexes: [], columns: {} } + let sel = this.last.selection + let lst = this.last.saved_sel + if (lst) for (let i = 0; i < lst.length; i++) { + if (w2utils.isPlainObject(lst[i])) { + // selectType: cell + let tmp = this.get(lst[i].recid, true) + if (tmp != null) { + if (sel.indexes.indexOf(tmp) == -1) sel.indexes.push(tmp) + if (!sel.columns[tmp]) sel.columns[tmp] = [] + sel.columns[tmp].push(lst[i].column) + } + } else { + // selectType: row + let tmp = this.get(lst[i], true) + if (tmp != null) sel.indexes.push(tmp) + } + } + delete this.last.saved_sel + if (noRefresh !== true) this.refresh() + return Date.now() - time + } + message(options) { + return w2utils.message({ + owner: this, + box : this.box, + after: '.w2ui-grid-header' + }, options) + } + confirm(options) { + return w2utils.confirm({ + owner: this, + box : this.box, + after: '.w2ui-grid-header' + }, options) + } +} + +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2tabs, w2toolbar, w2tooltip, w2field + * + * == TODO == + * - include delta on save + * - tabs below some fields (could already be implemented) + * - form with toolbar & tabs + * - promise for load, save, etc. + * + * == 2.0 changes + * - CSP - fixed inline events + * - removed jQuery dependency + * - better groups support tabs now + * - form.confirm - refactored + * - form.message - refactored + * - observeResize for the box + * - removed msgNotJSON, msgAJAXerror + * - applyFocus -> setFocus + * - getFieldValue(fieldName) = returns { curent, previous, original } + * - setFieldVallue(fieldName, value) + * - getValue(..., original) -- return original if any + * - added .hideErrors() + * - reuqest, save, submit - return promises + * - added prepareParams + * - this.recid = null if no record needs to be pulled + * - remove form.multiplart + * - this.method - for saving only + */ + +class w2form extends w2base { + constructor(options) { + super(options.name) + this.name = null + this.header = '' + this.box = null // HTML element that hold this element + this.url = '' + this.method = null // if defined, it will be http method when saving + this.routeData = {} // data for dynamic routes + this.formURL = '' // url where to get form HTML + this.formHTML = '' // form HTML (might be loaded from the url) + this.page = 0 // current page + this.pageStyle = '' + this.recid = null // if not null, then load record + this.fields = [] + this.actions = {} + this.record = {} + this.original = null + this.dataType = null // only used when not null, otherwise from w2utils.settings.dataType + this.postData = {} + this.httpHeaders = {} + this.toolbar = {} // if not empty, then it is toolbar + this.tabs = {} // if not empty, then it is tabs object + this.style = '' + this.focus = 0 // focus first or other element + this.autosize = true // autosize, if false the container must have a height set + this.nestedFields = true // use field name containing dots as separator to look into object + this.tabindexBase = 0 // this will be added to the auto numbering + this.isGenerated = false + this.last = { + fetchCtrl: null, // last fetch AbortController + fetchOptions: null, // last fetch options + errors: [] + } + this.onRequest = null + this.onLoad = null + this.onValidate = null + this.onSubmit = null + this.onProgress = null + this.onSave = null + this.onChange = null + this.onInput = null + this.onRender = null + this.onRefresh = null + this.onResize = null + this.onDestroy = null + this.onAction = null + this.onToolbar = null + this.onError = null + this.msgRefresh = 'Loading...' + this.msgSaving = 'Saving...' + this.msgServerError = 'Server error' + this.ALL_TYPES = [ 'text', 'textarea', 'email', 'pass', 'password', 'int', 'float', 'money', 'currency', + 'percent', 'hex', 'alphanumeric', 'color', 'date', 'time', 'datetime', 'toggle', 'checkbox', 'radio', + 'check', 'checks', 'list', 'combo', 'enum', 'file', 'select', 'map', 'array', 'div', 'custom', 'html', + 'empty'] + this.LIST_TYPES = ['select', 'radio', 'check', 'checks', 'list', 'combo', 'enum'] + this.W2FIELD_TYPES = ['int', 'float', 'money', 'currency', 'percent', 'hex', 'alphanumeric', 'color', + 'date', 'time', 'datetime', 'list', 'combo', 'enum', 'file'] + // mix in options + w2utils.extend(this, options) + // remember items + let record = options.record + let original = options.original + let fields = options.fields + let toolbar = options.toolbar + let tabs = options.tabs + // extend items + Object.assign(this, { record: {}, original: null, fields: [], tabs: {}, toolbar: {}, handlers: [] }) + // preprocess fields + if (fields) { + let sub =_processFields(fields) + this.fields = sub.fields + if (!tabs && sub.tabs.length > 0) { + tabs = sub.tabs + } + } + // prepare tabs + if (Array.isArray(tabs)) { + w2utils.extend(this.tabs, { tabs: [] }) + for (let t = 0; t < tabs.length; t++) { + let tmp = tabs[t] + if (typeof tmp === 'object') { + this.tabs.tabs.push(tmp) + if (tmp.active === true) { + this.tabs.active = tmp.id + } + } else { + this.tabs.tabs.push({ id: tmp, text: tmp }) + } + } + } else { + w2utils.extend(this.tabs, tabs) + } + w2utils.extend(this.toolbar, toolbar) + for (let p in record) { // it is an object + if (w2utils.isPlainObject(record[p])) { + this.record[p] = w2utils.clone(record[p]) + } else { + this.record[p] = record[p] + } + } + for (let p in original) { // it is an object + if (w2utils.isPlainObject(original[p])) { + this.original[p] = w2utils.clone(original[p]) + } else { + this.original[p] = original[p] + } + } + // generate html if necessary + if (this.formURL !== '') { + fetch(this.formURL) + .then(resp => resp.text()) + .then(text => { + this.formHTML = text + this.isGenerated = true + if (this.box) this.render(this.box) + }) + } else if (!this.formURL && !this.formHTML) { + this.formHTML = this.generateHTML() + this.isGenerated = true + } else if (this.formHTML) { + this.isGenerated = true + } + // render if box specified + if (typeof this.box == 'string') this.box = query(this.box).get(0) + if (this.box) this.render(this.box) + function _processFields(fields) { + let newFields = [] + let tabs = [] + // if it is an object + if (w2utils.isPlainObject(fields)) { + let tmp = fields + fields = [] + Object.keys(tmp).forEach((key) => { + let fld = tmp[key] + if (fld.type == 'group') { + fld.text = key + if (w2utils.isPlainObject(fld.fields)) { + let tmp2 = fld.fields + fld.fields = [] + Object.keys(tmp2).forEach((key2) => { + let fld2 = tmp2[key2] + fld2.field = key2 + fld.fields.push(_process(fld2)) + }) + } + fields.push(fld) + } else if (fld.type == 'tab') { + // add tab + let tab = { id: key, text: key } + if (fld.style) { + tab.style = fld.style + } + tabs.push(tab) + // add page to fields + let sub = _processFields(fld.fields).fields + sub.forEach(fld2 => { + fld2.html = fld2.html || {} + fld2.html.page = tabs.length -1 + _process2(fld, fld2) + }) + fields.push(...sub) + } else { + fld.field = key + fields.push(_process(fld)) + } + }) + function _process(fld) { + let ignore = ['html'] + if (fld.html == null) fld.html = {} + Object.keys(fld).forEach((key => { + if (ignore.indexOf(key) != -1) return + if (['label', 'attr', 'style', 'text', 'span', 'page', 'column', 'anchor', + 'group', 'groupStyle', 'groupTitleStyle', 'groupCollapsible'].indexOf(key) != -1) { + fld.html[key] = fld[key] + delete fld[key] + } + })) + return fld + } + function _process2(fld, fld2) { + let ignore = ['style', 'html'] + Object.keys(fld).forEach((key => { + if (ignore.indexOf(key) != -1) return + if (['span', 'column', 'attr', 'text', 'label'].indexOf(key) != -1) { + if (fld[key] && !fld2.html[key]) { + fld2.html[key] = fld[key] + } + } + })) + } + } + // process groups + fields.forEach(field => { + if (field.type == 'group') { + // group properties + let group = { + group: field.text || '', + groupStyle: field.style || '', + groupTitleStyle: field.titleStyle || '', + groupCollapsible: field.collapsible === true ? true : false, + } + // loop through fields + if (Array.isArray(field.fields)) { + field.fields.forEach(gfield => { + let fld = w2utils.clone(gfield) + if (fld.html == null) fld.html = {} + w2utils.extend(fld.html, group) + Array('span', 'column', 'attr', 'label', 'page').forEach(key => { + if (fld.html[key] == null && field[key] != null) { + fld.html[key] = field[key] + } + }) + if (fld.field == null && fld.name != null) { + console.log('NOTICE: form field.name property is deprecated, please use field.field. Field ->', field) + fld.field = fld.name + } + newFields.push(fld) + }) + } + } else { + let fld = w2utils.clone(field) + if (fld.field == null && fld.name != null) { + console.log('NOTICE: form field.name property is deprecated, please use field.field. Field ->', field) + fld.field = fld.name + } + newFields.push(fld) + } + }) + return { fields: newFields, tabs } + } + } + get(field, returnIndex) { + if (arguments.length === 0) { + let all = [] + for (let f1 = 0; f1 < this.fields.length; f1++) { + if (this.fields[f1].field != null) all.push(this.fields[f1].field) + } + return all + } else { + for (let f2 = 0; f2 < this.fields.length; f2++) { + if (this.fields[f2].field == field) { + if (returnIndex === true) return f2; else return this.fields[f2] + } + } + return null + } + } + set(field, obj) { + for (let f = 0; f < this.fields.length; f++) { + if (this.fields[f].field == field) { + w2utils.extend(this.fields[f] , obj) + this.refresh(field) + return true + } + } + return false + } + getValue(field, original) { + if (this.nestedFields) { + let val = undefined + try { // need this to make sure no error in fields + let rec = original === true ? this.original : this.record + val = String(field).split('.').reduce((rec, i) => { return rec[i] }, rec) + } catch (event) { + } + return val + } else { + return this.record[field] + } + } + setValue(field, value) { + // will not refresh the form! + if (value === '' || value == null + || (Array.isArray(value) && value.length === 0) + || (w2utils.isPlainObject(value) && Object.keys(value).length == 0)) { + value = null + } + if (this.nestedFields) { + try { // need this to make sure no error in fields + let rec = this.record + String(field).split('.').map((fld, i, arr) => { + if (arr.length - 1 !== i) { + if (rec[fld]) rec = rec[fld]; else { rec[fld] = {}; rec = rec[fld] } + } else { + rec[fld] = value + } + }) + return true + } catch (event) { + return false + } + } else { + this.record[field] = value + return true + } + } + getFieldValue(name) { + let field = this.get(name) + if (field == null) return + let el = field.el + let previous = this.getValue(name) + let original = this.getValue(name, true) + // orginary input control + let current = el.value + // should not be set to '', incosistent logic + // if (previous == null) previous = '' + // clean extra chars + if (['int', 'float', 'percent', 'money', 'currency'].includes(field.type)) { + current = field.w2field.clean(current) + } + // radio list + if (['radio'].includes(field.type)) { + let selected = query(el).closest('div').find('input:checked').get(0) + if (selected) { + let item = field.options.items[query(selected).data('index')] + current = item.id + } else { + current = null + } + } + // single checkbox + if (['toggle', 'checkbox'].includes(field.type)) { + current = el.checked + } + // check list + if (['check', 'checks'].indexOf(field.type) !== -1) { + current = [] + let selected = query(el).closest('div').find('input:checked') + if (selected.length > 0) { + selected.each(el => { + let item = field.options.items[query(el).data('index')] + current.push(item.id) + }) + } + if (!Array.isArray(previous)) previous = [] + } + // lists + let selected = el._w2field?.selected // drop downs and other w2field objects + if (['list', 'enum', 'file'].includes(field.type) && selected) { + // TODO: check when w2field is refactored + let nv = selected + let cv = previous + if (Array.isArray(nv)) { + current = [] + for (let i = 0; i < nv.length; i++) current[i] = w2utils.clone(nv[i]) // clone array + } + if (Array.isArray(cv)) { + previous = [] + for (let i = 0; i < cv.length; i++) previous[i] = w2utils.clone(cv[i]) // clone array + } + if (w2utils.isPlainObject(nv)) { + current = w2utils.clone(nv) // clone object + } + if (w2utils.isPlainObject(cv)) { + previous = w2utils.clone(cv) // clone object + } + } + // map, array + if (['map', 'array'].includes(field.type)) { + current = (field.type == 'map' ? {} : []) + field.$el.parent().find('.w2ui-map-field').each(div => { + let key = query(div).find('.w2ui-map.key').val() + let value = query(div).find('.w2ui-map.value').val() + if (field.type == 'map') { + current[key] = value + } else { + current.push(value) + } + }) + } + return { current, previous, original } // current - in input, previous - in form.record, original - before form change + } + setFieldValue(name, value) { + let field = this.get(name) + if (field == null) return + let el = field.el + switch (field.type) { + case 'toggle': + case 'checkbox': + el.checked = value ? true : false + break + case 'radio': { + value = value?.id ?? value + let inputs = query(el).closest('div').find('input') + let items = field.options.items + items.forEach((it, ind) => { + if (it.id === value) { // need exact match so to match empty string and 0 + inputs.filter(`[data-index="${ind}"]`).prop('checked', true) + } + }) + break + } + case 'check': + case 'checks': { + if (!Array.isArray(value)) { + if (value != null) { + value = [value] + } else { + value = [] + } + } + value = value.map(val => val?.id ?? val) // convert if array of objects + let inputs = query(el).closest('div').find('input') + let items = field.options.items + items.forEach((it, ind) => { + inputs.filter(`[data-index="${ind}"]`).prop('checked', value.includes(it.id) ? true : false) + }) + break + } + case 'list': + case 'combo': + let item = value + // find item in options.items, if any + if (item?.id == null && Array.isArray(field.options?.items)) { + field.options.items.forEach(it => { + if (it.id === value) item = it + }) + } + // if item is found in field.options, update it in the this.records + if (item != value) { + this.setValue(field.name, item) + } + if (field.type == 'list') { + field.w2field.selected = item + field.w2field.refresh() + } else { + field.el.value = item?.text ?? value + } + break + case 'enum': + case 'file': { + if (!Array.isArray(value)) { + value = value != null ? [value] : [] + } + let items = [...value] + // find item in options.items, if any + let updated = false + items.forEach((item, ind) => { + if (item?.id == null && Array.isArray(field.options.items)) { + field.options.items.forEach(it => { + if (it.id == item) { + items[ind] = it + updated = true + } + }) + } + }) + if (updated) { + this.setValue(field.name, items) + } + field.w2field.selected = items + field.w2field.refresh() + break + } + case 'map': + case 'array': { + // init map + if (field.type == 'map' && (value == null || !w2utils.isPlainObject(value))) { + this.setValue(field.field, {}) + value = this.getValue(field.field) + } + if (field.type == 'array' && (value == null || !Array.isArray(value))) { + this.setValue(field.field, []) + value = this.getValue(field.field) + } + let container = query(field.el).parent().find('.w2ui-map-container') + field.el.mapRefresh(value, container) + break + } + case 'div': + case 'custom': + query(el).html(value) + break + case 'html': + case 'empty': + break + default: + // regular text fields + el.value = value ?? '' + break + } + } + show() { + let effected = [] + for (let a = 0; a < arguments.length; a++) { + let fld = this.get(arguments[a]) + if (fld && fld.hidden) { + fld.hidden = false + effected.push(fld.field) + } + } + if (effected.length > 0) this.refresh.apply(this, effected) + this.updateEmptyGroups() + return effected + } + hide() { + let effected = [] + for (let a = 0; a < arguments.length; a++) { + let fld = this.get(arguments[a]) + if (fld && !fld.hidden) { + fld.hidden = true + effected.push(fld.field) + } + } + if (effected.length > 0) this.refresh.apply(this, effected) + this.updateEmptyGroups() + return effected + } + enable() { + let effected = [] + for (let a = 0; a < arguments.length; a++) { + let fld = this.get(arguments[a]) + if (fld && fld.disabled) { + fld.disabled = false + effected.push(fld.field) + } + } + if (effected.length > 0) this.refresh.apply(this, effected) + return effected + } + disable() { + let effected = [] + for (let a = 0; a < arguments.length; a++) { + let fld = this.get(arguments[a]) + if (fld && !fld.disabled) { + fld.disabled = true + effected.push(fld.field) + } + } + if (effected.length > 0) this.refresh.apply(this, effected) + return effected + } + updateEmptyGroups() { + // hide empty groups + query(this.box).find('.w2ui-group').each((group) =>{ + if (isHidden(query(group).find('.w2ui-field'))) { + query(group).hide() + } else { + query(group).show() + } + }) + function isHidden($els) { + let flag = true + $els.each((el) => { + if (el.style.display != 'none') flag = false + }) + return flag + } + } + change() { + Array.from(arguments).forEach((field) => { + let tmp = this.get(field) + if (tmp.$el) tmp.$el.change() + }) + } + reload(callBack) { + let url = (typeof this.url !== 'object' ? this.url : this.url.get) + if (url && this.recid != null) { + // this.clear(); + return this.request(callBack) // returns promise + } else { + // this.refresh(); // no need to refresh + if (typeof callBack === 'function') callBack() + return new Promise(resolve => { resolve() }) // resolved promise + } + } + clear() { + if (arguments.length != 0) { + Array.from(arguments).forEach((field) => { + let rec = this.record + String(field).split('.').map((fld, i, arr) => { + if (arr.length - 1 !== i) rec = rec[fld]; else delete rec[fld] + }) + this.refresh(field) + }) + } else { + this.recid = null + this.record = {} + this.original = null + this.refresh() + this.hideErrors() + } + } + error(msg) { + // let the management of the error outside of the form + let edata = this.trigger('error', { + target: this.name, + message: msg, + fetchCtrl: this.last.fetchCtrl, + fetchOptions: this.last.fetchOptions + }) + if (edata.isCancelled === true) return + // need a time out because message might be already up) + setTimeout(() => { this.message(msg) }, 1) + // event after + edata.finish() + } + message(options) { + return w2utils.message({ + owner: this, + box : this.box, + after: '.w2ui-form-header' + }, options) + } + confirm(options) { + return w2utils.confirm({ + owner: this, + box : this.box, + after: '.w2ui-form-header' + }, options) + } + validate(showErrors) { + if (showErrors == null) showErrors = true + // validate before saving + let errors = [] + for (let f = 0; f < this.fields.length; f++) { + let field = this.fields[f] + if (this.getValue(field.field) == null) this.setValue(field.field, '') + if (['int', 'float', 'currency', 'money'].indexOf(field.type) != -1) { + let val = this.getValue(field.field) + let min = field.options.min + let max = field.options.max + if (min != null && val < min) { + errors.push({ field: field, error: w2utils.lang('Should be more than ${min}', { min }) }) + } + if (max != null && val > max) { + errors.push({ field: field, error: w2utils.lang('Should be less than ${max}', { max }) }) + } + } + switch (field.type) { + case 'alphanumeric': + if (this.getValue(field.field) && !w2utils.isAlphaNumeric(this.getValue(field.field))) { + errors.push({ field: field, error: w2utils.lang('Not alpha-numeric') }) + } + break + case 'int': + if (this.getValue(field.field) && !w2utils.isInt(this.getValue(field.field))) { + errors.push({ field: field, error: w2utils.lang('Not an integer') }) + } + break + case 'percent': + case 'float': + if (this.getValue(field.field) && !w2utils.isFloat(this.getValue(field.field))) { + errors.push({ field: field, error: w2utils.lang('Not a float') }) + } + break + case 'currency': + case 'money': + if (this.getValue(field.field) && !w2utils.isMoney(this.getValue(field.field))) { + errors.push({ field: field, error: w2utils.lang('Not in money format') }) + } + break + case 'color': + case 'hex': + if (this.getValue(field.field) && !w2utils.isHex(this.getValue(field.field))) { + errors.push({ field: field, error: w2utils.lang('Not a hex number') }) + } + break + case 'email': + if (this.getValue(field.field) && !w2utils.isEmail(this.getValue(field.field))) { + errors.push({ field: field, error: w2utils.lang('Not a valid email') }) + } + break + case 'checkbox': + // convert true/false + if (this.getValue(field.field) == true) { + this.setValue(field.field, true) + } else { + this.setValue(field.field, false) + } + break + case 'date': + // format date before submit + if (!field.options.format) field.options.format = w2utils.settings.dateFormat + if (this.getValue(field.field) && !w2utils.isDate(this.getValue(field.field), field.options.format)) { + errors.push({ field: field, error: w2utils.lang('Not a valid date') + ': ' + field.options.format }) + } + break + case 'list': + case 'combo': + break + case 'enum': + break + } + // === check required - if field is '0' it should be considered not empty + let val = this.getValue(field.field) + if (field.hidden !== true && field.required + && !['div', 'custom', 'html', 'empty'].includes(field.type) + && (val == null || val === '' || (Array.isArray(val) && val.length === 0) + || (w2utils.isPlainObject(val) && Object.keys(val).length == 0))) { + errors.push({ field: field, error: w2utils.lang('Required field') }) + } + if (field.hidden !== true && field.options?.minLength > 0 + && !['enum', 'list', 'combo'].includes(field.type) // since minLength is used there for other purpose + && (val == null || val.length < field.options.minLength)) { + errors.push({ field: field, error: w2utils.lang('Field should be at least ${count} characters.', + { count: field.options.minLength })}) + } + } + // event before + let edata = this.trigger('validate', { target: this.name, errors: errors }) + if (edata.isCancelled === true) return + // show error + this.last.errors = errors + if (showErrors) this.showErrors() + // event after + edata.finish() + return errors + } + showErrors() { + // TODO: check edge cases + // -- scroll + // -- invisible pages + // -- form refresh + let errors = this.last.errors + if (errors.length <= 0) return + // show errors + this.goto(errors[0].field.page) + query(errors[0].field.$el).parents('.w2ui-field')[0].scrollIntoView({ block: 'nearest', inline: 'nearest' }) + // show errors + // show only for visible controls + errors.forEach(error => { + let opt = w2utils.extend({ + anchorClass: 'w2ui-error', + class: 'w2ui-light', + position: 'right|left', + hideOn: ['input'] + }, error.options) + if (error.field == null) return + let anchor = error.field.el + if (error.field.type === 'radio') { // for radio and checkboxes + anchor = query(error.field.el).closest('div').get(0) + } else if (['enum', 'file'].includes(error.field.type)) { + // TODO: check + // anchor = (error.field.el).data('w2field').helpers.multi + // $(fld).addClass('w2ui-error') + } + w2tooltip.show(w2utils.extend({ + anchor, + name: `${this.name}-${error.field.field}-error`, + html: error.error + }, opt)) + }) + // hide errors on scroll + query(errors[0].field.$el).parents('.w2ui-page') + .off('.hideErrors') + .on('scroll.hideErrors', (evt) => { this.hideErrors() }) + } + hideErrors() { + this.fields.forEach(field => { + w2tooltip.hide(`${this.name}-${field.field}-error`) + }) + } + getChanges() { + // TODO: not working on nested structures + let diff = {} + if (this.original != null && typeof this.original == 'object' && Object.keys(this.record).length !== 0) { + diff = doDiff(this.record, this.original, {}) + } + return diff + function doDiff(record, original, result) { + if (Array.isArray(record) && Array.isArray(original)) { + while (record.length < original.length) { + record.push(null) + } + } + for (let i in record) { + if (record[i] != null && typeof record[i] === 'object') { + result[i] = doDiff(record[i], original[i] || {}, {}) + if (!result[i] || (Object.keys(result[i]).length == 0 && Object.keys(original[i].length == 0))) delete result[i] + } else if (record[i] != original[i] || (record[i] == null && original[i] != null)) { // also catch field clear + result[i] = record[i] + } + } + return Object.keys(result).length != 0 ? result : null + } + } + getCleanRecord(strict) { + let data = w2utils.clone(this.record) + this.fields.forEach((fld) => { + if (['list', 'combo', 'enum'].indexOf(fld.type) != -1) { + let tmp = { nestedFields: true, record: data } + let val = this.getValue.call(tmp, fld.field) + if (w2utils.isPlainObject(val) && val.id != null) { // should be true if val.id === '' + this.setValue.call(tmp, fld.field, val.id) + } + if (Array.isArray(val)) { + val.forEach((item, ind) => { + if (w2utils.isPlainObject(item) && item.id) { + val[ind] = item.id + } + }) + } + } + if (fld.type == 'map') { + let tmp = { nestedFields: true, record: data } + let val = this.getValue.call(tmp, fld.field) + if (val._order) delete val._order + } + if (fld.type == 'file') { + let tmp = { nestedFields: true, record: data } + let val = this.getValue.call(tmp, fld.field) ?? [] + val.forEach(v => { + delete v.file + delete v.modified + }) + this.setValue.call(tmp, fld.field, val) + } + }) + // return only records present in description + if (strict === true) { + Object.keys(data).forEach((key) => { + if (!this.get(key)) delete data[key] + }) + } + return data + } + prepareParams(url, fetchOptions) { + let dataType = this.dataType ?? w2utils.settings.dataType + let postParams = fetchOptions.body + switch (dataType) { + case 'HTTPJSON': + postParams = { request: postParams } + body2params() + break + case 'HTTP': + body2params() + break + case 'RESTFULL': + if (fetchOptions.method == 'POST') { + fetchOptions.headers['Content-Type'] = 'application/json' + } else { + body2params() + } + break + case 'JSON': + if (fetchOptions.method == 'GET') { + postParams = { request: postParams } + body2params() + } else { + fetchOptions.headers['Content-Type'] = 'application/json' + fetchOptions.method = 'POST' + } + break + } + fetchOptions.body = typeof fetchOptions.body == 'string' ? fetchOptions.body : JSON.stringify(fetchOptions.body) + return fetchOptions + function body2params() { + Object.keys(postParams).forEach(key => { + let param = postParams[key] + if (typeof param == 'object') param = JSON.stringify(param) + url.searchParams.append(key, param) + }) + delete fetchOptions.body + } + } + request(postData, callBack) { // if (1) param then it is call back if (2) then postData and callBack + let self = this + let resolve, reject + let responseProm = new Promise((res, rej) => { resolve = res; reject = rej }) + // check for multiple params + if (typeof postData === 'function') { + callBack = postData + postData = null + } + if (postData == null) postData = {} + if (!this.url || (typeof this.url === 'object' && !this.url.get)) return + // build parameters list + let params = {} + // add list params + params.action = 'get' + params.recid = this.recid + params.name = this.name + // append other params + w2utils.extend(params, this.postData) + w2utils.extend(params, postData) + // event before + let edata = this.trigger('request', { target: this.name, url: this.url, httpMethod: 'GET', + postData: params, httpHeaders: this.httpHeaders }) + if (edata.isCancelled === true) return + // default action + this.record = {} + this.original = null + // call server to get data + this.lock(w2utils.lang(this.msgRefresh)) + let url = edata.detail.url + if (typeof url === 'object' && url.get) url = url.get + if (this.last.fetchCtrl) try { this.last.fetchCtrl.abort() } catch (e) {} + // process url with routeData + if (Object.keys(this.routeData).length != 0) { + let info = w2utils.parseRoute(url) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + if (this.routeData[info.keys[k].name] == null) continue + url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name]) + } + } + } + url = new URL(url, location) + let fetchOptions = this.prepareParams(url, { + method: edata.detail.httpMethod, + headers: edata.detail.httpHeaders, + body: edata.detail.postData + }) + this.last.fetchCtrl = new AbortController() + fetchOptions.signal = this.last.fetchCtrl.signal + this.last.fetchOptions = fetchOptions + fetch(url, fetchOptions) + .catch(processError) + .then((resp) => { + if (resp?.status != 200) { + // if resp is undefined, it means request was aborted + if (resp) processError(resp) + return + } + resp.json() + .catch(processError) + .then(data => { + // event before + let edata = self.trigger('load', { + target: self.name, + fetchCtrl: this.last.fetchCtrl, + fetchOptions: this.last.fetchOptions, + data + }) + if (edata.isCancelled === true) return + // for backward compatibility + if (data.error == null && data.status === 'error') { + data.error = true + } + // if data.record is not present, then assume that entire response is the record + if (!data.record) { + Object.assign(data, { record: w2utils.clone(data) }) + } + // server response error, not due to network issues + if (data.error === true) { + self.error(w2utils.lang(data.message ?? this.msgServerError)) + } else { + self.record = w2utils.clone(data.record) + } + // event after + self.unlock() + edata.finish() + self.refresh() + self.setFocus() + // call back + if (typeof callBack === 'function') callBack(data) + resolve(data) + }) + }) + // event after + edata.finish() + return responseProm + function processError(response) { + if (response.name === 'AbortError') { + // request was aborted by the form + return + } + self.unlock() + // trigger event + let edata2 = self.trigger('error', { response, fetchCtrl: self.last.fetchCtrl, fetchOptions: self.last.fetchOptions }) + if (edata2.isCancelled === true) return + // default behavior + if (response.status && response.status != 200) { + self.error(response.status + ': ' + response.statusText) + } else { + console.log('ERROR: Server request failed.', response, '. ', + 'Expected Response:', { error: false, record: { field1: 1, field2: 'item' }}, + 'OR:', { error: true, message: 'Error description' }) + self.error(String(response)) + } + // event after + edata2.finish() + reject(response) + } + } + submit(postData, callBack) { + return this.save(postData, callBack) + } + save(postData, callBack) { + let self = this + let resolve, reject + let saveProm = new Promise((res, rej) => { resolve = res; reject = rej }) + // check for multiple params + if (typeof postData === 'function') { + callBack = postData + postData = null + } + // validation + let errors = self.validate(true) + if (errors.length !== 0) return + // submit save + if (postData == null) postData = {} + if (!self.url || (typeof self.url === 'object' && !self.url.save)) { + console.log('ERROR: Form cannot be saved because no url is defined.') + return + } + self.lock(w2utils.lang(self.msgSaving) + ' ') + // build parameters list + let params = {} + // add list params + params.action = 'save' + params.recid = self.recid + params.name = self.name + // append other params + w2utils.extend(params, self.postData) + w2utils.extend(params, postData) + params.record = w2utils.clone(self.record) + // event before + let edata = self.trigger('submit', { target: self.name, url: self.url, httpMethod: this.method ?? 'POST', + postData: params, httpHeaders: self.httpHeaders }) + if (edata.isCancelled === true) return + // default action + let url = edata.detail.url + if (typeof url === 'object' && url.save) url = url.save + if (self.last.fetchCtrl) self.last.fetchCtrl.abort() + // process url with routeData + if (Object.keys(self.routeData).length > 0) { + let info = w2utils.parseRoute(url) + if (info.keys.length > 0) { + for (let k = 0; k < info.keys.length; k++) { + if (self.routeData[info.keys[k].name] == null) continue + url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), self.routeData[info.keys[k].name]) + } + } + } + url = new URL(url, location) + let fetchOptions = this.prepareParams(url, { + method: edata.detail.httpMethod, + headers: edata.detail.httpHeaders, + body: edata.detail.postData + }) + this.last.fetchCtrl = new AbortController() + fetchOptions.signal = this.last.fetchCtrl.signal + this.last.fetchOptions = fetchOptions + fetch(url, fetchOptions) + .catch(processError) + .then(resp => { + self.unlock() + if (resp?.status != 200) { + processError(resp ?? {}) + return + } + // parse server response + resp.json() + .catch(processError) + .then(data => { + // event before + let edata = self.trigger('save', { + target: self.name, + fetchCtrl: this.last.fetchCtrl, + fetchOptions: this.last.fetchOptions, + data + }) + if (edata.isCancelled === true) return + // server error, not due to network issues + if (data.error === true) { + self.error(w2utils.lang(data.message ?? this.msgServerError)) + } else { + self.original = null + } + // event after + edata.finish() + self.refresh() + // call back + if (typeof callBack === 'function') callBack(data) + resolve(data) + }) + }) + // event after + edata.finish() + return saveProm + function processError(response) { + if (response?.name === 'AbortError') { + // request was aborted by the form + return + } + self.unlock() + // trigger event + let edata2 = self.trigger('error', { response, fetchCtrl: self.last.fetchCtrl, fetchOptions: self.last.fetchOptions }) + if (edata2.isCancelled === true) return + // default behavior + if (response.status && response.status != 200) { + self.error(response.status + ': ' + response.statusText) + } else { + console.log('ERROR: Server request failed.', response, '. ', + 'Expected Response:', { error: false, record: { field1: 1, field2: 'item' }}, + 'OR:', { error: true, message: 'Error description' }) + self.error(String(response)) + } + // event after + edata2.finish() + reject() + } + } + lock(msg, showSpinner) { + let args = Array.from(arguments) + args.unshift(this.box) + w2utils.lock(...args) + } + unlock(speed) { + let box = this.box + w2utils.unlock(box, speed) + } + lockPage(page, msg, spinner) { + let $page = query(this.box).find('.page-' + page) + if ($page.length){ + // page found + w2utils.lock($page, msg, spinner) + return true + } + // page with this id not found! + return false + } + unlockPage(page, speed) { + let $page = query(this.box).find('.page-' + page) + if ($page.length) { + // page found + w2utils.unlock($page, speed) + return true + } + // page with this id not found! + return false + } + goto(page) { + if (this.page === page) return // already on this page + if (page != null) this.page = page + // if it was auto size, resize it + if (query(this.box).data('autoSize') === true) { + query(this.box).get(0).clientHeight = 0 + } + this.refresh() + } + generateHTML() { + let pages = [] // array for each page + let group = '' + let page + let column + let html + let tabindex + let tabindex_str + for (let f = 0; f < this.fields.length; f++) { + html = '' + tabindex = this.tabindexBase + f + 1 + tabindex_str = ' tabindex="'+ tabindex +'"' + let field = this.fields[f] + if (field.html == null) field.html = {} + if (field.options == null) field.options = {} + if (field.html.caption != null && field.html.label == null) { + console.log('NOTICE: form field.html.caption property is deprecated, please use field.html.label. Field ->', field) + field.html.label = field.html.caption + } + if (field.html.label == null) field.html.label = field.field + field.html = w2utils.extend({ label: '', span: 6, attr: '', text: '', style: '', page: 0, column: 0 }, field.html) + if (page == null) page = field.html.page + if (column == null) column = field.html.column + // input control + let input = `` + switch (field.type) { + case 'pass': + case 'password': + input = input.replace('type="text"', 'type="password"') + break + case 'checkbox': { + input = ` + ` + break + } + case 'check': + case 'checks': { + if (field.options.items == null && field.html.items != null) field.options.items = field.html.items + let items = field.options.items + input = '' + // normalized options + if (!Array.isArray(items)) items = [] + if (items.length > 0) { + items = w2utils.normMenu.call(this, items, field) + } + // generate + for (let i = 0; i < items.length; i++) { + input += ` + +
    ` + } + break + } + case 'radio': { + input = '' + // normalized options + if (field.options.items == null && field.html.items != null) field.options.items = field.html.items + let items = field.options.items + if (!Array.isArray(items)) items = [] + if (items.length > 0) { + items = w2utils.normMenu.call(this, items, field) + } + // generate + for (let i = 0; i < items.length; i++) { + input += ` + +
    ` + } + break + } + case 'select': { + input = `' + break + } + case 'textarea': + input = `` + break + case 'toggle': + input = ` +
    ` + break + case 'map': + case 'array': + field.html.key = field.html.key || {} + field.html.value = field.html.value || {} + field.html.tabindex_str = tabindex_str + input = '' + (field.html.text || '') + '' + + ''+ + '
    ' + break + case 'div': + case 'custom': + input = '
    '+ + (field && field.html && field.html.html ? field.html.html : '') + + '
    ' + break + case 'html': + case 'empty': + input = (field && field.html ? (field.html.html || '') + (field.html.text || '') : '') + break + } + if (group !== '') { + if (page != field.html.page || column != field.html.column || (field.html.group && (group != field.html.group))) { + pages[page][column] += '\n
    \n
    ' + group = '' + } + } + if (field.html.group && (group != field.html.group)) { + let collapsible = '' + if (field.html.groupCollapsible) { + collapsible = '' + } + html += '\n
    ' + + '\n
    ' + + collapsible + w2utils.lang(field.html.group) + '
    \n' + + '
    ' + group = field.html.group + } + if (field.html.anchor == null) { + let span = (field.html.span != null ? 'w2ui-span'+ field.html.span : '') + if (field.html.span == -1) span = 'w2ui-span-none' + let label = '' + w2utils.lang(field.type != 'checkbox' ? field.html.label : field.html.text) +'' + if (!field.html.label) label = '' + html += '\n
    '+ + '\n '+ label + + ((field.type === 'empty') ? input : '\n
    '+ input + (field.type != 'array' && field.type != 'map' ? w2utils.lang(field.type != 'checkbox' ? field.html.text : '') : '') + '
    ') + + '\n
    ' + } else { + pages[field.html.page].anchors = pages[field.html.page].anchors || {} + pages[field.html.page].anchors[field.html.anchor] = '
    '+ + ((field.type === 'empty') ? input : '
    '+ w2utils.lang(field.type != 'checkbox' ? field.html.label : field.html.text, true) + input + w2utils.lang(field.type != 'checkbox' ? field.html.text : '') + '
    ') + + '
    ' + } + if (pages[field.html.page] == null) pages[field.html.page] = {} + if (pages[field.html.page][field.html.column] == null) pages[field.html.page][field.html.column] = '' + pages[field.html.page][field.html.column] += html + page = field.html.page + column = field.html.column + } + if (group !== '') pages[page][column] += '\n
    \n
    ' + if (this.tabs.tabs) { + for (let i = 0; i < this.tabs.tabs.length; i++) if (pages[i] == null) pages[i] = [] + } + // buttons if any + let buttons = '' + if (Object.keys(this.actions).length > 0) { + buttons += '\n
    ' + tabindex = this.tabindexBase + this.fields.length + 1 + for (let a in this.actions) { // it is an object + let act = this.actions[a] + let info = { text: '', style: '', 'class': '' } + if (w2utils.isPlainObject(act)) { + if (act.text == null && act.caption != null) { + console.log('NOTICE: form action.caption property is deprecated, please use action.text. Action ->', act) + act.text = act.caption + } + if (act.text) info.text = act.text + if (act.style) info.style = act.style + if (act.class) info.class = act.class + } else { + info.text = a + if (['save', 'update', 'create'].indexOf(a.toLowerCase()) !== -1) info.class = 'w2ui-btn-blue'; else info.class = '' + } + buttons += '\n ' + tabindex++ + } + buttons += '\n
    ' + } + html = '' + for (let p = 0; p < pages.length; p++){ + html += '
    ' + if (!pages[p]) { + console.log(`ERROR: Page ${p} does not exist`) + return false + } + if (pages[p].before) { + html += pages[p].before + } + html += '
    ' + Object.keys(pages[p]).sort().forEach((c, ind) => { + if (c == parseInt(c)) { + html += '
    ' + (pages[p][c] || '') + '\n
    ' + } + }) + html += '\n
    ' + if (pages[p].after) { + html += pages[p].after + } + html += '\n
    ' + // process page anchors + if (pages[p].anchors) { + Object.keys(pages[p].anchors).forEach((key, ind) => { + html = html.replace(key, pages[p].anchors[key]) + }) + } + } + html += buttons + return html + } + toggleGroup(groupName, show) { + let el = query(this.box).find('.w2ui-group-title[data-group="' + w2utils.base64encode(groupName) + '"]') + if (el.length === 0) return + let el_next = query(el.prop('nextElementSibling')) + if (typeof show === 'undefined') { + show = (el_next.css('display') == 'none') + } + if (show) { + el_next.show() + el.find('span').addClass('w2ui-icon-collapse').removeClass('w2ui-icon-expand') + } else { + el_next.hide() + el.find('span').addClass('w2ui-icon-expand').removeClass('w2ui-icon-collapse') + } + } + action(action, event) { + let act = this.actions[action] + let click = act + if (w2utils.isPlainObject(act) && act.onClick) click = act.onClick + // event before + let edata = this.trigger('action', { target: action, action: act, originalEvent: event }) + if (edata.isCancelled === true) return + // default actions + if (typeof click === 'function') click.call(this, event) + // event after + edata.finish() + } + resize() { + let self = this + // event before + let edata = this.trigger('resize', { target: this.name }) + if (edata.isCancelled === true) return + // default behaviour + let header = query(this.box).find(':scope > div .w2ui-form-header') + let toolbar = query(this.box).find(':scope > div .w2ui-form-toolbar') + let tabs = query(this.box).find(':scope > div .w2ui-form-tabs') + let page = query(this.box).find(':scope > div .w2ui-page') + let dpage = query(this.box).find(':scope > div .w2ui-page.page-'+ this.page + ' > div') + let buttons = query(this.box).find(':scope > div .w2ui-buttons') + // if no height, calculate it + let { headerHeight, tbHeight, tabsHeight } = resizeElements() + if (this.autosize) { // we don't need autosize every time + let cHeight = query(this.box).get(0).clientHeight + if (cHeight === 0 || query(this.box).data('autosize') == 'yes') { + query(this.box).css({ + height: headerHeight + tbHeight + tabsHeight + 15 // 15 is extra height + + (page.length > 0 ? w2utils.getSize(dpage, 'height') : 0) + + (buttons.length > 0 ? w2utils.getSize(buttons, 'height') : 0) + + 'px' + }) + query(this.box).data('autosize', 'yes') + } + resizeElements() + } + // event after + edata.finish() + function resizeElements() { + let headerHeight = (self.header !== '' ? w2utils.getSize(header, 'height') : 0) + let tbHeight = (Array.isArray(self.toolbar?.items) && self.toolbar?.items?.length > 0) + ? w2utils.getSize(toolbar, 'height') + : 0 + let tabsHeight = (Array.isArray(self.tabs?.tabs) && self.tabs?.tabs?.length > 0) + ? w2utils.getSize(tabs, 'height') + : 0 + // resize elements + toolbar.css({ top: headerHeight + 'px' }) + tabs.css({ top: headerHeight + tbHeight + 'px' }) + page.css({ + top: headerHeight + tbHeight + tabsHeight + 'px', + bottom: (buttons.length > 0 ? w2utils.getSize(buttons, 'height') : 0) + 'px' + }) + // return some params + return { headerHeight, tbHeight, tabsHeight } + } + } + refresh() { + let time = Date.now() + let self = this + if (!this.box) return + if (!this.isGenerated || !query(this.box).html()) return + // event before + let edata = this.trigger('refresh', { target: this.name, page: this.page, field: arguments[0], fields: arguments }) + if (edata.isCancelled === true) return + let fields = Array.from(this.fields.keys()) + if (arguments.length > 0) { + fields = Array.from(arguments) + .map((fld, ind) => { + if (typeof fld != 'string') console.log('ERROR: Arguments in refresh functions should be field names') + return this.get(fld, true) // get index of field + }) + .filter((fld, ind) => { + if (fld != null) return true; else return false + }) + } else { + // update field.page with page it belongs too + query(this.box).find('input, textarea, select').each(el => { + let name = (query(el).attr('name') != null ? query(el).attr('name') : query(el).attr('id')) + let field = this.get(name) + if (field) { + // find page + let div = query(el).closest('.w2ui-page') + if (div.length > 0) { + for (let i = 0; i < 100; i++) { + if (div.hasClass('page-'+i)) { field.page = i; break } + } + } + } + }) + // default action + query(this.box).find('.w2ui-page').hide() + query(this.box).find('.w2ui-page.page-' + this.page).show() + query(this.box).find('.w2ui-form-header').html(w2utils.lang(this.header)) + // refresh tabs if needed + if (typeof this.tabs === 'object' && Array.isArray(this.tabs.tabs) && this.tabs.tabs.length > 0) { + query(this.box).find('#form_'+ this.name +'_tabs').show() + this.tabs.active = this.tabs.tabs[this.page].id + this.tabs.refresh() + } else { + query(this.box).find('#form_'+ this.name +'_tabs').hide() + } + // refresh tabs if needed + if (typeof this.toolbar === 'object' && Array.isArray(this.toolbar.items) && this.toolbar.items.length > 0) { + query(this.box).find('#form_'+ this.name +'_toolbar').show() + this.toolbar.refresh() + } else { + query(this.box).find('#form_'+ this.name +'_toolbar').hide() + } + } + // refresh values of fields + for (let f = 0; f < fields.length; f++) { + let field = this.fields[fields[f]] + if (field.name == null && field.field != null) field.name = field.field + if (field.field == null && field.name != null) field.field = field.name + field.$el = query(this.box).find(`[name='${String(field.name).replace(/\\/g, '\\\\')}']`) + field.el = field.$el.get(0) + if (field.el) field.el.id = field.name + // TODO: check + if (field.w2field) { + field.w2field.reset() + } + field.$el + .off('.w2form') + .on('change.w2form', function(event) { + let value = self.getFieldValue(field.field) + // clear error class + if (['enum', 'file'].includes(field.type)) { + let helper = field.el._w2field?.helpers?.multi + query(helper).removeClass('w2ui-error') + } + if (this._previous != null) { + value.previous = this._previous + delete this._previous + } + // event before + let edata2 = self.trigger('change', { target: this.name, field: this.name, value, originalEvent: event }) + if (edata2.isCancelled === true) return + // default behavior + self.setValue(this.name, value.current) + // event after + edata2.finish() + }) + .on('input.w2form', function(event) { + // remember original + if (self.original == null) { + if (Object.keys(self.record).length > 0) { + self.original = w2utils.clone(self.record) + } else { + self.original = {} + } + } + let value = self.getFieldValue(field.field) + // save previous for change event + if (this._previous == null) { + this._previous = value.previous + } + // event before + let edata2 = self.trigger('input', { target: self.name, value, originalEvent: event }) + if (edata2.isCancelled === true) return + // default action + self.setValue(this.name, value.current) + // event after + edata2.finish() + }) + // required + if (field.required) { + field.$el.closest('.w2ui-field').addClass('w2ui-required') + } else { + field.$el.closest('.w2ui-field').removeClass('w2ui-required') + } + // disabled + if (field.disabled != null) { + if (field.disabled) { + if (field.$el.data('tabIndex') == null) { + field.$el.data('tabIndex', field.$el.prop('tabIndex')) + } + field.$el + .prop('readOnly', true) + .prop('disabled', true) + .prop('tabIndex', -1) + .closest('.w2ui-field') + .addClass('w2ui-disabled') + } else { + field.$el + .prop('readOnly', false) + .prop('disabled', false) + .prop('tabIndex', field.$el.data('tabIndex') ?? field.$el.prop('tabIndex') ?? 0) + .closest('.w2ui-field') + .removeClass('w2ui-disabled') + } + } + // hidden + let tmp = field.el + if (!tmp) tmp = query(this.box).find('#' + field.field) + if (field.hidden) { + query(tmp).closest('.w2ui-field').hide() + } else { + query(tmp).closest('.w2ui-field').show() + } + } + // attach actions on buttons + query(this.box).find('button, input[type=button]').each(el => { + query(el).off('click').on('click', function(event) { + let action = this.value + if (this.id) action = this.id + if (this.name) action = this.name + self.action(action, event) + }) + }) + // init controls with record + for (let f = 0; f < fields.length; f++) { + let field = this.fields[fields[f]] + if (!field.el) continue + if (!field.$el.hasClass('w2ui-input')) field.$el.addClass('w2ui-input') + field.type = String(field.type).toLowerCase() + if (!field.options) field.options = {} + // list type + if (this.LIST_TYPES.includes(field.type)) { + let items = field.options.items + if (items == null) field.options.items = [] + field.options.items = w2utils.normMenu.call(this, items, field) + } + // HTML select + if (field.type == 'select') { + // generate options + let items = field.options.items + let options = '' + items.forEach(item => { + options += `` + }) + field.$el.html(options) + } + // w2fields + if (this.W2FIELD_TYPES.includes(field.type)) { + field.w2field = field.w2field + ?? new w2field(w2utils.extend({}, field.options, { type: field.type })) + field.w2field.render(field.el) + } + // map and arrays + if (['map', 'array'].includes(field.type)) { + // need closure + (function (obj, field) { + let keepFocus + field.el.mapAdd = function(field, div, cnt) { + let attr = (field.disabled ? ' readOnly ' : '') + (field.html.tabindex_str || '') + let html = ` +
    + ${field.type == 'map' + ? ` + ${field.html.key.text || ''} + ` + : '' + } + + ${field.html.value.text || ''} +
    ` + div.append(html) + } + field.el.mapRefresh = function(map, div) { + // generate options + let keys, $k, $v + if (field.type == 'map') { + if (!w2utils.isPlainObject(map)) map = {} + if (map._order == null) map._order = Object.keys(map) + keys = map._order + } + if (field.type == 'array') { + if (!Array.isArray(map)) map = [] + keys = map.map((item, ind) => { return ind }) + } + // delete extra fields (including empty one) + let all = div.find('.w2ui-map-field') + for (let i = all.length-1; i >= keys.length; i--) { + div.find(`div[data-index='${i}']`).remove() + } + for (let ind = 0; ind < keys.length; ind++) { + let key = keys[ind] + let fld = div.find(`div[data-index='${ind}']`) + // add if does not exists + if (fld.length == 0) { + field.el.mapAdd(field, div, ind) + fld = div.find(`div[data-index='${ind}']`) + } + fld.attr('data-key', key) + $k = fld.find('.w2ui-map.key') + $v = fld.find('.w2ui-map.value') + let val = map[key] + if (field.type == 'array') { + let tmp = map.filter((it) => { return it.key == key ? true : false}) + if (tmp.length > 0) val = tmp[0].value + } + $k.val(key) + $v.val(val) + if (field.disabled === true || field.disabled === false) { + $k.prop('readOnly', field.disabled ? true : false) + $v.prop('readOnly', field.disabled ? true : false) + } + } + let cnt = keys.length + let curr = div.find(`div[data-index='${cnt}']`) + // if not disabled - add next if needed + if (curr.length === 0 && (!$k || $k.val() != '' || $v.val() != '') + && !($k && ($k.prop('readOnly') === true || $k.prop('disabled') === true)) + ) { + field.el.mapAdd(field, div, cnt) + } + if (field.disabled === true || field.disabled === false) { + curr.find('.key').prop('readOnly', field.disabled ? true : false) + curr.find('.value').prop('readOnly', field.disabled ? true : false) + } + // attach events + let container = query(field.el).get(0)?.nextSibling // should be div + query(container).find('input.w2ui-map') + .off('.mapChange') + .on('keyup.mapChange', function(event) { + let $div = query(event.target).closest('.w2ui-map-field') + let next = $div.get(0).nextElementSibling + let prev = $div.get(0).previousElementSibling + if (event.keyCode == 13) { + let el = keepFocus ?? next + if (el instanceof HTMLElement) { + let inp = query(el).find('input') + if (inp.length > 0) { + inp.get(0).focus() + } + } + keepFocus = undefined + } + let className = query(event.target).hasClass('key') ? 'key' : 'value' + if (event.keyCode == 38 && prev) { // up key + query(prev).find(`input.${className}`).get(0).select() + event.preventDefault() + } + if (event.keyCode == 40 && next) { // down key + query(next).find(`input.${className}`).get(0).select() + event.preventDefault() + } + }) + .on('keydown.mapChange', function(event) { + if (event.keyCode == 38 || event.keyCode == 40) { + event.preventDefault() + } + }) + .on('input.mapChange', function(event) { + let fld = query(event.target).closest('div') + let cnt = fld.data('index') + let next = fld.get(0).nextElementSibling + // if last one, add new empty + if (fld.find('input').val() != '' && !next) { + field.el.mapAdd(field, div, parseInt(cnt) + 1) + } else if (fld.find('input').val() == '' && next) { + let isEmpty = true + query(next).find('input').each(el => { + if (el.value != '') isEmpty = false + }) + if (isEmpty) { + query(next).remove() + } + } + }) + .on('change.mapChange', function(event) { + // remember original + if (self.original == null) { + if (Object.keys(self.record).length > 0) { + self.original = w2utils.clone(self.record) + } else { + self.original = {} + } + } + // event before + let { current, previous, original } = self.getFieldValue(field.field) + let $cnt = query(event.target).closest('.w2ui-map-container') + if (field.type == 'map') current._order = [] + $cnt.find('.w2ui-map.key').each(el => { current._order.push(el.value) }) + let edata = self.trigger('change', { target: field.field, field: field.field, originalEvent: event, + value: { current, previous, original } + }) + if (edata.isCancelled === true) { + return + } + // delete empty + if (field.type == 'map') { + current._order = current._order.filter(k => k !== '') + delete current[''] + } + if (field.type == 'array') { + current = current.filter(k => k !== '') + } + if (query(event.target).parent().find('input').val() == '') { + keepFocus = event.target + } + self.setValue(field.field, current) + field.el.mapRefresh(current, div) + // event after + edata.finish() + }) + } + })(this, field) + } + // set value to HTML input field + this.setFieldValue(field.field, this.getValue(field.name)) + } + // event after + edata.finish() + this.resize() + return Date.now() - time + } + render(box) { + let time = Date.now() + let self = this + if (typeof box == 'string') box = query(box).get(0) + // event before + let edata = this.trigger('render', { target: this.name, box: box ?? this.box }) + if (edata.isCancelled === true) return + // default action + if (box != null) { + // clean previous box + if (query(this.box).find('#form_'+ this.name +'_form').length > 0) { + query(this.box).removeAttr('name') + .removeClass('w2ui-reset w2ui-form') + .html('') + } + this.box = box + } + if (!this.isGenerated && !this.formHTML) return + if (!this.box) return + // render form + let html = '
    ' + + (this.header !== '' ? '
    ' + w2utils.lang(this.header) + '
    ' : '') + + ' ' + + ' ' + + this.formHTML + + '
    ' + query(this.box).attr('name', this.name) + .addClass('w2ui-reset w2ui-form') + .html(html) + if (query(this.box).length > 0) query(this.box)[0].style.cssText += this.style + w2utils.bindEvents(query(this.box).find('.w2ui-eaction'), this) + // init toolbar regardless it is defined or not + if (typeof this.toolbar.render !== 'function') { + this.toolbar = new w2toolbar(w2utils.extend({}, this.toolbar, { name: this.name +'_toolbar', owner: this })) + this.toolbar.on('click', function(event) { + let edata = self.trigger('toolbar', { target: event.target, originalEvent: event }) + if (edata.isCancelled === true) return + // no default action + edata.finish() + }) + } + if (typeof this.toolbar === 'object' && typeof this.toolbar.render === 'function') { + this.toolbar.render(query(this.box).find('#form_'+ this.name +'_toolbar')[0]) + } + // init tabs regardless it is defined or not + if (typeof this.tabs.render !== 'function') { + this.tabs = new w2tabs(w2utils.extend({}, this.tabs, { name: this.name +'_tabs', owner: this, active: this.tabs.active })) + this.tabs.on('click', function(event) { + self.goto(this.get(event.target, true)) + }) + } + if (typeof this.tabs === 'object' && typeof this.tabs.render === 'function') { + this.tabs.render(query(this.box).find('#form_'+ this.name +'_tabs')[0]) + if (this.tabs.active) this.tabs.click(this.tabs.active) + } + // event after + edata.finish() + // after render actions + this.resize() + let url = (typeof this.url !== 'object' ? this.url : this.url.get) + if (url && this.recid != null) { + this.request().catch(error => this.refresh()) // even if there was error, still need refresh + } else { + this.refresh() + } + // observe div resize + this.last.observeResize = new ResizeObserver(() => { this.resize() }) + this.last.observeResize.observe(this.box) + // focus on load + if (this.focus != -1) { + let setCount = 0 + let setFocus = () => { + if (query(self.box).find('input, select, textarea').length > 0) { + self.setFocus() + } else { + setCount++ + if (setCount < 20) setTimeout(setFocus, 50) // 1 sec max + } + } + setFocus() + } + return Date.now() - time + } + setFocus(focus) { + if (typeof focus === 'undefined'){ + // no argument - use form's focus property + focus = this.focus + } + let $input + // focus field by index + if (w2utils.isInt(focus)){ + if (focus < 0) { + return + } + let inputs = query(this.box) + .find('div:not(.w2ui-field-helper) > input, select, textarea, div > label:nth-child(1) > [type=radio]') + .filter(':not(.file-input)') + // find visible (offsetParent == null for any element is not visible) + while (inputs[focus].offsetParent == null && inputs.length >= focus) { + focus++ + } + if (inputs[focus]) { + $input = query(inputs[focus]) + } + } else if (typeof focus === 'string') { + // focus field by name + $input = query(this.box).find(`[name='${focus}']`) + } + if ($input.length > 0){ + $input.get(0).focus() + } + return $input + } + destroy() { + // event before + let edata = this.trigger('destroy', { target: this.name }) + if (edata.isCancelled === true) return + // clean up + if (typeof this.toolbar === 'object' && this.toolbar.destroy) this.toolbar.destroy() + if (typeof this.tabs === 'object' && this.tabs.destroy) this.tabs.destroy() + if (query(this.box).find('#form_'+ this.name +'_tabs').length > 0) { + query(this.box) + .removeAttr('name') + .removeClass('w2ui-reset w2ui-form') + .html('') + } + this.last.observeResize?.disconnect() + delete w2ui[this.name] + // event after + edata.finish() + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: mQuery, w2utils, w2base, w2tooltip, w2color, w2menu, w2date + * + * == TODO == + * - upload (regular files) + * - BUG with prefix/postfix and arrows (test in different contexts) + * - multiple date selection + * - month selection, year selections + * - MultiSelect - Allow Copy/Paste for single and multi values + * - add routeData to list/enum + * - ENUM, LIST: should have same as grid (limit, offset, search, sort) + * - ENUM, LIST: should support wild chars + * - add selection of predefined times (used for appointments) + * - options.items - can be an array + * - options.msgNoItems - can be a function + * - REMOTE fields + * + * == 2.0 changes + * - removed jQuery dependency + * - enum options.autoAdd + * - [numeric, date] - options.autoCorrect to enforce range and validity + * - silent only left for files, removed form the rest + * - remote source response items => records or just an array + * - deprecated "success" field for remote source response + * - CSP - fixed inline events + * - remove clear, use reset instead + * - options.msgSearch + * - options.msgNoItems + */ + +class w2field extends w2base { + constructor(type, options) { + super() + // sanitization + if (typeof type == 'string' && options == null) { + options = { type: type } + } + if (typeof type == 'object' && options == null) { + options = w2utils.clone(type) + } + if (typeof type == 'string' && typeof options == 'object') { + options.type = type + } + options.type = String(options.type).toLowerCase() + this.el = options.el ?? null + this.selected = null + this.helpers = {} // object or helper elements + this.type = options.type ?? 'text' + this.options = w2utils.clone(options) + this.onSearch = options.onSearch ?? null + this.onRequest = options.onRequest ?? null + this.onLoad = options.onLoad ?? null + this.onError = options.onError ?? null + this.onClick = options.onClick ?? null + this.onAdd = options.onAdd ?? null + this.onNew = options.onNew ?? null + this.onRemove = options.onRemove ?? null + this.onMouseEnter= options.onMouseEnter ?? null + this.onMouseLeave= options.onMouseLeave ?? null + this.onScroll = options.onScroll ?? null + this.tmp = {} // temp object + // clean up some options + delete this.options.type + delete this.options.onSearch + delete this.options.onRequest + delete this.options.onLoad + delete this.options.onError + delete this.options.onClick + delete this.options.onMouseEnter + delete this.options.onMouseLeave + delete this.options.onScroll + if (this.el) { + this.render(this.el) + } + } + render(el) { + if (!(el instanceof HTMLElement)) { + console.log('ERROR: Cannot init w2field on empty subject') + return + } + if (el._w2field) { + el._w2field.reset() + } else { + el._w2field = this + } + this.el = el + this.init() + } + init() { + let options = this.options + let defaults + // only for INPUT or TEXTAREA + if (!['INPUT', 'TEXTAREA'].includes(this.el.tagName.toUpperCase())) { + console.log('ERROR: w2field could only be applied to INPUT or TEXTAREA.', this.el) + return + } + switch (this.type) { + case 'text': + case 'int': + case 'float': + case 'money': + case 'currency': + case 'percent': + case 'alphanumeric': + case 'bin': + case 'hex': + defaults = { + min: null, + max: null, + step: 1, + autoFormat: true, + autoCorrect: true, + currencyPrefix: w2utils.settings.currencyPrefix, + currencySuffix: w2utils.settings.currencySuffix, + currencyPrecision: w2utils.settings.currencyPrecision, + decimalSymbol: w2utils.settings.decimalSymbol, + groupSymbol: w2utils.settings.groupSymbol, + arrow: false, + keyboard: true, + precision: null, + prefix: '', + suffix: '' + } + this.options = w2utils.extend({}, defaults, options) + options = this.options // since object is re-created, need to re-assign + options.numberRE = new RegExp('['+ options.groupSymbol + ']', 'g') + options.moneyRE = new RegExp('['+ options.currencyPrefix + options.currencySuffix + options.groupSymbol +']', 'g') + options.percentRE = new RegExp('['+ options.groupSymbol + '%]', 'g') + // no keyboard support needed + if (['text', 'alphanumeric', 'hex', 'bin'].includes(this.type)) { + options.arrow = false + options.keyboard = false + } + break + case 'color': + defaults = { + prefix : '#', + suffix : `
     
    `, + arrow : false, + advanced : null, // open advanced by default + transparent : true + } + this.options = w2utils.extend({}, defaults, options) + options = this.options // since object is re-created, need to re-assign + break + case 'date': + defaults = { + format : w2utils.settings.dateFormat, // date format + keyboard : true, + autoCorrect : true, + start : null, + end : null, + blockDates : [], // array of blocked dates + blockWeekdays : [], // blocked weekdays 0 - sunday, 1 - monday, etc + colored : {}, // ex: { '3/13/2022': 'bg-color|text-color' } + btnNow : true + } + this.options = w2utils.extend({ type: 'date' }, defaults, options) + options = this.options // since object is re-created, need to re-assign + if (query(this.el).attr('placeholder') == null) { + query(this.el).attr('placeholder', options.format) + } + break + case 'time': + defaults = { + format : w2utils.settings.timeFormat, + keyboard : true, + autoCorrect : true, + start : null, + end : null, + btnNow : true, + noMinutes : false + } + this.options = w2utils.extend({ type: 'time' }, defaults, options) + options = this.options // since object is re-created, need to re-assign + if (query(this.el).attr('placeholder') == null) { + query(this.el).attr('placeholder', options.format) + } + break + case 'datetime': + defaults = { + format : w2utils.settings.dateFormat + '|' + w2utils.settings.timeFormat, + keyboard : true, + autoCorrect : true, + start : null, + end : null, + startTime : null, + endTime : null, + blockDates : [], // array of blocked dates + blockWeekdays : [], // blocked weekdays 0 - sunday, 1 - monday, etc + colored : {}, // ex: { '3/13/2022': 'bg-color|text-color' } + btnNow : true, + noMinutes : false + } + this.options = w2utils.extend({ type: 'datetime' }, defaults, options) + options = this.options // since object is re-created, need to re-assign + if (query(this.el).attr('placeholder') == null) { + query(this.el).attr('placeholder', options.placeholder || options.format) + } + break + case 'list': + case 'combo': + defaults = { + items : [], + selected : {}, + url : null, // url to pull data from // TODO: implement + recId : null, // map retrieved data from url to id, can be string or function + recText : null, // map retrieved data from url to text, can be string or function + method : null, // default httpMethod + interval : 350, // number of ms to wait before sending server call on search + postData : {}, + minLength : 1, // min number of chars when trigger search + cacheMax : 250, + maxDropHeight : 350, // max height for drop down menu + maxDropWidth : null, // if null then auto set + minDropWidth : null, // if null then auto set + match : 'begins', // ['contains', 'is', 'begins', 'ends'] + icon : null, + iconStyle : '', + align : 'both', // same width as control + altRows : true, // alternate row color + renderDrop : null, // render function for drop down item + compare : null, // compare function for filtering + filter : true, // weather to filter at all + hideSelected : false, // hide selected item from drop down + prefix : '', + suffix : '', + msgNoItems : 'No matches', + msgSearch : 'Type to search...', + openOnFocus : false, // if to show overlay onclick or when typing + markSearch : false, + onSearch : null, // when search needs to be performed + onRequest : null, // when request is submitted + onLoad : null, // when data is received + onError : null // when data fails to load due to server error or other failure modes + } + if (typeof options.items == 'function') { + options._items_fun = options.items + } + // need to be first + options.items = w2utils.normMenu.call(this, options.items) + if (this.type === 'list') { + // defaults.search = (options.items && options.items.length >= 10 ? true : false); + query(this.el).addClass('w2ui-select') + // if simple value - look it up + if (!w2utils.isPlainObject(options.selected) && Array.isArray(options.items)) { + options.items.forEach(item => { + if (item && item.id === options.selected) { + options.selected = w2utils.clone(item) + } + }) + } + } + options = w2utils.extend({}, defaults, options) + this.options = options + if (!w2utils.isPlainObject(options.selected)) options.selected = {} + this.selected = options.selected + query(this.el) + .attr('autocapitalize', 'off') + .attr('autocomplete', 'off') + .attr('autocorrect', 'off') + .attr('spellcheck', 'false') + if (options.selected.text != null) { + query(this.el).val(options.selected.text) + } + break + case 'enum': + defaults = { + items : [], // id, text, tooltip, icon + selected : [], + max : 0, // max number of selected items, 0 - unlimited + url : null, // not implemented + recId : null, // map retrieved data from url to id, can be string or function + recText : null, // map retrieved data from url to text, can be string or function + interval : 350, // number of ms to wait before sending server call on search + method : null, // default httpMethod + postData : {}, + minLength : 1, // min number of chars when trigger search + cacheMax : 250, + maxItemWidth : 250, // max width for a single item + maxDropHeight : 350, // max height for drop down menu + maxDropWidth : null, // if null then auto set + match : 'contains', // ['contains', 'is', 'begins', 'ends'] + align : '', // align drop down related to search field + altRows : true, // alternate row color + openOnFocus : false, // if to show overlay onclick or when typing + markSearch : false, + renderDrop : null, // render function for drop down item + renderItem : null, // render selected item + compare : null, // compare function for filtering + filter : true, // alias for compare + hideSelected : true, // hide selected item from drop down + style : '', // style for container div + msgNoItems : 'No matches', + msgSearch : 'Type to search...', + onSearch : null, // when search needs to be performed + onRequest : null, // when request is submitted + onLoad : null, // when data is received + onError : null, // when data fails to load due to server error or other failure modes + onClick : null, // when an item is clicked + onAdd : null, // when an item is added + onNew : null, // when new item should be added + onRemove : null, // when an item is removed + onMouseEnter : null, // when an item is mouse over + onMouseLeave : null, // when an item is mouse out + onScroll : null // when div with selected items is scrolled + } + options = w2utils.extend({}, defaults, options, { suffix: '' }) + if (typeof options.items == 'function') { + options._items_fun = options.items + } + options.items = w2utils.normMenu.call(this, options.items) + options.selected = w2utils.normMenu.call(this, options.selected) + this.options = options + if (!Array.isArray(options.selected)) options.selected = [] + this.selected = options.selected + break + case 'file': + defaults = { + selected : [], + max : 0, + maxSize : 0, // max size of all files, 0 - unlimited + maxFileSize : 0, // max size of a single file, 0 -unlimited + maxItemWidth : 250, // max width for a single item + maxDropHeight : 350, // max height for drop down menu + maxDropWidth : null, // if null then auto set + readContent : true, // if true, it will readAsDataURL content of the file + silent : true, + align : 'both', // same width as control + altRows : true, // alternate row color + renderItem : null, // render selected item + style : '', // style for container div + onClick : null, // when an item is clicked + onAdd : null, // when an item is added + onRemove : null, // when an item is removed + onMouseEnter : null, // when an item is mouse over + onMouseLeave : null // when an item is mouse out + } + options = w2utils.extend({}, defaults, options) + this.options = options + if (!Array.isArray(options.selected)) options.selected = [] + this.selected = options.selected + if (query(this.el).attr('placeholder') == null) { + query(this.el).attr('placeholder', w2utils.lang('Attach files by dragging and dropping or Click to Select')) + } + break + } + // attach events + query(this.el) + .css('box-sizing', 'border-box') + .addClass('w2field w2ui-input') + .off('.w2field') + .on('change.w2field', (event) => { this.change(event) }) + .on('click.w2field', (event) => { this.click(event) }) + .on('focus.w2field', (event) => { this.focus(event) }) + .on('blur.w2field', (event) => { if (this.type !== 'list') this.blur(event) }) + .on('keydown.w2field', (event) => { this.keyDown(event) }) + .on('keyup.w2field', (event) => { this.keyUp(event) }) + // suffix and prefix need to be after styles + this.addPrefix() // only will add if needed + this.addSuffix() // only will add if needed + this.addSearch() + this.addMultiSearch() + // this.refresh() // do not call refresh, on change will trigger refresh (for list at list) + // format initial value + this.change(new Event('change')) + } + get() { + let ret + if (['list', 'enum', 'file'].indexOf(this.type) !== -1) { + ret = this.selected + } else { + ret = query(this.el).val() + } + return ret + } + set(val, append) { + if (['list', 'enum', 'file'].indexOf(this.type) !== -1) { + if (this.type !== 'list' && append) { + if (!Array.isArray(this.selected)) this.selected = [] + this.selected.push(val) + // update selected array in overlay + let overlay = w2menu.get(this.el.id + '_menu') + if (overlay) overlay.options.selected = this.selected + query(this.el).trigger('input').trigger('change') + } else { + if (val == null) val = [] + let it = (this.type === 'enum' && !Array.isArray(val) ? [val] : val) + this.selected = it + query(this.el).trigger('input').trigger('change') + } + this.refresh() + } else { + query(this.el).val(val) + } + } + setIndex(ind, append) { + if (['list', 'enum'].indexOf(this.type) !== -1) { + let items = this.options.items + if (items && items[ind]) { + if (this.type == 'list') { + this.selected = items[ind] + } + if (this.type == 'enum') { + if (!append) this.selected = [] + this.selected.push(items[ind]) + } + let overlay = w2menu.get(this.el.id + '_menu') + if (overlay) overlay.options.selected = this.selected + query(this.el).trigger('input').trigger('change') + this.refresh() + return true + } + } + return false + } + refresh() { + let options = this.options + let time = Date.now() + let styles = getComputedStyle(this.el) + // enum + if (this.type == 'list') { + query(this.el).parent().css('white-space', 'nowrap') // needs this for arrow always to appear on the right side + // hide focus and show text + if (this.helpers.prefix) this.helpers.prefix.hide() + if (!this.helpers.search) return + // if empty show no icon + if (this.selected == null && options.icon) { + options.prefix = ` + + ` + this.addPrefix() + } else { + options.prefix = '' + this.addPrefix() + } + // focus helper + let focus = query(this.helpers.search_focus) + let icon = query(focus[0].previousElementSibling) + focus.css({ outline: 'none' }) + if (focus.val() === '') { + focus.css('opacity', 0) + icon.css('opacity', 0) + if (this.selected?.id) { + let text = this.selected.text + let ind = this.findItemIndex(options.items, this.selected.id) + if (text != null) { + query(this.el) + .val(w2utils.lang(text)) + .data({ + selected: text, + selectedIndex: ind[0] + }) + } + } else { + this.el.value = '' + query(this.el).removeData('selected selectedIndex') + } + } else { + focus.css('opacity', 1) + icon.css('opacity', 1) + query(this.el).val('') + setTimeout(() => { + if (this.helpers.prefix) this.helpers.prefix.hide() + if (options.icon) { + focus.css('margin-left', '17px') + query(this.helpers.search).find('.w2ui-icon-search') + .addClass('show-search') + } else { + focus.css('margin-left', '0px') + query(this.helpers.search).find('.w2ui-icon-search') + .removeClass('show-search') + } + }, 1) + } + // if readonly or disabled + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) { + setTimeout(() => { + if (this.helpers.prefix) query(this.helpers.prefix).css('opacity', '0.6') + if (this.helpers.suffix) query(this.helpers.suffix).css('opacity', '0.6') + }, 1) + } else { + setTimeout(() => { + if (this.helpers.prefix) query(this.helpers.prefix).css('opacity', '1') + if (this.helpers.suffix) query(this.helpers.suffix).css('opacity', '1') + }, 1) + } + } + let div = this.helpers.multi + if (['enum', 'file'].includes(this.type) && div) { + let html = '' + if (Array.isArray(this.selected)) { + this.selected.forEach((it, ind) => { + if (it == null) return + html += ` +
    + ${ + typeof options.renderItem === 'function' + ? options.renderItem(it, ind, `
      
    `) + : ` + ${it.icon ? `` : ''} +
      
    + ${(this.type === 'enum' ? it.text : it.name) ?? it.id ?? it } + ${it.size ? ` - ${w2utils.formatSize(it.size)}` : ''} + ` + } +
    ` + }) + } + let ul = div.find('.w2ui-multi-items') + if (options.style) { + div.attr('style', div.attr('style') + ';' + options.style) + } + query(this.el).css('z-index', '-1') + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) { + setTimeout(() => { + div[0].scrollTop = 0 // scroll to the top + div.addClass('w2ui-readonly') + .find('.li-item').css('opacity', '0.9') + .parent().find('.li-search').hide() + .find('input').prop('readOnly', true) + .closest('.w2ui-multi-items') + .find('.w2ui-list-remove').hide() + }, 1) + } else { + setTimeout(() => { + div.removeClass('w2ui-readonly') + .find('.li-item').css('opacity', '1') + .parent().find('.li-search').show() + .find('input').prop('readOnly', false) + .closest('.w2ui-multi-items') + .find('.w2ui-list-remove').show() + }, 1) + } + // clean + if (this.selected?.length > 0) { + query(this.el).attr('placeholder', '') + } + div.find('.w2ui-enum-placeholder').remove() + ul.find('.li-item').remove() + // add new list + if (html !== '') { + ul.prepend(html) + } else if (query(this.el).attr('placeholder') != null && div.find('input').val() === '') { + let style = w2utils.stripSpaces(` + padding-top: ${styles['padding-top']}; + padding-left: ${styles['padding-left']}; + box-sizing: ${styles['box-sizing']}; + line-height: ${styles['line-height']}; + font-size: ${styles['font-size']}; + font-family: ${styles['font-family']}; + `) + div.prepend(`
    ${query(this.el).attr('placeholder')}
    `) + } + // ITEMS events + div.off('.w2item') + .on('scroll.w2item', (event) => { + let edata = this.trigger('scroll', { target: this.el, originalEvent: event }) + if (edata.isCancelled === true) return + // hide tooltip if any + w2tooltip.hide(this.el.id + '_preview') + // event after + edata.finish() + }) + .find('.li-item') + .on('click.w2item', (event) => { + let target = query(event.target).closest('.li-item') + let index = target.attr('index') + let item = this.selected[index] + if (query(target).hasClass('li-search')) return + event.stopPropagation() + let edata + // default behavior + if (query(event.target).hasClass('w2ui-list-remove')) { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + // trigger event + edata = this.trigger('remove', { target: this.el, originalEvent: event, item }) + if (edata.isCancelled === true) return + // default behavior + this.selected.splice(index, 1) + query(this.el).trigger('input').trigger('change') + query(event.target).remove() + } else { + // trigger event + edata = this.trigger('click', { target: this.el, originalEvent: event.originalEvent, item }) + if (edata.isCancelled === true) return + // if file - show image preview + let preview = item.tooltip + if (this.type === 'file') { + if ((/image/i).test(item.type)) { // image + preview = ` +
    + +
    ` + } + preview += ` +
    +
    ${w2utils.lang('Name')}:
    +
    ${item.name}
    +
    ${w2utils.lang('Size')}:
    +
    ${w2utils.formatSize(item.size)}
    +
    ${w2utils.lang('Type')}:
    +
    ${item.type}
    +
    ${w2utils.lang('Modified')}:
    +
    ${w2utils.date(item.modified)}
    +
    ` + } + if (preview) { + let name = this.el.id + '_preview' + w2tooltip.show({ + name, + anchor: target.get(0), + html: preview, + hideOn: ['doc-click'], + class: '' + }) + .show((event) => { + let $img = query(`#w2overlay-${name} img`) + $img.on('load', function (event) { + let w = this.clientWidth + let h = this.clientHeight + if (w < 300 & h < 300) return + if (w >= h && w > 300) query(this).css('width', '300px') + if (w < h && h > 300) query(this).css('height', '300px') + }) + .on('error', function (event) { + this.style.display = 'none' + }) + }) + } + edata.finish() + } + }) + .on('mouseenter.w2item', (event) => { + let target = query(event.target).closest('.li-item') + if (query(target).hasClass('li-search')) return + let item = this.selected[query(event.target).attr('index')] + // trigger event + let edata = this.trigger('mouseEnter', { target: this.el, originalEvent: event, item }) + if (edata.isCancelled === true) return + // event after + edata.finish() + }) + .on('mouseleave.w2item', (event) => { + let target = query(event.target).closest('.li-item') + if (query(target).hasClass('li-search')) return + let item = this.selected[query(event.target).attr('index')] + // trigger event + let edata = this.trigger('mouseLeave', { target: this.el, originalEvent: event, item }) + if (edata.isCancelled === true) return + // event after + edata.finish() + }) + // update size for enum, hide for file + if (this.type === 'enum') { + let search = this.helpers.multi.find('input') + search.css({ width: '15px' }) + } else { + this.helpers.multi.find('.li-search').hide() + } + this.resize() + } + return Date.now() - time + } + // resizing width of list, enum, file controls + resize() { + let width = this.el.clientWidth + // let height = this.el.clientHeight + // if (this.tmp.current_width == width && height > 0) return + let styles = getComputedStyle(this.el) + let focus = this.helpers.search + let multi = this.helpers.multi + let suffix = this.helpers.suffix + let prefix = this.helpers.prefix + // resize helpers + if (focus) { + query(focus).css('width', width) + } + if (multi) { + query(multi).css('width', width - parseInt(styles['margin-left'], 10) - parseInt(styles['margin-right'], 10)) + } + if (suffix) { + this.addSuffix() + } + if (prefix) { + this.addPrefix() + } + // enum or file + let div = this.helpers.multi + if (['enum', 'file'].includes(this.type) && div) { + // adjust height + query(this.el).css('height', 'auto') + let cntHeight = query(div).find(':scope div.w2ui-multi-items').get(0).clientHeight + 5 + if (cntHeight < 20) cntHeight = 20 + // max height + if (cntHeight > this.tmp['max-height']) { + cntHeight = this.tmp['max-height'] + } + // min height + if (cntHeight < this.tmp['min-height']) { + cntHeight = this.tmp['min-height'] + } + let inpHeight = w2utils.getSize(this.el, 'height') - 2 + if (inpHeight > cntHeight) cntHeight = inpHeight + query(div).css({ + 'height': cntHeight + 'px', + overflow: (cntHeight == this.tmp['max-height'] ? 'auto' : 'hidden') + }) + query(div).css('height', cntHeight + 'px') + query(this.el).css({ 'height': cntHeight + 'px' }) + } + // remember width + this.tmp.current_width = width + } + reset() { + // restore paddings + if (this.tmp != null) { + query(this.el).css('height', 'auto') + Array('padding-left', 'padding-right', 'background-color', 'border-color').forEach(prop => { + if (this.tmp && this.tmp['old-'+ prop] != null) { + query(this.el).css(prop, this.tmp['old-' + prop]) + delete this.tmp['old-' + prop] + } + }) + // remove resize watcher + clearInterval(this.tmp.sizeTimer) + } + // remove events and (data) + query(this.el) + .val(this.clean(query(this.el).val())) + .removeClass('w2field') + .removeData('selected selectedIndex') + .off('.w2field') // remove only events added by w2field + // remove helpers + Object.keys(this.helpers).forEach(key => { + query(this.helpers[key]).remove() + }) + this.helpers = {} + } + clean(val) { + // issue #499 + if (typeof val === 'number'){ + return val + } + let options = this.options + val = String(val).trim() + // clean + if (['int', 'float', 'money', 'currency', 'percent'].includes(this.type)) { + if (typeof val === 'string') { + if (options.autoFormat) { + if (['money', 'currency'].includes(this.type)) { + val = String(val).replace(options.moneyRE, '') + } + if (this.type === 'percent') { + val = String(val).replace(options.percentRE, '') + } + if (['int', 'float'].includes(this.type)) { + val = String(val).replace(options.numberRE, '') + } + } + val = val.replace(/\s+/g, '') + .replace(new RegExp(options.groupSymbol, 'g'), '') + .replace(options.decimalSymbol, '.') + } + if (val !== '' && w2utils.isFloat(val)) val = Number(val); else val = '' + } + return val + } + format(val) { + let options = this.options + // auto format numbers or money + if (options.autoFormat && val !== '') { + switch (this.type) { + case 'money': + case 'currency': + val = w2utils.formatNumber(val, options.currencyPrecision, true) + if (val !== '') val = options.currencyPrefix + val + options.currencySuffix + break + case 'percent': + val = w2utils.formatNumber(val, options.precision, true) + if (val !== '') val += '%' + break + case 'float': + val = w2utils.formatNumber(val, options.precision, true) + break + case 'int': + val = w2utils.formatNumber(val, 0, true) + break + } + // if default group symbol does not match - replase it + let group = parseInt(1000).toLocaleString(w2utils.settings.locale, { useGrouping: true }).slice(1, 2) + if (group !== this.options.groupSymbol) { + val = val.replaceAll(group, this.options.groupSymbol) + } + } + return val + } + change(event) { + // numeric + if (['int', 'float', 'money', 'currency', 'percent'].indexOf(this.type) !== -1) { + // check max/min + let val = query(this.el).val() + let new_val = this.format(this.clean(query(this.el).val())) + // if was modified + if (val !== '' && val != new_val) { + query(this.el).val(new_val) + // cancel event + event.stopPropagation() + event.preventDefault() + return false + } + } + // color + if (this.type === 'color') { + let color = query(this.el).val() + if (color.substr(0, 3).toLowerCase() !== 'rgb') { + color = '#' + color + let len = query(this.el).val().length + if (len !== 8 && len !== 6 && len !== 3) color = '' + } + let next = query(this.el).get(0).nextElementSibling + query(next).find('div').css('background-color', color) + if (query(this.el).hasClass('has-focus')) { + this.updateOverlay() + } + } + // list, enum + if (['list', 'enum', 'file'].indexOf(this.type) !== -1) { + this.refresh() + } + // date, time + if (['date', 'time', 'datetime'].indexOf(this.type) !== -1) { + // convert linux timestamps + let tmp = parseInt(this.el.value) + if (w2utils.isInt(this.el.value) && tmp > 3000) { + if (this.type === 'time') tmp = w2utils.formatTime(new Date(tmp), this.options.format) + if (this.type === 'date') tmp = w2utils.formatDate(new Date(tmp), this.options.format) + if (this.type === 'datetime') tmp = w2utils.formatDateTime(new Date(tmp), this.options.format) + query(this.el).val(tmp).trigger('input').trigger('change') + } + } + } + click(event) { + // lists + if (['list', 'combo', 'enum'].includes(this.type)) { + if (!query(this.el).hasClass('has-focus')) { + this.focus(event) + } + if (this.type == 'combo') { + this.updateOverlay() + } + // since list has separate search input, in order to keep the overlay open, need to stop + if (this.type == 'list') { + this.updateOverlay() + event.stopPropagation() + } + } + // other fields with drops + if (['date', 'time', 'datetime', 'color'].includes(this.type)) { + this.updateOverlay() + } + } + focus(event) { + if (this.type == 'list' && document.activeElement == this.el) { + this.helpers.search_focus.focus() + return + } + // color, date, time + if (['color', 'date', 'time', 'datetime'].indexOf(this.type) !== -1) { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + this.updateOverlay() + } + // menu + if (['list', 'combo', 'enum'].indexOf(this.type) !== -1) { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) { + // still add focus + query(this.el).addClass('has-focus') + return + } + // regenerate items + if (typeof this.options._items_fun == 'function') { + this.options.items = w2utils.normMenu.call(this, this.options._items_fun) + } + if (this.helpers.search) { + let search = this.helpers.search_focus + search.value = '' + search.select() + } + if (this.type == 'enum') { + // file control in particular need to receive focus after file select + let search = query(this.el.previousElementSibling).find('.li-search input').get(0) + if (document.activeElement !== search) { + search.focus() + } + } + this.resize() + // update overlay if needed + if (event.showMenu !== false && (this.options.openOnFocus !== false || query(this.el).hasClass('has-focus'))) { + setTimeout(() => { this.updateOverlay() }, 100) // execute at the end of event loop + } + } + if (this.type == 'file') { + let prev = query(this.el).get(0).previousElementSibling + query(prev).addClass('has-focus') + } + query(this.el).addClass('has-focus') + } + blur(event) { + let val = query(this.el).val().trim() + query(this.el).removeClass('has-focus') + if (['int', 'float', 'money', 'currency', 'percent'].includes(this.type)) { + if (val !== '') { + let newVal = val + let error = '' + if (!this.isStrValid(val)) { // validity is also checked in blur + newVal = '' + } else { + let rVal = this.clean(val) + if (this.options.min != null && rVal < this.options.min) { + newVal = this.options.min + error = `Should be >= ${this.options.min}` + } + if (this.options.max != null && rVal > this.options.max) { + newVal = this.options.max + error = `Should be <= ${this.options.max}` + } + } + if (this.options.autoCorrect) { + query(this.el).val(newVal).trigger('input').trigger('change') + if (error) { + w2tooltip.show({ + name: this.el.id + '_error', + anchor: this.el, + html: error + }) + setTimeout(() => { w2tooltip.hide(this.el.id + '_error') }, 3000) + } + } + } + } + // date or time + if (['date', 'time', 'datetime'].includes(this.type) && this.options.autoCorrect) { + if (val !== '') { + let check = this.type == 'date' ? w2utils.isDate : + (this.type == 'time' ? w2utils.isTime : w2utils.isDateTime) + if (!w2date.inRange(this.el.value, this.options) + || !check.bind(w2utils)(this.el.value, this.options.format)) { + // if not in range or wrong value - clear it + query(this.el).val('').trigger('input').trigger('change') + } + } + } + // clear search input + if (this.type === 'enum') { + query(this.helpers.multi).find('input').val('').css('width', '15px') + } + if (this.type == 'file') { + let prev = this.el.previousElementSibling + query(prev).removeClass('has-focus') + } + if (this.type === 'list') { + this.el.value = this.selected?.text ?? '' + } + } + keyDown(event, extra) { + let options = this.options + let key = event.keyCode || (extra && extra.keyCode) + let cancel = false + let val, inc, daymil, dt, newValue, newDT + // ignore wrong pressed key + if (['int', 'float', 'money', 'currency', 'percent', 'hex', 'bin', 'color', 'alphanumeric'].includes(this.type)) { + if (!event.metaKey && !event.ctrlKey && !event.altKey) { + if (!this.isStrValid(event.key ?? '1', true) && // valid & is not arrows, dot, comma, etc keys + ![9, 8, 13, 27, 37, 38, 39, 40, 46].includes(event.keyCode)) { + event.preventDefault() + if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true + return false + } + } + } + // numeric + if (['int', 'float', 'money', 'currency', 'percent'].includes(this.type)) { + if (!options.keyboard || query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + val = parseFloat(query(this.el).val().replace(options.moneyRE, '')) || 0 + inc = options.step + if (event.ctrlKey || event.metaKey) inc = options.step * 10 + switch (key) { + case 38: // up + if (event.shiftKey) break // no action if shift key is pressed + newValue = (val + inc <= options.max || options.max == null ? Number((val + inc).toFixed(12)) : options.max) + query(this.el).val(newValue).trigger('input').trigger('change') + cancel = true + break + case 40: // down + if (event.shiftKey) break // no action if shift key is pressed + newValue = (val - inc >= options.min || options.min == null ? Number((val - inc).toFixed(12)) : options.min) + query(this.el).val(newValue).trigger('input').trigger('change') + cancel = true + break + } + if (cancel) { + event.preventDefault() + this.moveCaret2end() + } + } + // date/datetime + if (['date', 'datetime'].includes(this.type)) { + if (!options.keyboard || query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + let is = (this.type == 'date' ? w2utils.isDate : w2utils.isDateTime).bind(w2utils) + let format = (this.type == 'date' ? w2utils.formatDate : w2utils.formatDateTime).bind(w2utils) + daymil = 24*60*60*1000 + inc = 1 + if (event.ctrlKey || event.metaKey) inc = 10 // by month + dt = is(query(this.el).val(), options.format, true) + if (!dt) { dt = new Date(); daymil = 0 } + switch (key) { + case 38: // up + if (event.shiftKey) break // no action if shift key is pressed + if (inc == 10) { + dt.setMonth(dt.getMonth() + 1) + } else { + dt.setTime(dt.getTime() + daymil) + } + newDT = format(dt.getTime(), options.format) + query(this.el).val(newDT).trigger('input').trigger('change') + cancel = true + break + case 40: // down + if (event.shiftKey) break // no action if shift key is pressed + if (inc == 10) { + dt.setMonth(dt.getMonth() - 1) + } else { + dt.setTime(dt.getTime() - daymil) + } + newDT = format(dt.getTime(), options.format) + query(this.el).val(newDT).trigger('input').trigger('change') + cancel = true + break + } + if (cancel) { + event.preventDefault() + this.moveCaret2end() + this.updateOverlay() + } + } + // time + if (this.type === 'time') { + if (!options.keyboard || query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + inc = (event.ctrlKey || event.metaKey ? 60 : 1) + val = query(this.el).val() + let time = w2date.str2min(val) || w2date.str2min((new Date()).getHours() + ':' + ((new Date()).getMinutes() - 1)) + switch (key) { + case 38: // up + if (event.shiftKey) break // no action if shift key is pressed + time += inc + cancel = true + break + case 40: // down + if (event.shiftKey) break // no action if shift key is pressed + time -= inc + cancel = true + break + } + if (cancel) { + event.preventDefault() + query(this.el).val(w2date.min2str(time)).trigger('input').trigger('change') + this.moveCaret2end() + } + } + // list/enum + if (['list', 'enum'].includes(this.type)) { + switch (key) { + case 8: // delete + case 46: // backspace + if (this.type == 'list') { + let search = query(this.helpers.search_focus) + if (search.val() == '') { + this.selected = null + w2menu.hide(this.el.id + '_menu') + query(this.el).val('').trigger('input').trigger('change') + } + } else { + let search = query(this.helpers.multi).find('input') + if (search.val() == '') { + w2menu.hide(this.el.id + '_menu') + this.selected.pop() + // update selected array in overlay + let overlay = w2menu.get(this.el.id + '_menu') + if (overlay) overlay.options.selected = this.selected + this.refresh() + } + } + break + case 9: // tab key + case 16: // shift key (when shift+tab) + break + case 27: // escape + w2menu.hide(this.el.id + '_menu') + this.refresh() + break + default: { + // let overlay = w2menu.get(this.el.id + '_menu') + // if (!overlay && !overlay?.displayed) { + // this.updateOverlay() + // } + } + } + } + } + keyUp(event) { + if (this.type == 'list') { + let search = query(this.helpers.search_focus) + if (search.val() !== '') { + query(this.el).attr('placeholder', '') + } else { + query(this.el).attr('placeholder', this.tmp.pholder) + } + if (event.keyCode == 13) { + setTimeout(() => { + search.val('') + w2menu.hide(this.el.id + '_menu') + this.refresh() + }, 1) + } else { + // tab, shift+tab, esc, delete, backspace + if ([8, 9, 16, 27, 46].includes(event.keyCode)) { + w2menu.hide(this.el.id + '_menu') + } else { + this.updateOverlay() + } + } + this.refresh() + } + if (this.type == 'combo') { + this.updateOverlay() + } + if (this.type == 'enum') { + let search = this.helpers.multi.find('input') + let styles = getComputedStyle(search.get(0)) + let width = w2utils.getStrWidth(search.val(), + `font-family: ${styles['font-family']}; font-size: ${styles['font-size']};`) + search.css({ width: (width + 15) + 'px' }) + this.resize() + } + } + findItemIndex(items, id, parents) { + let inds = [] + if (!parents) parents = [] + items.forEach((item, ind) => { + if (item.id === id) { + inds = parents.concat([ind]) + this.options.index = [ind] + } + if (inds.length == 0 && item.items && item.items.length > 0) { + parents.push(ind) + inds = this.findItemIndex(item.items, id, parents) + parents.pop() + } + }) + return inds + } + updateOverlay(indexOnly) { + let options = this.options + let params + // color + if (this.type === 'color') { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + w2color.show(w2utils.extend({ + name: this.el.id + '_color', + anchor: this.el, + transparent: options.transparent, + advanced: options.advanced, + color: this.el.value, + liveUpdate: true + }, this.options)) + .select(event => { + let color = event.detail.color + query(this.el).val(color).trigger('input').trigger('change') + }) + .liveUpdate(event => { + let color = event.detail.color + query(this.helpers.suffix).find(':scope > div').css('background-color', '#' + color) + }) + } + // list + if (['list', 'combo', 'enum'].includes(this.type)) { + let el = this.el + let input = this.el + if (this.type === 'enum') { + el = this.helpers.multi.get(0) + input = query(el).find('input').get(0) + } + if (this.type === 'list') { + let sel = this.selected + if (w2utils.isPlainObject(sel) && Object.keys(sel).length > 0) { + let ind = this.findItemIndex(options.items, sel.id) + if (ind.length > 0) { + options.index = ind + } + } + input = this.helpers.search_focus + } + if (query(this.el).hasClass('has-focus') && !this.el.readOnly && !this.el.disabled) { + let msgNoItems = w2utils.lang(options.msgNoItems) + if (options.url != null && String(query(input).val()).length < options.minLength && this.tmp.emptySet !== true) { + msgNoItems = w2utils.lang('${count} letters or more...', { count: options.minLength }) + } + if (options.url != null && query(input).val() === '' && this.tmp.emptySet !== true) { + msgNoItems = w2utils.lang(options.msgSearch) + } + // TODO: remote url + // if (options.url == null && options.items.length === 0) msgNoItems = w2utils.lang('Empty list') + // if (options.msgNoItems != null) { + // let eventData = { + // search: query(input).val(), + // options: w2utils.clone(options) + // } + // if (options.url) { + // eventData.remote = { + // url: options.url, + // empty: this.tmp.emptySet ? true : false, + // error: this.tmp.lastError, + // minLength: options.minLength + // } + // } + // msgNoItems = (typeof options.msgNoItems === 'function' + // ? options.msgNoItems(eventData) + // : options.msgNoItems) + // } + // if (this.tmp.lastError) { + // msgNoItems = this.tmp.lastError + // } + // if (msgNoItems) { + // msgNoItems = '
    ' + msgNoItems + '
    ' + // } + params = w2utils.extend({}, options, { + name: this.el.id + '_menu', + anchor: input, + selected: this.selected, + search: false, + render: options.renderDrop, + anchorClass: '', + offsetY: 5, + maxHeight: options.maxDropHeight, // TODO: check + maxWidth: options.maxDropWidth, // TODO: check + minWidth: options.minDropWidth, // TODO: check + msgNoItems: msgNoItems, + }) + this.tmp.overlay = w2menu.show(params) + .select(event => { + if (['list', 'combo'].includes(this.type)) { + this.selected = event.detail.item + query(input).val('') + query(this.el).val(this.selected.text).trigger('input').trigger('change') + this.focus({ showMenu: false }) + } else { + let selected = this.selected + let newItem = event.detail?.item + if (newItem) { + // trigger event + let edata = this.trigger('add', { target: this.el, item: newItem, originalEvent: event }) + if (edata.isCancelled === true) return + // default behavior + if (selected.length >= options.max && options.max > 0) selected.pop() + delete newItem.hidden + selected.push(newItem) + query(this.el).trigger('input').trigger('change') + query(this.helpers.multi).find('input').val('') + // updaet selected array in overlays + let overlay = w2menu.get(this.el.id + '_menu') + if (overlay) overlay.options.selected = this.selected + // event after + edata.finish() + } + } + }) + } + } + // date + if (['date', 'time', 'datetime'].includes(this.type)) { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + w2date.show(w2utils.extend({ + name: this.el.id + '_date', + anchor: this.el, + value: this.el.value, + }, this.options)) + .select(event => { + let date = event.detail.date + if (date != null) { + query(this.el).val(date).trigger('input').trigger('change') + } + }) + } + } + /* + * INTERNAL FUNCTIONS + */ + isStrValid(ch, loose) { + let isValid = true + switch (this.type) { + case 'int': + if (loose && ['-', this.options.groupSymbol].includes(ch)) { + isValid = true + } else { + isValid = w2utils.isInt(ch.replace(this.options.numberRE, '')) + } + break + case 'percent': + ch = ch.replace(/%/g, '') + case 'float': + if (loose && ['-', '', this.options.decimalSymbol, this.options.groupSymbol].includes(ch)) { + isValid = true + } else { + isValid = w2utils.isFloat(ch.replace(this.options.numberRE, '')) + } + break + case 'money': + case 'currency': + if (loose && ['-', this.options.decimalSymbol, this.options.groupSymbol, this.options.currencyPrefix, + this.options.currencySuffix].includes(ch)) { + isValid = true + } else { + isValid = w2utils.isFloat(ch.replace(this.options.moneyRE, '')) + } + break + case 'bin': + isValid = w2utils.isBin(ch) + break + case 'color': + case 'hex': + isValid = w2utils.isHex(ch) + break + case 'alphanumeric': + isValid = w2utils.isAlphaNumeric(ch) + break + } + return isValid + } + addPrefix() { + if (!this.options.prefix) { + return + } + let helper + let styles = getComputedStyle(this.el) + if (this.tmp['old-padding-left'] == null) { + this.tmp['old-padding-left'] = styles['padding-left'] + } + // remove if already displayed + if (this.helpers.prefix) query(this.helpers.prefix).remove() + query(this.el).before(`
    ${this.options.prefix}
    `) + helper = query(this.el).get(0).previousElementSibling + query(helper) + .css({ + 'color' : styles.color, + 'font-family' : styles['font-family'], + 'font-size' : styles['font-size'], + 'height' : this.el.clientHeight + 'px', + 'padding-top' : styles['padding-top'], + 'padding-bottom' : styles['padding-bottom'], + 'padding-left' : this.tmp['old-padding-left'], + 'padding-right' : 0, + 'margin-top' : (parseInt(styles['margin-top'], 10) + 2) + 'px', + 'margin-bottom' : (parseInt(styles['margin-bottom'], 10) + 1) + 'px', + 'margin-left' : styles['margin-left'], + 'margin-right' : 0, + 'z-index' : 1, + }) + // only if visible + query(this.el).css('padding-left', helper.clientWidth + 'px !important') + // remember helper + this.helpers.prefix = helper + } + addSuffix() { + if (!this.options.suffix && !this.options.arrow) { + return + } + let helper + let self = this + let styles = getComputedStyle(this.el) + if (this.tmp['old-padding-right'] == null) { + this.tmp['old-padding-right'] = styles['padding-right'] + } + let pr = parseInt(styles['padding-right'] || 0) + if (this.options.arrow) { + // remove if already displayed + if (this.helpers.arrow) query(this.helpers.arrow).remove() + // add fresh + query(this.el).after( + '
     '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    ') + helper = query(this.el).get(0).nextElementSibling + query(helper).css({ + 'color' : styles.color, + 'font-family' : styles['font-family'], + 'font-size' : styles['font-size'], + 'height' : this.el.clientHeight + 'px', + 'padding' : 0, + 'margin-top' : (parseInt(styles['margin-top'], 10) + 1) + 'px', + 'margin-bottom' : 0, + 'border-left' : '1px solid silver', + 'width' : '16px', + 'transform' : 'translateX(-100%)' + }) + .on('mousedown', function(event) { + if (query(event.target).hasClass('arrow-up')) { + self.keyDown(event, { keyCode: 38 }) + } + if (query(event.target).hasClass('arrow-down')) { + self.keyDown(event, { keyCode: 40 }) + } + }) + pr += helper.clientWidth // width of the control + query(this.el).css('padding-right', pr + 'px !important') + this.helpers.arrow = helper + } + if (this.options.suffix !== '') { + // remove if already displayed + if (this.helpers.suffix) query(this.helpers.suffix).remove() + // add fresh + query(this.el).after(`
    ${this.options.suffix}
    `) + helper = query(this.el).get(0).nextElementSibling + query(helper) + .css({ + 'color' : styles.color, + 'font-family' : styles['font-family'], + 'font-size' : styles['font-size'], + 'height' : this.el.clientHeight + 'px', + 'padding-top' : styles['padding-top'], + 'padding-bottom' : styles['padding-bottom'], + 'padding-left' : 0, + 'padding-right' : styles['padding-right'], + 'margin-top' : (parseInt(styles['margin-top'], 10) + 2) + 'px', + 'margin-bottom' : (parseInt(styles['margin-bottom'], 10) + 1) + 'px', + 'transform' : 'translateX(-100%)' + }) + query(this.el).css('padding-right', helper.clientWidth + 'px !important') + this.helpers.suffix = helper + } + } + // Only used for list + addSearch() { + if (this.type !== 'list') return + // clean up & init + if (this.helpers.search) query(this.helpers.search).remove() + // remember original tabindex + let tabIndex = parseInt(query(this.el).attr('tabIndex')) + if (!isNaN(tabIndex) && tabIndex !== -1) this.tmp['old-tabIndex'] = tabIndex + if (this.tmp['old-tabIndex']) tabIndex = this.tmp['old-tabIndex'] + if (tabIndex == null || isNaN(tabIndex)) tabIndex = 0 + // if there is id, add to search with "_search" + let searchId = '' + if (query(this.el).attr('id') != null) { + searchId = 'id="' + query(this.el).attr('id') + '_search"' + } + // build helper + let html = ` +
    + + +
    ` + query(this.el).attr('tabindex', -1).before(html) + let helper = query(this.el).get(0).previousElementSibling + this.helpers.search = helper + this.helpers.search_focus = query(helper).find('input').get(0) + let styles = getComputedStyle(this.el) + query(helper).css({ + width : this.el.clientWidth + 'px', + 'margin-top' : styles['margin-top'], + 'margin-left' : styles['margin-left'], + 'margin-bottom' : styles['margin-bottom'], + 'margin-right' : styles['margin-right'] + }) + .find('input') + .css({ + cursor : 'default', + width : '100%', + opacity : 1, + padding : styles.padding, + margin : styles.margin, + border : '1px solid transparent', + 'background-color' : 'transparent' + }) + // INPUT events + query(helper).find('input') + .off('.helper') + .on('focus.helper', event => { + query(event.target).val('') + this.tmp.pholder = query(this.el).attr('placeholder') ?? '' + this.focus(event) + event.stopPropagation() + }) + .on('blur.helper', event => { + query(event.target).val('') + if (this.tmp.pholder != null) query(this.el).attr('placeholder', this.tmp.pholder) + this.blur(event) + event.stopPropagation() + }) + .on('keydown.helper', event => { this.keyDown(event) }) + .on('keyup.helper', event => { this.keyUp(event) }) + // MAIN div + query(helper).on('click', event => { + query(event.target).find('input').focus() + }) + } + // Used in enum/file + addMultiSearch() { + if (!['enum', 'file'].includes(this.type)) { + return + } + // clean up & init + query(this.helpers.multi).remove() + // build helper + let html = '' + let styles = getComputedStyle(this.el) + let margin = w2utils.stripSpaces(` + margin-top: 0px; + margin-bottom: 0px; + margin-left: ${styles['margin-left']}; + margin-right: ${styles['margin-right']}; + width: ${(w2utils.getSize(this.el, 'width') - parseInt(styles['margin-left'], 10) + - parseInt(styles['margin-right'], 10))}px; + `) + if (this.tmp['min-height'] == null) { + let min = this.tmp['min-height'] = parseInt((styles['min-height'] != 'none' ? styles['min-height'] : 0) || 0) + let current = parseInt(styles.height) + this.tmp['min-height'] = Math.max(min, current) + } + if (this.tmp['max-height'] == null && styles['max-height'] != 'none') { + this.tmp['max-height'] = parseInt(styles['max-height']) + } + // if there is id, add to search with "_search" + let searchId = '' + if (query(this.el).attr('id') != null) { + searchId = `id="${query(this.el).attr('id')}_search"` + } + // remember original tabindex + let tabIndex = parseInt(query(this.el).attr('tabIndex')) + if (!isNaN(tabIndex) && tabIndex !== -1) this.tmp['old-tabIndex'] = tabIndex + if (this.tmp['old-tabIndex']) tabIndex = this.tmp['old-tabIndex'] + if (tabIndex == null || isNaN(tabIndex)) tabIndex = 0 + if (this.type === 'enum') { + html = ` +
    +
    + +
    +
    ` + } + if (this.type === 'file') { + html = ` +
    +
    + +
    +
    + +
    +
    ` + } + // old bg and border + this.tmp['old-background-color'] = styles['background-color'] + this.tmp['old-border-color'] = styles['border-color'] + query(this.el) + .before(html) + .css({ + 'border-color': 'transparent', + 'background-color': 'transparent' + }) + let div = query(this.el.previousElementSibling) + this.helpers.multi = div + query(this.el).attr('tabindex', -1) + // click anywhere on the field + div.on('click', event => { this.focus(event) }) + // search field + div.find('input:not(.file-input)') + .on('click', event => { this.click(event) }) + .on('focus', event => { this.focus(event) }) + .on('blur', event => { this.blur(event) }) + .on('keydown', event => { this.keyDown(event) }) + .on('keyup', event => { this.keyUp(event) }) + // file input + if (this.type === 'file') { + div.find('input.file-input') + .off('.drag') + .on('click.drag', (event) => { + event.stopPropagation() + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + this.focus(event) + }) + .on('dragenter.drag', (event) => { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + div.addClass('w2ui-file-dragover') + }) + .on('dragleave.drag', (event) => { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + div.removeClass('w2ui-file-dragover') + }) + .on('drop.drag', (event) => { + if (query(this.el).prop('readOnly') || query(this.el).prop('disabled')) return + div.removeClass('w2ui-file-dragover') + let files = Array.from(event.dataTransfer.files) + files.forEach(file => { this.addFile(file) }) + this.focus(event) + // cancel to stop browser behaviour + event.preventDefault() + event.stopPropagation() + }) + .on('dragover.drag', (event) => { + // cancel to stop browser behaviour + event.preventDefault() + event.stopPropagation() + }) + .on('change.drag', (event) => { + if (typeof event.target.files !== 'undefined') { + Array.from(event.target.files).forEach(file => { this.addFile(file) }) + } + this.focus(event) + }) + } + this.refresh() + } + addFile(file) { + let options = this.options + let selected = this.selected + let newItem = { + name : file.name, + type : file.type, + modified : file.lastModifiedDate, + size : file.size, + content : null, + file : file + } + let size = 0 + let cnt = 0 + let errors = [] + if (Array.isArray(selected)) { + selected.forEach(item => { + if (item.name == file.name && item.size == file.size) { + errors.push(w2utils.lang('The file "${name}" (${size}) is already added.', { + name: file.name, size: w2utils.formatSize(file.size) })) + } + size += item.size + cnt++ + }) + } + if (options.maxFileSize !== 0 && newItem.size > options.maxFileSize) { + errors.push(w2utils.lang('Maximum file size is ${size}', { size: w2utils.formatSize(options.maxFileSize) })) + } + if (options.maxSize !== 0 && size + newItem.size > options.maxSize) { + errors.push(w2utils.lang('Maximum total size is ${size}', { size: w2utils.formatSize(options.maxSize) })) + } + if (options.max !== 0 && cnt >= options.max) { + errors.push(w2utils.lang('Maximum number of files is ${count}', { count: options.max })) + } + // trigger event + let edata = this.trigger('add', { target: this.el, file: newItem, total: cnt, totalSize: size, errors }) + if (edata.isCancelled === true) return + // if errors and not silent + if (options.silent !== true && errors.length > 0) { + w2tooltip.show({ + anchor: this.el, + html: 'Errors: ' + errors.join('
    ') + }) + console.log('ERRORS (while adding files): ', errors) + return + } + // check params + selected.push(newItem) + // read file as base64 + if (typeof FileReader !== 'undefined' && options.readContent === true) { + let reader = new FileReader() + let self = this + // need a closure + reader.onload = (function onload() { + return function closure(event) { + let fl = event.target.result + let ind = fl.indexOf(',') + newItem.content = fl.substr(ind + 1) + self.refresh() + query(self.el).trigger('input').trigger('change') + // event after + edata.finish() + } + })() + reader.readAsDataURL(file) + } else { + this.refresh() + query(this.el).trigger('input').trigger('change') + edata.finish() + } + } + // move cursror to end + moveCaret2end() { + setTimeout(() => { + this.el.setSelectionRange(this.el.value.length, this.el.value.length) + }, 0) + } +} +/** + * Part of w2ui 2.0 library + * - Dependencies: jQuery, w2ui.* + * + * This file provided compatibility for projects that conntinue to use jQuery. It extends jQuery with + * w2ui support, such as fn.w2grid, fn.w2form, ... fn.w2render, fn.w2destroy, fn.w2tag, etc + * + * It is not needed for projects that use ES6 module loading. + * + * == 2.0 changes + * - CSP - fixed inline events + */ + +// Register jQuery plugins +(function($) { + // register globals if needed + let w2globals = function() { + (function (win, obj) { + Object.keys(obj).forEach(key => { + win[key] = obj[key] + }) + })(window, { + w2ui, w2utils, query, w2locale, w2event, w2base, + w2popup, w2alert, w2confirm, w2prompt, Dialog, + w2tooltip, w2menu, w2color, w2date, Tooltip, + w2toolbar, w2sidebar, w2tabs, w2layout, w2grid, w2form, w2field + }) + } + // if url has globals at the end, then register globals + let param = String(undefined).split('?')[1] || '' + if (param == 'globals' || param.substr(0, 8) == 'globals=') { + w2globals() + } + // if jQuery is not defined, then exit + if (!$) return + $.w2globals = w2globals + $.fn.w2render = function(name) { + if ($(this).length > 0) { + if (typeof name === 'string' && w2ui[name]) w2ui[name].render($(this)[0]) + if (typeof name === 'object') name.render($(this)[0]) + } + } + $.fn.w2destroy = function(name) { + if (!name && this.length > 0) name = this.attr('name') + if (typeof name === 'string' && w2ui[name]) w2ui[name].destroy() + if (typeof name === 'object') name.destroy() + } + $.fn.w2field = function(type, options) { + // if without arguments - return the object + if (arguments.length === 0) { + let obj = $(this).data('w2field') + return obj + } + return this.each((index, el) => { + let obj = $(el).data('w2field') + // if object is not defined, define it + if (obj == null) { + obj = new w2field(type, options) + obj.render(el) + return obj + } else { // fully re-init + obj = new w2field(type, options) + obj.render(el) + return obj + } + return null + }) + } + $.fn.w2form = function(options) { return proc.call(this, options, 'w2form') } + $.fn.w2grid = function(options) { return proc.call(this, options, 'w2grid') } + $.fn.w2layout = function(options) { return proc.call(this, options, 'w2layout') } + $.fn.w2sidebar = function(options) { return proc.call(this, options, 'w2sidebar') } + $.fn.w2tabs = function(options) { return proc.call(this, options, 'w2tabs') } + $.fn.w2toolbar = function(options) { return proc.call(this, options, 'w2toolbar') } + function proc(options, type) { + if ($.isPlainObject(options)) { + let obj + if (type == 'w2form') { + obj = new w2form(options) + if (this.find('.w2ui-field').length > 0) { + obj.formHTML = this.html() + } + } + if (type == 'w2grid') obj = new w2grid(options) + if (type == 'w2layout') obj = new w2layout(options) + if (type == 'w2sidebar') obj = new w2sidebar(options) + if (type == 'w2tabs') obj = new w2tabs(options) + if (type == 'w2toolbar') obj = new w2toolbar(options) + if ($(this).length !== 0) { + obj.render(this[0]) + } + return obj + } else { + let obj = w2ui[$(this).attr('name')] + if (!obj) return null + if (arguments.length > 0) { + if (obj[options]) obj[options].apply(obj, Array.prototype.slice.call(arguments, 1)) + return this + } else { + return obj + } + } + } + $.fn.w2popup = function(options) { + if (this.length > 0 ) { + w2popup.template(this[0], null, options) + } else if (options.url) { + w2popup.load(options) + } + } + $.fn.w2marker = function() { + let str = Array.from(arguments) + if (Array.isArray(str[0])) str = str[0] + return $(this).each((index, el) => { + w2utils.marker(el, str) + }) + } + $.fn.w2tag = function(text, options) { + return this.each((index, el) => { + if (text == null && options == null) { + w2tooltip.hide() + return + } + if (typeof text == 'object') { + options = text + } else { + options = options ?? {} + options.html = text + } + w2tooltip.show(el, options) + }) + } + $.fn.w2overlay = function(html, options) { + return this.each((index, el) => { + if (html == null && options == null) { + w2tooltip.hide() + return + } + if (typeof html == 'object') { + options = html + } else { + options.html = html + } + Object.assign(options, { + class: 'w2ui-white', + hideOn: ['doc-click'] + }) + w2tooltip.show(el, options) + }) + } + $.fn.w2menu = function(menu, options) { + return this.each((index, el) => { + if (typeof menu == 'object') { + options = menu + } + if (typeof menu == 'object') { + options = menu + } else { + options.items = menu + } + w2menu.show(el, options) + }) + } + $.fn.w2color = function(options, callBack) { + return this.each((index, el) => { + let tooltip = w2color.show(el, options) + if (typeof callBack == 'function') { + tooltip.select(callBack) + } + }) + } +})(window.jQuery) + +// Compatibility with CommonJS and AMD modules +!(function(global, w2ui) { +if (typeof define == 'function' && define.amd) { + return define(() => w2ui) +} +if (typeof exports != 'undefined') { + if (typeof module != 'undefined' && module.exports) { + return exports = module.exports = w2ui + } + global = exports +} +if (global) { + Object.keys(w2ui).forEach(key => { + global[key] = w2ui[key] + }) +} +})(self, { + w2ui, w2utils, query, w2locale, w2event, w2base, + w2popup, w2alert, w2confirm, w2prompt, Dialog, + w2tooltip, w2menu, w2color, w2date, Tooltip, + w2toolbar, w2sidebar, w2tabs, w2layout, w2grid, w2form, w2field +}) \ No newline at end of file diff --git a/lib/w2ui/w2ui.min.css b/lib/w2ui/w2ui.min.css new file mode 100644 index 0000000..8c7a0c3 --- /dev/null +++ b/lib/w2ui/w2ui.min.css @@ -0,0 +1,2 @@ +/* w2ui 2.0.x (nightly) (12/24/2022, 8:38:11 AM) (c) http://w2ui.com, vitmalina@gmail.com */ +@font-face{font-family:w2ui-font;src:url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff");font-weight:400;font-style:normal}[class*=" w2ui-icon-"]:before,[class^=w2ui-icon-]:before{font-family:w2ui-font;display:block;vertical-align:middle;line-height:1;font-weight:400;font-style:normal;speak:none;text-decoration:inherit;text-transform:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.w2ui-icon-box:before{content:"A"}.w2ui-icon-check:before{content:"B"}.w2ui-icon-colors:before{content:"C"}.w2ui-icon-columns:before{content:"D"}.w2ui-icon-cross:before{content:"E"}.w2ui-icon-drop:before{content:"F"}.w2ui-icon-empty:before{content:"G"}.w2ui-icon-info:before{content:"H"}.w2ui-icon-paste:before{content:"I"}.w2ui-icon-pencil:before{content:"J"}.w2ui-icon-plus:before{content:"K"}.w2ui-icon-reload:before{content:"L"}.w2ui-icon-search:before{content:"M"}.w2ui-icon-settings:before{content:"N"}@font-face{font-family:OpenSans;src:url("data:application/x-font-ttf;charset=utf-8;base64,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KzUAAgCw/hQEdQYUABYAIgA+QB8gBhsUEBARBhEkIxIAERsMFgkDCR5GWQkWAxdGWQMQAD8rABg/KxESADk5GD8/ERIBOTkRMxEzMxEzMTABNjYzMhIREAIjIicjFxYVESMRMxEUByUiBgcVFBYzIBE0JgFYQqpq1/Dx1t56DAQIpqYGAUiomAKaqgEvlAO0WU/+1P71/vT+06EiTT/+NQgA/i40Whu4ySnnxwGw19H//wAC/hQEBgXTAiYAXAAAAQYAarUAAAq0AgErESYAKzU1//8AAAAABRAGtAImACQAAAEHAU0APwFSAAizAhIFJgArNf//AF7/7APNBWICJgBEAAABBgFN9QAACLMCKBEmACs1//8AAAAABRAHNwImACQAAAEHAU4AKwFSAAizAg8FJgArNf//AF7/7APNBeUCJgBEAAABBgFO5AAACLMCJREmACs1//8AAP5CBREFvAImACQAAAAHAVEDoAAA//8AXv5CBAAEWgImAEQAAAAHAVECjwAA//8Aff/sBM8HcwImACYAAAEHAHYBCAFSAAizASAFJgArNf//AHP/7AOLBiECJgBGAAABBgB2RAAACLMBIBEmACs1//8Aff/sBM8HcwImACYAAAEHAUsArAFSAAizASUFJgArNf//AHP/7AOLBiECJgBGAAABBgFL1AAACLMBJREmACs1//8Aff/sBM8HMQImACYAAAEHAU8CGwFSAAizASAFJgArNf//AHP/7AOLBd8CJgBGAAABBwFPAVAAAAAIswEgESYAKzX//wB9/+wEzwdzAiYAJgAAAQcBTADBAVIACLMBIgUmACs1//8Ac//sA6EGIQImAEYAAAEGAUzzAAAIswEiESYAKzX//wDJAAAFWAdzAiYAJwAAAQcBTABYAVIACLMCHQUmACs1//8Ac//sBYEGFAImAEcAAAEHAjgDDAAAAAeyAiMAAD81AP//AC8AAAVIBbYCBgCSAAAAAgBz/+wE0wYUABoAJwBkQDclBhIOAB4eFRkWGRAGBCgpGhUYEBEQR1kVDxEfES8RAwkDEREJEwABDAMJCSJGWQkQAxtGWQMWAD8rABg/KxESADk5GD8SOS9fXl0zKxEAMxg/ERIBFzkRMzMRMzMzETMxMCUjBiMiAhEQEjMyFzMmNTUhNSE1MxUzFSMRIyUyNjU1NCYjIgYVFBYDmglz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AAizAhkRJgArNf//AH3/7AW+B3MCJgAyAAABBwFTARQBUgAKtAMCKwUmACs1Nf//AHP/7ARiBiECJgBSAAABBgFTWgAACrQDAiwRJgArNTUAAgB9/+wG5wXNABQAHwBTQC4YBg8TEx0ADREdBgUgIQ8SSVkPDwALCw5JWQsDCRVJWQkEAxtJWQMSABNJWQASAD8rABg/KwAYPysAGD8rERIAORgvKxESARc5ETMRMxEzMTAhIQYjIAAREAAhMhchFSERIRUhESEBIgAREAAzMjcRJgbn/QBmXP65/p8BXAFAZloDDv2zAif92QJN/ET5/v8BAfdwV1cUAYkBagFoAYYXl/4plv3mBJ3+z/7Z/tf+zSEEdR4AAwBx/+wHHwRaAB4AKgAxAFVALR8IDgIWFiUvFRUcJQgEMjMrKAsoRlkuFkZZAgUOCy4uBRELEBgiBSJGWQAFFgA/MysRADMYPzMSOS8SORI5KysRADMREgEXOREzETMSOTkRMzEwBSAnBgYjIgAREAAzMhYXNjYzMhIVFSESITI2NxUGBgEUFjMyNjU0JiMiBiUiBgchNCYFlv7bfT7Rid/+9AEG64PNPjrAfsnu/ScIAUpeoVdYmPshmKejmZulppUER3+RDAIghBTrdHcBMQEIAQkBLHdycHn+9+Jp/ncjJ5QnIAI509vV0d3V2Niknp6k//8AyQAABM8HcwImADUAAAEHAHYAeQFSAAizAh8FJgArNf//ALAAAAMnBiECJgBVAAABBgB23AAACLMBGhEmACs1//8Ayf47BM8FtgImADUAAAAGAjl9AP//AGD+OwMnBFwCJgBVAAAABwI5/u8AAP//AMkAAATPB3MCJgA1AAABBwFMABsBUgAIswIhBSYAKzX//wCCAAADJwYhAiYAVQAAAQcBTP92AAAACLMBHBEmACs1//8Aav/sBAIHcwImADYAAAEHAHYAUAFSAAizAS4FJgArNf//AGr/7ANzBiECJgBWAAABBgB26gAACLMBLhEmACs1//8Aav/sBAIHcwImADYAAAEHAUv/6gFSAAizATMFJgArNf//AGr/7ANzBiECJgBWAAABBgFLlwAACLMBMxEmACs1//8Aav4UBAIFywImADYAAAAHAHoBJwAA//8Aav4UA3MEXAImAFYAAAAHAHoA1QAA//8Aav/sBAIHcwImADYAAAEH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WONZAlkhWUBZdVm3WfxaTVpuWtNbJ1snWydbJ1snWydbJ1snWydbJ1snWydbJ1snXHFczFzdXOVdbF2nXgteHF4tXjleRV5XXoxew17TXuNfQF+XX+BgMWA6YENgTGB6YJlgqmC7YMtg22FOYZlh7WI7Ypti/mM/Y4Bj1mQsZI9k9GVpZeBmjGcwZzhnQGedZ/ZoL2hnaHloi2kBaQ1pgGnzap1rO2vRbDpsfWy/bQNtM21gbYZtrG6QbxtvgW/fcDFwgnDXcUNxe3G0cgZyVXKocvtzB3MTc1BzjHPNdBB0WHSsdOZ1HnVddaJ13XYddnN2xndCd7l3xXfReAJ4NHg8eG94rXjxeTB5cXmueex6MHpzer97C3tDe3p76HxLfMF9LX01fUZ9V32sffx+RH6Hfsx/FX9Vf5Z/2oAegG+AvYDFgNaA5oD4gQmBEYEZgSqBOoGLgdqB7IH9gg+CIYIzgkSCkILaguuC+4MNgx6DMINBg0mDUYNjg3SDhoOXg6iDuIPKg9uD7YP+hBCEIYRMhHeEiYSbhKeEsoS+hMqFEIVWhZSFnIX2hmSGyYcnh4GH1IgriHmIxIkTiWaJsInvii2KioqSip6Kqoq2isKK04rkivaLCIsaiyyLPotQi2KLdIuJi52Lr4vBi9OL5Yv3jAmMG4wtjEKMVoxijG6Mf4yQjKGMsYzDjNWM54z5jQuNHY0vjUGNVo1qjXuNjI2YjaSNsI28jc2N3o3wjgKOFI4mjjiOSo5cjm6Og46XjqiOuI7JjtmO6o77jwyPHI8ojzSPQI9Mj12Pbo9/j4+PoI+wj8GP0o/jj/OP/5ALkBeQI5A0kEWQVpBmkHKQppDhkR2RapHCkfqSMpJ7ks2S9ZMYkzuTRJODk62T7pROlJOU3pTmlQmVEZVulXqV9pYClg6WcZaBlpGWopaylseW2JbplvqXDJcdly6XP5dKl1uXZ5d5l4GXk5ebl62XtZe9l86X2gAAAAEAAAABGdsfPbV9Xw889QAJCAAAAAAAyTUxiwAAAADVK8zV+5r91QmiCGIAAAAJAAIAAAAAAAAEzQDBAAAAAAQUAAACFAAAAiMAmAM1AIUFKwAzBJMAgwaWAGgF1wBxAcUAhQJeAFICXgA9BGoAVgSTAGgB9gA/ApMAVAIhAJgC8AAUBJMAZgSTALwEkwBkBJMAXgSTACsEkwCFBJMAdQSTAF4EkwBoBJMAagIhAJgCIQA/BJMAaASTAHcEkwBoA28AGwcxAHkFEAAABS8AyQUMAH0F1QDJBHMAyQQhAMkF0wB9BecAyQKqAFQCI/9gBOkAyQQnAMkHOQDJBggAyQY7AH0E0QDJBjsAfQTyAMkEZABqBG0AEgXTALoEwwAAB2gAGwSeAAgEewAABJEAUgKiAKYC8AAXAqIAMwRWADEDlv/8BJ4BiQRzAF4E5wCwA88AcwTnAHMEfQBzArYAHQRiACcE6QCwAgYAogIG/5EEMwCwAgYAsAdxALAE6QCwBNUAcwTnALAE5wBzA0QAsAPRAGoC0wAfBOkApAQCAAAGOQAXBDEAJwQIAAIDvgBSAwgAPQRoAe4DCABIBJMAaAIUAAACIwCYBJMAvgSTAD8EkwB7BJMAHwRoAe4EIQB7BJ4BNQaoAGQC1QBGA/oAUgSTAGgCkwBUBqgAZAQA//oDbQB/BJMAaALHADECxwAhBJ4BiQT0ALAFPQBxAiEAmAHRACUCxwBMAwAAQgP6AFAGPQBLBj0ALgY9ABoDbwAzBRAAAAUQAAAFEAAABRAAAAUQAAAFEAAABvz//gUMAH0EcwDJBHMAyQRzAMkEcwDJAqoAPAKqAFQCqv//AqoAPAXHAC8GCADJBjsAfQY7AH0GOwB9BjsAfQY7AH0EkwCFBjsAfQXTALoF0wC6BdMAugXTALoEewAABOMAyQT6ALAEcwBeBHMAXgRzAF4EcwBeBHMAXgRzAF4G3QBeA88AcwR9AHMEfQBzBH0AcwR9AHMCBv/aAgYAqQIG/7MCBv/sBMUAcQTpALAE1QBzBNUAcwTVAHME1QBzBNUAcwSTAGgE1QBzBOkApATpAKQE6QCkBOkApAQIAAIE5wCwBAgAAgUQAAAEcwBeBRAAAARzAF4FEAAABHMAXgUMAH0DzwBzBQwAfQPPAHMFDAB9A88AcwUMAH0DzwBzBdUAyQTnAHMFxwAvBOcAcwRzAMkEfQBzBHMAyQR9AHMEcwDJBH0AcwRzAMkEfQBzBHMAyQR9AHMF0wB9BGIAJwXTAH0EYgAnBdMAfQRiACcF0wB9BGIAJwXnAMkE6QCwBecAAATpABQCqv/iAgb/kAKqACoCBv/aAqoAHgIG/8wCqgBUAgYANQKqAFQCBgCwBM0AVAQMAKICI/9gAgb/kQTpAMkEMwCwBCUAsAQnAMkCBgCjBCcAyQIGAFkEJwDJAgYAsAQnAMkCgwCwBC8AHQIX//wGCADJBOkAsAYIAMkE6QCwBggAyQTpALAFcwABBggAyQTpALAGOwB9BNUAcwY7AH0E1QBzBjsAfQTVAHMHYgB9B4kAcQTyAMkDRACwBPIAyQNEAGAE8gDJA0QAggRkAGoD0QBqBGQAagPRAGoEZABqA9EAagRkAGoD0QBqBG0AEgLTAB8EbQASAtMAHwRtABIC0wAfBdMAugTpAKQF0wC6BOkApAXTALoE6QCkBdMAugTpAKQF0wC6BOkApAXTALoE6QCkB2gAGwY5ABcEewAABAgAAgR7AAAEkQBSA74AUgSRAFIDvgBSBJEAUgO+AFICjwCwBJ4AwwUUAAAEcwBeBvz//gbdAF4GOwB9BNUAcwRkAGoD0QBqBLwBDAS8AQwEsgEtBLwBJQIGAKIEngFvAZMAJQS8AQgEngDnBJ4B/ASeARsFEAAAAiEAmATy/9QGff/UA5j/5AaB/+QFhf/UBoH/5AK2/+kFEAAABS8AyQQpAMkEkwAnBHMAyQSRAFIF5wDJBjsAfQKqAFQE6QDJBNMAAAc5AMkGCADJBG0ASAY7AH0F1QDJBNEAyQSJAEoEbQASBHsAAAZiAGoEngAIBl4AbQZCAFACqgA8BHsAAATjAHMDzQBaBOkAsAK2AKgE3wCkBOMAcwUGALAEGQAKBKQAcQPNAFoD3QBzBOkAsAS8AHMCtgCoBCUAsARG//IE9ACwBFYAAAPNAHEE1QBzBTMAGQTVAKYD2wBzBOcAcwPJABIE3wCkBb4AcwRe/+wGBgCkBi8AcwK2AAkE3wCkBNUAcwTfAKQGLwBzBHMAyQXfABIEKQDJBR0AfQRkAGoCqgBUAqoAPAIj/2AHbwAAB6AAyQXfABIE5QDJBPgAGwXVAMkFEAAABOcAyQUvAMkEKQDJBXcADgRzAMkGwQACBKYASgYZAMsGGQDLBOUAyQWiAAAHOQDJBecAyQY7AH0F1QDJBNEAyQUMAH0EbQASBPgAGwZiAGoEngAIBeUAyQWPAKoIQgDJCEQAyQWBABIG0wDJBSUAyQUKAD0IZgDJBRcAMwRzAF4ExQB3BI0AsANtALAEkwApBH0AcwXjAAQD3QBEBRIAsAUSALAEJwCwBJEAEAXhALAFEgCwBNUAcwT4ALAE5wCw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AAsAOAHsAAMAAQQJAAwAXAIkAAMAAQQJAA0AXAKAAAMAAQQJAA4AVALcAEQAaQBnAGkAdABpAHoAZQBkACAAZABhAHQAYQAgAGMAbwBwAHkAcgBpAGcAaAB0ACAAqQAgADIAMAAxADAALQAyADAAMQAxACwAIABHAG8AbwBnAGwAZQAgAEMAbwByAHAAbwByAGEAdABpAG8AbgAuAE8AcABlAG4AIABTAGEAbgBzAFIAZQBnAHUAbABhAHIAMQAuADEAMAA7ADEAQQBTAEMAOwBPAHAAZQBuAFMAYQBuAHMALQBSAGUAZwB1AGwAYQByAE8AcABlAG4AIABTAGEAbgBzACAAUgBlAGcAdQBsAGEAcgBWAGUAcgBzAGkAbwBuACAAMQAuADEAMABPAHAAZQBuAFMAYQBuAHMALQBSAGUAZwB1AGwAYQByAE8AcABlAG4AIABTAGEAbgBzACAAaQBzACAAYQAgAHQAcgBhAGQAZQBtAGEAcgBrACAAbwBmACAARwBvAG8AZwBsAGUAIABhAG4AZAAgAG0AYQB5ACAAYgBlACAAcgBlAGcAaQBzAHQAZQByAGUAZAAgAGkAbgAgAGMAZQByAHQAYQBpAG4AIABqAHUAcgBpAHMAZABpAGMAdABpAG8AbgBzAC4AQQBzAGMAZQBuAGQAZQByACAAQwBvAHIAcABvAHIAYQB0AGkAbwBuAGgAdAB0AHAAOgAvAC8AdwB3AHcALgBhAHMAYwBlAG4AZABlAHIAYwBvAHIAcAAuAGMAbwBtAC8AaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGEAcwBjAGUAbgBkAGUAcgBjAG8AcgBwAC4AYwBvAG0ALwB0AHkAcABlAGQAZQBzAGkAZwBuAGUAcgBzAC4AaAB0AG0AbABMAGkAYwBlAG4AcwBlAGQAIAB1AG4AZABlAHIAIAB0AGgAZQAgAEEAcABhAGMAaABlACAATABpAGMAZQBuAHMAZQAsACAAVgBlAHIAcwBpAG8AbgAgADIALgAwAGgAdAB0AHAAOgAvAC8AdwB3AHcALgBhAHAAYQBjAGgAZQAuAG8AcgBnAC8AbABpAGMAZQBuAHMAZQBzAC8ATABJAEMARQBOAFMARQAtADIALgAwAAAAAgAAAAAAAP9mAGYAAAAAAAAAAAAAAAAAAAAAAAAAAAOqAAABAgACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwEDAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQCsAKMAhACFAL0AlgDoAIYAjgCLAJ0AqQCkAQQAigEFAIMAkwDyAPMAjQCXAIgAwwDeAPEAngCqAPUA9AD2AKIArQDJAMcArgBiAGMAkABkAMsAZQDIAMoBBgEHAQgBCQDpAGYA0wDQANEArwBnAPAAkQDWANQA1QBoAOsA7QCJAGoAaQBrAG0AbABuAKAAbwBxAHAAcgBzAHUAdAB2AHcA6gB4AHoAeQB7AH0AfAC4AKEAfwB+AIAAgQDsAO4AugEKAQsBDAENAQ4BDwD9AP4BEAERARIBEwD/AQABFAEVARYBAQEXARgBGQEaARsBHAEdAR4BHwEgASEBIgD4APkBIwEkASUBJgEnASgBKQEqASsBLAEtAS4BLwEwATEBMgEzANcBNAE1ATYBNwE4ATkBOgE7ATwBPQE+AT8BQAFBAUIA4gDjAUMBRAFFAUYBRwFIAUkBSgFLAUwBTQFOAU8BUAFRALAAsQFSAVMBVAFVAVYBVwFYAVkBWgFbAPsA/ADkAOUBXAFdAV4BXwFgAWEBYgFjAWQBZQFmAWcBaAFpAWoBawFsAW0BbgFvAXABcQC7AXIBcwF0AXUA5gDnAXYApgF3AXgBeQF6AXsBfAF9AX4A2ADhANoA2wDcAN0A4ADZAN8BfwGAAYEBggGDAYQBhQGGAYcBiAGJAYoBiwGMAY0BjgGPAZABkQGSAZMBlAGVAZYBlwGYAZkBmgGbAZwBnQGeAZ8BoAGhAaIBowGkAaUBpgGnAagBqQGqAasBrAGtAa4BrwGwAbEBsgGzAbQBtQG2AbcAmwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wIAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisAsgCzAiwCLQC2ALcAxAIuALQAtQDFAIIAwgCHAKsAxgIvAjAAvgC/AjEAvAIyAPcCMwI0AjUCNgI3AjgAjACfAjkCOgI7AjwCPQCYAKgAmgCZAO8ApQCSAJwApwCPAJQAlQC5Aj4CPwJAAkECQgJDAkQCRQJGAkcCSAJJAkoCSwJMAk0CTgJPAlACUQJSAlMCVAJVAlYCVwJYAlkCWgJbAlwCXQJeAl8CYAJhAmICYwJkAmUCZgJnAmgCaQJqAmsCbAJtAm4CbwJwAnECcgJzAnQCdQJ2AncCeAJ5AnoCewJ8An0CfgJ/AoACgQKCAoMChAKFAoYChwKIAokCigKLAowCjQKOAo8CkAKRApICkwKUApUClgKXApgCmQKaApsCnAKdAp4CnwKgAqECogKjAqQCpQKmAqcCqAKpAqoCqwKsAq0CrgKvArACsQKyArMCtAK1ArYCtwK4ArkCugK7ArwCvQK+Ar8CwALBAsICwwLEAsUCxgLHAsgCyQLKAssCzALNAs4CzwLQAtEC0gLTAtQC1QLWAtcC2ALZAtoC2wLcAt0C3gLfAuAC4QLiAuMC5ALlAuYC5wLoAukC6gLrAuwC7QLuAu8C8ALxAvIC8wL0AvUC9gL3AvgC+QL6AvsC/AL9Av4C/wMAAwEDAgMDAwQDBQMGAwcDCAMJAwoDCwMMAw0DDgMPAxADEQMSAxMDFAMVAxYDFwMYAxkDGgMbAxwDHQMeAx8DIAMhAyIDIwMkAyUDJgMnAygDKQMqAysDLAMtAy4DLwMwAzEDMgMzAzQDNQM2AzcDOAM5AzoDOwM8Az0DPgM/A0ADQQNCA0MDRANFA0YDRwNIA0kDSgNLA0wDTQNOA08DUANRA1IDUwNUA1UDVgNXA1gDWQNaA1sDXANdA14DXwNgA2EDYgNjA2QDZQNmA2cDaANpA2oDawNsA20DbgNvA3ADcQNyA3MDdAN1A3YDdwN4A3kDegN7A3wDfQN+A38DgAOBA4IDgwOEA4UDhgOHA4gDiQOKA4sDjAONA44DjwOQA5EDkgOTA5QDlQOWA5cDmAOZA5oDmwOcA50DngOfACwAzwDMAM0AzgOgA6EDogOjAPoDpAOlA6YDpwOoA6kDqgOrA6wDrQRudWxsBUkuYWx0B3VuaTAwQUQJb3ZlcnNjb3JlCklncmF2ZS5hbHQKSWFjdXRlLmFsdA9JY2lyY3VtZmxleC5hbHQNSWRpZXJlc2lzLmFsdAdBbWFjcm9uB2FtYWNyb24GQWJyZXZlBmFicmV2ZQdBb2dvbmVrB2FvZ29uZWsLQ2NpcmN1bWZsZXgLY2NpcmN1bWZsZXgEQ2RvdARjZG90BkRjYXJvbgZkY2Fyb24GRGNyb2F0B0VtYWNyb24HZW1hY3JvbgZFYnJldmUGZWJyZXZlCkVkb3RhY2NlbnQKZWRvdGFjY2VudAdFb2dvbmVrB2VvZ29uZWsGRWNhcm9uBmVjYXJvbgtHY2lyY3VtZmxleAtnY2lyY3VtZmxleARHZG90BGdkb3QMR2NvbW1hYWNjZW50DGdjb21tYWFjY2VudAtIY2lyY3VtZmxleAtoY2lyY3VtZmxleARIYmFyBGhiYXIKSXRpbGRlLmFsdAZpdGlsZGULSW1hY3Jvbi5hbHQHaW1hY3JvbgpJYnJldmUuYWx0BmlicmV2ZQtJb2dvbmVrLmFsdAdpb2dvbmVrDklkb3RhY2NlbnQuYWx0BklKLmFsdAJpagtKY2lyY3VtZmxleAtqY2lyY3VtZmxleAxLY29tbWFhY2NlbnQMa2NvbW1hYWNjZW50DGtncmVlbmxhbmRpYwZMYWN1dGUGbGFjdXRlDExjb21tYWFjY2VudAxsY29tbWFhY2NlbnQGTGNhcm9uBmxjYXJvbgRMZG90BGxkb3QGTmFjdXRlBm5hY3V0ZQxOY29tbWFhY2NlbnQMbmNvbW1hYWNjZW50Bk5jYXJvbgZuY2Fyb24LbmFwb3N0cm9waGUDRW5nA2VuZwdPbWFjcm9uB29tYWNyb24GT2JyZXZlBm9icmV2ZQ1PaHVuZ2FydW1sYXV0DW9odW5nYXJ1bWxhdXQGUmFjdXRlBnJhY3V0ZQxSY29tbWFhY2NlbnQMcmNvbW1hYWNjZW50BlJjYXJvbgZyY2Fyb24GU2FjdXRlBnNhY3V0ZQtTY2lyY3VtZmxleAtzY2lyY3VtZmxleAxUY29tbWFhY2NlbnQMdGNvbW1hYWNjZW50BlRjYXJvbgZ0Y2Fyb24EVGJhcgR0YmFyBlV0aWxkZQZ1dGlsZGUHVW1hY3Jvbgd1bWFjcm9uBlVicmV2ZQZ1YnJldmUFVXJpbmcFdXJpbmcNVWh1bmdhcnVtbGF1dA11aHVuZ2FydW1sYXV0B1VvZ29uZWsHdW9nb25lawtXY2lyY3VtZmxleAt3Y2lyY3VtZmxleAtZY2lyY3VtZmxleAt5Y2lyY3VtZmxleAZaYWN1dGUGemFjdXRlClpkb3RhY2NlbnQKemRvdGFjY2VudAVsb25ncwpBcmluZ2FjdXRlCmFyaW5nYWN1dGUHQUVhY3V0ZQdhZWFjdXRlC09zbGFzaGFjdXRlC29zbGFzaGFjdXRlDFNjb21tYWFjY2VudAxzY29tbWFhY2NlbnQFdG9ub3MNZGllcmVzaXN0b25vcwpBbHBoYXRvbm9zCWFub3RlbGVpYQxFcHNpbG9udG9ub3MIRXRhdG9ub3MNSW90YXRvbm9zLmFsdAxPbWljcm9udG9ub3MMVXBzaWxvbnRvbm9zCk9tZWdhdG9ub3MRaW90YWRpZXJlc2lzdG9ub3MFQWxwaGEEQmV0YQVHYW1tYQd1bmkwMzk0B0Vwc2lsb24EWmV0YQNFdGEFVGhldGEISW90YS5hbHQFS2FwcGEGTGFtYmRhAk11Ak51AlhpB09taWNyb24CUGkDUmhvBVNpZ21hA1RhdQdVcHNpbG9uA1BoaQNDaGkDUHNpB3VuaTAzQTkQSW90YWRpZXJlc2lzLmFsdA9VcHNpbG9uZGllcmVzaXMKYWxwaGF0b25vcwxlcHNpbG9udG9ub3MIZXRhdG9ub3MJaW90YXRvbm9zFHVwc2lsb25kaWVyZXNpc3Rvbm9zBWFscGhhBGJldGEFZ2FtbWEFZGVsdGEHZXBzaWxvbgR6ZXRhA2V0YQV0aGV0YQRpb3RhBWthcHBhBmxhbWJkYQd1bmkwM0JDAm51AnhpB29taWNyb24DcmhvBnNpZ21hMQVzaWdtYQN0YXUHdXBzaWxvbgNwaGkDY2hpA3BzaQVvbWVnYQxpb3RhZGllcmVzaXMPdXBzaWxvbmRpZXJlc2lzDG9taWNyb250b25vcwx1cHNpbG9udG9ub3MKb21lZ2F0b25vcwlhZmlpMTAwMjMJYWZpaTEwMDUxCWFmaWkxMDA1MglhZmlpMTAwNTMJYWZpaTEwMDU0DWFmaWkxMDA1NS5hbHQNYWZpaTEwMDU2LmFsdAlhZmlpMTAwNTcJYWZpaTEwMDU4CWFmaWkxMDA1OQlhZmlpMTAwNjAJYWZpaTEwMDYxCWFmaWkxMDA2MglhZmlpMTAxNDUJYWZpaTEwMDE3CWFmaWkxMDAxOAlhZmlpMTAwMTkJYWZpaTEwMDIwCWFmaWkxMDAyMQlhZmlpMTAwMjIJYWZpaTEwMDI0CWFmaWkxMDAyNQlhZmlpMTAwMjYJYWZpaTEwMDI3CWFmaWkxMDAyOAlhZmlpMTAwMjkJYWZpaTEwMDMwCWFmaWkxMDAzMQlhZmlpMTAwMzIJYWZpaTEwMDMzCWFmaWkxMDAzNAlhZmlpMTAwMzUJYWZpaTEwMDM2CWFmaWkxMDAzNwlhZmlpMTAwMzgJYWZpaTEwMDM5CWFmaWkxMDA0MAlhZmlpMTAwNDEJYWZpaTEwMDQyCWFmaWkxMDA0MwlhZmlpMTAwNDQJYWZpaTEwMDQ1CWFmaWkxMDA0NglhZmlpMTAwNDcJYWZpaTEwMDQ4CWFmaWkxMDA0OQlhZmlpMTAwNjUJYWZpaTEwMDY2CWFmaWkxMDA2NwlhZmlpMTAwNjgJYWZpaTEwMDY5CWFmaWkxMDA3MAlhZmlpMTAwNzIJYWZpaTEwMDczCWFmaWkxMDA3NAlhZmlpMTAwNzUJYWZpaTEwMDc2CWFmaWkxMDA3NwlhZmlpMTAwNzgJYWZpaTEwMDc5CWFmaWkxMDA4MAlhZmlpMTAwODEJYWZpaTEwMDgyCWFmaWkxMDA4MwlhZmlpMTAwODQJYWZpaTEwMDg1CWFmaWkxMDA4NglhZmlpMTAwODcJYWZpaTEwMDg4CWFmaWkxMDA4OQlhZmlpMTAwOTAJYWZpaTEwMDkxCWFmaWkxMDA5MglhZmlpMTAwOTMJYWZpaTEwMDk0CWFmaWkxMDA5NQlhZmlpMTAwOTYJYWZpaTEwMDk3CWFmaWkxMDA3MQlhZmlpMTAwOTkJYWZpaTEwMTAwCWFmaWkxMDEwMQlhZmlpMTAxMDIJYWZpaTEwMTAzCWFmaWkxMDEwNAlhZmlpMTAxMDUJYWZpaTEwMTA2CWFmaWkxMDEwNwlhZmlpMTAxMDgJYWZpaTEwMTA5CWFmaWkxMDExMAlhZmlpMTAxOTMJYWZpaTEwMDUwCWFmaWkxMDA5OAZXZ3JhdmUGd2dyYXZlBldhY3V0ZQZ3YWN1dGUJV2RpZXJlc2lzCXdkaWVyZXNpcwZZZ3JhdmUGeWdyYXZlCWFmaWkwMDIwOA11bmRlcnNjb3JlZGJsDXF1b3RlcmV2ZXJzZWQGbWludXRlBnNlY29uZAlleGNsYW1kYmwJbnN1cGVyaW9yCWFmaWkwODk0MQZwZXNldGEERXVybwlhZmlpNjEyNDgJYWZpaTYxMjg5CWFmaWk2MTM1Mgllc3RpbWF0ZWQJb25lZWlnaHRoDHRocmVlZWlnaHRocwtmaXZlZWlnaHRocwxzZXZlbmVpZ2h0aHMHdW5pRkIwMQd1bmlGQjAyDWN5cmlsbGljYnJldmUIZG90bGVzc2oQY2Fyb25jb21tYWFjY2VudAtjb21tYWFjY2VudBFjb21tYWFjY2VudHJvdGF0ZQx6ZXJvc3VwZXJpb3IMZm91cnN1cGVyaW9yDGZpdmVzdXBlcmlvcgtzaXhzdXBlcmlvcg1zZXZlbnN1cGVyaW9yDWVpZ2h0c3VwZXJpb3IMbmluZXN1cGVyaW9yB3VuaTIwMDAHdW5pMjAwMQd1bmkyMDAyB3VuaTIwMDMHdW5pMjAwNAd1bmkyMDA1B3VuaTIwMDYHdW5pMjAwNwd1bmkyMDA4B3VuaTIwMDkHdW5pMjAwQQd1bmkyMDBCB3VuaUZFRkYHdW5pRkZGQwd1bmlGRkZEB3VuaTAxRjAHdW5pMDJCQwd1bmkwM0QxB3VuaTAzRDIHdW5pMDNENgd1bmkxRTNFB3VuaTFFM0YHdW5pMUUwMAd1bmkxRTAxB3VuaTFGNEQHdW5pMDJGMwlkYXNpYW94aWEHdW5pRkIwMwd1bmlGQjA0BU9ob3JuBW9ob3JuBVVob3JuBXVob3JuB3VuaTAzMDAHdW5pMDMwMQd1bmkwMzAzBGhvb2sIZG90YmVsb3cHdW5pMDQwMAd1bmkwNDBEB3VuaTA0NTAHdW5pMDQ1RAd1bmkwNDYwB3VuaTA0NjEHdW5pMDQ2Mgd1bmkwNDYzB3VuaTA0NjQHdW5pMDQ2NQd1bmkwNDY2B3VuaTA0NjcHdW5pMDQ2OAd1bmkwNDY5B3VuaTA0NkEHdW5pMDQ2Qgd1bmkwNDZDB3VuaTA0NkQHdW5pMDQ2RQd1bmkwNDZGB3VuaTA0NzAHdW5pMDQ3MQd1bmkwNDcyB3VuaTA0NzMHdW5pMDQ3NAd1bmkwNDc1B3VuaTA0NzYHdW5pMDQ3Nwd1bmkwNDc4B3VuaTA0NzkHdW5pMDQ3QQd1bmkwNDdCB3VuaTA0N0MHdW5pMDQ3RAd1bmkwNDdFB3VuaTA0N0YHdW5pMDQ4MAd1bmkwNDgxB3VuaTA0ODIHdW5pMDQ4Mwd1bmkwNDg0B3VuaTA0ODUHdW5pMDQ4Ngd1bmkwNDg4B3VuaTA0ODkHdW5pMDQ4QQd1bmkwNDhCB3VuaTA0OEMHdW5pMDQ4RAd1bmkwNDhFB3VuaTA0OEYHdW5pMDQ5Mgd1bmkwNDkzB3VuaTA0OTQHdW5pMDQ5NQd1bmkwNDk2B3VuaTA0OTcHdW5pMDQ5OAd1bmkwNDk5B3VuaTA0OUEHdW5pMDQ5Qgd1bmkwNDlDB3VuaTA0OUQHdW5pMDQ5RQd1bmkwNDlGB3VuaTA0QTAHdW5pMDRBMQd1bmkwNEEyB3VuaTA0QTMHdW5pMDRBNAd1bmkwNEE1B3VuaTA0QTYHdW5pMDRBNwd1bmkwNEE4B3VuaTA0QTkHdW5pMDRBQQd1bmkwNEFCB3VuaTA0QUMHdW5pMDRBRAd1bmkwNEFFB3VuaTA0QUYHdW5pMDRCMAd1bmkwNEIxB3VuaTA0QjIHdW5pMDRCMwd1bmkwNEI0B3VuaTA0QjUHdW5pMDRCNgd1bmkwNEI3B3VuaTA0QjgHdW5pMDRCOQd1bmkwNEJBB3VuaTA0QkIHdW5pMDRCQwd1bmkwNEJEB3VuaTA0QkUHdW5pMDRCRgt1bmkwNEMwLmFsdAd1bmkwNEMxB3VuaTA0QzIHdW5pMDRDMwd1bmkwNEM0B3VuaTA0QzUHdW5pMDRDNgd1bmkwNEM3B3VuaTA0QzgHdW5pMDRDOQd1bmkwNENBB3VuaTA0Q0IHdW5pMDRDQwd1bmkwNENEB3VuaTA0Q0ULdW5pMDRDRi5hbHQHdW5pMDREMAd1bmkwNEQxB3VuaTA0RDIHdW5pMDREMwd1bmkwNEQ0B3VuaTA0RDUHdW5pMDRENgd1bmkwNEQ3B3VuaTA0RDgHdW5pMDREOQd1bmkwNERBB3VuaTA0REIHdW5pMDREQwd1bmkwNEREB3VuaTA0REUHdW5pMDRERgd1bmkwNEUwB3VuaTA0RTEHdW5pMDRFMgd1bmkwNEUzB3VuaTA0RTQHdW5pMDRFNQd1bmkwNEU2B3VuaTA0RTcHdW5pMDRFOAd1bmkwNEU5B3VuaTA0RUEHdW5pMDRFQgd1bmkwNEVDB3VuaTA0RUQHdW5pMDRFRQd1bmkwNEVGB3VuaTA0RjAHdW5pMDRGMQd1bmkwNEYyB3VuaTA0RjMHdW5pMDRGNAd1bmkwNEY1B3VuaTA0RjYHdW5pMDRGNwd1bmkwNEY4B3VuaTA0RjkHdW5pMDRGQQd1bmkwNEZCB3VuaTA0RkMHdW5pMDRGRAd1bmkwNEZFB3VuaTA0RkYHdW5pMDUwMAd1bmkwNTAxB3VuaTA1MDIHdW5pMDUwMwd1bmkwNTA0B3VuaTA1MDUHdW5pMDUwNgd1bmkwNTA3B3VuaTA1MDgHdW5pMDUwOQd1bmkwNTBBB3VuaTA1MEIHdW5pMDUwQwd1bmkwNTBEB3VuaTA1MEUHdW5pMDUwRgd1bmkwNTEwB3VuaTA1MTEHdW5pMDUxMgd1bmkwNTEzB3VuaTFFQTAHdW5pMUVBMQd1bmkxRUEyB3VuaTFFQTMHdW5pMUVBNAd1bmkxRUE1B3VuaTFFQTYHdW5pMUVBNwd1bmkxRUE4B3VuaTFFQTkHdW5pMUVBQQd1bmkxRUFCB3VuaTFFQUMHdW5pMUVBRAd1bmkxRUFFB3VuaTFFQUYHdW5pMUVCMAd1bmkxRUIxB3VuaTFFQjIHdW5pMUVCMwd1bmkxRUI0B3VuaTFFQjUHdW5pMUVCNgd1bmkxRUI3B3VuaTFFQjgHdW5pMUVCOQd1bmkxRUJBB3VuaTFFQkIHdW5pMUVCQwd1bmkxRUJEB3VuaTFFQkUHdW5pMUVCRgd1bmkxRUMwB3VuaTFFQzEHdW5pMUVDMgd1bmkxRUMzB3VuaTFFQzQHdW5pMUVDNQd1bmkxRUM2B3VuaTFFQzcLdW5pMUVDOC5hbHQHdW5pMUVDOQt1bmkxRUNBLmFsdAd1bmkxRUNCB3VuaTFFQ0MHdW5pMUVDRAd1bmkxRUNFB3VuaTFFQ0YHdW5pMUVEMAd1bmkxRUQxB3VuaTFFRDIHdW5pMUVEMwd1bmkxRUQ0B3VuaTFFRDUHdW5pMUVENgd1bmkxRUQ3B3VuaTFFRDgHdW5pMUVEOQd1bmkxRURBB3VuaTFFREIHdW5pMUVEQwd1bmkxRUREB3VuaTFFREUHdW5pMUVERgd1bmkxRUUwB3VuaTFFRTEHdW5pMUVFMgd1bmkxRUUzB3VuaTFFRTQHdW5pMUVFNQd1bmkxRUU2B3VuaTFFRTcHdW5pMUVFOAd1bmkxRUU5B3VuaTFFRUEHdW5pMUVFQgd1bmkxRUVDB3VuaTFFRUQHdW5pMUVFRQd1bmkxRUVGB3VuaTFFRjAHdW5pMUVGMQd1bmkxRUY0B3VuaTFFRjUHdW5pMUVGNgd1bmkxRUY3B3VuaTFFRjgHdW5pMUVGOQd1bmkyMEFCB3VuaTAzMEYTY2lyY3VtZmxleGFjdXRlY29tYhNjaXJjdW1mbGV4Z3JhdmVjb21iEmNpcmN1bWZsZXhob29rY29tYhNjaXJjdW1mbGV4dGlsZGVjb21iDmJyZXZlYWN1dGVjb21iDmJyZXZlZ3JhdmVjb21iDWJyZXZlaG9va2NvbWIOYnJldmV0aWxkZWNvbWIQY3lyaWxsaWNob29rbGVmdBFjeXJpbGxpY2JpZ2hvb2tVQxFjeXJpbGxpY2JpZ2hvb2tMQwhvbmUucG51bQd6ZXJvLm9zBm9uZS5vcwZ0d28ub3MIdGhyZWUub3MHZm91ci5vcwdmaXZlLm9zBnNpeC5vcwhzZXZlbi5vcwhlaWdodC5vcwduaW5lLm9zAmZmB3VuaTIxMjAIVGNlZGlsbGEIdGNlZGlsbGEFZy5hbHQPZ2NpcmN1bWZsZXguYWx0CmdicmV2ZS5hbHQIZ2RvdC5hbHQQZ2NvbW1hYWNjZW50LmFsdAZJdGlsZGUHSW1hY3JvbgZJYnJldmUHSW9nb25lawJJSglJb3RhdG9ub3MESW90YQxJb3RhZGllcmVzaXMJYWZpaTEwMDU1CWFmaWkxMDA1Ngd1bmkwNEMwB3VuaTA0Q0YHdW5pMUVDOAd1bmkxRUNBAAABAAMACAAKAA0AB///AA8AAQAAAAwAAAAAAAAAAgAFAAACNQABAjcCNwABAjsCWwABAl0DdgABA4IDqQABAAAAAQAAAAoADAAOAAAAAAAAAAEAAAAKAG4BWgABbGF0bgAIABAAAk1PTCAAKFJPTSAAQgAA//8ACQADAAgACwAAAA4AEQAUABcAGgAA//8ACgAEAAYACQAMAAEADwASABUAGAAbAAD//wAKAAUABwAKAA0AAgAQABMAFgAZABwAHWxpZ2EAsGxpZ2EAsGxpZ2EAsGxudW0AtmxudW0AtmxudW0AtmxvY2wAvGxvY2wAvG9udW0Awm9udW0Awm9udW0AwnBudW0AynBudW0AynBudW0AynNhbHQA0HNhbHQA0HNhbHQA0HNzMDEA0HNzMDEA0HNzMDEA0HNzMDIA2HNzMDIA2HNzMDIA2HNzMDMA3nNzMDMA3nNzMDMA3nRudW0A5HRudW0A5HRudW0A5AAAAAEACQAAAAEABwAAAAEACAAAAAIAAgADAAAAAQAEAAAAAgAAAAEAAAABAAAAAAABAAEAAAACAAUABgAKABYAPAB8AJQAzADgAO4BAgEuAVAAAQAAAAEACAACABAABQORA5IDkwOUA5UAAQAFAEoA3wDhAOMA5QABAAAAAQAIAAIALgAUACwAjgCPAJAAkQDqAOwA7gDwAPIA9AFaAWcBdwGhAaICyQLYA0UDRwACAAEDlgOpAAAAAQAAAAEACAABAAYDcAACAAEAEwAcAAAAAQAAAAEACAACABoACgODA4UDhgOHA4gDiQOKA4sDjAOEAAIAAwATABMAAAAVABwAAQOCA4IACQABAAAAAQAIAAEABgNuAAEAAQAUAAEAAAABAAgAAQA8/JAAAQAAAAEACAABAAb8kgABAAEDggABAAAAAQAIAAIAGgAKABMDggAVABYAFwAYABkAGgAbABwAAgABA4MDjAAAAAEAAAABAAgAAgAOAAQDjwOQASABIQABAAQBJAElAUkBSgAEAAAAAQAIAAEANgABAAgABQAMABQAHAAiACgCXgADAEkATwJdAAMASQBMA40AAgBJAjUAAgBPAjQAAgBMAAEAAQBJAAA=") format("truetype");font-weight:400;font-style:normal}.w2ui-reset{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;font-family:OpenSans;font-size:12px}.w2ui-reset *{color:default;line-height:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0}.w2ui-reset table{max-width:none;background-color:transparent;border-collapse:separate;border-spacing:0;border:none}.w2ui-reset table tr td,.w2ui-reset table tr th{font-family:OpenSans;font-size:12px}.w2ui-reset input:not([type=button]):not([type=submit]):not([type=checkbox]):not([type=radio]),.w2ui-reset select,.w2ui-reset textarea{display:inline-block;width:auto;height:auto;vertical-align:baseline;padding:6px;margin:0;font-size:12px;background-color:#f8fafa;border:1px solid #e0e0e0}.w2ui-reset input:not([type=button]):not([type=submit]):not([type=checkbox]):not([type=radio]):focus,.w2ui-reset select:focus,.w2ui-reset textarea:focus{background-color:#fff}.w2ui-reset select{padding:5px;height:26px;font-size:12px}.w2ui-centered{position:absolute;left:0;right:0;top:0;bottom:0;display:flex;flex-wrap:wrap;align-items:center;justify-content:center;text-align:center;padding:10px}.w2ui-disabled,.w2ui-readonly{background-color:#f1f1f1;color:#777}div[contenteditable].w2ui-focus,input.w2ui-focus:not(button),select.w2ui-focus,textarea.w2ui-focus{outline-style:auto;outline-color:#72b2ff}div.w2ui-input:focus,select.w2ui-input:focus{outline-color:#72b2ff}input:not([type=button]):not([type=submit]).w2ui-input,textarea.w2ui-input{padding:6px;border:1px solid #e0e0e0;border-radius:3px;color:#000;background-color:#f8fafa;line-height:normal}input:not([type=button]):not([type=submit]).w2ui-input:focus,textarea.w2ui-input:focus{outline-color:#72b2ff;background-color:#fff}input:not([type=button]):not([type=submit]).w2ui-input:disabled,input:not([type=button]):not([type=submit]).w2ui-input[readonly],textarea.w2ui-input:disabled,textarea.w2ui-input[readonly]{background-color:#f1f1f1;color:#777}select.w2ui-input{color:#000;padding:0 20px 0 7px;line-height:1.8;border-radius:3px;border:1px solid #e0e0e0;-webkit-appearance:none;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAALCAQAAACnzwd+AAAAcklEQVR4AcXMsQFBQQDG4P9tAgC0gJYRQJZgKQMwCqCku6vVAAAA+NJHP4KHOk0aV2pRw61n4BBmyOxKQ8I4ehZeuhd3HTx6DQEGZ7sBfr2OOOOj3Yi43kMKs9sZknofOexqZ8npMygwWZTX51CipP+YA1OiZJbYYg9lAAAAAElFTkSuQmCC),linear-gradient(to bottom,#f8f8f8 20%,#f8f8f8 50%,#f8f8f8 52%,#f8f8f8 100%);background-size:17px 6px,100% 100%;background-position:right center,left top;background-repeat:no-repeat,no-repeat}.w2ui-icon-expand:before{position:relative;top:1px;left:1px;content:' ';width:5px;height:5px;border:2px solid rgba(150,150,150,.8);border-bottom:0;border-left:0;transform:rotateZ(45deg)}.w2ui-icon-collapse:before{position:relative;top:-1px;left:3px;content:' ';width:5px;height:5px;border:2px solid rgba(150,150,150,.8);border-bottom:0;border-left:0;transform:rotateZ(135deg)}input[type=checkbox].w2ui-toggle{position:absolute;opacity:0;width:46px;height:22px;padding:0;margin:0;margin-left:2px}input[type=checkbox].w2ui-toggle:focus{box-shadow:0 0 1px 2px #a8cfff}input[type=checkbox].w2ui-toggle+div{display:inline-block;width:46px;height:22px;border:1px solid #bbb;border-radius:30px;background-color:#eee;transition-duration:.3s;transition-property:background-color,box-shadow;box-shadow:inset 0 0 0 0 rgba(0,0,0,.4);margin-left:2px}input[type=checkbox].w2ui-toggle.w2ui-small+div{width:30px;height:16px}input[type=checkbox].w2ui-toggle:focus+div{box-shadow:0 0 3px 2px #91baed}input[type=checkbox].w2ui-toggle:disabled+div{opacity:.3}input[type=checkbox].w2ui-toggle+div>div{float:left;width:22px;height:22px;border-radius:inherit;background:#f5f5f5;transition-duration:.3s;transition-property:transform,background-color,box-shadow;box-shadow:0 0 1px #323232,0 0 0 1px rgba(200,200,200,.6);pointer-events:none;margin-top:-1px;margin-left:-1px}input[type=checkbox].w2ui-toggle.w2ui-small+div>div{width:16px;height:16px}input[type=checkbox].w2ui-toggle:checked+div>div{transform:translate3d(24px,0,0);background-color:#fff}input[type=checkbox].w2ui-toggle.w2ui-small:checked+div>div{transform:translate3d(14px,0,0)}input[type=checkbox].w2ui-toggle:focus{outline:0}input[type=checkbox].w2ui-toggle:checked+div{border:1px solid #206fad;box-shadow:inset 0 0 0 12px #35a6eb}input[type=checkbox].w2ui-toggle:checked:focus+div{box-shadow:0 0 3px 2px #91baed,inset 0 0 0 12px #35a6eb}input[type=checkbox].w2ui-toggle:checked+div>div{box-shadow:0 2px 5px rgba(0,0,0,.3),0 0 0 1px #206fad}input[type=checkbox].w2ui-toggle.green:checked+div{border:1px solid #00a23f;box-shadow:inset 0 0 0 12px #54b350}input[type=checkbox].w2ui-toggle.green:checked:focus+div{box-shadow:0 0 3px 2px #91baed,inset 0 0 0 12px #54b350}input[type=checkbox].w2ui-toggle.green:checked+div>div{box-shadow:0 2px 5px rgba(0,0,0,.3),0 0 0 1px #00a23f}.w2ui-marker{background-color:rgba(214,161,252,.5)}.w2ui-spinner{display:inline-block;background-size:100%;background-repeat:no-repeat;background-image:url(data:image/gif;base64,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)}.w2ui-icon{background-repeat:no-repeat;height:16px;width:16px;overflow:hidden;margin:2px 2px;display:inline-block}.w2ui-icon.icon-folder{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGrSURBVDjLxZO7ihRBFIa/6u0ZW7GHBUV0UQQTZzd3QdhMQxOfwMRXEANBMNQX0MzAzFAwEzHwARbNFDdwEd31Mj3X7a6uOr9BtzNjYjKBJ6nicP7v3KqcJFaxhBVtZUAK8OHlld2st7Xl3DJPVONP+zEUV4HqL5UDYHr5xvuQAjgl/Qs7TzvOOVAjxjlC+ePSwe6DfbVegLVuT4r14eTr6zvA8xSAoBLzx6pvj4l+DZIezuVkG9fY2H7YRQIMZIBwycmzH1/s3F8AapfIPNF3kQk7+kw9PWBy+IZOdg5Ug3mkAATy/t0usovzGeCUWTjCz0B+Sj0ekfdvkZ3abBv+U4GaCtJ1iEm6ANQJ6fEzrG/engcKw/wXQvEKxSEKQxRGKE7Izt+DSiwBJMUSm71rguMYhQKrBygOIRStf4TiFFRBvbRGKiQLWP29yRSHKBTtfdBmHs0BUpgvtgF4yRFR+NUKi0XZcYjCeCG2smkzLAHkbRBmP0/Uk26O5YnUActBp1GsAI+S5nRJJJal5K1aAMrq0d6Tm9uI6zjyf75dAe6tx/SsWeD//o2/Ab6IH3/h25pOAAAAAElFTkSuQmCC) no-repeat center}.w2ui-icon.icon-page{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAINSURBVBgZBcG/r55zGAfg6/4+z3va01NHlYgzEfE7MdCIGISFgS4Gk8ViYyM2Mdlsko4GSf8Do0FLRCIkghhYJA3aVBtEz3nP89wf11VJvPDepdd390+8Nso5nESBQoq0pfvXm9fzWf19453LF85vASqJlz748vInb517dIw6EyYBIIG49u+xi9/c9MdvR//99MPPZ7+4cP4IZhhTPbwzT2d+vGoaVRRp1rRliVvHq+cfvM3TD82+7mun0o/ceO7NT+/4/KOXjwZU1ekk0840bAZzMQ2mooqh0A72d5x/6sB9D5zYnff3PoYBoWBgFKPKqDKqjCpjKr//dcu9p489dra88cydps30KswACfNEKanSaxhlntjJ8Mv12Paie+vZ+0+oeSwwQ0Iw1xAR1CiFNJkGO4wu3ZMY1AAzBI0qSgmCNJsJUEOtJSMaCTBDLyQ0CknAGOgyTyFFiLI2awMzdEcSQgSAAKVUmAeNkxvWJWCGtVlDmgYQ0GFtgg4pNtOwbBcwQy/Rife/2yrRRVI0qYCEBly8Z+P4qMEMy7JaVw72N568e+iwhrXoECQkfH91kY7jwwXMsBx1L93ZruqrK6uuiAIdSnTIKKPLPFcvay8ww/Hh+ufeznTXu49v95IMoQG3784gYXdTqvRmqn/Wpa/ADFX58MW3L71SVU9ETgEIQQQIOOzub+fhIvwPRDgeVjWDahIAAAAASUVORK5CYII=) no-repeat center}.w2ui-lock{display:none;position:absolute;z-index:1400;top:0;left:0;width:100%;height:100%;opacity:.15;background-color:#333}.w2ui-lock-msg{display:none;position:absolute;z-index:1400;top:50%;left:50%;transform:translateX(-50%) translateY(-50%);min-width:100px;max-width:95%;padding:30px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-size:13px;font-family:OpenSans;opacity:.8;background-color:#555;color:#fff;text-align:center;border-radius:5px;border:2px solid #444}.w2ui-lock-msg .w2ui-spinner{display:inline-block;width:24px;height:24px;margin:-3px 8px -7px -10px}.w2ui-scroll-wrapper{overflow:hidden}.w2ui-scroll-left,.w2ui-scroll-right{top:0;width:18px;height:100%;cursor:default;z-index:10;display:none;position:absolute}.w2ui-scroll-left:hover,.w2ui-scroll-right:hover{background-color:#ddd}.w2ui-scroll-left{left:0;box-shadow:0 0 7px #5f5f5f;background:#f7f7f7 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAzklEQVR4Ae2THRDEMBCFzy1ucatb3eJ2uhi3uNUtbnGrW9zi1rOdNzdvdl7nDpvYt/9/r7+/51myZZf/zXkD2iMHHRSb0x3oskwMieK05PwEXqP4ExSL0wp0ROao2OOuMPOMdUL6XU1/oGLcFWb+NqyTd2W/P/2qTr9h+nFXhOkHXRHiNyjrgp/U/V+WaQcaNY13zZI0A1JvcVqAnrGDTdtDtZUHjHIJhxxVLN0iqXgCP1l/7h8U9kc6abyJ4/eNWPpGdBv+XdUK0K8cnvcBly2rDr7C1HQAAAAASUVORK5CYII=) center center no-repeat;background-size:15px 12px}.w2ui-scroll-right{right:0;box-shadow:0 0 7px #5f5f5f;background:#f7f7f7 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVR4Ae2UG7TGMBCEr1vd4la3uMUtuli3utWtbnGLW9zi9l/bDMzJG7u12cfJfLunf1+UEC9Bv0vVQwJ8hjRCaZafflb1C9RQf4OD0gSDE+i+PiJAabFhQc1y1AYYsJGLY3lgxM17uWPO56yPiFDqVPWgRtpIHSd1zPnwkBsdI58OlNwx4fP2X0TgfMTOoHSdKOXkpyNvEyQh7ul+4swxJSTQuwNDxz68l/ukVNbu0Neen5Z+KvzWxBAqHds349uPFJ/jVOrPjxUq++OLf+20q5+noXo0AAAAAElFTkSuQmCC) center center no-repeat;background-size:15px 13px}#w2ui-notify{position:absolute;display:flex;flex-direction:column;align-items:center;left:0;right:0;bottom:15px;z-index:10000;overflow:hidden}#w2ui-notify>div{position:relative;background-color:#292828ba;color:#fff;padding:8px 44px 8px 16px;border-radius:4px;box-shadow:3px 3px 10px #9c9c9c;max-height:76px;min-width:100px;max-width:800px;font-size:16px;text-shadow:1px 0 0 #000}#w2ui-notify>div a{color:#6cd0e8;text-decoration:none;cursor:pointer}#w2ui-notify>div a:hover{color:#a2f0ff}#w2ui-notify>div span.w2ui-notify-close{padding:6px 6px;border-radius:3px;font-size:13px;color:#c3c3c3;position:absolute;right:5px;top:5px}#w2ui-notify>div span.w2ui-notify-close:hover{background-color:#807e7e;color:#fff}#w2ui-notify>div.w2ui-notify-error{text-shadow:none;background-color:rgba(255,0,0,.8)}#w2ui-notify>div.w2ui-notify-error .w2ui-notify-close{color:#fff}#w2ui-notify>div.w2ui-notify-error .w2ui-notify-close:hover{background-color:#fcadad;color:rgba(255,0,0,.8)}button.w2ui-btn,input[type=button].w2ui-btn{position:relative;display:inline-block;border-radius:14px;margin:0 3px;padding:6px 12px;color:#666;font-size:12px;border:1px solid transparent;background-image:linear-gradient(#e8e8ee 0,#e8e8ee 100%);outline:0;box-shadow:0 1px 0 #fff;cursor:default;min-width:75px;line-height:110%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}button.w2ui-btn:hover,input[type=button].w2ui-btn:hover{text-decoration:none;background-image:linear-gradient(#ddd 0,#ddd 100%);color:#333}button.w2ui-btn.clicked,button.w2ui-btn:active,input[type=button].w2ui-btn.clicked,input[type=button].w2ui-btn:active{background-image:linear-gradient(#ccc 0,#ccc 100%);text-shadow:1px 1px 1px #eee}button.w2ui-btn:focus:before,input[type=button].w2ui-btn:focus:before{content:"";border:1px dashed #aaa;border-radius:15px;position:absolute;top:2px;bottom:2px;left:2px;right:2px;pointer-events:none}button.w2ui-btn-blue,input[type=button].w2ui-btn-blue{color:#fff;background-image:linear-gradient(#269df0 0,#269df0 100%);border:1px solid #269df0;text-shadow:0 0 1px #111}button.w2ui-btn-blue:hover,input[type=button].w2ui-btn-blue:hover{color:#fff;background-image:linear-gradient(#2391dd 0,#2391dd 100%);border:1px solid #2391dd;text-shadow:0 0 1px #111}button.w2ui-btn-blue.clicked,button.w2ui-btn-blue:active,input[type=button].w2ui-btn-blue.clicked,input[type=button].w2ui-btn-blue:active{color:#fff;background-image:linear-gradient(#1e83c9 0,#1e83c9 100%);border:1px solid #1268a6;text-shadow:0 0 1px #111}button.w2ui-btn-blue:focus:before,input[type=button].w2ui-btn-blue:focus:before{border:1px dashed #e8e8e8}button.w2ui-btn-green,input[type=button].w2ui-btn-green{color:#fff;background-image:linear-gradient(#52a452 0,#52a452 100%);border:1px solid #52a452;text-shadow:0 0 1px #111}button.w2ui-btn-green:hover,input[type=button].w2ui-btn-green:hover{color:#fff;background-image:linear-gradient(#3f8f3d 0,#3f8f3d 100%);border:1px solid #3f8f3d;text-shadow:0 0 1px #111}button.w2ui-btn-green.clicked,button.w2ui-btn-green:active,input[type=button].w2ui-btn-green.clicked,input[type=button].w2ui-btn-green:active{color:#fff;background-image:linear-gradient(#377d36 0,#377d36 100%);border:1px solid #555;text-shadow:0 0 1px #111}button.w2ui-btn-green:focus:before,input[type=button].w2ui-btn-green:focus:before{border:1px dashed #e8e8e8}button.w2ui-btn-orange,input[type=button].w2ui-btn-orange{color:#fff;background-image:linear-gradient(#fb8822 0,#fb8822 100%);border:1px solid #fb8822;text-shadow:0 0 1px #111}button.w2ui-btn-orange:hover,input[type=button].w2ui-btn-orange:hover{color:#fff;background-image:linear-gradient(#f1731f 0,#f1731f 100%);border:1px solid #f1731f;text-shadow:0 0 1px #111}button.w2ui-btn-orange.clicked,button.w2ui-btn-orange:active,input[type=button].w2ui-btn-orange.clicked,input[type=button].w2ui-btn-orange:active{color:#fff;border:1px solid #666;background-image:linear-gradient(#b98747 0,#b98747 100%);text-shadow:0 0 1px #111}button.w2ui-btn-orange:focus:before,input[type=button].w2ui-btn-orange:focus:before{border:1px dashed #f9f9f9}button.w2ui-btn-red,input[type=button].w2ui-btn-red{color:#fff;background-image:linear-gradient(#f9585a 0,#f9585a 100%);border:1px solid #f9585a;text-shadow:0 0 1px #111}button.w2ui-btn-red:hover,input[type=button].w2ui-btn-red:hover{color:#fff;background-image:linear-gradient(#de4446 0,#de4446 100%);border:1px solid #de4446;text-shadow:0 0 1px #111}button.w2ui-btn-red.clicked,button.w2ui-btn-red:active,input[type=button].w2ui-btn-red.clicked,input[type=button].w2ui-btn-red:active{color:#fff;border:1px solid #861c1e;background-image:linear-gradient(#9c2123 0,#9c2123 100%);text-shadow:0 0 1px #111}button.w2ui-btn-red:focus:before,input[type=button].w2ui-btn-red:focus:before{border:1px dashed #ddd}button.w2ui-btn-small,input[type=button].w2ui-btn-small{padding:5px;border-radius:4px;margin:0;min-width:0}button.w2ui-btn-small:focus:before,input[type=button].w2ui-btn-small:focus:before{border-radius:2px;top:2px;bottom:2px;left:2px;right:2px}button.w2ui-btn:disabled,input[type=button].w2ui-btn:disabled{border:1px solid #e6e6e6;background:#f7f7f7;color:#bdbcbc;text-shadow:none}.w2ui-overlay{--tip-size:8px;position:fixed;z-index:1700;opacity:0;transition:opacity .1s;border-radius:4px}.w2ui-overlay *{box-sizing:border-box}.w2ui-overlay .w2ui-overlay-body{display:inline-block;border:1px solid #474747;border-radius:4px;padding:4px 8px;margin:0;font-size:12px;font-family:OpenSans;color:#fff;text-shadow:0 1px 1px #4a4a4a;background-color:#777;line-height:1.4;letter-spacing:.1px;overflow:auto}.w2ui-overlay .w2ui-overlay-body.w2ui-light{color:#3c3c3c;text-shadow:none;background-color:#fffde9;border:1px solid #d2d2d2;box-shadow:0 1px 1px 1px #fff}.w2ui-overlay .w2ui-overlay-body.w2ui-white{color:#3c3c3c;text-shadow:none;background-color:#fafafa;border:1px solid #cccace;box-shadow:0 0 1px 1px #fff}.w2ui-overlay .w2ui-overlay-body.w2ui-arrow-right:before{content:"";position:absolute;left:calc(var(--tip-size,8px) * -.5 - 1px);top:calc(50% - 1px);transform:rotate(-45deg) translateY(-50%);transform-origin:top center;margin:0;border:inherit;border-color:inherit;background-color:inherit;width:var(--tip-size,8px);height:var(--tip-size,8px);border-bottom-right-radius:200px;border-bottom-width:0;border-right-width:0}.w2ui-overlay .w2ui-overlay-body.w2ui-arrow-left:after{content:"";position:absolute;right:calc(var(--tip-size,8px) * -.5 - 1px);top:calc(50% - 1px);transform:rotate(135deg) translateY(-50%);transform-origin:top center;margin:0;border:inherit;border-color:inherit;background-color:inherit;width:var(--tip-size,8px);height:var(--tip-size,8px);border-bottom-right-radius:200px;border-bottom-width:0;border-right-width:0}.w2ui-overlay .w2ui-overlay-body.w2ui-arrow-top:before{content:"";position:absolute;bottom:calc(var(--tip-size,8px) * -.5 + 3px);left:50%;transform-origin:center left;transform:rotate(-135deg) translateX(-50%);margin:0;border:inherit;border-color:inherit;background-color:inherit;width:var(--tip-size,8px);height:var(--tip-size,8px);border-bottom-right-radius:200px;border-bottom-width:0;border-right-width:0}.w2ui-overlay .w2ui-overlay-body.w2ui-arrow-bottom:after{content:"";position:absolute;top:calc(var(--tip-size,8px) * -.5);left:50%;transform:rotate(45deg) translateX(-50%);transform-origin:center left;margin:0;border:inherit;border-color:inherit;background-color:inherit;width:var(--tip-size,8px);height:var(--tip-size,8px);border-bottom-right-radius:200px;border-bottom-width:0;border-right-width:0}.w2ui-colors{padding:8px;padding-bottom:0;background-color:#fff;border-radius:3px;overflow:hidden;width:270px;height:240px}.w2ui-colors *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.w2ui-colors .w2ui-color-tabs{display:flex;background-color:#f7f7f7;height:34px;margin:14px -8px 0 -8px;border-top:1px solid #d6d6d6}.w2ui-colors .w2ui-color-tabs .w2ui-color-tab{display:inline-block;width:65px;height:32px;border:0;border-top:2px solid transparent;border-radius:1px;margin:-1.5px 4px;text-align:center;font-size:15px;padding-top:4px;color:#7b7b7b}.w2ui-colors .w2ui-color-tabs .w2ui-color-tab:hover{background-color:#e1e1e1}.w2ui-colors .w2ui-color-tabs .w2ui-color-tab.w2ui-selected{border-top-color:#0175ff}.w2ui-colors .w2ui-color-tabs .w2ui-color-tab .w2ui-icon{padding-top:1px;width:30px}.w2ui-colors .w2ui-tab-content.tab-1 .w2ui-color-row{display:flex}.w2ui-colors .w2ui-tab-content.tab-1 .w2ui-color-row .w2ui-color{cursor:default;text-align:center;display:inline-block;width:18px;height:18px;padding:6px;margin:1.5px;border:1px solid transparent}.w2ui-colors .w2ui-tab-content.tab-1 .w2ui-color-row .w2ui-color:hover{outline:1px solid #666;border:1px solid #fff}.w2ui-colors .w2ui-tab-content.tab-1 .w2ui-color-row .w2ui-color.w2ui-no-color{border:1px solid #efefef;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABlBMVEX/////TgCFoIUYAAAAGUlEQVR42uXHIQEAAACDsNO/NJ4Kn9uC8wsJkAARUrXAjwAAAABJRU5ErkJggg==) 15px 15px}.w2ui-colors .w2ui-tab-content.tab-1 .w2ui-color-row .w2ui-color.w2ui-selected:before{content:'\2022';position:relative;left:-1px;top:-8px;color:#fff;font-size:14px;text-shadow:0 0 2px #222}.w2ui-colors .w2ui-tab-content.tab-2{height:184px;padding:1px 2px}.w2ui-colors .w2ui-tab-content.tab-2 .palette{position:relative;width:150px;height:125px;outline:1px solid #d2d2d2}.w2ui-colors .w2ui-tab-content.tab-2 .palette .palette-bg{height:100%;background-image:linear-gradient(0deg,#000,rgba(204,154,129,0));pointer-events:none}.w2ui-colors .w2ui-tab-content.tab-2 .rainbow{position:relative;width:150px;height:12px;margin:10px 0 0 0;background:linear-gradient(90deg,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%)}.w2ui-colors .w2ui-tab-content.tab-2 .alpha{position:relative;width:150px;height:12px;margin:20px 0 0 0;background-color:#fff;background-image:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb),linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb);background-size:12px 12px;background-position:0 0,6px 6px}.w2ui-colors .w2ui-tab-content.tab-2 .alpha .alpha-bg{height:100%;background-image:linear-gradient(90deg,rgba(80,80,80,0) 0,#505050 100%);pointer-events:none}.w2ui-colors .w2ui-tab-content.tab-2 .value1{pointer-events:none;position:absolute;top:0;display:inline-block;width:8px;height:8px;border-radius:10px;border:1px solid #999;outline:1px solid #bbb;background-color:transparent;box-shadow:0 0 1px #fff;transform:translateX(-3px) translateY(-3px)}.w2ui-colors .w2ui-tab-content.tab-2 .value2{pointer-events:none;position:absolute;top:-2px;display:inline-block;width:8px;height:16px;border-radius:2px;border:1px solid #696969;background-color:#fff;box-shadow:0 0 1px #fff;transform:translateX(-1px)}.w2ui-colors .w2ui-tab-content.tab-2 .color-info{float:right;margin-right:-5px}.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-preview-bg{box-shadow:0 0 1px #c3c3c3;height:40px;background-color:#fff;background-image:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb),linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb);background-size:16px 16px;background-position:0 0,8px 8px;margin-bottom:10px}.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-original,.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-preview{height:40px;width:50px;float:left}.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-part{padding-top:7px}.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-part span{display:inline-block;width:8px;margin:2px 1px 2px 5px;color:#666}.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-part input{font-size:12px;border-radius:2px;border:1px solid #ccc;width:30px;text-align:right;padding:4px;color:#333}.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-part.opacity{margin:11px 0 0 8px}.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-part.opacity span{width:42px}.w2ui-colors .w2ui-tab-content.tab-2 .color-info .color-part.opacity input{width:38px;text-align:center}.w2ui-menu-search,.w2ui-menu-top{position:sticky;top:0;background-color:#fff;border-bottom:1px dotted silver}.w2ui-menu-search{padding:6px 4px}.w2ui-menu-search .w2ui-icon{position:absolute;top:11px;left:6px;color:#90819c;font-size:14px}.w2ui-menu-search #menu-search{width:100%;padding:5px 5px 5px 25px}.w2ui-menu{display:block;color:#000;padding:5px 0;border-radius:5px;overflow-x:hidden;cursor:default}.w2ui-menu .w2ui-menu-item{display:flex;align-content:stretch;padding:8px 5px;user-select:none}.w2ui-menu .w2ui-menu-item.w2ui-even{color:inherit;background-color:#fff}.w2ui-menu .w2ui-menu-item.w2ui-odd{color:inherit;background-color:#fbfbfb}.w2ui-menu .w2ui-menu-item:hover{background-color:#f0f3ff}.w2ui-menu .w2ui-menu-item.w2ui-selected{background-color:#e1e7ff}.w2ui-menu .w2ui-menu-item.w2ui-disabled{opacity:.4;color:inherit;background-color:transparent}.w2ui-menu .w2ui-menu-item .menu-icon{flex:none;width:26px;height:16px;padding:0;margin:0}.w2ui-menu .w2ui-menu-item .menu-icon span{width:18px;font-size:14px;color:#8d99a7;display:inline-block;padding-top:1px}.w2ui-menu .w2ui-menu-item .menu-text{flex-grow:1;white-space:nowrap}.w2ui-menu .w2ui-menu-item .menu-extra{flex:none;min-width:10px}.w2ui-menu .w2ui-menu-item .menu-extra span{border:1px solid #f6fcf4;border-radius:20px;width:auto;height:18px;padding:2px 7px;margin:0 0 0 10px;background-color:#f2f8f0;color:#666;box-shadow:0 0 2px #474545;text-shadow:1px 1px 0 #fff}.w2ui-menu .w2ui-menu-item .menu-extra span.hotkey{border:none;border-radius:0;background-color:transparent;color:#888;box-shadow:none;text-shadow:none}.w2ui-menu .w2ui-menu-item .menu-extra span.remove{background-color:transparent;border-color:transparent;box-shadow:none;padding:0 5px;border-radius:3px;position:relative;margin-top:-3px;display:block;height:20px;width:20px;text-align:center;user-select:none}.w2ui-menu .w2ui-menu-item .menu-extra span.remove:hover{background-color:#f9e7e7;color:red}.w2ui-menu .w2ui-menu-item .menu-extra span.remove:active{background-color:#ffd1d1}.w2ui-menu .w2ui-menu-divider{padding:5px}.w2ui-menu .w2ui-menu-divider .line{border-top:1px dotted silver}.w2ui-menu .w2ui-menu-divider.has-text{height:26px;background-color:#fafafa;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;text-align:center}.w2ui-menu .w2ui-menu-divider.has-text .line{display:block;margin-top:7px}.w2ui-menu .w2ui-menu-divider.has-text .text{display:inline-block;position:relative;top:-10px;background-color:#fafafa;padding:0 7px;color:#a9a9a9}.w2ui-menu .w2ui-no-items{padding:5px 15px;text-align:center;color:gray}.w2ui-menu .w2ui-no-items .w2ui-spinner{position:relative;left:-2px;margin-bottom:-5px;width:18px;height:18px}.w2ui-menu .w2ui-sub-menu-box{background-color:#fafafd;border-top:1px solid #d6e2e6;border-bottom:1px solid #d6e2e6;padding:0 3px}.w2ui-menu .collapsed .menu-extra span,.w2ui-menu .expanded .menu-extra span{position:relative;border-color:transparent;background-color:transparent;box-shadow:none;padding:0 6px;border-radius:0;margin-left:5px}.w2ui-menu .collapsed .menu-extra span:after,.w2ui-menu .expanded .menu-extra span:after{content:"";position:absolute;border-left:5px solid grey;border-top:5px solid transparent;border-bottom:5px solid transparent;transform:rotateZ(-90deg);pointer-events:none;margin-left:-2px;margin-top:3px}.w2ui-menu .collapsed .menu-extra span:hover,.w2ui-menu .expanded .menu-extra span:hover{border-color:transparent;background-color:transparent}.w2ui-menu .collapsed .menu-extra span:after{transform:rotateZ(90deg)}.w2ui-calendar{margin:0;line-height:1.1;user-select:none}.w2ui-calendar.w2ui-overlay-body{border:1px solid #cccace;color:#3c3c3c;text-shadow:none;background-color:#fff;box-shadow:0 1px 6px 1px #ebeaec}.w2ui-calendar .w2ui-cal-title,.w2ui-calendar .w2ui-time-title{margin:0;padding:7px 2px;background-color:#fafafa;border-top:1px solid #fefefe;border-bottom:1px solid #ddd;color:#555;text-align:center;text-shadow:1px 1px 1px #eee;font-size:16px;cursor:pointer}.w2ui-calendar .w2ui-cal-title .arrow-down,.w2ui-calendar .w2ui-time-title .arrow-down{position:relative;top:-3px;left:5px;opacity:.6}.w2ui-calendar .w2ui-cal-next,.w2ui-calendar .w2ui-cal-previous{width:30px;height:30px;color:#666;border:1px solid transparent;border-radius:3px;padding:7px 5px;margin:-4px 1px 0 1px;cursor:default}.w2ui-calendar .w2ui-cal-next:hover,.w2ui-calendar .w2ui-cal-previous:hover{color:#000;border:1px solid #f5f5f5;background-color:#f9f7f7}.w2ui-calendar .w2ui-cal-next:active,.w2ui-calendar .w2ui-cal-previous:active{color:#000;background-color:#f2f1f4;border:1px solid #e6dbfb}.w2ui-calendar .w2ui-cal-next>div,.w2ui-calendar .w2ui-cal-previous>div{position:absolute;border-left:4px solid #888;border-top:4px solid #888;border-right:4px solid transparent;border-bottom:4px solid transparent;width:0;height:0;padding:0;margin:3px 0 0 0}.w2ui-calendar .w2ui-cal-previous{float:left}.w2ui-calendar .w2ui-cal-previous>div{-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg);margin-left:6px}.w2ui-calendar .w2ui-cal-next{float:right}.w2ui-calendar .w2ui-cal-next>div{-webkit-transform:rotate(135deg);-moz-transform:rotate(135deg);-ms-transform:rotate(135deg);-o-transform:rotate(135deg);transform:rotate(135deg);margin-left:2px;margin-right:2px}.w2ui-calendar .w2ui-cal-jump{display:flex;background-color:#fdfdfd}.w2ui-calendar .w2ui-cal-jump .w2ui-jump-month,.w2ui-calendar .w2ui-cal-jump .w2ui-jump-year{cursor:default;text-align:center;border:1px solid transparent;border-radius:3px;font-size:14px}.w2ui-calendar .w2ui-cal-jump #w2ui-jump-month{width:186px;padding:10px 5px 4px 3px;border-right:1px solid #efefef;display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(4,52px);grid-gap:4px}.w2ui-calendar .w2ui-cal-jump #w2ui-jump-month .w2ui-jump-month{padding:15px 0 0 0}.w2ui-calendar .w2ui-cal-jump #w2ui-jump-year{width:90px;height:240px;overflow-x:hidden;overflow-y:auto;margin:0 2px;display:flex;flex-wrap:wrap}.w2ui-calendar .w2ui-cal-jump #w2ui-jump-year .w2ui-jump-year{width:95%;height:30px;padding:5px 0;margin:1px 0}.w2ui-calendar .w2ui-cal-jump .w2ui-jump-month:hover,.w2ui-calendar .w2ui-cal-jump .w2ui-jump-year:hover{color:#000;border:1px solid #f5f5f5;background-color:#f9f7f7}.w2ui-calendar .w2ui-cal-jump .w2ui-jump-month.w2ui-selected,.w2ui-calendar .w2ui-cal-jump .w2ui-jump-year.w2ui-selected{color:#000;background-color:#f2f1f4;border:1px solid #e6dbfb}.w2ui-calendar .w2ui-cal-now{cursor:default;padding:3px;text-align:center;background-color:#f4f4f4;margin:5px;border:1px solid #e5e5e5;border-radius:4px}.w2ui-calendar .w2ui-cal-now:hover{color:#28759e;border:1px solid #c3d6df}.w2ui-calendar .w2ui-cal-days{width:280px;height:240px;padding:2px;display:grid;grid-template-columns:repeat(7,1fr)}.w2ui-calendar .w2ui-cal-days .w2ui-day{border:1px solid #fff;border-radius:3px;color:#000;background-color:#f7f7f7;padding:8px 0 0 0;cursor:default;text-align:center}.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-saturday,.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-sunday{border:1px solid #fff;color:#c8493b;background-color:#f7f7f7}.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-today{background-color:#e2f7cd}.w2ui-calendar .w2ui-cal-days .w2ui-day:hover{background-color:#f2f1f4;border:1px solid #e6dbfb}.w2ui-calendar .w2ui-cal-days .w2ui-day:active{background-color:#eeebf3;border:1px solid #cec2e5}.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-selected{border:1px solid #8cb067}.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-weekday{text-align:center;background-color:#fff;color:#a99cc2}.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-weekday:hover{border:1px solid #fff;background-color:#fff}.w2ui-calendar .w2ui-cal-days .w2ui-day.outside{color:#b5b5b5;background-color:#fff}.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-blocked{color:#555;background-color:#fff;border:1px solid #fff}.w2ui-calendar .w2ui-cal-days .w2ui-day.w2ui-blocked:after{content:" ";position:absolute;color:#b3b3b378;font-size:27px;padding:0;font-family:verdana;transform:translate(-15px,15px) rotate(-36deg);border-top:1px solid #c9c2c2;width:24px;transform-origin:top left}.w2ui-cal-time{display:grid;grid-template-columns:repeat(3,1fr);background-color:#fff;cursor:default}.w2ui-cal-time .w2ui-cal-column{width:90px;display:flex;flex-wrap:wrap;padding:4px}.w2ui-cal-time .w2ui-cal-column:nth-child(even){background-color:#fafafa}.w2ui-cal-time .w2ui-cal-column span{width:100%;padding:8px;margin:1px;text-align:center;border:1px solid transparent;border-radius:2px;white-space:nowrap}.w2ui-cal-time .w2ui-cal-column span:hover{background-color:#f2f1f4;border:1px solid #e6dbfb}.w2ui-cal-time .w2ui-cal-column span:active{background-color:#eeebf3;border:1px solid #cec2e5}.w2ui-cal-time .w2ui-cal-column span.w2ui-blocked{pointer-events:none;text-decoration:line-through;color:silver}.w2ui-form{position:relative;color:#000;background-color:#fcfcfb;border:1px solid #e1e1e1;border-radius:3px;padding:0;overflow:hidden}.w2ui-form>.w2ui-form-box{position:absolute;overflow:hidden;top:0;bottom:0;left:0;right:0}.w2ui-form .w2ui-form-header{position:absolute;top:0;left:0;right:0;height:36px;padding:10px;overflow:hidden;font-size:16px;color:#444;background-color:#fff;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom:1px solid #f1f1f1}.w2ui-form .w2ui-form-toolbar{position:absolute;left:0;right:0;margin:0;padding:2px;border-top-left-radius:3px;border-top-right-radius:3px;border-bottom:1px solid #f1f1f1}.w2ui-form .w2ui-form-tabs{position:absolute;left:0;right:0;margin:0;padding:0;height:32px;border-top-left-radius:3px;border-top-right-radius:3px;padding-top:4px;background-color:#fff}.w2ui-form .w2ui-form-tabs .w2ui-tab.active{background-color:#fcfcfb}.w2ui-form .w2ui-page{position:absolute;left:0;right:0;overflow:auto;padding:10px 5px 0 5px;border-left:1px solid inherit;border-right:1px solid inherit;background-color:inherit;border-radius:3px}.w2ui-form .w2ui-column-container{display:flex;padding:0}.w2ui-form .w2ui-column-container .w2ui-column{width:100%}.w2ui-form .w2ui-column-container .w2ui-column.col-0,.w2ui-form .w2ui-column-container .w2ui-column.col-1,.w2ui-form .w2ui-column-container .w2ui-column.col-10,.w2ui-form .w2ui-column-container .w2ui-column.col-2,.w2ui-form .w2ui-column-container .w2ui-column.col-3,.w2ui-form .w2ui-column-container .w2ui-column.col-4,.w2ui-form .w2ui-column-container .w2ui-column.col-5,.w2ui-form .w2ui-column-container .w2ui-column.col-6,.w2ui-form .w2ui-column-container .w2ui-column.col-7,.w2ui-form .w2ui-column-container .w2ui-column.col-8,.w2ui-form .w2ui-column-container .w2ui-column.col-9{padding:0;padding-left:10px}.w2ui-form .w2ui-column-container .w2ui-column.col-0{padding-left:0}.w2ui-form .w2ui-buttons{position:absolute;left:0;right:0;bottom:0;text-align:center;border-top:1px solid #f1f1f1;border-bottom:0 solid #f1f1f1;background-color:#fff;padding:15px 0;border-bottom-left-radius:3px;border-bottom-right-radius:3px}.w2ui-form .w2ui-buttons button,.w2ui-form .w2ui-buttons input[type=button]{min-width:80px;margin-right:5px}.w2ui-form input[type=checkbox]:not(.w2ui-toggle),.w2ui-form input[type=radio]{margin-top:4px;margin-bottom:4px;width:14px;height:14px}.w2ui-group-title{padding:5px 2px 0 5px;color:#656164cc;text-shadow:1px 1px 2px #fdfdfd;font-size:120%}.w2ui-group-fields{background-color:#fff;margin:5px 0 14px 0;padding:10px 5px;border-top:1px dotted #e1e1e1;border-bottom:1px dotted #e1e1e1}.w2ui-field>label{display:block;float:left;margin-top:10px;margin-bottom:0;width:120px;padding:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right;min-height:20px;color:#666}.w2ui-field>div{margin-bottom:3px;margin-left:128px;padding:4px;min-height:28px;float:none}.w2ui-field.w2ui-required>div{position:relative}.w2ui-field.w2ui-required:not(.w2ui-field-inline)>div::before{content:'*';position:absolute;margin-top:7px;margin-left:-8px;color:red}.w2ui-field.w2ui-required.w2ui-field-inline>div::before{content:''!important}.w2ui-field.w2ui-disabled{opacity:.45;background-color:transparent!important}.w2ui-field.w2ui-disabled input:not([type=button]):not([type=submit]):not([type=checkbox]):not([type=radio]),.w2ui-field.w2ui-disabled select,.w2ui-field.w2ui-disabled textarea{border:1px solid #bdc0c3!important;background-color:#f5f5f5!important}.w2ui-field.w2ui-span-none>label{margin:0;padding:5px 12px 0 4px;display:block;width:98%;text-align:left}.w2ui-field.w2ui-span-none>div{margin-left:0}.w2ui-field.w2ui-span0>label{display:none}.w2ui-field.w2ui-span0>div{margin-left:0}.w2ui-field.w2ui-span1>label{width:20px}.w2ui-field.w2ui-span1>div{margin-left:28px}.w2ui-field.w2ui-span2>label{width:40px}.w2ui-field.w2ui-span2>div{margin-left:48px}.w2ui-field.w2ui-span3>label{width:60px}.w2ui-field.w2ui-span3>div{margin-left:68px}.w2ui-field.w2ui-span4>label{width:80px}.w2ui-field.w2ui-span4>div{margin-left:88px}.w2ui-field.w2ui-span5>label{width:100px}.w2ui-field.w2ui-span5>div{margin-left:108px}.w2ui-field.w2ui-span6>label{width:120px}.w2ui-field.w2ui-span6>div{margin-left:128px}.w2ui-field.w2ui-span7>label{width:140px}.w2ui-field.w2ui-span7>div{margin-left:148px}.w2ui-field.w2ui-span8>label{width:160px}.w2ui-field.w2ui-span8>div{margin-left:168px}.w2ui-field.w2ui-span9>label{width:180px}.w2ui-field.w2ui-span9>div{margin-left:188px}.w2ui-field.w2ui-span10>label{width:200px}.w2ui-field.w2ui-span10>div{margin-left:208px}.w2ui-field.w2ui-field-inline{display:inline}.w2ui-field.w2ui-field-inline>div{display:inline;margin:0;padding:0}.w2ui-field .w2ui-box-label{user-select:none;vertical-align:middle}.w2ui-field .w2ui-box-label input,.w2ui-field .w2ui-box-label span{display:inline-block;vertical-align:middle}.w2ui-field .w2ui-box-label span{padding-left:3px}.w2ui-field .w2ui-box-label input{margin:4px 0 3px 0}input:not([type=button]):not([type=submit]):not([type=checkbox]):not([type=radio]).w2ui-error,textarea.w2ui-error{border:1px solid #ffa8a8;background-color:#fff4eb}.w2field{padding:3px;border-radius:3px;border:1px solid silver}.w2ui-field-helper{position:absolute;display:inline-block;line-height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.w2ui-field-helper .w2ui-field-up{position:absolute;top:0;padding:2px 3px;cursor:pointer;pointer-events:all}.w2ui-field-helper .w2ui-field-down{position:absolute;bottom:0;padding:2px 3px;cursor:pointer;pointer-events:all}.w2ui-field-helper .arrow-up:hover{border-bottom-color:#81c6ff}.w2ui-field-helper .arrow-down:hover{border-top-color:#81c6ff}.w2ui-field-helper .w2ui-icon-search{position:absolute;margin:8px 0 0 -2px;display:none;color:#777;width:21px!important;font-size:13px}.w2ui-field-helper .w2ui-icon-search.show-search{display:block}.w2ui-field-helper.w2ui-list{color:inherit;position:absolute;padding:0;margin:0;min-height:28px;overflow:auto;border:1px solid #e0e0e0;border-radius:3px;font-size:6px;line-height:100%;box-sizing:border-box;pointer-events:all;background-color:#f7fafa}.w2ui-field-helper.w2ui-list.has-focus,.w2ui-field-helper.w2ui-list:focus-within{outline:auto #72b2ff;background-color:#fff}.w2ui-field-helper.w2ui-list input[type=text]{-webkit-box-shadow:none;-moz-box-shadow:none;-ms-box-shadow:none;-o-box-shadow:none;box-shadow:none}.w2ui-field-helper.w2ui-list .w2ui-multi-items{position:absolute;display:inline-block;margin:0;padding:0;pointer-events:none}.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item{pointer-events:all;float:left;margin:3px 0 0 5px;border-radius:15px;width:auto;padding:3px 24px 1px 12px;border:1px solid #b4d0de;background-color:#eff3f5;white-space:nowrap;cursor:default;font-family:OpenSans;font-size:11px;line-height:100%;height:20px;overflow:hidden;text-overflow:ellipsis;box-sizing:border-box}.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item:hover{background-color:#d0dbe1}.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item:last-child{border-radius:0;border:1px solid transparent;background-color:transparent}.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item:last-child input{padding:1px;padding-top:0;margin:0;border:0;outline:0;height:auto;line-height:100%;font-size:inherit;font-family:inherit;background-color:transparent}.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item .w2ui-icon{float:left;color:#828aa7;margin:1px 2px 0 -6px}.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item .w2ui-list-remove{float:right;width:16px;height:16px;margin:-2px -20px 0 0;border-radius:2px;font-size:12px;border:1px solid transparent}.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item .w2ui-list-remove:hover{background-color:#f6e5e5;border:1px solid #fac2c2;color:red;opacity:1}.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item .w2ui-list-remove:before{position:relative;display:inline-block;left:4px;opacity:.7;content:'x';line-height:1}.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-item>span.file-size{pointer-events:none;color:#777}.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-search{float:left;margin:0;padding:0}.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-search input[type=text]{pointer-events:all;width:0;height:20px;padding:3px 0 3px 0;margin:3px 0 0 5px;border:0;background-color:transparent}.w2ui-field-helper.w2ui-list .w2ui-multi-items .li-search input[type=text]:focus{outline:0;border:0}.w2ui-field-helper.w2ui-list .w2ui-multi-file{position:absolute;left:0;right:0;top:0;bottom:0}.w2ui-field-helper.w2ui-list.w2ui-readonly .w2ui-multi-items>.li-item:hover{background-color:#eff3f5}.w2ui-field-helper.w2ui-list.w2ui-file-dragover{background-color:#e4ffda;border:1px solid #93e07d}.w2ui-field-helper.w2ui-list .w2ui-enum-placeholder{display:inline;position:absolute;pointer-events:none;color:#999;box-sizing:border-box}.w2ui-overlay .w2ui-file-preview{padding:1px;background-color:#fff}.w2ui-overlay .w2ui-file-info{display:grid;grid-template-columns:1fr 2fr;color:#fff;padding:6px 0}.w2ui-overlay .w2ui-file-info .file-caption{text-align:right;color:silver;padding-right:10px}.w2ui-overlay .w2ui-file-info .file-value{color:#fff}.w2ui-overlay .w2ui-file-info .file-type{max-width:200px;display:block-inline;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.arrow-up{background:0 0;width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-bottom:5px solid #777;font-size:0;line-height:0}.arrow-down{background:0 0;width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:5px solid #777;font-size:0;line-height:0}.arrow-left{background:0 0;width:0;height:0;border-bottom:4px solid transparent;border-top:4px solid transparent;border-right:5px solid #777;font-size:0;line-height:0}.arrow-right{background:0 0;width:0;height:0;border-bottom:4px solid transparent;border-top:4px solid transparent;border-left:5px solid #777;font-size:0;line-height:0}.w2ui-select{cursor:default;color:#000!important;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAALCAQAAACnzwd+AAAAcklEQVR4AcXMsQFBQQDG4P9tAgC0gJYRQJZgKQMwCqCku6vVAAAA+NJHP4KHOk0aV2pRw61n4BBmyOxKQ8I4ehZeuhd3HTx6DQEGZ7sBfr2OOOOj3Yi43kMKs9sZknofOexqZ8npMygwWZTX51CipP+YA1OiZJbYYg9lAAAAAElFTkSuQmCC);background-size:17px 6px;background-position:right center;background-repeat:no-repeat}.w2ui-select.has-focus{outline:auto #72b2ff;background-color:#fff!important}.w2ui-select[disabled],.w2ui-select[readonly]{background-image:none;background-color:#f1f1f1!important;color:#777!important}.w2ui-layout{position:relative;overflow:hidden}.w2ui-layout>div{position:absolute;overflow:hidden;left:0;top:0;right:0;bottom:0}.w2ui-layout>div .w2ui-panel{display:none;position:absolute;z-index:120}.w2ui-layout>div .w2ui-panel .w2ui-panel-title{position:absolute;left:0;top:0;right:0;padding:5px;background-color:#fff;color:#656164cc;border:1px solid #efefef;border-bottom:1px solid #f5f5f5}.w2ui-layout>div .w2ui-panel .w2ui-panel-tabs{position:absolute;left:0;top:0;right:0;z-index:2;display:none;overflow:hidden;background-color:#fff;padding:0}.w2ui-layout>div .w2ui-panel .w2ui-panel-tabs>.w2ui-tab.active{background-color:#fcfcfc}.w2ui-layout>div .w2ui-panel .w2ui-panel-toolbar{position:absolute;left:0;top:0;right:0;z-index:2;display:none;overflow:hidden;background-color:#fafafa;border-bottom:1px solid #efefef;padding:2px}.w2ui-layout>div .w2ui-panel .w2ui-panel-content{position:absolute;left:0;top:0;right:0;bottom:0;z-index:1;color:inherit;background-color:#fcfcfc}.w2ui-layout>div .w2ui-resizer{display:none;position:absolute;z-index:121;background-color:transparent}.w2ui-layout>div .w2ui-resizer.active,.w2ui-layout>div .w2ui-resizer:hover{background-color:#c8cad1}.w2ui-grid{position:relative;border:1px solid #e1e1e1;border-radius:2px;overflow:hidden!important}.w2ui-grid>.w2ui-grid-box{position:absolute;overflow:hidden;left:0;top:0;right:0;bottom:0}.w2ui-grid .w2ui-grid-header{position:absolute;top:0;left:0;right:0;height:36px;padding:10px;overflow:hidden;font-size:16px;color:#444;background-color:#fff;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom:1px solid #e1e1e1!important}.w2ui-grid .w2ui-grid-toolbar{position:absolute;border-bottom:1px solid #efefef;background-color:#fafafa;height:52px;padding:9px 3px 0 3px;margin:0;box-shadow:0 1px 2px #f5f5f5}.w2ui-grid .w2ui-grid-toolbar .w2ui-tb-button .w2ui-tb-icon{margin:3px 0 0 0!important}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input{position:relative;width:300px;left:0;top:-4px}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-down{position:absolute;top:7px;left:4px;color:#8c99a7;font-size:13px}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-grid-search-name{position:absolute;margin:5px 0 0 3px;padding:4px 27px 4px 10px;background-color:#fbfbfb;border:1px solid #b9b9b9;border-radius:15px;pointer-events:none}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-grid-search-name .name-icon{position:absolute;margin-left:-6px;color:#8c99a7}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-grid-search-name .name-text{padding-left:14px}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-grid-search-name .name-cross{position:absolute;margin-top:-4px;margin-left:7px;padding:4px 5px;pointer-events:all}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-grid-search-name .name-cross:hover{color:red}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-all{outline:0!important;border-radius:4px!important;line-height:normal!important;height:30px!important;width:300px!important;border:1px solid #e1e1e1!important;color:#000!important;background-color:#f1f1f1!important;padding:1px 28px 0 28px!important;margin:0!important;margin-top:1px!important;font-size:13px!important}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-all:focus{border:1px solid #007cff!important;background-color:#fff!important}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-drop{position:absolute;right:2px;top:3px;height:26px;width:26px;font-size:16px;color:#a4adb1;cursor:pointer;padding:7px 2px 7px 2px;border-radius:4px;background-color:transparent}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-drop span.w2ui-icon-drop{position:relative;top:-2px}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-drop.checked,.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-drop:hover{color:#fff;background-color:#56a1e2}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches{display:flex;flex-direction:row;flex-wrap:nowrap;border-top:1px solid #ececec;border-bottom:1px solid #ececec;background-color:#fcfdff;margin:7px -20px 0 -20px;padding:6px 50px 6px 20px;height:36px}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches>div{white-space:nowrap}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches>span{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;border:1px solid #88c3f7;border-radius:15px;padding:4px 12px;margin:0 4px;color:#4c9ad6;font-size:12px;font-weight:700;background-color:#f5f9fe}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches>span>span{font-size:9px;position:relative;top:-1px;left:2px}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches .grid-search-line{border-left:1px solid #ececec;width:11px;height:22px;margin-left:7px;margin-top:1px}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches .w2ui-grid-search-logic{border:1px solid #c8c9ca!important;color:#676767!important}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches button.grid-search-btn{margin:0 3px;padding:0;height:24px;font-size:11px}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches button.grid-search-btn.btn-remove{min-width:26px;position:absolute;left:calc(100% - 35px)}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches .grid-search-count{background-color:#4cb1fd;border-radius:10px;color:#fff;padding:0 6px 1px 6px;font-size:11px!important;position:relative!important;top:0!important}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches .grid-search-list li{padding:5px}.w2ui-grid .w2ui-grid-toolbar .w2ui-grid-searches .grid-search-list input{position:relative;top:2px;left:-3px}.w2ui-grid .w2ui-grid-save-search{padding-top:30px;text-align:center}.w2ui-grid .w2ui-grid-save-search span{width:280px;display:inline-block;text-align:left;padding-bottom:4px}.w2ui-grid .w2ui-grid-save-search .search-name{width:280px!important}.w2ui-grid .w2ui-grid-body{position:absolute;overflow:hidden;padding:0;background-color:#fff;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.w2ui-grid .w2ui-grid-body input,.w2ui-grid .w2ui-grid-body select,.w2ui-grid .w2ui-grid-body textarea{-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;-o-user-select:text;user-select:text}.w2ui-grid .w2ui-grid-body .w2ui-grid-columns,.w2ui-grid .w2ui-grid-body .w2ui-grid-fcolumns{overflow:hidden;position:absolute;left:0;top:0;right:0;box-shadow:0 1px 4px #efefef;height:auto}.w2ui-grid .w2ui-grid-body .w2ui-grid-columns table,.w2ui-grid .w2ui-grid-body .w2ui-grid-fcolumns table{height:auto}.w2ui-grid .w2ui-grid-body .w2ui-grid-columns .w2ui-resizer,.w2ui-grid .w2ui-grid-body .w2ui-grid-fcolumns .w2ui-resizer{position:absolute;z-index:1000;display:block;background-image:none;background-color:rgba(0,0,0,0);padding:0;margin:0;width:6px;height:12px;cursor:ew-resize}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords,.w2ui-grid .w2ui-grid-body .w2ui-grid-records{position:absolute;left:0;right:0;top:0;bottom:0}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd,.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd{color:inherit;background-color:#fff;border-bottom:1px solid #f5f5f5}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd.w2ui-record-hover,.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd:hover,.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd.w2ui-record-hover,.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd:hover{color:inherit;background-color:#f3f3f3}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd.w2ui-empty-record:hover,.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd.w2ui-empty-record:hover{background-color:#fff}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even,.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even{color:inherit;background-color:#fbfbfb;border-bottom:1px dotted #f5f5f5}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even.w2ui-record-hover,.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even:hover,.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even.w2ui-record-hover,.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even:hover{color:inherit;background-color:#f3f3f3}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even.w2ui-empty-record:hover,.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even.w2ui-empty-record:hover{background-color:#fbfbfb}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr td.w2ui-selected,.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-selected,.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr td.w2ui-selected,.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-selected{color:#000!important;background-color:#d9eaff!important;border-bottom:1px solid transparent}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr td.w2ui-inactive,.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-inactive,.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr td.w2ui-inactive,.w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-inactive{background-color:#e8edf5!important}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-expanded1,.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-expanded1{height:0;border-bottom:1px solid #b2bac0}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-expanded1>div,.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-expanded1>div{height:0;border:0;transition:height .3s,opacity .3s}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-expanded2,.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-expanded2{height:0;border-radius:0;border-bottom:1px solid #b2bac0}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-expanded2>div,.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-expanded2>div{height:0;border:0;transition:height .3s,opacity .3s}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-load-more,.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-load-more{cursor:pointer;background-color:rgba(233,237,243,.5);border-right:1px solid #f1f1f1;height:43px}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-load-more>div,.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-load-more>div{text-align:center;color:#777;background-color:rgba(233,237,243,.5);padding:10px 0 15px 0;height:43px;border-top:1px dashed #d6d5d7;border-bottom:1px dashed #d6d5d7;font-size:12px}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-load-more>div:hover,.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-load-more>div:hover{color:#438ba2;background-color:#f3f3f3}.w2ui-grid .w2ui-grid-body .w2ui-grid-frecords .w2ui-reoder-empty,.w2ui-grid .w2ui-grid-body .w2ui-grid-records .w2ui-reoder-empty{background-color:#eee;border-bottom:1px dashed #aaa;border-top:1px dashed #aaa}.w2ui-grid .w2ui-grid-body table{border-spacing:0;border-collapse:collapse;table-layout:fixed;width:1px}.w2ui-grid .w2ui-grid-body table .w2ui-head{margin:0;padding:0;border-right:1px solid #dcdcdc;border-bottom:1px solid #dcdcdc;color:#656164;background-image:linear-gradient(#fff,#f9f9f9)}.w2ui-grid .w2ui-grid-body table .w2ui-head>div{padding:7px 6px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;position:relative}.w2ui-grid .w2ui-grid-body table td{border-right:1px solid #f1f1f1;border-bottom:0 solid #d6d5d7;cursor:default;overflow:hidden}.w2ui-grid .w2ui-grid-body table td.w2ui-soft-hidden,.w2ui-grid .w2ui-grid-body table td.w2ui-soft-span{border-right-color:transparent}.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data{margin:0;padding:0}.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data .w2ui-info{position:relative;top:2px;left:-1px;font-size:13px;color:#8d99a7;cursor:pointer;width:18px;display:inline-block;margin-right:3px;text-align:center}.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data .w2ui-clipboard-copy{float:right;margin-top:-15px;width:20px;height:16px;padding:0;text-align:center;cursor:pointer;font-size:13px;color:#8d98a7}.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data .w2ui-clipboard-copy:hover{color:#545961}.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data>div{padding:5px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data>div.flexible-record{height:auto;overflow:visible;white-space:normal}.w2ui-grid .w2ui-grid-body table td.w2ui-grid-data .w2ui-show-children{width:16px;height:10px;display:inline-block;position:relative;top:-1px;cursor:pointer}.w2ui-grid .w2ui-grid-body table td:last-child{border-right:0}.w2ui-grid .w2ui-grid-body table td:last-child div{text-overflow:clip}.w2ui-grid .w2ui-grid-body table .w2ui-col-number{width:34px;color:#777;background-color:rgba(233,237,243,.5)}.w2ui-grid .w2ui-grid-body table .w2ui-col-number div{padding:0 7px 0 3px;text-align:right}.w2ui-grid .w2ui-grid-body table .w2ui-col-number.w2ui-head{cursor:pointer}.w2ui-grid .w2ui-grid-body table .w2ui-col-select{width:26px}.w2ui-grid .w2ui-grid-body table .w2ui-col-select div{padding:0 0;text-align:center;overflow:hidden}.w2ui-grid .w2ui-grid-body table .w2ui-col-select div input[type=checkbox]{margin-top:3px;margin-bottom:0;position:relative}.w2ui-grid .w2ui-grid-body table .w2ui-col-expand{width:26px}.w2ui-grid .w2ui-grid-body table .w2ui-col-expand div{padding:0 0;text-align:center;font-weight:700}.w2ui-grid .w2ui-grid-body table .w2ui-col-order{width:26px}.w2ui-grid .w2ui-grid-body table .w2ui-col-order.w2ui-grid-data div{cursor:move;height:18px;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxyZWN0IHN0eWxlPSJmaWxsOiAjYWFhOyIgeD0iMCIgeT0iNCIgaGVpZ2h0PSIzIiB3aWR0aD0iMTYiPjwvcmVjdD4KICA8cmVjdCBzdHlsZT0iZmlsbDogI2FhYTsiIHg9IjAiIHk9IjkiIGhlaWdodD0iMyIgd2lkdGg9IjE2Ij48L3JlY3Q+Cjwvc3ZnPg==);background-position:5px 2px;background-size:14px 12px;background-repeat:no-repeat}.w2ui-grid .w2ui-grid-body table .w2ui-col-selected{background-color:#d1d1d1!important}.w2ui-grid .w2ui-grid-body table .w2ui-row-selected{background-color:#e2e2e2!important}.w2ui-grid .w2ui-grid-body .w2ui-intersection-marker{position:absolute;top:0;left:0;margin-left:-5px;height:26px;width:10px}.w2ui-grid .w2ui-grid-body .w2ui-intersection-marker.left{left:0;margin-left:-5px}.w2ui-grid .w2ui-grid-body .w2ui-intersection-marker.right{right:0;margin-right:5px}.w2ui-grid .w2ui-grid-body .w2ui-intersection-marker .top-marker{position:absolute;top:0;height:0;width:0;border-top:5px solid #72b2ff;border-left:5px solid transparent;border-right:5px solid transparent}.w2ui-grid .w2ui-grid-body .w2ui-intersection-marker .bottom-marker{position:absolute;bottom:0;height:0;width:0;border-bottom:5px solid #72b2ff;border-left:5px solid transparent;border-right:5px solid transparent}.w2ui-grid .w2ui-grid-body div.w2ui-col-header{height:auto!important;width:100%;overflow:hidden;padding-right:10px!important}.w2ui-grid .w2ui-grid-body div.w2ui-col-header>div.w2ui-sort-up{border:4px solid transparent;border-bottom:5px solid #8d99a7;margin-top:-2px;margin-right:-7px;float:right}.w2ui-grid .w2ui-grid-body div.w2ui-col-header>div.w2ui-sort-down{border:4px solid transparent;border-top:5px solid #8d99a7;margin-top:2px;margin-right:-7px;float:right}.w2ui-grid .w2ui-grid-body .w2ui-col-group{text-align:center}.w2ui-grid .w2ui-grid-body .w2ui-grid-scroll1{position:absolute;left:0;bottom:0;border-top:1px solid #ddd;border-right:1px solid #ddd;background-color:#fafafa}.w2ui-grid .w2ui-grid-empty-msg{position:absolute;top:27px;left:0;right:0;bottom:0;background-color:rgba(255,255,255,.65)}.w2ui-grid .w2ui-grid-empty-msg>div{position:absolute;left:0;right:0;top:45%;transform:translateY(-45%);text-align:center;font-size:13px;color:#666}.w2ui-grid .w2ui-changed{background:url(data:image/gif;base64,R0lGODlhCgAKAJEAALAABf///wAAAAAAACH5BAEAAAIALAAAAAAKAAoAAAIPlI8Hy8mbxIsSUnup3rQAADs=) no-repeat top right}.w2ui-grid .w2ui-edit-box{position:absolute;z-index:1001;border:1.5px solid #6299da;pointer-events:auto;padding:2px!important;margin:0!important;background-color:#fff}.w2ui-grid .w2ui-edit-box .w2ui-editable div.w2ui-input{outline:0;padding:.5px 1.5px!important}.w2ui-grid .w2ui-edit-box .w2ui-editable input{top:-2px!important;padding:1.5px!important}.w2ui-grid .w2ui-editable{overflow:hidden;height:100%!important;margin:0!important;padding:3.5px 2px 2px 2px!important}.w2ui-grid .w2ui-editable input{position:relative;top:-1px;border:0!important;border-radius:0!important;border-color:transparent!important;padding:3px!important;display:inline-block;width:100%!important;height:100%!important;pointer-events:auto!important}.w2ui-grid .w2ui-editable div.w2ui-input{position:relative;top:-.5px;border:0 transparent;border-radius:0!important;margin:0!important;padding:5px 3px!important;display:inline-block;width:100%!important;height:100%!important;pointer-events:auto!important;background-color:#fff;white-space:pre;overflow:hidden;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;-o-user-select:text;user-select:text}.w2ui-grid .w2ui-editable input.w2ui-select{outline:0!important;background:#fff}.w2ui-grid .w2ui-grid-summary{position:absolute;border-top:1px solid #dcdcdc;box-shadow:0 -1px 4px #f0eeee}.w2ui-grid .w2ui-grid-summary table{color:inherit}.w2ui-grid .w2ui-grid-summary table .w2ui-odd{background-color:#fff}.w2ui-grid .w2ui-grid-summary table .w2ui-even{background-color:#fbfbfb}.w2ui-grid .w2ui-grid-footer{position:absolute;bottom:0;left:0;right:0;margin:0;padding:0;text-align:center;font-size:11px;height:24px;overflow:hidden;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;-o-user-select:text;user-select:text;box-shadow:0 -1px 4px #f5f5f5;color:#444;background-color:#f8f8f8;border-top:1px solid #e4e4e4;border-bottom-left-radius:2px;border-bottom-right-radius:2px}.w2ui-grid .w2ui-grid-footer .w2ui-footer-left{float:left;padding-top:5px;padding-left:5px}.w2ui-grid .w2ui-grid-footer .w2ui-footer-right{float:right;padding-top:5px;padding-right:5px}.w2ui-grid .w2ui-grid-footer .w2ui-footer-center{padding:2px;text-align:center}.w2ui-grid .w2ui-grid-footer .w2ui-footer-center .w2ui-footer-nav{width:110px;margin:0 auto;padding:0;text-align:center}.w2ui-grid .w2ui-grid-footer .w2ui-footer-center .w2ui-footer-nav input[type=text]{padding:1px 2px 2px 2px;border-radius:3px;width:40px;text-align:center}.w2ui-grid .w2ui-grid-footer .w2ui-footer-center .w2ui-footer-nav a.w2ui-footer-btn{display:inline-block;border-radius:3px;cursor:pointer;font-size:11px;line-height:16px;padding:1px 5px;width:30px;height:18px;margin-top:-1px;color:#000;background-color:transparent}.w2ui-grid .w2ui-grid-footer .w2ui-footer-center .w2ui-footer-nav a.w2ui-footer-btn:hover{color:#000;background-color:#aec8ff}.w2ui-grid .w2ui-grid-focus-input{position:absolute;top:0;right:0;z-index:-1;opacity:0;overflow:hidden;padding:0;margin:0;width:1px;height:1px;resize:none;border:0}.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr td.w2ui-selected{background-color:#eef4fe!important}.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr td.w2ui-inactive{background-color:#f4f6f9!important}.w2ui-ss .w2ui-grid-body .w2ui-grid-records table td{border-right-width:1px;border-bottom:1px solid #efefef}.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even,.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr.w2ui-even:hover,.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd,.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr.w2ui-odd:hover{background-color:inherit}.w2ui-ss .w2ui-grid-body .w2ui-grid-records table tr:first-child td{border-top:0;border-bottom:0}.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr td.w2ui-selected{background-color:#eef4fe!important}.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr td.w2ui-inactive{background-color:#f4f6f9!important}.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table td{border-right-width:1px;border-bottom:1px solid #efefef}.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even,.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-even:hover,.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd,.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr.w2ui-odd:hover{background-color:inherit}.w2ui-ss .w2ui-grid-body .w2ui-grid-frecords table tr:first-child td{border-bottom:0}.w2ui-ss .w2ui-grid-body .w2ui-selection{position:absolute;z-index:1000;border:1.5px solid #6299da;pointer-events:none}.w2ui-ss .w2ui-grid-body .w2ui-selection .w2ui-selection-resizer{cursor:crosshair;position:absolute;bottom:0;right:0;width:6px;height:6px;margin-right:-3px;margin-bottom:-3px;background-color:#457fc2;border:.5px solid #fff;outline:1px solid #fff;pointer-events:auto}.w2ui-ss .w2ui-grid-body .w2ui-selection.w2ui-inactive{border:1.5px solid #c0c2c5}.w2ui-ss .w2ui-grid-body .w2ui-selection.w2ui-inactive .w2ui-selection-resizer{background-color:#b0b0b0}.w2ui-ss .w2ui-grid-body .w2ui-soft-range{position:absolute;pointer-events:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.w2ui-ss .w2ui-grid-body .w2ui-changed{background:inherit}.w2ui-ss .w2ui-grid-body .w2ui-editable input{outline:0!important}.w2ui-info-bubble table{font-family:OpenSans;font-size:12px;color:#fff;text-shadow:1px 1px solid #999}.w2ui-info-bubble table tr td:first-child{white-space:nowrap;padding:2px;padding-right:10px;color:#ddd;vertical-align:top}.w2ui-info-bubble table tr td:last-child{white-space:pre;padding:2px}.w2ui-overlay .w2ui-grid-search-suggest{border-top-left-radius:5px;border-top-right-radius:5px;padding:10px;background-color:#fff;border-bottom:1px solid #e6e6e6;color:#444}.w2ui-overlay .w2ui-grid-search-single{font-size:12px;padding-top:10px}.w2ui-overlay .w2ui-grid-search-single .field{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;border:1px solid #a9b6c2;border-radius:4px;padding:4px 12px;margin:0 2px;color:#4295d4;background-color:#f5f9fe}.w2ui-overlay .w2ui-grid-search-single .operator{display:inline-block;color:#000;background-color:#e6e6e6;border-radius:4px;margin:0 4px;padding:6px 10px}.w2ui-overlay .w2ui-grid-search-single .value{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;border:1px solid #a9b6c2;border-radius:4px;margin:0 2px;padding:4px 12px}.w2ui-overlay .w2ui-grid-search-single .buttons{text-align:left;padding:15px 10px 10px 0}.w2ui-overlay .w2ui-grid-search-advanced{text-align:left;padding:0;background-color:#fff;text-shadow:none;border:1px solid #cdcdd8;box-shadow:0 3px 14px 1px #e8e8e8}.w2ui-overlay .w2ui-grid-search-advanced .search-title{padding:20px 0 9px 20px;font-size:17px;font-weight:700;color:#555}.w2ui-overlay .w2ui-grid-search-advanced .search-title .search-logic{float:right;padding-right:10px}.w2ui-overlay .w2ui-grid-search-advanced table{color:#5f5f5f;font-size:13px;padding:12px 4px 0 4px}.w2ui-overlay .w2ui-grid-search-advanced table td{padding:4px;min-height:40px}.w2ui-overlay .w2ui-grid-search-advanced table td.caption{text-align:right;padding-right:5px;padding-left:20px}.w2ui-overlay .w2ui-grid-search-advanced table td.operator{text-align:left;padding:5px}.w2ui-overlay .w2ui-grid-search-advanced table td.operator select{width:100%;color:#000}.w2ui-overlay .w2ui-grid-search-advanced table td.value{padding-right:5px;padding-left:5px}.w2ui-overlay .w2ui-grid-search-advanced table td.value input[type=text]{border-radius:3px;padding:5px;margin-right:3px;height:28px}.w2ui-overlay .w2ui-grid-search-advanced table td.value select{padding:0 20px 5px 5px;margin-right:3px;height:28px}.w2ui-overlay .w2ui-grid-search-advanced table td.actions:nth-child(1){padding:25px 10px 10px 10px;text-align:left}.w2ui-overlay .w2ui-grid-search-advanced table td.actions:nth-child(2){padding:25px 10px 10px 10px;text-align:right;background-color:#fff}.w2ui-grid-skip{width:50px;margin:-6px 3px;padding:3px!important}.w2ui-popup{position:fixed;z-index:1600;overflow:hidden;font-family:OpenSans;border-radius:6px;padding:0;margin:0;border:1px solid #777;background-color:#fafafa;box-shadow:0 0 25px #555}.w2ui-popup,.w2ui-popup *{box-sizing:border-box}.w2ui-popup.w2ui-anim-open{opacity:0;transform:scale(.8)}.w2ui-popup.w2ui-anim-close{opacity:0;transform:scale(.9)}.w2ui-popup .w2ui-popup-title-btns{float:right;margin:10px 10px 0 0;font-size:17px}.w2ui-popup .w2ui-popup-title-btns .w2ui-popup-button{float:right;width:25px;height:23px;cursor:pointer;color:#888;margin:0 0 0 2px;z-index:301;position:relative}.w2ui-popup .w2ui-popup-title-btns .w2ui-popup-button span.w2ui-icon{width:24px;height:23px}.w2ui-popup .w2ui-popup-title-btns .w2ui-popup-button.w2ui-popup-close:hover{color:#222}.w2ui-popup .w2ui-popup-title-btns .w2ui-popup-button.w2ui-popup-max:hover{color:#222}.w2ui-popup .w2ui-popup-title{padding:10px;border-radius:6px 6px 0 0;background-color:#fff;border-bottom:1px solid #eee;position:absolute;overflow:hidden;height:42px;left:0;right:0;top:0;text-overflow:ellipsis;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;cursor:move;font-size:17px;color:#555;z-index:300}.w2ui-popup .w2ui-box,.w2ui-popup .w2ui-box-temp{position:absolute;left:0;right:0;top:42px;bottom:58px;z-index:100}.w2ui-popup .w2ui-popup-body{font-size:12px;line-height:130%;padding:0 7px 7px 7px;color:#000;background-color:#fafafa;position:absolute;overflow:auto;width:100%;height:100%}.w2ui-popup .w2ui-popup-buttons{font-size:11px;padding:14px;border-radius:0 0 6px 6px;border-top:1px solid #eee;background-color:#fff;text-align:center;position:absolute;overflow:hidden;height:56px;left:0;right:0;bottom:0;z-index:200}.w2ui-popup .w2ui-popup-no-title{border-top-left-radius:6px;border-top-right-radius:6px;top:0}.w2ui-popup .w2ui-popup-no-buttons{border-bottom-left-radius:6px;border-bottom-right-radius:6px;bottom:0}.w2ui-popup .w2ui-msg-text{font-size:14px;line-height:1.5}.w2ui-popup .w2ui-prompt{font-size:12px;padding:0 10px}.w2ui-popup .w2ui-prompt.textarea{margin-top:20px}.w2ui-popup .w2ui-prompt>div{margin-bottom:5px}.w2ui-popup .w2ui-prompt>label{margin-right:5px}.w2ui-popup .w2ui-prompt input{width:230px}.w2ui-popup .w2ui-prompt textarea{width:100%;height:50px;resize:none}.w2ui-message{font-size:12px;position:absolute;z-index:250;background-color:#fcfcfc;border:1px solid #999;box-shadow:0 0 15px #aaa;box-sizing:border-box;border-top:0;border-radius:0 0 6px 6px;overflow:auto}.w2ui-message .w2ui-msg-text{font-size:14px;line-height:1.5}.w2ui-message .w2ui-message-body{position:absolute;top:0;bottom:45px;left:0;right:0;overflow:auto}.w2ui-message .w2ui-message-body .w2ui-centered{line-height:1.5}.w2ui-message .w2ui-message-buttons{position:absolute;height:45px;bottom:0;left:0;right:0;border-top:1px solid #efefef;background-color:#fff;text-align:center;padding:8px}.w2ui-sidebar{position:relative;cursor:default;overflow:hidden;background-color:#fbfbfb;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.w2ui-sidebar *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.w2ui-sidebar>div{position:absolute;overflow:hidden}.w2ui-sidebar .w2ui-sidebar-top{position:absolute;z-index:2;top:0;left:0;right:0}.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-left,.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-right{position:absolute;right:2px;top:2px;height:24px;padding:5px;border-radius:2px;background-size:16px 12px;background-position:center center;background-repeat:no-repeat;background-color:#fbfbfb}.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-left:hover,.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-right:hover{background-color:#f1f1f1}.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-left{left:auto;width:25px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAzklEQVR4Ae2THRDEMBCFzy1ucatb3eJ2uhi3uNUtbnGrW9zi1rOdNzdvdl7nDpvYt/9/r7+/51myZZf/zXkD2iMHHRSb0x3oskwMieK05PwEXqP4ExSL0wp0ROao2OOuMPOMdUL6XU1/oGLcFWb+NqyTd2W/P/2qTr9h+nFXhOkHXRHiNyjrgp/U/V+WaQcaNY13zZI0A1JvcVqAnrGDTdtDtZUHjHIJhxxVLN0iqXgCP1l/7h8U9kc6abyJ4/eNWPpGdBv+XdUK0K8cnvcBly2rDr7C1HQAAAAASUVORK5CYII=)}.w2ui-sidebar .w2ui-sidebar-top .w2ui-flat-right{left:2px;width:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVR4Ae2UG7TGMBCEr1vd4la3uMUtuli3utWtbnGLW9zi9l/bDMzJG7u12cfJfLunf1+UEC9Bv0vVQwJ8hjRCaZafflb1C9RQf4OD0gSDE+i+PiJAabFhQc1y1AYYsJGLY3lgxM17uWPO56yPiFDqVPWgRtpIHSd1zPnwkBsdI58OlNwx4fP2X0TgfMTOoHSdKOXkpyNvEyQh7ul+4swxJSTQuwNDxz68l/ukVNbu0Neen5Z+KvzWxBAqHds349uPFJ/jVOrPjxUq++OLf+20q5+noXo0AAAAAElFTkSuQmCC)}.w2ui-sidebar .w2ui-sidebar-bottom{position:absolute;z-index:2;bottom:0;left:0;right:0}.w2ui-sidebar .w2ui-sidebar-body{position:absolute;z-index:1;overflow:auto;top:0;bottom:0;left:0;right:0;padding:2px 0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node{position:relative;border-radius:4px;margin:0 3px;padding:1px 0;border:1px solid transparent}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-text{color:#000;text-shadow:0 0 0 #fff;pointer-events:none}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-text:hover{color:inherit}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-image>span{color:#737485}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-handle{display:inline-block;padding:0;margin:0;height:100%;position:absolute}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node:hover{background-color:#f1f1f1}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-image{width:22px;text-align:center;pointer-events:none}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node .w2ui-node-image>span{color:#888}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled,.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled:hover{background:0 0}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled .w2ui-node-image,.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled .w2ui-node-image>span,.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled .w2ui-node-text,.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled:hover .w2ui-node-image,.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled:hover .w2ui-node-image>span,.w2ui-sidebar .w2ui-sidebar-body .w2ui-node.w2ui-disabled:hover .w2ui-node-text{opacity:.4;color:#000;text-shadow:0 0 0 #fff}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node button,.w2ui-sidebar .w2ui-sidebar-body .w2ui-node input{pointer-events:auto}.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected,.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:hover{background-color:#f3f5ff;position:relative;border:1px solid #dee1ff}.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected .w2ui-node-image,.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected .w2ui-node-image>span,.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected .w2ui-node-text,.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:hover .w2ui-node-image,.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:hover .w2ui-node-image>span,.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:hover .w2ui-node-text{color:inherit;text-shadow:0 0 0 #fff}.w2ui-sidebar .w2ui-sidebar-body .w2ui-selected:before{content:"";border:1px dashed transparent;border-radius:4px;position:absolute;top:-1px;bottom:-1px;left:-1px;right:-1px;pointer-events:none}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-text{white-space:nowrap;padding:5px 0 5px 3px;margin:1px 0 1px 22px;position:relative;z-index:1;font-size:12px}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-group{white-space:nowrap;overflow:hidden;padding:10px 0 10px 10px;margin:0;cursor:default;color:#6a5e88;background-color:transparent}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-group :nth-child(1){margin-right:10px;float:right;color:transparent}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-group :nth-child(2){font-weight:400;text-transform:uppercase}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-sub{overflow:hidden}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data{padding:2px}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-node-image{padding:3px 0 0 0;float:left}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-node-image>span{font-size:16px;color:#737485;text-shadow:0 0 0 #fff}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-node-image.w2ui-icon{margin-top:3px}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-node-count{float:right;border:1px solid #f6fcf4;border-radius:20px;width:auto;padding:2px 7px;margin:3px 4px -2px 0;background-color:#f2f8f0;color:#666;box-shadow:0 0 2px #474545;text-shadow:1px 1px 0 #fff;position:relative;z-index:2}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed,.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-expanded{float:right;width:auto;height:18px;position:relative;z-index:2}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed span,.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-expanded span{border-color:transparent;background-color:transparent;box-shadow:none;padding:2px 5px;border-radius:0}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed span:after,.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-expanded span:after{content:"";position:absolute;border-left:5px solid grey;border-top:5px solid transparent;border-bottom:5px solid transparent;transform:rotateZ(-90deg);pointer-events:none;margin-left:-4px;margin-top:7px}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed span:hover,.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-expanded span:hover{border-color:transparent;background-color:transparent}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-data .w2ui-collapsed span:after{transform:rotateZ(90deg)}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-flat{display:block;padding:2px 0;text-align:center}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-flat .w2ui-node-image{float:none;text-align:center;width:auto;padding:1px 0}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-flat .w2ui-node-image>span{font-size:16px;color:#737485;text-shadow:0 0 0 #fff}.w2ui-sidebar .w2ui-sidebar-body .w2ui-node-flat .w2ui-node-image.w2ui-icon{width:21px}.w2ui-tabs{cursor:default;overflow:hidden;position:relative;background-color:#fff;min-height:28px;padding:0;margin:0}.w2ui-tabs .w2ui-tabs-line{position:absolute;left:0;right:0;bottom:0;z-index:1;border:0;height:1px;background-color:#e2e2e2}.w2ui-tabs .w2ui-scroll-left,.w2ui-tabs .w2ui-scroll-right{z-index:30;display:flex}.w2ui-tabs .w2ui-scroll-wrapper{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:flex-start;align-content:flex-start;padding:0 2px}.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab{height:28px;position:relative;z-index:20;padding:7px 20px 4px 20px;text-align:center;color:#000;background-color:transparent;border:2px solid transparent;white-space:nowrap;margin:0 1px;border-radius:0;cursor:default;user-select:none}.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab.active{color:#0175ff;background-color:transparent;border:2px solid transparent;border-bottom:2px solid #0175ff;margin-bottom:0}.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab:hover{background-color:#dfe1e630}.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab.moving{color:inherit;background-color:#eee;border:2px solid transparent;border-radius:0;margin-bottom:0}.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab.closable{padding:6px 28px 6px 20px}.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab .w2ui-tab-close{position:absolute;right:3px;top:5px;color:#555;float:right;margin-top:-3px;padding:2px 4px;width:20px;height:20px;opacity:.6;border:0;border-top:3px solid transparent;border-radius:3px}.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab .w2ui-tab-close:hover{background-color:#f9e7e7;color:red;opacity:1;font-weight:700}.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab .w2ui-tab-close:active{background-color:#ffd1d1}.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tab .w2ui-tab-close:before{position:relative;top:-2px;left:0;color:inherit;text-shadow:inherit;content:'x'}.w2ui-tabs .w2ui-scroll-wrapper .w2ui-tabs-right{padding:8px 2px;width:100%;text-align:right;white-space:nowrap}.w2ui-tabs.w2ui-tabs-up .w2ui-tabs-line{top:0;bottom:auto}.w2ui-tabs.w2ui-tabs-up .w2ui-scroll-wrapper .w2ui-tab{border:2px solid transparent;border-top:2px solid transparent;border-radius:0 0 4px 4px}.w2ui-tabs.w2ui-tabs-up .w2ui-scroll-wrapper .w2ui-tab.active{border:2px solid transparent;border-top:2px solid #0175ff;margin-top:0}.w2ui-toolbar{background-color:#f5f5f5;user-select:none;padding:2px}.w2ui-toolbar .w2ui-tb-line{overflow:hidden;position:relative;min-height:28px;padding:2px;margin:0}.w2ui-toolbar .disabled{opacity:.3}.w2ui-toolbar .w2ui-scroll-left,.w2ui-toolbar .w2ui-scroll-right{z-index:30;display:flex}.w2ui-toolbar .w2ui-tb-line:nth-child(2),.w2ui-toolbar .w2ui-tb-line:nth-child(3),.w2ui-toolbar .w2ui-tb-line:nth-child(4){border-top:1px solid #e7e7e7;padding-top:4px;margin:0}.w2ui-toolbar .w2ui-scroll-wrapper{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:flex-start;align-content:flex-start;padding:0}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button{position:relative;z-index:20;height:30px;min-width:30px;padding:2px;border:1px solid transparent;border-radius:4px;background-color:transparent;white-space:nowrap;margin:0 1px;cursor:default;user-select:none;flex-shrink:0}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-icon{float:left;width:22px;margin:4px 0 0 1px;text-align:center}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-icon>span{font-size:15px;color:#8d99a7}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-text{margin-left:20px;color:#000;padding:5px}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-text .w2ui-tb-color-box{display:inline-block;height:13px;width:13px;margin:0 -1px -2px 0;border-radius:1px;border:1px solid #fff;box-shadow:0 0 1px #555}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-text .w2ui-tb-count{padding:0 0 0 4px}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-text .w2ui-tb-count>span{border:1px solid #f6fcf4;border-radius:11px;width:auto;height:18px;padding:0 6px 1px 6px;background-color:#f2f8f0;color:#666;box-shadow:0 0 2px #474545;text-shadow:1px 1px 0 #fff}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-text .w2ui-tb-down{display:inline-block;width:10px}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button .w2ui-tb-text .w2ui-tb-down>span{display:inline-block;position:relative;top:3px;left:3px;border:4px solid transparent;border-top:5px solid #8d99a7}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.over{border:1px solid transparent;background-color:#eaeaed}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.over .w2ui-tb-text{color:#000}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.checked{border:1px solid #d2d2d2;background-color:#fff}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.checked .w2ui-tb-text{color:#000}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.down{border:1px solid #ccc;background-color:#eaeaed}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.down .w2ui-tb-text{color:#666}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button.no-icon .w2ui-tb-text{margin-left:0}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-right{width:100%;text-align:right;white-space:nowrap}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-break{background-image:linear-gradient(to bottom,rgba(153,153,153,.1) 0,#999 40%,#999 60%,rgba(153,153,153,.1) 100%);width:1px;height:24px;padding:0;margin:3px 6px}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-html{white-space:nowrap}.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-spacer{width:100%} \ No newline at end of file diff --git a/lib/w2ui/w2ui.min.js b/lib/w2ui/w2ui.min.js new file mode 100644 index 0000000..932bc10 --- /dev/null +++ b/lib/w2ui/w2ui.min.js @@ -0,0 +1,486 @@ +/* w2ui 2.0.x (nightly) (1/16/2023, 8:50:38 AM) (c) http://w2ui.com, vitmalina@gmail.com */ +class w2event{constructor(e,t){Object.assign(this,{type:t.type??null,detail:t,owner:e,target:t.target??null,phase:t.phase??"before",object:t.object??null,execute:null,isStopped:!1,isCancelled:!1,onComplete:null,listeners:[]}),delete t.type,delete t.target,delete t.object,this.complete=new Promise((e,t)=>{this._resolve=e,this._reject=t}),this.complete.catch(()=>{})}finish(e){e&&w2utils.extend(this.detail,e),this.phase="after",this.owner.trigger.call(this.owner,this)}done(e){this.listeners.push(e)}preventDefault(){this._reject(),this.isCancelled=!0}stopPropagation(){this.isStopped=!0}}class w2base{constructor(e){if(this.activeEvents=[],this.listeners=[],void 0!==e){if(!w2utils.checkName(e))return;w2ui[e]=this}this.debug=!1}on(e,r){return(e="string"==typeof e?e.split(/[,\s]+/):[e]).forEach(e=>{var t,i,s,l="string"==typeof e?e:e.type+":"+e.execute+"."+e.scope;"string"==typeof e&&([i,t]=e.split("."),[i,s]=i.replace(":complete",":after").replace(":done",":after").split(":"),e={type:i,execute:s??"before",scope:t}),(e=w2utils.extend({type:null,execute:"before",onComplete:null},e)).type?r?(Array.isArray(this.listeners)||(this.listeners=[]),this.listeners.push({name:l,edata:e,handler:r}),this.debug&&console.log("w2base: add event",{name:l,edata:e,handler:r})):console.log("ERROR: You must specify event handler function when calling .on() method of "+this.name):console.log("ERROR: You must specify event type when calling .on() method of "+this.name)}),this}off(e,r){return(e="string"==typeof e?e.split(/[,\s]+/):[e]).forEach(i=>{var e,t,s,l="string"==typeof i?i:i.type+":"+i.execute+"."+i.scope;if("string"==typeof i&&([t,e]=i.split("."),[t,s]=t.replace(":complete",":after").replace(":done",":after").split(":"),i={type:t||"*",execute:s||"",scope:e||""}),(i=w2utils.extend({type:null,execute:null,onComplete:null},i)).type||i.scope){r=r||null;let t=0;this.listeners=this.listeners.filter(e=>"*"!==i.type&&i.type!==e.edata.type||""!==i.execute&&i.execute!==e.edata.execute||""!==i.scope&&i.scope!==e.edata.scope||null!=i.handler&&i.handler!==e.edata.handler||(t++,!1)),this.debug&&console.log(`w2base: remove event (${t})`,{name:l,edata:i,handler:r})}else console.log("ERROR: You must specify event type when calling .off() method of "+this.name)}),this}trigger(e,i){if(1==arguments.length?i=e:(i.type=e,i.target=i.target??this),w2utils.isPlainObject(i)&&"after"==i.phase){if(!(i=this.activeEvents.find(e=>e.type==i.type&&e.target==i.target)))return void console.log(`ERROR: Cannot find even handler for "${i.type}" on "${i.target}".`);console.log("NOTICE: This syntax \"edata.trigger({ phase: 'after' })\" is outdated. Use edata.finish() instead.")}else i instanceof w2event||(i=new w2event(this,i),this.activeEvents.push(i));let s,t,l;Array.isArray(this.listeners)||(this.listeners=[]),this.debug&&console.log(`w2base: trigger "${i.type}:${i.phase}"`,i);for(let e=this.listeners.length-1;0<=e;e--){let t=this.listeners[e];if(!(null==t||t.edata.type!==i.type&&"*"!==t.edata.type||t.edata.target!==i.target&&null!=t.edata.target||t.edata.execute!==i.phase&&"*"!==t.edata.execute&&"*"!==t.edata.phase)&&(Object.keys(t.edata).forEach(e=>{null==i[e]&&null!=t.edata[e]&&(i[e]=t.edata[e])}),s=[],l=new RegExp(/\((.*?)\)/).exec(String(t.handler).split("=>")[0]),2===(s=l?l[1].split(/\s*,\s*/):s).length?(t.handler.call(this,i.target,i),this.debug&&console.log(" - call (old)",t.handler)):(t.handler.call(this,i),this.debug&&console.log(" - call",t.handler)),!0===i.isStopped||!0===i.stop))return i}e="on"+i.type.substr(0,1).toUpperCase()+i.type.substr(1);if(!("before"===i.phase&&"function"==typeof this[e]&&(t=this[e],s=[],l=new RegExp(/\((.*?)\)/).exec(String(t).split("=>")[0]),2===(s=l?l[1].split(/\s*,\s*/):s).length?(t.call(this,i.target,i),this.debug&&console.log(" - call: on[Event] (old)",t)):(t.call(this,i),this.debug&&console.log(" - call: on[Event]",t)),!0===i.isStopped||!0===i.stop)||null!=i.object&&"before"===i.phase&&"function"==typeof i.object[e]&&(t=i.object[e],s=[],l=new RegExp(/\((.*?)\)/).exec(String(t).split("=>")[0]),2===(s=l?l[1].split(/\s*,\s*/):s).length?(t.call(this,i.target,i),this.debug&&console.log(" - call: edata.object (old)",t)):(t.call(this,i),this.debug&&console.log(" - call: edata.object",t)),!0===i.isStopped||!0===i.stop)||"after"!==i.phase)){"function"==typeof i.onComplete&&i.onComplete.call(this,i);for(let e=0;e{this[t]=e})}static _fragment(e){let i=document.createElement("template");return i.innerHTML=e,i.content.childNodes.forEach(e=>{var t=Query._scriptConvert(e);t!=e&&i.content.replaceChild(t,e)}),i.content}static _scriptConvert(e){let t=e=>{var t=e.ownerDocument.createElement("script"),i=(t.text=e.text,e.attributes);for(let e=0;e{e.parentNode.replaceChild(t(e),e)}),e}static _fixProp(e){var t={cellpadding:"cellPadding",cellspacing:"cellSpacing",class:"className",colspan:"colSpan",contenteditable:"contentEditable",for:"htmlFor",frameborder:"frameBorder",maxlength:"maxLength",readonly:"readOnly",rowspan:"rowSpan",tabindex:"tabIndex",usemap:"useMap"};return t[e]||e}_insert(l,i){let r=[],n=this.length;if(!(n<1)){let e=this;if("string"==typeof i)this.each(e=>{var t=Query._fragment(i);r.push(...t.childNodes),e[l](t)});else if(i instanceof Query){let s=1==n;i.each(i=>{this.each(e=>{var t=s?i:i.cloneNode(!0);r.push(t),e[l](t),Query._scriptConvert(t)})}),s||i.remove()}else{if(!(i instanceof Node))throw new Error(`Incorrect argument for "${l}(html)". It expects one string argument.`);this.each(e=>{var t=1===n?i:Query._fragment(i.outerHTML);r.push(...1===n?[i]:t.childNodes),e[l](t)}),1{e=Array.from(e.querySelectorAll(t));0{(e===t||"string"==typeof t&&e.matches&&e.matches(t)||"function"==typeof t&&t(e))&&i.push(e)}),new Query(i,this.context,this)}next(){let t=[];return this.each(e=>{e=e.nextElementSibling;e&&t.push(e)}),new Query(t,this.context,this)}prev(){let t=[];return this.each(e=>{e=e.previousElementSibling;e&&t.push(e)}),new Query(t,this.context,this)}shadow(e){let t=[];this.each(e=>{e.shadowRoot&&t.push(e.shadowRoot)});var i=new Query(t,this.context,this);return e?i.find(e):i}closest(t){let i=[];return this.each(e=>{e=e.closest(t);e&&i.push(e)}),new Query(i,this.context,this)}host(t){let i=[],s=e=>e.parentNode?s(e.parentNode):e,l=e=>{e=s(e);i.push(e.host||e),e.host&&t&&l(e.host)};return this.each(e=>{l(e)}),new Query(i,this.context,this)}parent(e){return this.parents(e,!0)}parents(e,t){let i=[],s=e=>{if(-1==i.indexOf(e)&&i.push(e),!t&&e.parentNode)return s(e.parentNode)};this.each(e=>{e.parentNode&&s(e.parentNode)});var l=new Query(i,this.context,this);return e?l.filter(e):l}add(e){e=e instanceof Query?e.nodes:Array.isArray(e)?e:[e];return new Query(this.nodes.concat(e),this.context,this)}each(i){return this.nodes.forEach((e,t)=>{i(e,t,this)}),this}append(e){return this._insert("append",e)}prepend(e){return this._insert("prepend",e)}after(e){return this._insert("after",e)}before(e){return this._insert("before",e)}replace(e){return this._insert("replaceWith",e)}remove(){return this.each(e=>{e.remove()}),this}css(e,t){let s=e;var i,l=arguments.length;return 0===l||1===l&&"string"==typeof e?this[0]?(l=this[0].style,"string"==typeof e?(i=l.getPropertyPriority(e),l.getPropertyValue(e)+(i?"!"+i:"")):Object.fromEntries(this[0].style.cssText.split(";").filter(e=>!!e).map(e=>e.split(":").map(e=>e.trim())))):void 0:("object"!=typeof e&&((s={})[e]=t),this.each((i,e)=>{Object.keys(s).forEach(e=>{var t=String(s[e]).toLowerCase().includes("!important")?"important":"";i.style.setProperty(e,String(s[e]).replace(/\!important/i,""),t)})}),this)}addClass(e){return this.toggleClass(e,!0),this}removeClass(e){return this.toggleClass(e,!1),this}toggleClass(t,s){return"string"==typeof t&&(t=t.split(/[,\s]+/)),this.each(i=>{let e=t;(e=null==e&&!1===s?Array.from(i.classList):e).forEach(t=>{if(""!==t){let e=null!=s?s?"add":"remove":"toggle";i.classList[e](t)}})}),this}hasClass(e){if(null==(e="string"==typeof e?e.split(/[,\s]+/):e)&&0{i=i||e.every(e=>Array.from(t.classList??[]).includes(e))}),i}on(e,s,l){"function"==typeof s&&(l=s,s=void 0);let r;return s?.delegate&&(r=s.delegate,delete s.delegate),(e=e.split(/[,\s]+/)).forEach(e=>{let[t,i]=String(e).toLowerCase().split(".");if(r){let i=l;l=e=>{var t=query(e.target).parents(r);0{this._save(e,"events",[{event:t,scope:i,callback:l,options:s}]),e.addEventListener(t,l,s)})}),this}off(e,t,r){return"function"==typeof t&&(r=t,t=void 0),(e=(e??"").split(/[,\s]+/)).forEach(e=>{let[s,l]=String(e).toLowerCase().split(".");this.each(t=>{if(Array.isArray(t._mQuery?.events))for(let e=t._mQuery.events.length-1;0<=e;e--){var i=t._mQuery.events[e];null==l||""===l?i.event!=s&&""!==s||i.callback!=r&&null!=r||(t.removeEventListener(i.event,i.callback,i.options),t._mQuery.events.splice(e,1)):i.event!=s&&""!==s||i.scope!=l||(t.removeEventListener(i.event,i.callback,i.options),t._mQuery.events.splice(e,1))}})}),this}trigger(e,t){let i;return i=e instanceof Event||e instanceof CustomEvent?e:new(["click","dblclick","mousedown","mouseup","mousemove"].includes(e)?MouseEvent:["keydown","keyup","keypress"].includes(e)?KeyboardEvent:Event)(e,t),this.each(e=>{e.dispatchEvent(i)}),this}attr(t,i){if(void 0===i&&"string"==typeof t)return this[0]?this[0].getAttribute(t):void 0;{let e={};return"object"==typeof t?e=t:e[t]=i,this.each(i=>{Object.entries(e).forEach(([e,t])=>{i.setAttribute(e,t)})}),this}}removeAttr(){return this.each(t=>{Array.from(arguments).forEach(e=>{t.removeAttribute(e)})}),this}prop(t,i){if(void 0===i&&"string"==typeof t)return this[0]?this[0][t]:void 0;{let e={};return"object"==typeof t?e=t:e[t]=i,this.each(i=>{Object.entries(e).forEach(([e,t])=>{e=Query._fixProp(e);i[e]=t,"innerHTML"==e&&Query._scriptConvert(i)})}),this}}removeProp(){return this.each(t=>{Array.from(arguments).forEach(e=>{delete t[Query._fixProp(e)]})}),this}data(i,t){if(i instanceof Object)Object.entries(i).forEach(e=>{this.data(e[0],e[1])});else{if(i&&-1!=i.indexOf("-")&&console.error(`Key "${i}" contains "-" (dash). Dashes are not allowed in property names. Use camelCase instead.`),!(arguments.length<2))return this.each(e=>{null!=t?e.dataset[i]=t instanceof Object?JSON.stringify(t):t:delete e.dataset[i]}),this;if(this[0]){let t=Object.assign({},this[0].dataset);return Object.keys(t).forEach(e=>{if(t[e].startsWith("[")||t[e].startsWith("{"))try{t[e]=JSON.parse(t[e])}catch(e){}}),i?t[i]:t}}}removeData(e){return"string"==typeof e&&(e=e.split(/[,\s]+/)),this.each(t=>{e.forEach(e=>{delete t.dataset[e]})}),this}show(){return this.toggle(!0)}hide(){return this.toggle(!1)}toggle(l){return this.each(e=>{var t=e.style.display,i=getComputedStyle(e).display,s="none"==t||"none"==i;!s||null!=l&&!0!==l||(e.style.display=e._mQuery?.prevDisplay??(t==i&&"none"!=i?"":"block"),this._save(e,"prevDisplay",null)),s||null!=l&&!1!==l||("none"!=i&&this._save(e,"prevDisplay",i),e.style.setProperty("display","none"))})}empty(){return this.html("")}html(e){return this.prop("innerHTML",e)}text(e){return this.prop("textContent",e)}val(e){return this.prop("value",e)}change(){return this.trigger("change")}click(){return this.trigger("click")}}let query=function(e,t){if("function"!=typeof e)return new Query(e,t);"complete"==document.readyState?e():window.addEventListener("load",e)},w2ui=(query.html=e=>{e=Query._fragment(e);return query(e.children,e)},query.version=Query.version,{});class Utils{constructor(){this.version="2.0.x",this.tmp={},this.settings=this.extend({},{dataType:"HTTPJSON",dateStartYear:1950,dateEndYear:2030,macButtonOrder:!1,warnNoPhrase:!1},w2locale,{phrases:null}),this.i18nCompare=Intl.Collator().compare,this.hasLocalStorage=function(){var e="w2ui_test";try{return localStorage.setItem(e,e),localStorage.removeItem(e),!0}catch(e){return!1}}(),this.isMac=/Mac/i.test(navigator.platform),this.isMobile=/(iphone|ipod|ipad|mobile|android)/i.test(navigator.userAgent),this.isIOS=/(iphone|ipod|ipad)/i.test(navigator.platform),this.isAndroid=/(android)/i.test(navigator.userAgent),this.isSafari=/^((?!chrome|android).)*safari/i.test(navigator.userAgent),this.formatters={number(e,t){return 20'+w2utils.formatDate(i,t)+""},datetime(e,t){if(""===t&&(t=w2utils.settings.datetimeFormat),null==e||0===e||""===e)return"";let i=w2utils.isDateTime(e,t,!0);return''+w2utils.formatDateTime(i,t)+""},time(e,t){if(""===t&&(t=w2utils.settings.timeFormat),null==e||0===e||""===e)return"";let i=w2utils.isDateTime(e,t="h24"===(t="h12"===t?"hh:mi pm":t)?"h24:mi":t,!0);return''+w2utils.formatTime(e,t)+""},timestamp(e,t){if(""===t&&(t=w2utils.settings.datetimeFormat),null==e||0===e||""===e)return"";let i=w2utils.isDateTime(e,t,!0);return(i=!1===i?w2utils.isDate(e,t,!0):i).toString?i.toString():""},gmt(e,t){if(""===t&&(t=w2utils.settings.datetimeFormat),null==e||0===e||""===e)return"";let i=w2utils.isDateTime(e,t,!0);return(i=!1===i?w2utils.isDate(e,t,!0):i).toUTCString?i.toUTCString():""},age(e,t){if(null==e||0===e||""===e)return"";let i=w2utils.isDateTime(e,null,!0);return''+w2utils.age(e)+(t?" "+t:"")+""},interval(e,t){return null==e||0===e||""===e?"":w2utils.interval(e)+(t?" "+t:"")},toggle(e,t){return e?"Yes":""},password(t,e){let i="";for(let e=0;ei||!this.isInt(e[0])||2'+(r=l==e?this.lang("Yesterday"):r)+""}formatSize(e){var t;return this.isFloat(e)&&""!==e?0===(e=parseFloat(e))?0:(t=parseInt(Math.floor(Math.log(e)/Math.log(1024))),(Math.floor(e/Math.pow(1024,t)*10)/10).toFixed(0===t?0:1)+" "+(["Bt","KB","MB","GB","TB","PB","EB","ZB"][t]||"??")):""}formatNumber(e,t,i){return null==e||""===e||"object"==typeof e?"":(i={minimumFractionDigits:parseInt(t),maximumFractionDigits:parseInt(t),useGrouping:!!i},(null==t||t<0)&&(i.minimumFractionDigits=0,i.maximumFractionDigits=20),parseFloat(e).toLocaleString(this.settings.locale,i))}formatDate(e,t){if(t=t||this.settings.dateFormat,""===e||null==e||"object"==typeof e&&!e.getMonth)return"";let i=new Date(e);var s,l;return this.isInt(e)&&(i=new Date(Number(e))),"Invalid Date"===String(i)?"":(e=i.getFullYear(),s=i.getMonth(),l=i.getDate(),t.toLowerCase().replace("month",this.settings.fullmonths[s]).replace("mon",this.settings.shortmonths[s]).replace(/yyyy/g,("000"+e).slice(-4)).replace(/yyy/g,("000"+e).slice(-4)).replace(/yy/g,("0"+e).slice(-2)).replace(/(^|[^a-z$])y/g,"$1"+e).replace(/mm/g,("0"+(s+1)).slice(-2)).replace(/dd/g,("0"+l).slice(-2)).replace(/th/g,1==l?"st":"th").replace(/th/g,2==l?"nd":"th").replace(/th/g,3==l?"rd":"th").replace(/(^|[^a-z$])m/g,"$1"+(s+1)).replace(/(^|[^a-z$])d/g,"$1"+l))}formatTime(e,t){if(t=t||this.settings.timeFormat,""===e||null==e||"object"==typeof e&&!e.getMonth)return"";let i=new Date(e);if(this.isInt(e)&&(i=new Date(Number(e))),this.isTime(e)&&(e=this.isTime(e,!0),(i=new Date).setHours(e.hours),i.setMinutes(e.minutes)),"Invalid Date"===String(i))return"";let s="am",l=i.getHours();e=i.getHours();let r=i.getMinutes(),n=i.getSeconds();return r<10&&(r="0"+r),n<10&&(n="0"+n),-1===t.indexOf("am")&&-1===t.indexOf("pm")||(12<=l&&(s="pm"),12{i[t]=this.stripSpaces(e)}):(i=this.extend({},i),Object.keys(i).forEach(e=>{i[e]=this.stripSpaces(i[e])}))}return i}stripTags(i){if(null!=i)switch(typeof i){case"number":break;case"string":i=String(i).replace(/<(?:[^>=]|='[^']*'|="[^"]*"|=[^'"][^\s>]*)*>/gi,"");break;case"object":Array.isArray(i)?(i=this.extend([],i)).forEach((e,t)=>{i[t]=this.stripTags(e)}):(i=this.extend({},i),Object.keys(i).forEach(e=>{i[e]=this.stripTags(i[e])}))}return i}encodeTags(i){if(null!=i)switch(typeof i){case"number":break;case"string":i=String(i).replace(/&/g,"&").replace(/>/g,">").replace(/{i[t]=this.encodeTags(e)}):(i=this.extend({},i),Object.keys(i).forEach(e=>{i[e]=this.encodeTags(i[e])}))}return i}decodeTags(i){if(null!=i)switch(typeof i){case"number":break;case"string":i=String(i).replace(/>/g,">").replace(/</g,"<").replace(/"/g,'"').replace(/&/g,"&");break;case"object":Array.isArray(i)?(i=this.extend([],i)).forEach((e,t)=>{i[t]=this.decodeTags(e)}):(i=this.extend({},i),Object.keys(i).forEach(e=>{i[e]=this.decodeTags(i[e])}))}return i}escapeId(e){return""===e||null==e?"":(e+"").replace(/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,(e,t)=>t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e)}unescapeId(e){return""===e||null==e?"":e.replace(/\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\([^\r\n\f])/g,(e,t)=>{e="0x"+e.slice(1)-65536;return t||(e<0?String.fromCharCode(65536+e):String.fromCharCode(e>>10|55296,1023&e|56320))})}base64encode(e){return btoa(e)}base64decode(e){return atob(e)}async sha256(e){e=(new TextEncoder).encode(e);return crypto.subtle.digest("SHA-256",e).then(e=>{return Array.from(new Uint8Array(e)).map(e=>e.toString(16).padStart(2,"0")).join("")})}transition(r,n,a,o){return new Promise((e,t)=>{var i=getComputedStyle(r);let s=parseInt(i.width),l=parseInt(i.height);if(r&&n){switch(r.parentNode.style.cssText+="perspective: 900px; overflow: hidden;",r.style.cssText+="; position: absolute; z-index: 1019; backface-visibility: hidden",n.style.cssText+="; position: absolute; z-index: 1020; backface-visibility: hidden",a){case"slide-left":r.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0)",n.style.cssText+="overflow: hidden; transform: translate3d("+s+"px, 0, 0)",query(n).show(),setTimeout(()=>{n.style.cssText+="transition: 0.5s; transform: translate3d(0, 0, 0)",r.style.cssText+="transition: 0.5s; transform: translate3d(-"+s+"px, 0, 0)"},1);break;case"slide-right":r.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0)",n.style.cssText+="overflow: hidden; transform: translate3d(-"+s+"px, 0, 0)",query(n).show(),setTimeout(()=>{n.style.cssText+="transition: 0.5s; transform: translate3d(0px, 0, 0)",r.style.cssText+="transition: 0.5s; transform: translate3d("+s+"px, 0, 0)"},1);break;case"slide-down":r.style.cssText+="overflow: hidden; z-index: 1; transform: translate3d(0, 0, 0)",n.style.cssText+="overflow: hidden; z-index: 0; transform: translate3d(0, 0, 0)",query(n).show(),setTimeout(()=>{n.style.cssText+="transition: 0.5s; transform: translate3d(0, 0, 0)",r.style.cssText+="transition: 0.5s; transform: translate3d(0, "+l+"px, 0)"},1);break;case"slide-up":r.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0)",n.style.cssText+="overflow: hidden; transform: translate3d(0, "+l+"px, 0)",query(n).show(),setTimeout(()=>{n.style.cssText+="transition: 0.5s; transform: translate3d(0, 0, 0)",r.style.cssText+="transition: 0.5s; transform: translate3d(0, 0, 0)"},1);break;case"flip-left":r.style.cssText+="overflow: hidden; transform: rotateY(0deg)",n.style.cssText+="overflow: hidden; transform: rotateY(-180deg)",query(n).show(),setTimeout(()=>{n.style.cssText+="transition: 0.5s; transform: rotateY(0deg)",r.style.cssText+="transition: 0.5s; transform: rotateY(180deg)"},1);break;case"flip-right":r.style.cssText+="overflow: hidden; transform: rotateY(0deg)",n.style.cssText+="overflow: hidden; transform: rotateY(180deg)",query(n).show(),setTimeout(()=>{n.style.cssText+="transition: 0.5s; transform: rotateY(0deg)",r.style.cssText+="transition: 0.5s; transform: rotateY(-180deg)"},1);break;case"flip-down":r.style.cssText+="overflow: hidden; transform: rotateX(0deg)",n.style.cssText+="overflow: hidden; transform: rotateX(180deg)",query(n).show(),setTimeout(()=>{n.style.cssText+="transition: 0.5s; transform: rotateX(0deg)",r.style.cssText+="transition: 0.5s; transform: rotateX(-180deg)"},1);break;case"flip-up":r.style.cssText+="overflow: hidden; transform: rotateX(0deg)",n.style.cssText+="overflow: hidden; transform: rotateX(-180deg)",query(n).show(),setTimeout(()=>{n.style.cssText+="transition: 0.5s; transform: rotateX(0deg)",r.style.cssText+="transition: 0.5s; transform: rotateX(180deg)"},1);break;case"pop-in":r.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0)",n.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0); transform: scale(.8); opacity: 0;",query(n).show(),setTimeout(()=>{n.style.cssText+="transition: 0.5s; transform: scale(1); opacity: 1;",r.style.cssText+="transition: 0.5s;"},1);break;case"pop-out":r.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0); transform: scale(1); opacity: 1;",n.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0); opacity: 0;",query(n).show(),setTimeout(()=>{n.style.cssText+="transition: 0.5s; opacity: 1;",r.style.cssText+="transition: 0.5s; transform: scale(1.7); opacity: 0;"},1);break;default:r.style.cssText+="overflow: hidden; transform: translate3d(0, 0, 0)",n.style.cssText+="overflow: hidden; translate3d(0, 0, 0); opacity: 0;",query(n).show(),setTimeout(()=>{n.style.cssText+="transition: 0.5s; opacity: 1;",r.style.cssText+="transition: 0.5s"},1)}setTimeout(()=>{"slide-down"===a&&(query(r).css("z-index","1019"),query(n).css("z-index","1020")),n&&query(n).css({opacity:"1"}).css({transition:"",transform:""}),r&&query(r).css({opacity:"1"}).css({transition:"",transform:""}),"function"==typeof o&&o(),e()},500)}else console.log("ERROR: Cannot do transition when one of the divs is null")})}lock(l,r={}){if(null!=l){"string"==typeof r&&(r={msg:r}),arguments[2]&&(r.spinner=arguments[2]),r=this.extend({spinner:!1},r),l?.[0]instanceof Node&&(l=Array.isArray(l)?l:l.get()),r.msg||0===r.msg||(r.msg=""),this.unlock(l);var n=query(l).get(0);let e=n.scrollWidth,t=n.scrollHeight,i=("BODY"==n.tagName&&(e
    `+'
    '),query(l).find(".w2ui-lock"));n=query(l).find(".w2ui-lock-msg"),l=(r.msg||n.css({"background-color":"transparent","background-image":"none",border:"0px","box-shadow":"none"}),!0===r.spinner&&(r.msg=`
    `+r.msg),r.msg?n.html(r.msg).css("display","block"):n.remove(),null!=r.opacity&&i.css("opacity",r.opacity),i.css({display:"block"}),r.bgColor&&i.css({"background-color":r.bgColor}),getComputedStyle(i.get(0)));let s=l.opacity??.15;i.on("mousedown",function(){"function"==typeof r.onClick?r.onClick():i.css({transition:".2s",opacity:1.5*s})}).on("mouseup",function(){"function"!=typeof r.onClick&&i.css({transition:".2s",opacity:s})}).on("mousewheel",function(e){e&&(e.stopPropagation(),e.preventDefault())})}}unlock(e,t){var i;null!=e&&(clearTimeout(e._prevUnlock),e?.[0]instanceof Node&&(e=Array.isArray(e)?e:e.get()),this.isInt(t)&&0{query(e).find(".w2ui-lock").remove()},t)):query(e).find(".w2ui-lock").remove(),query(e).find(".w2ui-lock-msg").remove())}message(r,s){let e,t,l;var i=()=>{var e=query(r?.box).find(".w2ui-message");0!=e.length&&"function"==typeof(s=e.get(0)._msg_options||{})?.close&&s.close()};let n=e=>{var t,i=e.box._msg_prevFocus;query(r.box).find(".w2ui-message").length<=1?r.owner?r.owner.unlock(r.param,150):this.unlock(r.box,150):query(r.box).find(`#w2ui-message-${r.owner?.name}-`+(e.msgIndex-1)).css("z-index",1500),i?0<(t=query(i).closest(".w2ui-message")).length?t.get(0)._msg_options.setFocus(i):i.focus():"function"==typeof r.owner?.focus&&r.owner.focus(),query(e.box).remove(),0===e.msgIndex&&(c.css("z-index",e.tmp.zIndex),query(r.box).css("overflow",e.tmp.overflow)),e.trigger&&l.finish()};if("object"!=typeof(s="string"!=typeof s&&"number"!=typeof s?s:{width:String(s).length<300?350:550,height:String(s).length<300?170:250,text:String(s)}))return void i();null!=s.text&&(s.body=`
    ${s.text}
    `),null==s.width&&(s.width=350),null==s.height&&(s.height=170),null==s.hideOn&&(s.hideOn=["esc"]),null==s.on&&(h=s,s=new w2base,w2utils.extend(s,h)),s.on("open",e=>{w2utils.bindEvents(query(s.box).find(".w2ui-eaction"),s),query(e.detail.box).find("button, input, textarea, [name=hidden-first]").off(".message").on("keydown.message",function(e){27==e.keyCode&&s.hideOn.includes("esc")&&(s.cancelAction?s.action(s.cancelAction):s.close())}),setTimeout(()=>s.setFocus(s.focus),300)}),s.off(".prom");let a={self:s,action(e){return s.on("action.prom",e),a},close(e){return s.on("close.prom",e),a},open(e){return s.on("open.prom",e),a},then(e){return s.on("open:after.prom",e),a}},o=(null==s.actions&&null==s.buttons&&null==s.html&&(s.actions={Ok(e){e.detail.self.close()}}),s.off(".buttons"),null!=s.actions&&(s.buttons="",Object.keys(s.actions).forEach(e=>{var t=s.actions[e];let i=e;"function"==typeof t&&(s.buttons+=``),"object"==typeof t&&(s.buttons+=``,i=Array.isArray(s.actions)?t.text:e),"string"==typeof t&&(s.buttons+=``,i=t),"string"==typeof i&&(i=i[0].toLowerCase()+i.substr(1).replace(/\s+/g,"")),a[i]=function(t){return s.on("action.buttons",e=>{e.detail.action[0].toLowerCase()+e.detail.action.substr(1).replace(/\s+/g,"")==i&&t(e)}),a}})),Array("html","body","buttons").forEach(e=>{s[e]=String(s[e]??"").trim()}),""===s.body&&""===s.buttons||(s.html=` +
    ${s.body||""}
    +
    ${s.buttons||""}
    + `),getComputedStyle(query(r.box).get(0)));var h=parseFloat(o.width),d=parseFloat(o.height);let u=0,c=(0h&&(s.width=h-10),s.height>d-u&&(s.height=d-10-u),s.originalWidth=s.width,s.originalHeight=s.height,parseInt(s.width)<0&&(s.width=h+s.width),parseInt(s.width)<10&&(s.width=10),parseInt(s.height)<0&&(s.height=d+s.height-u),parseInt(s.height)<10&&(s.height=10),s.originalHeight<0&&(s.height=d+s.originalHeight-u),s.originalWidth<0&&(s.width=h+2*s.originalWidth),query(r.box).find(r.after));return s.tmp||(s.tmp={zIndex:c.css("z-index"),overflow:o.overflow}),""===s.html&&""===s.body&&""===s.buttons?i():(s.msgIndex=query(r.box).find(".w2ui-message").length,0===s.msgIndex&&"function"==typeof this.lock&&(query(r.box).css("overflow","hidden"),r.owner?r.owner.lock(r.param):this.lock(r.box)),query(r.box).find(".w2ui-message").css("z-index",1390),c.css("z-index",1501),d=` +
    + + ${s.html} + +
    `,0{!0===(l=s.trigger("open",{target:this.name,box:s.box,self:s})).isCancelled?(query(r.box).find(`#w2ui-message-${r.owner?.name}-`+s.msgIndex).remove(),0===s.msgIndex&&(c.css("z-index",s.tmp.zIndex),query(r.box).css("overflow",s.tmp.overflow))):query(s.box).css({transition:"0.3s",transform:"translateY(0px)"})},0),t=setTimeout(()=>{query(r.box).find(`#w2ui-message-${r.owner?.name}-`+s.msgIndex).removeClass("animating").css({transition:"0s"}),l.finish()},300)),s.action=(e,t)=>{let i=s.actions[e];i instanceof Object&&i.onClick&&(i=i.onClick);e=s.trigger("action",{target:this.name,action:e,self:s,originalEvent:t,value:s.input?s.input.value:null});!0!==e.isCancelled&&("function"==typeof i&&i(e),e.finish())},s.close=()=>{!0!==(l=s.trigger("close",{target:"self",box:s.box,self:s})).isCancelled&&(clearTimeout(t),query(s.box).hasClass("animating")?(clearTimeout(e),n(s)):(query(s.box).addClass("w2ui-closing animating").css({transition:"0.15s",transform:"translateY(-"+s.height+"px)"}),0!==s.msgIndex&&query(r.box).find(`#w2ui-message-${r.owner?.name}-`+(s.msgIndex-1)).css("z-index",1499),e=setTimeout(()=>{n(s)},150)))},s.setFocus=e=>{var t=query(r.box).find(".w2ui-message").length-1;let s=query(r.box).find(`#w2ui-message-${r.owner?.name}-`+t),l="input, button, select, textarea, [contentEditable], .w2ui-input";(null!=e?isNaN(e)?s.find(l).filter(e).get(0):s.find(l).get(e):s.find("[name=hidden-first]").get(0))?.focus(),query(r.box).find(".w2ui-message").find(l+",[name=hidden-first],[name=hidden-last]").off(".keep-focus"),query(s).find(l+",[name=hidden-first],[name=hidden-last]").on("blur.keep-focus",function(e){setTimeout(()=>{var e=document.activeElement,t=0{if("object"==typeof i&&(i=(s=i).text),(s=s||{}).where=s.where??document.body,s.timeout=s.timeout??15e3,"function"==typeof this.tmp.notify_resolve&&(this.tmp.notify_resolve(),query(this.tmp.notify_where).find("#w2ui-notify").remove()),this.tmp.notify_resolve=t,this.tmp.notify_where=s.where,clearTimeout(this.tmp.notify_timer),i){if("object"==typeof s.actions){let t={};Object.keys(s.actions).forEach(e=>{t[e]=`${e}`}),i=this.execTemplate(i,t)}var e=` +
    +
    + ${i} + +
    +
    `;query(s.where).append(e),query(s.where).find("#w2ui-notify").find(".w2ui-notify-close").on("click",e=>{query(s.where).find("#w2ui-notify").remove(),t()}),s.actions&&query(s.where).find("#w2ui-notify .w2ui-notify-link").on("click",e=>{e=query(e.target).attr("value");s.actions[e](),query(s.where).find("#w2ui-notify").remove(),t()}),0{query(s.where).find("#w2ui-notify").remove(),t()},s.timeout))}})}confirm(e,t){w2utils.normButtons(t="string"==typeof t?{text:t}:t,{yes:"Yes",no:"No"});e=w2utils.message(e,t);return e&&e.action(e=>{e.detail.self.close()}),e}normButtons(i,s){i.actions=i.actions??{};var e=Object.keys(s);return e.forEach(t=>{var e=i["btn_"+t];e&&(s[t]={text:w2utils.lang(e.text??""),class:e.class??"",style:e.style??"",attrs:e.attrs??""},delete i["btn_"+t]),Array("text","class","style","attrs").forEach(e=>{i[t+"_"+e]&&("string"==typeof s[t]&&(s[t]={text:s[t]}),s[t][e]=i[t+"_"+e],delete i[t+"_"+e])})}),e.includes("yes")&&e.includes("no")&&(w2utils.settings.macButtonOrder?w2utils.extend(i.actions,{no:s.no,yes:s.yes}):w2utils.extend(i.actions,{yes:s.yes,no:s.no})),e.includes("ok")&&e.includes("cancel")&&(w2utils.settings.macButtonOrder?w2utils.extend(i.actions,{cancel:s.cancel,ok:s.ok}):w2utils.extend(i.actions,{ok:s.ok,cancel:s.cancel})),i}getSize(e,t){let i=0;if(0<(e=query(e)).length){e=e[0];var s=getComputedStyle(e);switch(t){case"width":i=parseFloat(s.width),"auto"===s.width&&(i=0);break;case"height":i=parseFloat(s.height),"auto"===s.height&&(i=0);break;default:i=parseFloat(s[t]??0)||0}}return i}getStrWidth(e,t){query("body").append(` +
    + ${this.encodeTags(e)} +
    `);t=query("#_tmp_width")[0].clientWidth;return query("#_tmp_width").remove(),t}execTemplate(e,i){return"string"==typeof e&&i&&"object"==typeof i?e.replace(/\${([^}]+)?}/g,function(e,t){return i[t]||t}):e}marker(e,s,l={onlyFirst:!1,wholeWord:!1}){Array.isArray(s)||(s=null!=s&&""!==s?[s]:[]);let r=l.wholeWord;query(e).each(t=>{for(var e=t,i=/\((.|\n|\r)*)\<\/span\>/gi;-1!==e.innerHTML.indexOf('{e=(e="string"!=typeof e?String(e):e).replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&").replace(/&/g,"&").replace(//g,"<");e=new RegExp((r?"\\b":"")+e+(r?"\\b":"")+"(?!([^<]+)?>)","i"+(l.onlyFirst?"":"g"));t.innerHTML=t.innerHTML.replace(e,e=>''+e+"")})})}lang(e,t){if(!e||null==this.settings.phrases||"string"!=typeof e||"<=>=".includes(e))return this.execTemplate(e,t);let i=this.settings.phrases[e];return null==i?(i=e,this.settings.warnNoPhrase&&(this.settings.missing||(this.settings.missing={}),this.settings.missing[e]="---",this.settings.phrases[e]="---",console.log(`Missing translation for "%c${e}%c", see %c w2utils.settings.phrases %c with value "---"`,"color: orange","","color: #999",""))):"---"!==i||this.settings.warnNoPhrase||(i=e),"---"===i&&(i=`---`),this.execTemplate(i,t)}locale(l,i,r){return new Promise((s,t)=>{if(Array.isArray(l)){this.settings.phrases={};let i=[],t={};l.forEach((e,t)=>{5===e.length&&(e="locale/"+e.toLowerCase()+".json",l[t]=e),i.push(this.locale(e,!0,!1))}),void Promise.allSettled(i).then(e=>{e.forEach(e=>{e.value&&(t[e.value.file]=e.value.data)}),l.forEach(e=>{this.settings=this.extend({},this.settings,t[e])}),s()})}else(l=l||"en-us")instanceof Object?this.settings=this.extend({},this.settings,w2locale,l):(5===l.length&&(l="locale/"+l.toLowerCase()+".json"),fetch(l,{method:"GET"}).then(e=>e.json()).then(e=>{!0!==r&&(this.settings=i?this.extend({},this.settings,e):this.extend({},this.settings,w2locale,{phrases:{}},e)),s({file:l,data:e})}).catch(e=>{console.log("ERROR: Cannot load locale "+l),t(e)}))})}scrollBarSize(){return this.tmp.scrollBarSize||(query("body").append(` +
    +
    1
    +
    + `),this.tmp.scrollBarSize=100-query("#_scrollbar_width > div")[0].clientWidth,query("#_scrollbar_width").remove()),this.tmp.scrollBarSize}checkName(e){return null==e?(console.log('ERROR: Property "name" is required but not supplied.'),!1):null!=w2ui[e]?(console.log(`ERROR: Object named "${e}" is already registered as w2ui.${e}.`),!1):!!this.isAlphaNumeric(e)||(console.log('ERROR: Property "name" has to be alpha-numeric (a-z, 0-9, dash and underscore).'),!1)}checkUniqueId(t,i,s,l){Array.isArray(i)||(i=[i]);let r=!0;return i.forEach(e=>{e.id===t&&(console.log(`ERROR: The item id="${t}" is not unique within the ${s} "${l}".`,i),r=!1)}),r}encodeParams(t,i=""){let s="";return Object.keys(t).forEach(e=>{""!=s&&(s+="&"),"object"==typeof t[e]?s+=this.encodeParams(t[e],i+e+(i?"]":"")+"["):s+=""+i+e+(i?"]":"")+"="+t[e]}),s}parseRoute(e){let n=[];e=e.replace(/\/\(/g,"(?:/").replace(/\+/g,"__plus__").replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g,(e,t,i,s,l,r)=>(n.push({name:s,optional:!!r}),t=t||"",(r?"":t)+"(?:"+(r?t:"")+(i||"")+(l||(i?"([^/.]+?)":"([^/]+?)"))+")"+(r||""))).replace(/([\/.])/g,"\\$1").replace(/__plus__/g,"(.+)").replace(/\*/g,"(.*)");return{path:new RegExp("^"+e+"$","i"),keys:n}}getCursorPosition(e){if(null==e)return null;let t=0;var i,s=e.ownerDocument||e.document,l=s.defaultView||s.parentWindow;let r;return["INPUT","TEXTAREA"].includes(e.tagName)?t=e.selectionStart:l.getSelection?0<(r=l.getSelection()).rangeCount&&((i=(l=r.getRangeAt(0)).cloneRange()).selectNodeContents(e),i.setEnd(l.endContainer,l.endOffset),t=i.toString().length):(r=s.selection)&&"Control"!==r.type&&(l=r.createRange(),(i=s.body.createTextRange()).moveToElementText(e),i.setEndPoint("EndToEnd",l),t=i.text.length),t}setCursorPosition(s,l,t){if(null!=s){var r=document.createRange();let i,e=window.getSelection();if(["INPUT","TEXTAREA"].includes(s.tagName))s.setSelectionRange(l,t??l);else{for(let t=0;t").replace(/&/g,"&").replace(/"/g,'"').replace(/ /g," "):e).length){(i=(i=s.childNodes[t]).childNodes&&0i.length&&(l=i.length),r.setStart(i,l),t?r.setEnd(i,t):r.collapse(!0),e.removeAllRanges(),e.addRange(r))}}}parseColor(e){if("string"!=typeof e)return null;let t={};if(3===(e="#"===(e=e.trim().toUpperCase())[0]?e.substr(1):e).length)t={r:parseInt(e[0]+e[0],16),g:parseInt(e[1]+e[1],16),b:parseInt(e[2]+e[2],16),a:1};else if(6===e.length)t={r:parseInt(e.substr(0,2),16),g:parseInt(e.substr(2,2),16),b:parseInt(e.substr(4,2),16),a:1};else if(8===e.length)t={r:parseInt(e.substr(0,2),16),g:parseInt(e.substr(2,2),16),b:parseInt(e.substr(4,2),16),a:Math.round(parseInt(e.substr(6,2),16)/255*100)/100};else if(4{s[t]=this.clone(e,i)}):this.isPlainObject(e)?(s={},Object.assign(s,e),i.exclude&&i.exclude.forEach(e=>{delete s[e]}),Object.keys(s).forEach(e=>{s[e]=this.clone(s[e],i),void 0===s[e]&&delete s[e]})):e instanceof Function&&!i.functions||e instanceof Node&&!i.elements||e instanceof Event&&!i.events||(s=e),s}extend(i,s){if(Array.isArray(i)){if(!Array.isArray(s))throw new Error("Arrays can be extended with arrays only");i.splice(0,i.length),s.forEach(e=>{i.push(this.clone(e))})}else{if(i instanceof Node||i instanceof Event)throw new Error("HTML elmenents and events cannot be extended");if(i&&"object"==typeof i&&null!=s){if("object"!=typeof s)throw new Error("Object can be extended with other objects only.");Object.keys(s).forEach(e=>{var t;null!=i[e]&&"object"==typeof i[e]&&null!=s[e]&&"object"==typeof s[e]?(t=this.clone(s[e]),i[e]instanceof Node||i[e]instanceof Event?i[e]=t:(Array.isArray(i[e])&&this.isPlainObject(t)&&(i[e]={}),this.extend(i[e],t))):i[e]=this.clone(s[e])})}else if(null!=s)throw new Error("Object is not extendable, only {} or [] can be extended.")}if(2{"string"==typeof e||"number"==typeof e?i[t]={id:e,text:String(e)}:null!=e?(null!=e.caption&&null==e.text&&(e.text=e.caption),null!=e.text&&null==e.id&&(e.id=e.text),null==e.text&&null!=e.id&&(e.text=e.id)):i[t]={id:null,text:"null"}}),i):"function"==typeof i?(e=i.call(this,i,e),w2utils.normMenu.call(this,e)):"object"==typeof i?Object.keys(i).map(e=>({id:e,text:i[e]})):void 0}bindEvents(e,r){0!=e.length&&(e?.[0]instanceof Node&&(e=Array.isArray(e)?e:e.get()),query(e).each(s=>{let l=query(s).data();Object.keys(l).forEach(i=>{if(-1!=["click","dblclick","mouseenter","mouseleave","mouseover","mouseout","mousedown","mousemove","mouseup","contextmenu","focus","focusin","focusout","blur","input","change","keydown","keyup","keypress"].indexOf(String(i).toLowerCase())){let e=l[i],t=(e="string"==typeof e?e.split("|").map(e=>{"null"===(e="undefined"===(e="false"===(e="true"===e?!0:e)?!1:e)?void 0:e)&&(e=null);var t=["'",'"',"`"];return e="string"==typeof(e=parseFloat(e)==e?parseFloat(e):e)&&t.includes(e[0])&&t.includes(e[e.length-1])?e.substring(1,e.length-1):e}):e)[0];e=e.slice(1),query(s).off(i+".w2utils-bind").on(i+".w2utils-bind",function(i){switch(t){case"alert":alert(e[0]);break;case"stop":i.stopPropagation();break;case"prevent":i.preventDefault();break;case"stopPrevent":return i.stopPropagation(),i.preventDefault(),!1;default:if(null==r[t])throw new Error(`Cannot dispatch event as the method "${t}" does not exist.`);r[t].apply(r,e.map((e,t)=>{switch(String(e).toLowerCase()){case"event":return i;case"this":return this;default:return e}}))}})}})}))}debounce(t,i=250){let s;return(...e)=>{clearTimeout(s),s=setTimeout(()=>{t(...e)},i)}}}var w2utils=new Utils;class Dialog extends w2base{constructor(){super(),this.defaults={title:"",text:"",body:"",buttons:"",width:450,height:250,focus:null,actions:null,style:"",speed:.3,modal:!1,maximized:!1,keyboard:!0,showClose:!0,showMax:!1,transition:null,openMaximized:!1,moved:!1},this.name="popup",this.status="closed",this.onOpen=null,this.onClose=null,this.onMax=null,this.onMin=null,this.onToggle=null,this.onKeydown=null,this.onAction=null,this.onMove=null,this.tmp={},this.handleResize=e=>{this.options.moved||this.center(void 0,void 0,!0)}}open(s){let l=this;"closing"!=this.status&&!query("#w2ui-popup").hasClass("animating")||this.close(!0);var e=this.options;null!=(s=["string","number"].includes(typeof s)?w2utils.extend({title:"Notification",body:`
    ${s}
    `,actions:{Ok(){l.close()}},cancelAction:"ok"},arguments[1]??{}):s).text&&(s.body=`
    ${s.text}
    `),s=Object.assign({},this.defaults,e,{title:"",body:""},s,{maximized:!1}),this.options=s,0===query("#w2ui-popup").length&&(this.off("*"),Object.keys(this).forEach(e=>{e.startsWith("on")&&"on"!=e&&(this[e]=null)})),Object.keys(s).forEach(e=>{e.startsWith("on")&&"on"!=e&&s[e]&&(this[e]=s[e])}),s.width=parseInt(s.width),s.height=parseInt(s.height);let r,t,i;var{top:n,left:a}=this.center();let o={self:this,action(e){return l.on("action.prom",e),o},close(e){return l.on("close.prom",e),o},then(e){return l.on("open:after.prom",e),o}};if(null==s.actions||s.buttons||(s.buttons="",Object.keys(s.actions).forEach(e=>{var t=s.actions[e];let i=e;"function"==typeof t&&(s.buttons+=``),"object"==typeof t&&(s.buttons+=``,i=Array.isArray(s.actions)?t.text:e),"string"==typeof t&&(s.buttons+=``,i=t),"string"==typeof i&&(i=i[0].toLowerCase()+i.substr(1).replace(/\s+/g,"")),o[i]=function(t){return l.on("action.buttons",e=>{e.detail.action[0].toLowerCase()+e.detail.action.substr(1).replace(/\s+/g,"")==i&&t(e)}),o}})),0===query("#w2ui-popup").length){if(!0===(r=this.trigger("open",{target:"popup",present:!1})).isCancelled)return;this.status="opening",w2utils.lock(document.body,{opacity:.3,onClick:s.modal?null:()=>{this.close()}});let e="";s.showClose&&(e+=`
    + +
    `),s.showMax&&(e+=`
    + +
    `);a=` + left: ${a}px; + top: ${n}px; + width: ${parseInt(s.width)}px; + height: ${parseInt(s.height)}px; + transition: ${s.speed}s + `;t=`
    `,query("body").append(t),query("#w2ui-popup")[0]._w2popup={self:this,created:new Promise(e=>{this._promCreated=e}),opened:new Promise(e=>{this._promOpened=e}),closing:new Promise(e=>{this._promClosing=e}),closed:new Promise(e=>{this._promClosed=e})},a=`${s.title?"":"top: 0px !important;"} `+(s.buttons?"":"bottom: 0px !important;"),t=` + +
    ${e}
    +
    +
    +
    +
    +
    +
    + + `,query("#w2ui-popup").html(t),s.title&&query("#w2ui-popup .w2ui-popup-title").append(w2utils.lang(s.title)),s.buttons&&query("#w2ui-popup .w2ui-popup-buttons").append(s.buttons),s.body&&query("#w2ui-popup .w2ui-popup-body").append(s.body),setTimeout(()=>{query("#w2ui-popup").css("transition",s.speed+"s").removeClass("w2ui-anim-open"),w2utils.bindEvents("#w2ui-popup .w2ui-eaction",this),query("#w2ui-popup").find(".w2ui-popup-body").show(),this._promCreated()},1),clearTimeout(this._timer),this._timer=setTimeout(()=>{this.status="open",l.setFocus(s.focus),r.finish(),this._promOpened(),query("#w2ui-popup").removeClass("animating")},1e3*s.speed)}else{if(!0===(r=this.trigger("open",{target:"popup",present:!0})).isCancelled)return;this.status="opening",null!=e&&(e.maximized||e.width==s.width&&e.height==s.height||this.resize(s.width,s.height),s.prevSize=s.width+"px:"+s.height+"px",s.maximized=e.maximized);n=query("#w2ui-popup .w2ui-box").get(0).cloneNode(!0);query(n).removeClass("w2ui-box").addClass("w2ui-box-temp").find(".w2ui-popup-body").empty().append(s.body),query("#w2ui-popup .w2ui-box").after(n),s.buttons?(query("#w2ui-popup .w2ui-popup-buttons").show().html("").append(s.buttons),query("#w2ui-popup .w2ui-popup-body").removeClass("w2ui-popup-no-buttons"),query("#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp").css("bottom","")):(query("#w2ui-popup .w2ui-popup-buttons").hide().html(""),query("#w2ui-popup .w2ui-popup-body").addClass("w2ui-popup-no-buttons"),query("#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp").css("bottom","0px")),s.title?(query("#w2ui-popup .w2ui-popup-title").show().html((s.showClose?`
    + +
    `:"")+(s.showMax?`
    + +
    `:"")).append(s.title),query("#w2ui-popup .w2ui-popup-body").removeClass("w2ui-popup-no-title"),query("#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp").css("top","")):(query("#w2ui-popup .w2ui-popup-title").hide().html(""),query("#w2ui-popup .w2ui-popup-body").addClass("w2ui-popup-no-title"),query("#w2ui-popup .w2ui-box, #w2ui-popup .w2ui-box-temp").css("top","0px"));let t=query("#w2ui-popup .w2ui-box")[0],i=query("#w2ui-popup .w2ui-box-temp")[0];query("#w2ui-popup").addClass("animating"),w2utils.transition(t,i,s.transition,()=>{query(t).remove(),query(i).removeClass("w2ui-box-temp").addClass("w2ui-box");var e=query(i).find(".w2ui-popup-body");1==e.length&&(e[0].style.cssText=s.style,e.show()),l.setFocus(s.focus),query("#w2ui-popup").removeClass("animating")}),this.status="open",r.finish(),w2utils.bindEvents("#w2ui-popup .w2ui-eaction",this),query("#w2ui-popup").find(".w2ui-popup-body").show()}return s.openMaximized&&this.max(),s._last_focus=document.activeElement,s.keyboard&&query(document.body).on("keydown",e=>{this.keydown(e)}),query(window).on("resize",this.handleResize),i={resizing:!1,mvMove:function(e){1==i.resizing&&(e=e||window.event,i.div_x=e.screenX-i.x,i.div_y=e.screenY-i.y,!0!==(e=l.trigger("move",{target:"popup",div_x:i.div_x,div_y:i.div_y,originalEvent:e})).isCancelled)&&(query("#w2ui-popup").css({transition:"none",transform:"translate3d("+i.div_x+"px, "+i.div_y+"px, 0px)"}),l.options.moved=!0,e.finish())},mvStop:function(e){1!=i.resizing||(e=e||window.event,l.status="open",i.div_x=e.screenX-i.x,i.div_y=e.screenY-i.y,query("#w2ui-popup").css({left:i.pos_x+i.div_x+"px",top:i.pos_y+i.div_y+"px"}).css({transition:"none",transform:"translate3d(0px, 0px, 0px)"}),i.resizing=!1,query(document.body).off(".w2ui-popup"),i.isLocked)||l.unlock()}},query("#w2ui-popup .w2ui-popup-title").on("mousedown",function(e){var t;l.options.maximized||(e=(e=e)||window.event,l.status="moving",t=query("#w2ui-popup").get(0).getBoundingClientRect(),Object.assign(i,{resizing:!0,isLocked:1==query("#w2ui-popup > .w2ui-lock").length,x:e.screenX,y:e.screenY,pos_x:t.x,pos_y:t.y}),i.isLocked||l.lock({opacity:0}),query(document.body).on("mousemove.w2ui-popup",i.mvMove).on("mouseup.w2ui-popup",i.mvStop),e.stopPropagation?e.stopPropagation():e.cancelBubble=!0,e.preventDefault&&e.preventDefault())}),o}load(s){return new Promise((i,e)=>{if(null==(s="string"==typeof s?{url:s}:s).url)console.log("ERROR: The url is not defined."),e("The url is not defined");else{this.status="loading";let[e,t]=String(s.url).split("#");e&&fetch(e).then(e=>e.text()).then(e=>{i(this.template(e,t,s))})}})}template(t,e,i={}){let s;try{s=query(t)}catch(e){s=query.html(t)}return e&&(s=s.filter("#"+e)),Object.assign(i,{width:parseInt(query(s).css("width")),height:parseInt(query(s).css("height")),title:query(s).find("[rel=title]").html(),body:query(s).find("[rel=body]").html(),buttons:query(s).find("[rel=buttons]").html(),style:query(s).find("[rel=body]").get(0).style.cssText}),this.open(i)}action(e,t){let i=this.options.actions[e];i instanceof Object&&i.onClick&&(i=i.onClick);e=this.trigger("action",{action:e,target:"popup",self:this,originalEvent:t,value:this.input?this.input.value:null});!0!==e.isCancelled&&("function"==typeof i&&i.call(this,t),e.finish())}keydown(e){var t;this.options&&!this.options.keyboard||!0!==(t=this.trigger("keydown",{target:"popup",originalEvent:e})).isCancelled&&(27===e.keyCode&&(e.preventDefault(),0==query("#w2ui-popup .w2ui-message").length)&&(this.options.cancelAction?this.action(this.options.cancelAction):this.close()),t.finish())}close(e){let t=this.trigger("close",{target:"popup"});var i;!0!==t.isCancelled&&(i=()=>{query("#w2ui-popup").remove(),this.options._last_focus&&0{e.finish()},1e3*this.options.speed+50))}max(){if(!0!==this.options.maximized){let e=this.trigger("max",{target:"popup"});var t;!0!==e.isCancelled&&(this.status="resizing",t=query("#w2ui-popup").get(0).getBoundingClientRect(),this.options.prevSize=t.width+":"+t.height,this.resize(1e4,1e4,()=>{this.status="open",this.options.maximized=!0,e.finish()}))}}min(){if(!0===this.options.maximized){var t=this.options.prevSize.split(":");let e=this.trigger("min",{target:"popup"});!0!==e.isCancelled&&(this.status="resizing",this.options.maximized=!1,this.resize(parseInt(t[0]),parseInt(t[1]),()=>{this.status="open",this.options.prevSize=null,e.finish()}))}}clear(){query("#w2ui-popup .w2ui-popup-title").html(""),query("#w2ui-popup .w2ui-popup-body").html(""),query("#w2ui-popup .w2ui-popup-buttons").html("")}reset(){this.open(this.defaults)}message(e){return w2utils.message({owner:this,box:query("#w2ui-popup").get(0),after:".w2ui-popup-title"},e)}confirm(e){return w2utils.confirm({owner:this,box:query("#w2ui-popup"),after:".w2ui-popup-title"},e)}setFocus(e){let s=query("#w2ui-popup"),l="input, button, select, textarea, [contentEditable], .w2ui-input";null!=e?(isNaN(e)?s.find(l).filter(e).get(0):s.find(l).get(e))?.focus():(e=s.find("[name=hidden-first]").get(0))&&e.focus(),query(s).find(l+",[name=hidden-first],[name=hidden-last]").off(".keep-focus").on("blur.keep-focus",function(e){setTimeout(()=>{var e=document.activeElement,t=0{s.resizeMessages()},10);setTimeout(()=>{clearInterval(a),s.resizeMessages(),"function"==typeof i&&i()},1e3*this.options.speed+50)}resizeMessages(){query("#w2ui-popup .w2ui-message").each(e=>{var t=e._msg_options,i=query("#w2ui-popup"),s=(parseInt(t.width)<10&&(t.width=10),parseInt(t.height)<10&&(t.height=10),i[0].getBoundingClientRect()),i=parseInt(i.find(".w2ui-popup-title")[0].clientHeight),l=parseInt(s.width),s=parseInt(s.height);t.width=t.originalWidth,t.width>l-10&&(t.width=l-10),t.height=t.originalHeight,t.height>s-i-5&&(t.height=s-i-5),t.originalHeight<0&&(t.height=s+t.originalHeight-i),t.originalWidth<0&&(t.width=l+2*t.originalWidth),query(e).css({left:(l-t.width)/2+"px",width:t.width+"px",height:t.height+"px"})})}}function w2alert(e,t,i){let s;t={title:w2utils.lang(t??"Notification"),body:`
    ${e}
    `,showClose:!1,actions:["Ok"],cancelAction:"ok"};return(s=0{"function"==typeof e.detail.self?.close&&e.detail.self.close(),"function"==typeof i&&i()}),s}function w2confirm(e,t,i){let s,l=e;return(l=["string","number"].includes(typeof l)?{msg:l}:l).msg&&(l.body=`
    ${l.msg}
    `,delete l.msg),w2utils.extend(l,{title:w2utils.lang(t??"Confirmation"),showClose:!1,modal:!0,cancelAction:"no"}),w2utils.normButtons(l,{yes:"Yes",no:"No"}),(s=0{"function"==typeof e.detail.self?.close&&e.detail.self.close(),"function"==typeof i&&i(e.detail.action)}),s}function w2prompt(e,t,i){let s,l=e;return(l=["string","number"].includes(typeof l)?{label:l}:l).label&&(l.focus=0,l.body=l.textarea?`
    +
    ${l.label}
    + +
    `:`
    + + +
    `),w2utils.extend(l,{title:w2utils.lang(t??"Notification"),showClose:!1,modal:!0,cancelAction:"cancel"}),w2utils.normButtons(l,{ok:"Ok",cancel:"Cancel"}),(s=0{e=e.detail.box||query("#w2ui-popup .w2ui-popup-body").get(0);w2utils.bindEvents(query(e).find("#w2prompt"),{keydown(e){27==e.keyCode&&e.stopPropagation()},change(e){var t=s.self.trigger("change",{target:"prompt",originalEvent:e});!0!==t.isCancelled&&(13==e.keyCode&&e.ctrlKey&&s.self.action("Ok",e),27==e.keyCode&&s.self.action("Cancel",e),t.finish())}}),query(e).find(".w2ui-eaction").trigger("keyup")}).on("action:after.prompt",e=>{"function"==typeof e.detail.self?.close&&e.detail.self.close(),"function"==typeof i&&i(e.detail.action)}),s}let w2popup=new Dialog;class Tooltip{static active={};constructor(){this.defaults={name:null,html:"",style:"",class:"",position:"top|bottom",align:"",anchor:null,anchorClass:"",anchorStyle:"",autoShow:!1,autoShowOn:null,autoHideOn:null,arrowSize:8,margin:0,margin:1,screenMargin:2,autoResize:!0,offsetX:0,offsetY:0,maxWidth:null,maxHeight:null,watchScroll:null,watchResize:null,hideOn:null,onThen:null,onShow:null,onHide:null,onUpdate:null,onMove:null}}static observeRemove=new MutationObserver(e=>{let t=0;Object.keys(Tooltip.active).forEach(e=>{e=Tooltip.active[e];e.displayed&&(e.anchor&&e.anchor.isConnected?t++:e.hide())}),0===t&&Tooltip.observeRemove.disconnect()});trigger(e,t){var i;if(2==arguments.length&&(i=e,(e=t).type=i),e.overlay)return e.overlay.trigger(e);console.log("ERROR: cannot find overlay where to trigger events")}get(e){return 0==arguments.length?Object.keys(Tooltip.active):!0===e?Tooltip.active:Tooltip.active[e.replace(/[\s\.#]/g,"_")]}attach(t,s){let l,r,n=this;if(0!=arguments.length){1==arguments.length&&t.anchor?t=(l=t).anchor:2===arguments.length&&"string"==typeof s?s=(l={anchor:t,html:s}).html:2===arguments.length&&null!=s&&"object"==typeof s&&(s=(l=s).html),l=w2utils.extend({},this.defaults,l||{}),!(s=!s&&l.text?l.text:s)&&l.html&&(s=l.html),delete l.anchor;let e=l.name||t.id;t!=document&&t!=document.body||(t=document.body,e="context-menu"),e||(e="noname-"+Object.keys(Tooltip.active).length,console.log("NOTICE: name property is not defined for tooltip, could lead to too many instances")),e=e.replace(/[\s\.#]/g,"_"),Tooltip.active[e]?((r=Tooltip.active[e]).prevOptions=r.options,r.options=l,r.anchor=t,r.prevOptions.html==r.options.html&&r.prevOptions.class==r.options.class&&r.prevOptions.style==r.options.style||(r.needsUpdate=!0),l=r.options):(r=new w2base,Object.assign(r,{id:"w2overlay-"+e,name:e,options:l,anchor:t,displayed:!1,tmp:{observeResize:new ResizeObserver(()=>{this.resize(r.name)})},hide(){n.hide(e)}}),Tooltip.active[e]=r),Object.keys(r.options).forEach(e=>{var t=r.options[e];e.startsWith("on")&&"function"==typeof t&&(r[e]=t,delete r.options[e])}),!0===l.autoShow&&(l.autoShowOn=l.autoShowOn??"mouseenter",l.autoHideOn=l.autoHideOn??"mouseleave",l.autoShow=!1),l.autoShowOn&&(s="autoShow-"+r.name,query(t).off("."+s).on(l.autoShowOn+"."+s,e=>{n.show(r.name),e.stopPropagation()}),delete l.autoShowOn),l.autoHideOn&&(s="autoHide-"+r.name,query(t).off("."+s).on(l.autoHideOn+"."+s,e=>{n.hide(r.name),e.stopPropagation()}),delete l.autoHideOn),r.off(".attach");let i={overlay:r,then:t=>(r.on("show:after.attach",e=>{t(e)}),i),show:t=>(r.on("show.attach",e=>{t(e)}),i),hide:t=>(r.on("hide.attach",e=>{t(e)}),i),update:t=>(r.on("update.attach",e=>{t(e)}),i),move:t=>(r.on("move.attach",e=>{t(e)}),i)};return i}}update(e,t){var i=Tooltip.active[e];i?(i.needsUpdate=!0,i.options.html=t,this.show(e)):console.log(`Tooltip "${e}" is not displayed. Cannot update it.`)}show(i){if(i instanceof HTMLElement||i instanceof Object){let e=i,t=(i instanceof HTMLElement&&((e=arguments[1]||{}).anchor=i),this.attach(e));return query(t.overlay.anchor).off(".autoShow-"+t.overlay.name).off(".autoHide-"+t.overlay.name),setTimeout(()=>{this.show(t.overlay.name)},1),t}let t,r=this,n=Tooltip.active[i.replace(/[\s\.#]/g,"_")];if(n){let l=n.options;if(!n||n.displayed&&!n.needsUpdate)this.resize(n?.name);else{var s=l.position.split("|"),s=["top","bottom"].includes(s[0]);let e="both"==l.align&&s?"":"white-space: nowrap;";if(l.maxWidth&&w2utils.getStrWidth(l.html,"")>l.maxWidth&&(e="width: "+l.maxWidth+"px; white-space: inherit; overflow: auto;"),e+=" max-height: "+(l.maxHeight||window.innerHeight-40)+"px;",""!==l.html&&null!=l.html){if(n.box){if(!0===(t=this.trigger("update",{target:i,overlay:n})).isCancelled)return void(n.prevOptions&&(n.options=n.prevOptions,delete n.prevOptions));query(n.box).find(".w2ui-overlay-body").attr("style",(l.style||"")+"; "+e).removeClass().addClass("w2ui-overlay-body "+l.class).html(l.html)}else{if(!0===(t=this.trigger("show",{target:i,overlay:n})).isCancelled)return;query("body").append(``),n.box=query("#"+w2utils.escapeId(n.id))[0],n.displayed=!0;s=query(n.anchor).data("tooltipName")??[];s.push(i),query(n.anchor).data("tooltipName",s),w2utils.bindEvents(n.box,{}),n.tmp.originalCSS="",0{r.hide(n.name)},i=query(n.anchor),s="tooltip-"+n.name;query("body").off("."+s),l.hideOn.includes("doc-click")&&(["INPUT","TEXTAREA"].includes(n.anchor.tagName)&&i.off(`.${s}-doc`).on(`click.${s}-doc`,e=>{e.stopPropagation()}),query("body").on("click."+s,t));l.hideOn.includes("focus-change")&&query("body").on("focusin."+s,e=>{document.activeElement!=n.anchor&&r.hide(n.name)});["INPUT","TEXTAREA"].includes(n.anchor.tagName)&&(i.off("."+s),l.hideOn.forEach(e=>{-1==["doc-click","focus-change"].indexOf(e)&&i.on(e+"."+s,{once:!0},t)}))}{var a=document.body;let e="tooltip-"+n.name,t=a;"BODY"==a.tagName&&(t=a.ownerDocument);query(t).off("."+e).on("scroll."+e,e=>{Object.assign(n.tmp,{scrollLeft:a.scrollLeft,scrollTop:a.scrollTop}),r.resize(n.name)})}return query(n.box).show(),n.tmp.observeResize.observe(n.box),Tooltip.observeRemove.observe(document.body,{subtree:!0,childList:!0}),query(n.box).css("opacity",1).find(".w2ui-overlay-body").html(l.html),setTimeout(()=>{query(n.box).css({"pointer-events":"auto"}).data("ready","yes")},100),delete n.needsUpdate,n.box.overlay=n,t&&t.finish(),{overlay:n}}r.hide(i)}}}hide(e){let i;if(0==arguments.length)Object.keys(Tooltip.active).forEach(e=>{this.hide(e)});else if(e instanceof HTMLElement)(query(e).data("tooltipName")??[]).forEach(e=>{this.hide(e)});else if("string"==typeof e&&(e=e.replace(/[\s\.#]/g,"_"),i=Tooltip.active[e]),i&&i.box){delete Tooltip.active[e];e=this.trigger("hide",{target:e,overlay:i});if(!0!==e.isCancelled){var s="tooltip-"+i.name;i.tmp.observeResize?.disconnect(),i.options.watchScroll&&query(i.options.watchScroll).off(".w2scroll-"+i.name);let t=0;Object.keys(Tooltip.active).forEach(e=>{Tooltip.active[e].displayed&&t++}),0==t&&Tooltip.observeRemove.disconnect(),query("body").off("."+s),query(document).off("."+s),i.box.remove(),i.box=null,i.displayed=!1;var l=query(i.anchor).data("tooltipName")??[];-1!=l.indexOf(i.name)&&l.splice(l.indexOf(i.name),1),0==l.length?query(i.anchor).removeData("tooltipName"):query(i.anchor).data("tooltipName",l),i.anchor.style.cssText=i.tmp.originalCSS,query(i.anchor).off("."+s).removeClass(i.options.anchorClass),e.finish()}}}resize(i){if(0==arguments.length)Object.keys(Tooltip.active).forEach(e=>{e=Tooltip.active[e];e.displayed&&this.resize(e.name)});else{var s=Tooltip.active[i.replace(/[\s\.#]/g,"_")];let t=this.getPosition(s.name);var l=t.left+"x"+t.top;let e;s.tmp.lastPos!=l&&(e=this.trigger("move",{target:i,overlay:s,pos:t})),query(s.box).css({left:t.left+"px",top:t.top+"px"}).then(e=>{null!=t.width&&e.css("width",t.width+"px").find(".w2ui-overlay-body").css("width","100%"),null!=t.height&&e.css("height",t.height+"px").find(".w2ui-overlay-body").css("height","100%")}).find(".w2ui-overlay-body").removeClass("w2ui-arrow-right w2ui-arrow-left w2ui-arrow-top w2ui-arrow-bottom").addClass(t.arrow.class).closest(".w2ui-overlay").find("style").text(t.arrow.style),s.tmp.lastPos!=l&&e&&(s.tmp.lastPos=l,e.finish())}}getPosition(e){let g=Tooltip.active[e.replace(/[\s\.#]/g,"_")];if(g&&g.box){let t=g.options;(g.tmp.resizedY||g.tmp.resizedX)&&query(g.box).css({width:"",height:"",scroll:"auto"});var e=w2utils.scrollBarSize(),y=!(document.body.scrollWidth==document.body.clientWidth),w=!(document.body.scrollHeight==document.body.clientHeight);let i={width:window.innerWidth-(w?e:0),height:window.innerHeight-(y?e:0)};var b,v=("auto"==t.position?"top|bottom|right|left":t.position).split("|");let s=["top","bottom"].includes(v[0]),l=g.box.getBoundingClientRect(),r=g.anchor.getBoundingClientRect(),n=(g.anchor==document.body&&({x,y:_,width:q,height:C}=t.originalEvent,r={left:x-2,top:_-4,width:q,height:C,arrow:"none"}),t.arrowSize),a=("none"==r.arrow&&(n=0),{top:r.top,bottom:i.height-(r.top+r.height)-+(y?e:0),left:r.left,right:i.width-(r.left+r.width)+(w?e:0)});l.width<22&&(l.width=22),l.height<14&&(l.height=14);let o,h,d,u,c="",p={offset:0,class:"",style:`#${g.id} { --tip-size: ${n}px; }`},f={left:0,top:0},m={posX:"",x:0,posY:"",y:0};v.forEach(e=>{["top","bottom"].includes(e)&&(!c&&l.height+n/1.893m.y)&&Object.assign(m,{posY:e,y:a[e]}),["left","right"].includes(e)&&(!c&&l.width+n/1.893m.x)&&Object.assign(m,{posX:e,x:a[e]})}),c=c||(s?m.posY:m.posX),t.autoResize&&(["top","bottom"].includes(c)&&(l.height>a[c]?(u=a[c],g.tmp.resizedY=!0):g.tmp.resizedY=!1),["left","right"].includes(c))&&(l.width>a[c]?(d=a[c],g.tmp.resizedX=!0):g.tmp.resizedX=!1);var x=c;switch(p.class=r.arrow||"w2ui-arrow-"+x,x){case"top":o=r.left+(r.width-(d??l.width))/2,h=r.top-(u??l.height)-n/1.5+1;break;case"bottom":o=r.left+(r.width-(d??l.width))/2,h=r.top+r.height+n/1.25+1;break;case"left":o=r.left-(d??l.width)-n/1.2-1,h=r.top+(r.height-(u??l.height))/2;break;case"right":o=r.left+r.width+n/1.2+1,h=r.top+(r.height-(u??l.height))/2}if(s)"left"==t.align&&(f.left=r.left-o,o=r.left),"right"==t.align&&(f.left=r.left+r.width-(d??l.width)-o,o=r.left+r.width-(d??l.width)),["top","bottom"].includes(c)&&t.align.startsWith("both")&&(b=t.align.split(":")[1]??50,r.width>=b)&&(o=r.left,d=r.width),"top"==t.align&&(f.top=r.top-h,h=r.top),"bottom"==t.align&&(f.top=r.top+r.height-(u??l.height)-h,h=r.top+r.height-(u??l.height)),["left","right"].includes(c)&&t.align.startsWith("both")&&(b=t.align.split(":")[1]??50,r.height>=b)&&(h=r.top,u=r.height);{let e;(["left","right"].includes(t.align)&&r.width<(d??l.width)||["top","bottom"].includes(t.align)&&r.height<(u??l.height))&&(e=!0);var _="right"==c?n:t.screenMargin,q="bottom"==c?n:t.screenMargin,C=i.width-(d??l.width)-("left"==c?n:t.screenMargin),y=i.height-(u??l.height)-("top"==c?n:t.screenMargin)+3;(["top","bottom"].includes(c)||t.autoResize)&&(o<_&&(e=!0,f.left-=o,o=_),o>C)&&(e=!0,f.left-=o-C,o+=C-o);(["left","right"].includes(c)||t.autoResize)&&(hy)&&(e=!0,f.top-=h-y,h+=y-h);e&&(_=s?"left":"top",C=s?"width":"height",p.offset=-f[_],q=l[C]/2-n,Math.abs(p.offset)>q+n&&(p.class=""),Math.abs(p.offset)>q&&(p.offset=p.offset<0?-q:q),p.style=w2utils.stripSpaces(`#${g.id} .w2ui-overlay-body:after, + #${g.id} .w2ui-overlay-body:before { + --tip-size: ${n}px; + margin-${_}: ${p.offset}px; + }`))}w="top"==c?-t.margin:"bottom"==c?t.margin:0,e="left"==c?-t.margin:"right"==c?t.margin:0;return h=Math.floor(100*(h+parseFloat(t.offsetY)+parseFloat(w)))/100,{left:o=Math.floor(100*(o+parseFloat(t.offsetX)+parseFloat(e)))/100,top:h,arrow:p,adjust:f,width:d,height:u,pos:c}}}}class ColorTooltip extends Tooltip{constructor(){super(),this.palette=[["000000","333333","555555","777777","888888","999999","AAAAAA","CCCCCC","DDDDDD","EEEEEE","F7F7F7","FFFFFF"],["FF011B","FF9838","FFC300","FFFD59","86FF14","14FF7A","2EFFFC","2693FF","006CE7","9B24F4","FF21F5","FF0099"],["FFEAEA","FCEFE1","FCF4DC","FFFECF","EBFFD9","D9FFE9","E0FFFF","E8F4FF","ECF4FC","EAE6F4","FFF5FE","FCF0F7"],["F4CCCC","FCE5CD","FFF1C2","FFFDA1","D5FCB1","B5F7D0","BFFFFF","D6ECFF","CFE2F3","D9D1E9","FFE3FD","FFD9F0"],["EA9899","F9CB9C","FFE48C","F7F56F","B9F77E","84F0B1","83F7F7","B5DAFF","9FC5E8","B4A7D6","FAB9F6","FFADDE"],["E06666","F6B26B","DEB737","E0DE51","8FDB48","52D189","4EDEDB","76ACE3","6FA8DC","8E7CC3","E07EDA","F26DBD"],["CC0814","E69138","AB8816","B5B20E","6BAB30","27A85F","1BA8A6","3C81C7","3D85C6","674EA7","A14F9D","BF4990"],["99050C","B45F17","80650E","737103","395E14","10783D","13615E","094785","0A5394","351C75","780172","782C5A"]],this.defaults=w2utils.extend({},this.defaults,{advanced:!1,transparent:!0,position:"top|bottom",class:"w2ui-white",color:"",liveUpdate:!0,arrowSize:12,autoResize:!1,anchorClass:"w2ui-focus",autoShowOn:"focus",hideOn:["doc-click","focus-change"],onSelect:null,onLiveUpdate:null})}attach(e,t){let i;1==arguments.length&&e.anchor?e=(i=e).anchor:2===arguments.length&&null!=t&&"object"==typeof t&&((i=t).anchor=e);t=i.hideOn;i=w2utils.extend({},this.defaults,i||{}),t&&(i.hideOn=t),i.style+="; padding: 0;",i.transparent&&"333333"==this.palette[0][1]&&(this.palette[0].splice(1,1),this.palette[0].push("")),i.transparent||"333333"==this.palette[0][1]||(this.palette[0].splice(1,0,"333333"),this.palette[0].pop()),i.color&&(i.color=String(i.color).toUpperCase()),"string"==typeof i.color&&"#"===i.color.substr(0,1)&&(i.color=i.color.substr(1)),this.index=[-1,-1];let s=super.attach(i),l=s.overlay;return l.options.html=this.getColorHTML(l.name,i),l.on("show.attach",e=>{var e=e.detail.overlay,t=e.anchor,i=e.options;["INPUT","TEXTAREA"].includes(t.tagName)&&!i.color&&t.value&&(e.tmp.initColor=t.value),delete e.newColor}),l.on("show:after.attach",e=>{var t;s.overlay?.box&&(t=query(s.overlay.box).find(".w2ui-eaction"),w2utils.bindEvents(t,this),this.initControls(s.overlay))}),l.on("update:after.attach",e=>{var t;s.overlay?.box&&(t=query(s.overlay.box).find(".w2ui-eaction"),w2utils.bindEvents(t,this),this.initControls(s.overlay))}),l.on("hide.attach",e=>{var e=e.detail.overlay,t=e.anchor,i=e.newColor??e.options.color??"",t=(["INPUT","TEXTAREA"].includes(t.tagName)&&t.value!=i&&(t.value=i),this.trigger("select",{color:i,target:e.name,overlay:e}));!0!==t.isCancelled&&t.finish()}),s.liveUpdate=t=>(l.on("liveUpdate.attach",e=>{t(e)}),s),s.select=t=>(l.on("select.attach",e=>{t(e)}),s),s}select(e,t){let i;this.index=[-1,-1],"string"!=typeof t&&(i=t.target,this.index=query(i).attr("index").split(":"),t=query(i).closest(".w2ui-overlay").attr("name"));var s=this.get(t),t=this.trigger("liveUpdate",{color:e,target:t,overlay:s,param:arguments[1]});!0!==t.isCancelled&&(["INPUT","TEXTAREA"].includes(s.anchor.tagName)&&s.options.liveUpdate&&query(s.anchor).val(e),s.newColor=e,query(s.box).find(".w2ui-selected").removeClass("w2ui-selected"),i&&query(i).addClass("w2ui-selected"),t.finish())}nextColor(e){var t=this.palette;switch(e){case"up":this.index[0]--;break;case"down":this.index[0]++;break;case"right":this.index[1]++;break;case"left":this.index[1]--}return this.index[0]<0&&(this.index[0]=0),this.index[0]>t.length-2&&(this.index[0]=t.length-2),this.index[1]<0&&(this.index[1]=0),this.index[1]>t[0].length-1&&(this.index[1]=t[0].length-1),t[this.index[0]][this.index[1]]}tabClick(e,t){"string"!=typeof t&&(t=query(t.target).closest(".w2ui-overlay").attr("name"));var t=this.get(t),i=query(t.box).find(`.w2ui-color-tab:nth-child(${e})`);query(t.box).find(".w2ui-color-tab").removeClass("w2ui-selected"),query(i).addClass("w2ui-selected"),query(t.box).find(".w2ui-tab-content").hide().closest(".w2ui-colors").find(".tab-"+e).show()}getColorHTML(s,l){let r=` +
    +
    `;for(let i=0;i';for(let t=0;t  +
    `}r+="
    ",i<2&&(r+='
    ')}return r=(r=(r+="")+` + `)+` +
    +
    +
    +
    + ${"string"==typeof l.html?l.html:""} +
    +
    `}initControls(a){let n,o=this;var e=a.options;let h=w2utils.parseColor(e.color||a.tmp.initColor),d=(null==h&&(h={r:140,g:150,b:160,a:1}),w2utils.rgb2hsv(h));!0===e.advanced&&this.tabClick(2,a.name),u(d,!0,!0),query(a.box).find("input").off(".w2color").on("change.w2color",e=>{e=query(e.target);let t=parseFloat(e.val());var i=parseFloat(e.attr("max")),i=(isNaN(t)&&(t=0,e.val(0)),1i&&(e.val(i),t=i),t<0&&(e.val(0),t=0),e.attr("name")),e={};-1!==["r","g","b","a"].indexOf(i)?(h[i]=t,d=w2utils.rgb2hsv(h)):-1!==["h","s","v"].indexOf(i)&&(e[i]=t),u(e,!0)}),query(a.box).find(".color-original").off(".w2color").on("click.w2color",e=>{e=w2utils.parseColor(query(e.target).css("background-color"));null!=e&&(h=e,u(d=w2utils.rgb2hsv(h),!0))});e=`${w2utils.isIOS?"touchstart":"mousedown"}.w2color`;let s=`${w2utils.isIOS?"touchend":"mouseup"}.w2color`,l=`${w2utils.isIOS?"touchmove":"mousemove"}.w2color`;function u(e,t,i){null!=e.h&&(d.h=e.h),null!=e.s&&(d.s=e.s),null!=e.v&&(d.v=e.v),null!=e.a&&(h.a=e.a,d.a=e.a);let s="rgba("+(h=w2utils.hsv2rgb(d)).r+","+h.g+","+h.b+","+h.a+")",l=[Number(h.r).toString(16).toUpperCase(),Number(h.g).toString(16).toUpperCase(),Number(h.b).toString(16).toUpperCase(),Math.round(255*Number(h.a)).toString(16).toUpperCase()];var r,n;l.forEach((e,t)=>{1===e.length&&(l[t]="0"+e)}),s=l[0]+l[1]+l[2]+l[3],1===h.a&&(s=l[0]+l[1]+l[2]),query(a.box).find(".color-preview").css("background-color","#"+s),query(a.box).find("input").each(e=>{e.name&&(null!=h[e.name]&&(e.value=h[e.name]),null!=d[e.name]&&(e.value=d[e.name]),"a"===e.name)&&(e.value=h.a)}),i?(e=a.tmp?.initColor||s,query(a.box).find(".color-original").css("background-color","#"+e),query(a.box).find(".w2ui-colors .w2ui-selected").removeClass("w2ui-selected"),query(a.box).find(`.w2ui-colors [name="${e}"]`).addClass("w2ui-selected"),8==s.length&&o.tabClick(2,a.name)):o.select(s,a.name),t&&(i=query(a.box).find(".palette .value1"),e=query(a.box).find(".rainbow .value2"),t=query(a.box).find(".alpha .value2"),r=parseInt(i[0].clientWidth)/2,n=parseInt(e[0].clientWidth)/2,i.css({left:150*d.s/100-r+"px",top:125*(100-d.v)/100-r+"px"}),e.css("left",d.h/2.4-n+"px"),t.css("left",150*h.a-n+"px"),c())}function c(){var e=w2utils.hsv2rgb(d.h,100,100),e=`${e.r},${e.g},`+e.b;query(a.box).find(".palette").css("background-image",`linear-gradient(90deg, rgba(${e},0) 0%, rgba(${e},1) 100%)`)}function r(e){query("body").off(".w2color")}function p(e){var t=n.el,i=e.pageX-n.x,e=e.pageY-n.y;let s=n.left+i,l=n.top+e;var i=parseInt(t.prop("clientWidth"))/2,e=(s<-i&&(s=-i),l<-i&&(l=-i),s>n.width-i&&(s=n.width-i),l>n.height-i&&(l=n.height-i),t.hasClass("move-x")&&t.css({left:s+"px"}),t.hasClass("move-y")&&t.css({top:l+"px"}),query(t.get(0).parentNode).attr("name")),r=parseInt(t.css("left"))+i,t=parseInt(t.css("top"))+i;"palette"===e&&u({s:Math.round(r/n.width*100),v:Math.round(100-t/n.height*100)}),"rainbow"===e&&(u({h:Math.round(2.4*r)}),c()),"alpha"===e&&u({a:parseFloat(Number(r/150).toFixed(2))})}query(a.box).find(".palette, .rainbow, .alpha").off(".w2color").on(e+".w2color",function(e){var t=query(this).find(".value1, .value2"),i=parseInt(t.prop("clientWidth"))/2;t.hasClass("move-x")&&t.css({left:e.offsetX-i+"px"});t.hasClass("move-y")&&t.css({top:e.offsetY-i+"px"});n={el:t,x:e.pageX,y:e.pageY,width:t.prop("parentNode").clientWidth,height:t.prop("parentNode").clientHeight,left:parseInt(t.css("left")),top:parseInt(t.css("top"))},p(e),query("body").off(".w2color").on(l,p).on(s,r)})}}class MenuTooltip extends Tooltip{constructor(){super(),this.defaults=w2utils.extend({},this.defaults,{type:"normal",items:[],index:null,render:null,spinner:!1,msgNoItems:w2utils.lang("No items found"),topHTML:"",menuStyle:"",filter:!1,markSearch:!1,match:"contains",search:!1,altRows:!1,arrowSize:10,align:"left",position:"bottom|top",class:"w2ui-white",anchorClass:"w2ui-focus",autoShowOn:"focus",hideOn:["doc-click","focus-change","select"],onSelect:null,onSubMenu:null,onRemove:null})}attach(e,t){let i;1==arguments.length&&e.anchor?e=(i=e).anchor:2===arguments.length&&null!=t&&"object"==typeof t&&((i=t).anchor=e);t=i.hideOn;i=w2utils.extend({},this.defaults,i||{}),t&&(i.hideOn=t),i.style+="; padding: 0;",null==i.items&&(i.items=[]),i.html=this.getMenuHTML(i);let s=super.attach(i),l=s.overlay;return l.on("show:after.attach, update:after.attach",e=>{if(s.overlay?.box){let e="";l.selected=null,l.options.items=w2utils.normMenu(l.options.items),["INPUT","TEXTAREA"].includes(l.anchor.tagName)&&(e=l.anchor.value,l.selected=l.anchor.dataset.selectedIndex);var t=query(s.overlay.box).find(".w2ui-eaction"),t=(w2utils.bindEvents(t,this),this.applyFilter(l.name,null,e));l.tmp.searchCount=t,l.tmp.search=e,this.refreshSearch(l.name),this.initControls(s.overlay),this.refreshIndex(l.name)}}),l.on("hide:after.attach",e=>{w2tooltip.hide(l.name+"-tooltip")}),s.select=t=>(l.on("select.attach",e=>{t(e)}),s),s.remove=t=>(l.on("remove.attach",e=>{t(e)}),s),s.subMenu=t=>(l.on("subMenu.attach",e=>{t(e)}),s),s}update(e,t){var i,s=Tooltip.active[e];s?((i=s.options).items!=t&&(i.items=t),t=this.getMenuHTML(i),i.html!=t&&(i.html=t,s.needsUpdate=!0,this.show(e))):console.log(`Tooltip "${e}" is not displayed. Cannot update it.`)}initControls(i){query(i.box).find(".w2ui-menu:not(.w2ui-sub-menu)").off(".w2menu").on("mouseDown.w2menu",{delegate:".w2ui-menu-item"},e=>{var t=e.delegate.dataset;this.menuDown(i,e,t.index,t.parents)}).on((w2utils.isIOS?"touchStart":"click")+".w2menu",{delegate:".w2ui-menu-item"},e=>{var t=e.delegate.dataset;this.menuClick(i,e,parseInt(t.index),t.parents)}).find(".w2ui-menu-item").off(".w2menu").on("mouseEnter.w2menu",e=>{var t=e.target.dataset,t=i.options.items[t.index]?.tooltip;t&&w2tooltip.show({name:i.name+"-tooltip",anchor:e.target,html:t,position:"right|left",hideOn:["doc-click"]})}).on("mouseLeave.w2menu",e=>{w2tooltip.hide(i.name+"-tooltip")}),["INPUT","TEXTAREA"].includes(i.anchor.tagName)&&query(i.anchor).off(".w2menu").on("input.w2menu",e=>{}).on("keyup.w2menu",e=>{e._searchType="filter",this.keyUp(i,e)}),i.options.search&&query(i.box).find("#menu-search").off(".w2menu").on("keyup.w2menu",e=>{e._searchType="search",this.keyUp(i,e)})}getCurrent(e,t){var e=Tooltip.active[e.replace(/[\s\.#]/g,"_")],i=e.options;let s=(t||(e.selected??"")).split("-");var t=s.length-1,e=s[t],l=s.slice(0,s.length-1).join("-"),e=w2utils.isInt(e)?parseInt(e):0;let r=i.items;return s.forEach((e,t)=>{t +
    +
    + ${w2utils.lang("Loading...")} +
    + `;u=u||[],null==e&&(e=h.items),Array.isArray(e)||(e=[]);let c=0,t=null,i="",p=(!d&&h.search&&(i+=` + `,e.forEach(e=>e.hidden=!1)),!d&&h.topHTML&&(i+=`
    ${h.topHTML}
    `),` + ${i} +
    + `);return e.forEach((r,n)=>{t=r.icon;var a=(0`),s=``),"break"!==r.type&&null!=i&&""!==i&&"--"!=String(i).substr(0,2)){var o=["w2ui-menu-item"];1==h.altRows&&o.push(c%2==0?"w2ui-even":"w2ui-odd");let e=1,t=(""===s&&e++,null==r.count&&null==r.hotkey&&!0!==r.remove&&null==r.items&&e++,null==r.tooltip&&null!=r.hint&&(r.tooltip=r.hint),"");if(!0===r.remove)t='x';else if(null!=r.items){let e=[];"function"==typeof r.items?e=r.items(r):Array.isArray(r.items)&&(e=r.items),t="",l=` +
    + ${this.getMenuHTML(h,e,!0,u.concat(n))} +
    `}else null!=r.count&&(t+=""+r.count+""),null!=r.hotkey&&(t+=''+r.hotkey+"");!0===r.disabled&&o.push("w2ui-disabled"),!0===r._noSearchInside&&o.push("w2ui-no-search-inside"),""!==l&&(o.push("has-sub-menu"),r.expanded?o.push("expanded"):o.push("collapsed")),p+=` +
    +
    + ${s} + + +
    + `+l,c++}else{o=(i??"").replace(/^-+/g,"");p+=` +
    +
    + ${o?`
    ${o}
    `:""} +
    `}}e[n]=r}),0===c&&h.msgNoItems&&(p+=` +
    + ${w2utils.lang(h.msgNoItems)} +
    `),p+="
    "}refreshIndex(e){var t,i,e=Tooltip.active[e.replace(/[\s\.#]/g,"_")];e&&(e.displayed||this.show(e.name),t=query(e.box).find(".w2ui-overlay-body").get(0),i=query(e.box).find(".w2ui-menu-search, .w2ui-menu-top").get(0),query(e.box).find(".w2ui-menu-item.w2ui-selected").removeClass("w2ui-selected"),e=query(e.box).find(`.w2ui-menu-item[index="${e.selected}"]`).addClass("w2ui-selected").get(0))&&(e.offsetTop+e.clientHeight>t.clientHeight+t.scrollTop&&e.scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),e.offsetTop{var t;this.getCurrent(i,e.getAttribute("index")).item.hidden?query(e).hide():((t=s.tmp?.search)&&s.options.markSearch&&w2utils.marker(e,t,{onlyFirst:"begins"==s.options.match}),query(e).show())}),query(s.box).find(".w2ui-sub-menu").each(e=>{var t=query(e).find(".w2ui-menu-item").get().some(e=>"none"!=e.style.display);this.getCurrent(i,e.dataset.parent).item.expanded&&(t?query(e).parent().show():query(e).parent().hide())}),0!=s.tmp.searchCount&&0!=s.options?.items?.length||(0==query(s.box).find(".w2ui-no-items").length&&query(s.box).find(".w2ui-menu:not(.w2ui-sub-menu)").append(` +
    + ${w2utils.lang(s.options.msgNoItems)} +
    `),query(s.box).find(".w2ui-no-items").show()))}applyFilter(r,e,n){let a=0;var t=Tooltip.active[r.replace(/[\s\.#]/g,"_")];let o=t.options;if(!1!==o.filter){null==e&&(e=t.options.items),null==n&&(n=["INPUT","TEXTAREA"].includes(t.anchor.tagName)?t.anchor.value:"");let l=[];return o.selected&&(Array.isArray(o.selected)?l=o.selected.map(e=>e?.id??e):o.selected?.id&&(l=[o.selected.id])),e.forEach(e=>{let t="",i="";-1!==["is","begins","begins with"].indexOf(o.match)&&(t="^"),-1!==["is","ends","ends with"].indexOf(o.match)&&(i="$");try{new RegExp(t+n+i,"i").test(e.text)||"..."===e.text?e.hidden=!1:e.hidden=!0}catch(e){}var s;o.hideSelected&&l.includes(e.id)&&(e.hidden=!0),Array.isArray(e.items)&&0{e.hidden||e.disabled||e?.text?.startsWith("--")||(l.push(s.concat([t]).join("-")),Array.isArray(e.items)&&0{l=l[e].items}),l[i]);if(!a.disabled){let l=(i,s)=>{i.forEach((e,t)=>{e.id!=a.id&&(e.group===a.group&&e.checked&&(n.find(`.w2ui-menu-item[index="${(s?s+"-":"")+t}"] .w2ui-icon`).removeClass("w2ui-icon-check").addClass("w2ui-icon-empty"),i[t].checked=!1),Array.isArray(e.items))&&l(e.items,t)})};"check"!==e.type&&"radio"!==e.type||!1===a.group||query(t.target).hasClass("remove")||query(t.target).closest(".w2ui-menu-item").hasClass("has-sub-menu")||(a.checked="radio"==e.type||!a.checked,a.checked?("radio"===e.type&&query(t.target).closest(".w2ui-menu").find(".w2ui-icon").removeClass("w2ui-icon-check").addClass("w2ui-icon-empty"),"check"===e.type&&null!=a.group&&l(e.items),r.removeClass("w2ui-icon-empty").addClass("w2ui-icon-check")):"check"===e.type&&r.removeClass("w2ui-icon-check").addClass("w2ui-icon-empty")),query(t.target).hasClass("remove")||(n.find(".w2ui-menu-item").removeClass("w2ui-selected"),query(t.delegate).addClass("w2ui-selected"))}}menuClick(t,i,s,l){var r=t.options;let n=r.items;var a=query(i.delegate).closest(".w2ui-menu-item");let o=!r.hideOn.includes("select");(i.shiftKey||i.metaKey||i.ctrlKey)&&(o=!0),"string"==typeof l&&""!==l?l.split("-").forEach(e=>{n=n[e].items}):l=null;var h=(n="function"==typeof n?n({overlay:t,index:s,parentIndex:l,event:i}):n)[s];if(!h.disabled||query(i.target).hasClass("remove")){let e;if(query(i.target).hasClass("remove")){if(!0===(e=this.trigger("remove",{originalEvent:i,target:t.name,overlay:t,item:h,index:s,parentIndex:l,el:a[0]})).isCancelled)return;o=!r.hideOn.includes("item-remove"),a.remove()}else if(a.hasClass("has-sub-menu")){if(!0===(e=this.trigger("subMenu",{originalEvent:i,target:t.name,overlay:t,item:h,index:s,parentIndex:l,el:a[0]})).isCancelled)return;o=!0,a.hasClass("expanded")?(h.expanded=!1,a.removeClass("expanded").addClass("collapsed"),query(a.get(0).nextElementSibling).hide()):(h.expanded=!0,a.addClass("expanded").removeClass("collapsed"),query(a.get(0).nextElementSibling).show()),t.selected=parseInt(a.attr("index"))}else{r=this.findChecked(r.items);if(t.selected=parseInt(a.attr("index")),!0===(e=this.trigger("select",{originalEvent:i,target:t.name,overlay:t,item:h,index:s,parentIndex:l,selected:r,keepOpen:o,el:a[0]})).isCancelled)return;null!=h.keepOpen&&(o=h.keepOpen),["INPUT","TEXTAREA"].includes(t.anchor.tagName)&&(t.anchor.dataset.selected=h.id,t.anchor.dataset.selectedIndex=t.selected)}o||this.hide(t.name),e.finish()}}findChecked(e){let t=[];return e.forEach(e=>{e.checked&&t.push(e),Array.isArray(e.items)&&(t=t.concat(this.findChecked(e.items)))}),t}keyUp(s,l){var e,r=s.options,t=l.target.value;let n=!0,a=!1;switch(l.keyCode){case 8:""!==t||s.displayed||(n=!1);break;case 13:if(!s.displayed||!s.selected)return;var{index:i,parents:o}=this.getCurrent(s.name);l.delegate=query(s.box).find(".w2ui-selected").get(0),this.menuClick(s,l,parseInt(i),o),n=!1;break;case 27:n=!1,s.displayed?this.hide(s.name):(i=s.anchor,["INPUT","TEXTAREA"].includes(i.tagName)&&(i.value="",delete i.dataset.selected,delete i.dataset.selectedIndex));break;case 37:{if(!s.displayed)return;let{item:e,index:t,parents:i}=this.getCurrent(s.name);i&&(e=r.items[i],t=parseInt(i),i="",a=!0),Array.isArray(e?.items)&&0{var e=e.detail.overlay,t=e.anchor,i=e.options;["INPUT","TEXTAREA"].includes(t.tagName)&&!i.value&&t.value&&(e.tmp.initValue=t.value),delete e.newValue,delete e.newDate}),l.on("show:after.attach",e=>{s.overlay?.box&&this.initControls(s.overlay)}),l.on("update:after.attach",e=>{s.overlay?.box&&this.initControls(s.overlay)}),l.on("hide.attach",e=>{var e=e.detail.overlay,t=e.anchor;null!=e.newValue&&(e.newDate&&(e.newValue=e.newDate+" "+e.newValue),["INPUT","TEXTAREA"].includes(t.tagName)&&t.value!=e.newValue&&(t.value=e.newValue),!0!==(t=this.trigger("select",{date:e.newValue,target:e.name,overlay:e})).isCancelled)&&t.finish()}),s.select=t=>(l.on("select.attach",e=>{t(e)}),s),s}initControls(l){let r=l.options,t=e=>{let{month:t,year:i}=l.tmp;12<(t+=e)&&(t=1,i++),t<1&&(t=12,i--);e=this.getMonthHTML(r,t,i);Object.assign(l.tmp,e),query(l.box).find(".w2ui-overlay-body").html(e.html),this.initControls(l)},i=(e,t)=>{query(e.target).parent().find(".w2ui-jump-month, .w2ui-jump-year").removeClass("w2ui-selected"),query(e.target).addClass("w2ui-selected");e=new Date;let{jumpMonth:i,jumpYear:s}=l.tmp;(i=t&&(null==s&&(s=e.getFullYear()),null==i)?e.getMonth()+1:i)&&s&&(t=this.getMonthHTML(r,i,s),Object.assign(l.tmp,t),query(l.box).find(".w2ui-overlay-body").html(t.html),l.tmp.jump=!1,this.initControls(l))};query(l.box).find(".w2ui-cal-title").off(".calendar").on("click.calendar",e=>{var t,i;Object.assign(l.tmp,{jumpYear:null,jumpMonth:null}),l.tmp.jump?({month:t,year:i}=l.tmp,t=this.getMonthHTML(r,t,i),query(l.box).find(".w2ui-overlay-body").html(t.html),l.tmp.jump=!1):(query(l.box).find(".w2ui-overlay-body .w2ui-cal-days").replace(this.getYearHTML()),(i=query(l.box).find(`[name="${l.tmp.year}"]`).get(0))&&i.scrollIntoView(!0),l.tmp.jump=!0),this.initControls(l),e.stopPropagation()}).find(".w2ui-cal-previous").off(".calendar").on("click.calendar",e=>{t(-1),e.stopPropagation()}).parent().find(".w2ui-cal-next").off(".calendar").on("click.calendar",e=>{t(1),e.stopPropagation()}),query(l.box).find(".w2ui-cal-now").off(".calendar").on("click.calendar",e=>{"datetime"==r.type?l.newDate?l.newValue=w2utils.formatTime(new Date,r.format.split("|")[1]):l.newValue=w2utils.formatDateTime(new Date,r.format):"date"==r.type?l.newValue=w2utils.formatDate(new Date,r.format):"time"==r.type&&(l.newValue=w2utils.formatTime(new Date,r.format)),this.hide(l.name)}),query(l.box).off(".calendar").on("click.calendar",{delegate:".w2ui-day.w2ui-date"},e=>{"datetime"==r.type?(l.newDate=query(e.target).attr("date"),query(l.box).find(".w2ui-overlay-body").html(this.getHourHTML(l.options).html),this.initControls(l)):(l.newValue=query(e.target).attr("date"),this.hide(l.name))}).on("click.calendar",{delegate:".w2ui-jump-month"},e=>{l.tmp.jumpMonth=parseInt(query(e.target).attr("name")),i(e)}).on("dblclick.calendar",{delegate:".w2ui-jump-month"},e=>{l.tmp.jumpMonth=parseInt(query(e.target).attr("name")),i(e,!0)}).on("click.calendar",{delegate:".w2ui-jump-year"},e=>{l.tmp.jumpYear=parseInt(query(e.target).attr("name")),i(e)}).on("dblclick.calendar",{delegate:".w2ui-jump-year"},e=>{l.tmp.jumpYear=parseInt(query(e.target).attr("name")),i(e,!0)}).on("click.calendar",{delegate:".w2ui-time.hour"},e=>{var e=query(e.target).attr("hour");let t=this.str2min(r.value)%60;l.tmp.initValue&&!r.value&&(t=this.str2min(l.tmp.initValue)%60),r.noMinutes?(l.newValue=this.min2str(60*e,r.format),this.hide(l.name)):(l.newValue=e+":"+t,e=this.getMinHTML(e,r).html,query(l.box).find(".w2ui-overlay-body").html(e),this.initControls(l))}).on("click.calendar",{delegate:".w2ui-time.min"},e=>{e=60*Math.floor(this.str2min(l.newValue)/60)+parseInt(query(e.target).attr("min"));l.newValue=this.min2str(e,r.format),this.hide(l.name)})}getMonthHTML(l,r,e){var t=w2utils.settings.fulldays.slice(),i=w2utils.settings.shortdays.slice();"M"!==w2utils.settings.weekStarts&&(t.unshift(t.pop()),i.unshift(i.pop()));let s=new Date;var t="datetime"===l.type?w2utils.isDateTime(l.value,l.format,!0):w2utils.isDate(l.value,l.format,!0),n=w2utils.formatDate(t);null!=r&&null!=e||(e=(t||s).getFullYear(),r=t?t.getMonth()+1:s.getMonth()+1),12${i[e]}`}let c=` +
    +
    +
    +
    +
    +
    +
    + ${w2utils.settings.fullmonths[r-1]}, ${e} + +
    +
    + ${o} + `,p=new Date(e+`/${r}/1`);t=(p=new Date(p.getTime()+432e5)).getDay();"M"==w2utils.settings.weekStarts&&a--,0 + ${g} +
    `,p=new Date(p.getTime()+864e5)}return c+="",l.btnNow&&(t=w2utils.lang("Today"+("datetime"==l.type?" & Now":"")),c+=`
    ${t}
    `),{html:c,month:r,year:e}}getYearHTML(){let t="",i="";for(let e=0;e${w2utils.settings.shortmonths[e]}`;for(let e=w2utils.settings.dateStartYear;e<=w2utils.settings.dateEndYear;e++)i+=`
    ${e}
    `;return`
    +
    ${t}
    +
    ${i}
    +
    `}getHourHTML(l){(l=l??{}).format||(l.format=w2utils.settings.timeFormat);var r=-1${e}
    `}return{html:`
    +
    ${w2utils.lang("Select Hour")}
    +
    +
    ${a[0]}
    +
    ${a[1]}
    +
    ${a[2]}
    +
    + ${l.btnNow?`
    ${w2utils.lang("Now")}
    `:""} +
    `}}getMinHTML(i,s){null==i&&(i=0),(s=s??{}).format||(s.format=w2utils.settings.timeFormat);var l=-1${a}
    `}return{html:`
    +
    ${w2utils.lang("Select Minute")}
    +
    +
    ${n[0]}
    +
    ${n[1]}
    +
    ${n[2]}
    +
    + ${s.btnNow?`
    ${w2utils.lang("Now")}
    `:""} +
    `}}inRange(i,s,e){let l=!1;if("date"===s.type){var r=w2utils.isDate(i,s.format,!0);if(r){if(s.start||s.end){var n="string"==typeof s.start?s.start:query(s.start).val(),a="string"==typeof s.end?s.end:query(s.end).val();let e=w2utils.isDate(n,s.format,!0),t=w2utils.isDate(a,s.format,!0);n=new Date(r);e=e||n,t=t||n,n>=e&&n<=t&&(l=!0)}else l=!0;Array.isArray(s.blockDates)&&s.blockDates.includes(i)&&(l=!1),Array.isArray(s.blockWeekdays)&&s.blockWeekdays.includes(r.getDay())&&(l=!1)}}else if("time"===s.type)if(s.start||s.end){a=this.str2min(i);let e=this.str2min(s.start),t=this.str2min(s.end);e=e||a,t=t||a,a>=e&&a<=t&&(l=!0)}else l=!0;else"datetime"===s.type&&(n=w2utils.isDateTime(i,s.format,!0))&&(r=s.format.split("|").map(e=>e.trim()),e?(a=w2utils.formatDate(n,r[0]),i=w2utils.extend({},s,{type:"date",format:r[0]}),this.inRange(a,i)&&(l=!0)):(e=w2utils.formatTime(n,r[1]),a={type:"time",format:r[1],start:s.startTime,end:s.endTime},this.inRange(e,a)&&(l=!0)));return l}str2min(e){var t;return"string"!=typeof e||2!==(t=e.split(":")).length?null:(t[0]=parseInt(t[0]),t[1]=parseInt(t[1]),-1!==e.indexOf("pm")&&12!==t[0]&&(t[0]+=12),e.includes("am")&&12==t[0]&&(t[0]=0),60*t[0]+t[1])}min2str(e,t){let i="";1440<=e&&(e%=1440),e<0&&(e=1440+e);var s=Math.floor(e/60),e=(e%60<10?"0":"")+e%60;return t=t||w2utils.settings.timeFormat,i=-1!==t.indexOf("h24")?s+":"+e:(s<=12?s:s-12)+":"+e+" "+(12<=s?"pm":"am")}}let w2tooltip=new Tooltip,w2menu=new MenuTooltip,w2color=new ColorTooltip,w2date=new DateTooltip;class w2toolbar extends w2base{constructor(e){super(e.name),this.box=null,this.name=null,this.routeData={},this.items=[],this.right="",this.tooltip="top|left",this.onClick=null,this.onMouseDown=null,this.onMouseUp=null,this.onMouseEnter=null,this.onMouseLeave=null,this.onRender=null,this.onRefresh=null,this.onResize=null,this.onDestroy=null,this.item_template={id:null,type:"button",text:null,html:"",tooltip:null,count:null,hidden:!1,disabled:!1,checked:!1,icon:null,route:null,arrow:null,style:null,group:null,items:null,selected:null,color:null,overlay:{anchorClass:""},onClick:null,onRefresh:null},this.last={badge:{}};var t=e.items;delete e.items,Object.assign(this,e),Array.isArray(t)&&this.add(t,!0),e.items=t,"string"==typeof this.box&&(this.box=query(this.box).get(0)),this.box&&this.render(this.box)}add(e,t){this.insert(null,e,t)}insert(r,e,n){(e=Array.isArray(e)?e:[e]).forEach((e,t,i)=>{"string"==typeof e&&(e=i[t]={id:e,text:e});var l,s=["button","check","radio","drop","menu","menu-radio","menu-check","color","text-color","html","break","spacer","new-line"];if(s.includes(String(e.type)))if(null!=e.id||["break","spacer","new-line"].includes(e.type)){if(null==e.type)console.log('ERROR: The parameter "type" is required but not supplied.',e);else if(w2utils.checkUniqueId(e.id,this.items,"toolbar",this.name)){let s=w2utils.extend({},this.item_template,e);"menu-check"==s.type?(Array.isArray(s.selected)||(s.selected=[]),Array.isArray(s.items)&&s.items.forEach(e=>{(e="string"==typeof e?i[t]={id:e,text:e}:e).checked&&!s.selected.includes(e.id)&&s.selected.push(e.id),!e.checked&&s.selected.includes(e.id)&&(e.checked=!0),null==e.checked&&(e.checked=!1)})):"menu-radio"==s.type&&Array.isArray(s.items)&&s.items.forEach((e,t,i)=>{(e="string"==typeof e?i[t]={id:e,text:e}:e).checked&&null==s.selected?s.selected=e.id:e.checked=!1,e.checked||s.selected!=e.id||(e.checked=!0),null==e.checked&&(e.checked=!1)}),null==r?this.items.push(s):(l=this.get(r,!0),this.items=this.items.slice(0,l).concat([s],this.items.slice(l))),s.line=s.line??1,!0!==n&&this.refresh(s.id)}}else console.log('ERROR: The parameter "id" is required but not supplied.',e);else console.log('ERROR: The parameter "type" should be one of the following:',s,`, but ${e.type} is supplied.`,e)}),!0!==n&&this.resize()}remove(){let i=0;return Array.from(arguments).forEach(e=>{var t=this.get(e);t&&-1==String(e).indexOf(":")&&(i++,query(this.box).find("#tb_"+this.name+"_item_"+w2utils.escapeId(t.id)).remove(),null!=(e=this.get(t.id,!0)))&&this.items.splice(e,1)}),this.resize(),i}set(e,t){var i=this.get(e);return null!=i&&(Object.assign(i,t),this.refresh(String(e).split(":")[0]),!0)}get(e,i){if(0===arguments.length){var t=[];for(let e=0;e span`);0{var t=this.get(e);t&&(t.hidden=!1,i.push(String(e).split(":")[0]))}),setTimeout(()=>{i.forEach(e=>{this.refresh(e),this.resize()})},15),i}hide(){let i=[];return Array.from(arguments).forEach(e=>{var t=this.get(e);t&&(t.hidden=!0,i.push(String(e).split(":")[0]))}),setTimeout(()=>{i.forEach(e=>{this.refresh(e),this.tooltipHide(e),this.resize()})},15),i}enable(){let i=[];return Array.from(arguments).forEach(e=>{var t=this.get(e);t&&(t.disabled=!1,i.push(String(e).split(":")[0]))}),setTimeout(()=>{i.forEach(e=>{this.refresh(e)})},15),i}disable(){let i=[];return Array.from(arguments).forEach(e=>{var t=this.get(e);t&&(t.disabled=!0,i.push(String(e).split(":")[0]))}),setTimeout(()=>{i.forEach(e=>{this.refresh(e),this.tooltipHide(e)})},15),i}check(){let i=[];return Array.from(arguments).forEach(e=>{var t=this.get(e);t&&-1==String(e).indexOf(":")&&(t.checked=!0,i.push(String(e).split(":")[0]))}),setTimeout(()=>{i.forEach(e=>{this.refresh(e)})},15),i}uncheck(){let i=[];return Array.from(arguments).forEach(e=>{var t=this.get(e);t&&-1==String(e).indexOf(":")&&(["menu","menu-radio","menu-check","drop","color","text-color"].includes(t.type)&&t.checked&&w2tooltip.hide(this.name+"-drop"),t.checked=!1,i.push(String(e).split(":")[0]))}),setTimeout(()=>{i.forEach(e=>{this.refresh(e)})},15),i}click(e,t){var i=String(e).split(":");let l=this.get(i[0]),r=l&&l.items?w2utils.normMenu.call(this,l.items,l):[];if(1{var t=(e,t)=>{let i=this;return function(){i.set(e,{checked:!1})}},i=query(this.box).find("#tb_"+this.name+"_item_"+w2utils.escapeId(l.id));if(w2utils.isPlainObject(l.overlay)||(l.overlay={}),"drop"==l.type&&w2tooltip.show(w2utils.extend({html:l.html,class:"w2ui-white",hideOn:["doc-click"]},l.overlay,{anchor:i[0],name:this.name+"-drop",data:{item:l,btn:s}})).hide(t(l.id,s)),["menu","menu-radio","menu-check"].includes(l.type)){let e="normal";"menu-radio"==l.type&&(e="radio",r.forEach(e=>{l.selected==e.id?e.checked=!0:e.checked=!1})),"menu-check"==l.type&&(e="check",r.forEach(e=>{Array.isArray(l.selected)&&l.selected.includes(e.id)?e.checked=!0:e.checked=!1})),w2menu.show(w2utils.extend({items:r},l.overlay,{type:e,name:this.name+"-drop",anchor:i[0],data:{item:l,btn:s}})).hide(t(l.id,s)).remove(e=>{this.menuClick({name:this.name,remove:!0,item:l,subItem:e.detail.item,originalEvent:e})}).select(e=>{this.menuClick({name:this.name,item:l,subItem:e.detail.item,originalEvent:e})})}["color","text-color"].includes(l.type)&&w2color.show(w2utils.extend({color:l.color},l.overlay,{anchor:i[0],name:this.name+"-drop",data:{item:l,btn:s}})).hide(t(l.id,s)).select(e=>{null!=e.detail.color&&this.colorClick({name:this.name,item:l,color:e.detail.color})})},0)}if(["check","menu","menu-radio","menu-check","drop","color","text-color"].includes(l.type)&&(l.checked=!l.checked,l.checked?query(this.box).find(s).addClass("checked"):query(this.box).find(s).removeClass("checked")),l.route){let t=String("/"+l.route).replace(/\/{2,}/g,"/");var a=w2utils.parseRoute(t);if(0{window.location.hash=t},1)}this.tooltipShow(e),i.finish()}}}scroll(a,o,h){return new Promise((e,t)=>{var i=query(this.box).find(`.w2ui-tb-line:nth-child(${o}) .w2ui-scroll-wrapper`),s=i.get(0).scrollLeft,l=i.find(".w2ui-tb-right").get(0),r=i.parent().get(0).getBoundingClientRect().width,n=s+parseInt(l.offsetLeft)+parseInt(l.clientWidth);switch(a){case"left":(scroll=s-r+50)<=0&&(scroll=0),i.get(0).scrollTo({top:0,left:scroll,behavior:h?"atuo":"smooth"});break;case"right":(scroll=s+r-50)>=n-r&&(scroll=n-r),i.get(0).scrollTo({top:0,left:scroll,behavior:h?"atuo":"smooth"})}setTimeout(()=>{this.resize(),e()},h?0:500)})}render(e){var s=Date.now(),l=("string"==typeof e&&(e=query(e).get(0)),this.trigger("render",{target:this.name,box:e??this.box}));if(!0!==l.isCancelled&&(null!=e&&(0 ",r),null!=r.hint&&console.log("NOTICE: toolbar item.hint property is deprecated, please use item.tooltip. Item -> ",r),0!==e&&"new-line"!=r.type||(i++,t+=` +
    +
    +
    ${this.right[i-1]??""}
    +
    +
    +
    +
    + `),r.line=i)}return query(this.box).attr("name",this.name).addClass("w2ui-reset w2ui-toolbar").html(t),0{this.resize()}),this.last.observeResize.observe(this.box),this.refresh(),this.resize(),l.finish(),Date.now()-s}}refresh(t){var i=Date.now(),l=this.trigger("refresh",{target:null!=t?t:this.name,item:this.get(t)});if(!0!==l.isCancelled){let e;if(null==t)for(let e=0;e{i[e].anchor==s.get(0)&&(i[e].anchor=t)})}if(["menu","menu-radio","menu-check"].includes(r.type)&&r.checked){let t=Array.isArray(r.selected)?r.selected:[r.selected];r.items.forEach(e=>{t.includes(e.id)?e.checked=!0:e.checked=!1}),w2menu.update(this.name+"-drop",r.items)}return"function"==typeof r.onRefresh&&e.finish(),l.finish(),Date.now()-i}}}}resize(){var e=Date.now(),t=this.trigger("resize",{target:this.name});if(!0!==t.isCancelled)return query(this.box).find(".w2ui-tb-line").each(e=>{var e=query(e),t=(e.find(".w2ui-scroll-left, .w2ui-scroll-right").hide(),e.find(".w2ui-scroll-wrapper").get(0)),i=e.find(".w2ui-tb-right"),s=e.get(0).getBoundingClientRect().width,i=0e.id==t)}),""),s="function"==typeof i.text?i.text.call(this,i):i.text;i.icon&&(t=i.icon,"function"==typeof i.icon&&(t=i.icon.call(this,i)),t=`
    ${t="<"!==String(t).slice(0,1)?``:t}
    `);var l=["w2ui-tb-button"];switch(i.checked&&l.push("checked"),i.disabled&&l.push("disabled"),i.hidden&&l.push("hidden"),t||l.push("no-icon"),i.type){case"color":case"text-color":"string"==typeof i.color&&("#"==i.color.slice(0,1)&&(i.color=i.color.slice(1)),[3,6,8].includes(i.color.length))&&(i.color="#"+i.color),"color"==i.type&&(s=` + `+(i.text?`
    ${w2utils.lang(i.text)}
    `:"")),"text-color"==i.type&&(s=''+(i.text?w2utils.lang(i.text):"Aa")+"");case"menu":case"menu-check":case"menu-radio":case"button":case"check":case"radio":case"drop":var r=!0===i.arrow||!1!==i.arrow&&["menu","menu-radio","menu-check","drop","color","text-color"].includes(i.type);e=` +
    + ${t} + ${""!=s?`
    + ${w2utils.lang(s)} + ${null!=i.count?w2utils.stripSpaces(` + ${i.count} + `):""} + ${r?'':""} +
    `:""} +
    + `;break;case"break":e=`
    +   +
    `;break;case"spacer":e=`
    +
    `;break;case"html":e=`
    + ${"function"==typeof i.html?i.html.call(this,i):i.html} +
    `}return e}tooltipShow(t){if(null!=this.tooltip){var i=query(this.box).find("#tb_"+this.name+"_item_"+w2utils.escapeId(t)).get(0),t=this.get(t),s=this.tooltip;let e=t.tooltip;"function"==typeof e&&(e=e.call(this,t)),["menu","menu-radio","menu-check","drop","color","text-color"].includes(t.type)&&1==t.checked||w2tooltip.show({anchor:i,name:this.name+"-tooltip",html:e,position:s})}}tooltipHide(e){null!=this.tooltip&&w2tooltip.hide(this.name+"-tooltip")}menuClick(t){if(t.item&&!t.item.disabled){var i=this.trigger(!0!==t.remove?"click":"remove",{target:t.item.id+":"+t.subItem.id,item:t.item,subItem:t.subItem,originalEvent:t.originalEvent});if(!0!==i.isCancelled){let l=t.subItem,r=this.get(t.item.id),e=r.items;if("function"==typeof e&&(e=r.items()),"menu"==r.type&&(r.selected=l.id),"menu-radio"==r.type&&(r.selected=l.id,Array.isArray(e)&&e.forEach(e=>{!0===e.checked&&delete e.checked,Array.isArray(e.items)&&e.items.forEach(e=>{!0===e.checked&&delete e.checked})}),l.checked=!0),"menu-check"==r.type)if(Array.isArray(r.selected)||(r.selected=[]),null==l.group){var n=r.selected.indexOf(l.id);-1==n?(r.selected.push(l.id),l.checked=!0):(r.selected.splice(n,1),l.checked=!1)}else if(!1!==l.group){let i=[];n=r.selected.indexOf(l.id);let s=e=>{e.forEach(e=>{var t;e.group===l.group&&-1!=(t=r.selected.indexOf(e.id))&&(e.id!=l.id&&i.push(e.id),r.selected.splice(t,1)),Array.isArray(e.items)&&s(e.items)})};s(e),-1==n&&(r.selected.push(l.id),l.checked=!0)}if("string"==typeof l.route){let t=""!==l.route?String("/"+l.route).replace(/\/{2,}/g,"/"):"";var s=w2utils.parseRoute(t);if(0{window.location.hash=t},1)}this.refresh(t.item.id),i.finish()}}}colorClick(e){var t;e.item&&!e.item.disabled&&!0!==(t=this.trigger("click",{target:e.item.id,item:e.item,color:e.color,final:e.final,originalEvent:e.originalEvent})).isCancelled&&(e.item.color=e.color,this.refresh(e.item.id),t.finish())}mouseAction(e,t,i,s){var l=this.get(s),e=this.trigger("mouse"+i,{target:s,item:l,object:l,originalEvent:e});if(!0!==e.isCancelled&&!l.disabled&&!l.hidden){switch(i){case"Enter":query(t).addClass("over"),this.tooltipShow(s);break;case"Leave":query(t).removeClass("over down"),this.tooltipHide(s);break;case"Down":query(t).addClass("down");break;case"Up":query(t).removeClass("down")}e.finish()}}}class w2sidebar extends w2base{constructor(e){super(e.name),this.name=null,this.box=null,this.sidebar=null,this.parent=null,this.nodes=[],this.menu=[],this.routeData={},this.selected=null,this.icon=null,this.style="",this.topHTML="",this.bottomHTML="",this.flatButton=!1,this.keyboard=!0,this.flat=!1,this.hasFocus=!1,this.levelPadding=12,this.skipRefresh=!1,this.tabIndex=null,this.handle={size:0,style:"",html:"",tooltip:""},this.onClick=null,this.onDblClick=null,this.onMouseEnter=null,this.onMouseLeave=null,this.onContextMenu=null,this.onMenuClick=null,this.onExpand=null,this.onCollapse=null,this.onKeydown=null,this.onRender=null,this.onRefresh=null,this.onResize=null,this.onDestroy=null,this.onFocus=null,this.onBlur=null,this.onFlat=null,this.node_template={id:null,text:"",order:null,count:null,icon:null,nodes:[],style:"",route:null,selected:!1,expanded:!1,hidden:!1,disabled:!1,group:!1,groupShowHide:!0,collapsible:!1,plus:!1,onClick:null,onDblClick:null,onContextMenu:null,onExpand:null,onCollapse:null,parent:null,sidebar:null},this.last={badge:{}};var t=e.nodes;delete e.nodes,Object.assign(this,e),Array.isArray(t)&&this.add(t),e.nodes=t,"string"==typeof this.box&&(this.box=query(this.box).get(0)),this.box&&this.render(this.box)}add(e,t){return 1==arguments.length&&(t=arguments[0],e=this),"string"==typeof e&&(e=this.get(e)),this.insert(e=null!=e&&""!=e?e:this,null,t)}insert(t,i,s){let l,r,n,a,o;if(2==arguments.length&&"string"==typeof t)if(s=arguments[1],null!=(i=arguments[0])){if(null==(r=this.get(i)))return null!=(s=Array.isArray(s)?s:[s])[0].caption&&null==s[0].text&&(console.log("NOTICE: sidebar node.caption property is deprecated, please use node.text. Node -> ",s[0]),s[0].text=s[0].caption),l=s[0].text,console.log('ERROR: Cannot insert node "'+l+'" because cannot find node "'+i+'" to insert before.'),null;t=this.get(i).parent}else t=this;null!=(t="string"==typeof t?this.get(t):t)&&""!=t||(t=this),Array.isArray(s)||(s=[s]);for(let e=0;e{null!=(i=this.get(e))&&(null!=this.selected&&this.selected===i.id&&(this.selected=null),null!=(e=this.get(i.parent,e,!0)))&&(i.parent.nodes[e].selected&&i.sidebar.unselect(i.id),i.parent.nodes.splice(e,1),i.parent.collapsible=0{var e=i.nodes&&0{e.nodes&&0{t.call(this,e),e.nodes&&0{-1===e.text.toLowerCase().indexOf(i)?e.hidden=!0:(t++,function e(t){t.parent&&(t.parent.hidden=!1,e(t.parent))}(e),e.hidden=!1)}),this.refresh(),t}show(){let t=[];return Array.from(arguments).forEach(e=>{e=this.get(e);null!=e&&!1!==e.hidden&&(e.hidden=!1,t.push(e.id))}),0{e=this.get(e);null!=e&&!0!==e.hidden&&(e.hidden=!0,t.push(e.id))}),0{e=this.get(e);null!=e&&!1!==e.disabled&&(e.disabled=!1,t.push(e.id))}),0{e=this.get(e);null!=e&&!0!==e.disabled&&(e.disabled=!0,e.selected&&this.unselect(e.id),t.push(e.id))}),0{t.refresh(e)},0),!0):void 0)}expand(e){var t=this.get(e),i=this.trigger("expand",{target:e,object:t});if(!0!==i.isCancelled)return query(this.box).find("#node_"+w2utils.escapeId(e)+"_sub").show(),query(this.box).find("#node_"+w2utils.escapeId(e)+" .w2ui-collapsed").removeClass("w2ui-collapsed").addClass("w2ui-expanded"),t.expanded=!0,i.finish(),this.refresh(e),!0}collapseAll(t){if(null==(t="string"==typeof(t=null==t?this:t)?this.get(t):t).nodes)return!1;for(let e=0;e{var t=query(e).attr("id").replace("node_",""),t=n.get(t);null!=t&&(t.selected=!1),query(e).removeClass("w2ui-selected").find(".w2ui-icon").removeClass("w2ui-icon-selected")});let t=query(n.box).find("#node_"+w2utils.escapeId(l)),s=query(n.box).find("#node_"+w2utils.escapeId(n.selected));t.addClass("w2ui-selected").find(".w2ui-icon").addClass("w2ui-icon-selected"),setTimeout(()=>{var e=n.trigger("click",{target:l,originalEvent:r,node:a,object:a});if(!0===e.isCancelled)t.removeClass("w2ui-selected").find(".w2ui-icon").removeClass("w2ui-icon-selected"),s.addClass("w2ui-selected").find(".w2ui-icon").addClass("w2ui-icon-selected");else{if(null!=s&&(s.selected=!1),n.get(l).selected=!0,n.selected=l,"string"==typeof a.route){let t=""!==a.route?String("/"+a.route).replace(/\/{2,}/g,"/"):"";var i=w2utils.parseRoute(t);if(0{window.location.hash=t},1)}e.finish()}},1)}}focus(e){let t=this;e=this.trigger("focus",{target:this.name,originalEvent:e});if(!0===e.isCancelled)return!1;this.hasFocus=!0,query(this.box).find(".w2ui-sidebar-body").addClass("w2ui-focus"),setTimeout(()=>{var e=query(t.box).find("#sidebar_"+t.name+"_focus").get(0);document.activeElement!=e&&e.focus()},10),e.finish()}blur(e){e=this.trigger("blur",{target:this.name,originalEvent:e});if(!0===e.isCancelled)return!1;this.hasFocus=!1,query(this.box).find(".w2ui-sidebar-body").removeClass("w2ui-focus"),e.finish()}keydown(e){let n=this,t=n.get(n.selected);var i;function s(e,t){null==e||e.hidden||e.disabled||e.group||(n.click(e.id,t),n.inView(e.id))||n.scrollIntoView(e.id)}function l(e,t){for(e=t(e);null!=e&&(e.hidden||e.disabled)&&!e.group;)e=t(e);return e}function r(e){if(null==e)return null;var t=e.parent,e=n.get(e.id,!0);let i=0t.clientHeight+t.scrollTop)}scrollIntoView(i,s){return new Promise((e,t)=>{null==i&&(i=this.selected),null!=this.get(i)&&(query(this.box).find("#node_"+w2utils.escapeId(i)).get(0).scrollIntoView({block:"center",inline:"center",behavior:s?"atuo":"smooth"}),setTimeout(()=>{this.resize(),e()},s?0:500))})}dblClick(e,t){var i=this.get(e),t=this.trigger("dblClick",{target:e,originalEvent:t,object:i});!0!==t.isCancelled&&(this.toggle(e),t.finish())}contextMenu(t,i){var e=this.get(t),s=(t!=this.selected&&this.click(t),this.trigger("contextMenu",{target:t,originalEvent:i,object:e,allowOnDisabled:!1}));!0===s.isCancelled||e.disabled&&!s.allowOnDisabled||(0{this.menuClick(t,parseInt(e.detail.index),i)}),i.preventDefault&&i.preventDefault(),s.finish())}menuClick(e,t,i){e=this.trigger("menuClick",{target:e,originalEvent:i,menuIndex:t,menuItem:this.menu[t]});!0!==e.isCancelled&&e.finish()}goFlat(){var e=this.trigger("flat",{goFlat:!this.flat});!0!==e.isCancelled&&(this.flat=!this.flat,this.refresh(),e.finish())}render(e){var i=Date.now();let s=this;"string"==typeof e&&(e=query(e).get(0));var l=this.trigger("render",{target:this.name,box:e??this.box});if(!0!==l.isCancelled&&(null!=e&&(0 +
    + +
    +
    + `);e=query(this.box).get(0).getBoundingClientRect();query(this.box).find(":scope > div").css({width:e.width+"px",height:e.height+"px"}),query(this.box).get(0).style.cssText+=this.style;let t;return query(this.box).find("#sidebar_"+this.name+"_focus").on("focus",function(e){clearTimeout(t),s.hasFocus||s.focus(e)}).on("blur",function(e){t=setTimeout(()=>{s.hasFocus&&s.blur(e)},100)}).on("keydown",function(e){9!=e.keyCode&&w2ui[s.name].keydown.call(w2ui[s.name],e)}),query(this.box).off("mousedown").on("mousedown",function(t){setTimeout(()=>{var e;-1==["INPUT","TEXTAREA","SELECT"].indexOf(t.target.tagName.toUpperCase())&&(e=query(s.box).find("#sidebar_"+s.name+"_focus"),document.activeElement!=e.get(0))&&e.get(0).focus()},1)}),this.last.observeResize=new ResizeObserver(()=>{this.resize()}),this.last.observeResize.observe(this.box),l.finish(),this.refresh(),Date.now()-i}}update(e,t){var i,s,e=this.get(e);let l;return e&&(i=query(this.box).find("#node_"+w2utils.escapeId(e.id)),e.group?(t.text&&(e.text=t.text,i.find(".w2ui-group-text").replace("function"==typeof e.text?e.text.call(this,e):''+e.text+""),delete t.text),t.class&&(e.class=t.class,l=i.data("level"),i.get(0).className="w2ui-node-group w2ui-level-"+l+(e.class?" "+e.class:""),delete t.class),t.style&&(e.style=t.style,i.get(0).nextElementSibling.style=e.style+";"+(!e.hidden&&e.expanded?"":"display: none;"),delete t.style)):(t.icon&&0<(s=i.find(".w2ui-node-image > span")).length&&(e.icon=t.icon,s[0].className="function"==typeof e.icon?e.icon.call(this,e):e.icon,delete t.icon),t.count&&(e.count=t.count,i.find(".w2ui-node-count").html(e.count),0`),null!=l||""===this.topHTML&&""===e||(query(this.box).find(".w2ui-sidebar-top").html(this.topHTML+e),query(this.box).find(".w2ui-sidebar-body").css("top",query(this.box).find(".w2ui-sidebar-top").get(0)?.clientHeight+"px"),query(this.box).find(".w2ui-flat").off("click").on("click",e=>{this.goFlat()})),null!=l&&""!==this.bottomHTML&&(query(this.box).find(".w2ui-sidebar-bottom").html(this.bottomHTML),query(this.box).find(".w2ui-sidebar-body").css("bottom",query(this.box).find(".w2ui-sidebar-bottom").get(0)?.clientHeight+"px")),query(this.box).find(":scope > div").removeClass("w2ui-sidebar-flat").addClass(this.flat?"w2ui-sidebar-flat":"").css({width:query(this.box).get(0)?.clientWidth+"px",height:query(this.box).get(0)?.clientHeight+"px"}),0'),query(this.box).find(o).remove(),query(this.box).find(i).remove(),query(this.box).find("#sidebar_"+this.name+"_tmp").before(s),query(this.box).find("#sidebar_"+this.name+"_tmp").remove());var l=query(this.box).find(":scope > div").get(0),d={top:l?.scrollTop,left:l?.scrollLeft};query(this.box).find(i).html("");for(let e=0;e ",t),t.text=t.caption),Array.isArray(t.nodes)&&0${e}
    `),i=` +
    + ${t.groupShowHide&&t.collapsible?`${!t.hidden&&t.expanded?w2utils.lang("Hide"):w2utils.lang("Show")}`:""} ${e} +
    +
    +
    `,h.flat&&(i=` +
     
    +
    `)}else{t.selected&&!t.disabled&&(h.selected=t.id),l="",s&&(l=` +
    + +
    `);let e="";var n=null!=t.count?`
    + ${t.count} +
    `:"",a=(!0===t.collapsible&&(e=`
    `),w2utils.lang("function"==typeof t.text?t.text.call(h,t):t.text)),o=["w2ui-node","w2ui-level-"+r,"w2ui-eaction"];t.selected&&o.push("w2ui-selected"),t.disabled&&o.push("w2ui-disabled"),t.class&&o.push(t.class),i=` +
    + ${h.handle.html?`
    + ${"function"==typeof h.handle.html?h.handle.html.call(h,t):h.handle.html} +
    `:""} +
    + ${e} ${l} ${n} +
    ${a}
    +
    +
    +
    `,h.flat&&(i=` +
    +
    ${l}
    +
    +
    `)}return i}}}}mouseAction(e,t,i,s,l){var r=this.get(i),n=w2utils.lang("function"==typeof r.text?r.text.call(this,r):r.text)+(r.count||0===r.count?' - '+r.count+"":""),e=this.trigger("mouse"+e,{target:i,node:r,tooltip:n,originalEvent:s});"tooltip"==l&&this.tooltip(t,n,i),"handle"==l&&this.handleTooltip(t,i),e.finish()}tooltip(e,t,i){e=query(e).find(".w2ui-node-data");""!==t?w2tooltip.show({anchor:e.get(0),name:this.name+"_tooltip",html:t,position:"right|left"}):w2tooltip.hide(this.name+"_tooltip")}handleTooltip(e,t){let i=this.handle.tooltip;""!==(i="function"==typeof i?i(t):i)&&null!=t?w2tooltip.show({anchor:e,name:this.name+"_tooltip",html:i,position:"top|bottom"}):w2tooltip.hide(this.name+"_tooltip")}showPlus(e,t){query(e).find("span:nth-child(1)").css("color",t)}resize(){var e,t=Date.now(),i=this.trigger("resize",{target:this.name});if(!0!==i.isCancelled)return e=query(this.box).get(0).getBoundingClientRect(),query(this.box).css("overflow","hidden"),query(this.box).find(":scope > div").css({width:e.width+"px",height:e.height+"px"}),i.finish(),Date.now()-t}destroy(){var e=this.trigger("destroy",{target:this.name});!0!==e.isCancelled&&(0{var t,i;null==e.id?console.log(`ERROR: The parameter "id" is required but not supplied. (obj: ${this.name})`):w2utils.checkUniqueId(e.id,this.tabs,"tabs",this.name)&&(e=Object.assign({},this.tab_template,e),null==s?(this.tabs.push(e),l.push(this.animateInsert(null,e))):(t=this.get(s,!0),i=this.tabs[t].id,this.tabs.splice(t,0,e),l.push(this.animateInsert(i,e))))}),Promise.all(l)}remove(){let t=0;return Array.from(arguments).forEach(e=>{e=this.get(e);e&&(t++,this.tabs.splice(this.get(e.id,!0),1),query(this.box).find(`#tabs_${this.name}_tab_`+w2utils.escapeId(e.id)).remove())}),this.resize(),t}select(e){return this.active!=e&&null!=this.get(e)&&(this.active=e,this.refresh(),!0)}set(e,t){var i=this.get(e,!0);return null!=i&&(w2utils.extend(this.tabs[i],t),this.refresh(e),!0)}get(t,i){if(0===arguments.length){var s=[];for(let e=0;e{e=this.get(e);e&&!1!==e.hidden&&(e.hidden=!1,t.push(e.id))}),setTimeout(()=>{t.forEach(e=>{this.refresh(e),this.resize()})},15),t}hide(){let t=[];return Array.from(arguments).forEach(e=>{e=this.get(e);e&&!0!==e.hidden&&(e.hidden=!0,t.push(e.id))}),setTimeout(()=>{t.forEach(e=>{this.refresh(e),this.resize()})},15),t}enable(){let t=[];return Array.from(arguments).forEach(e=>{e=this.get(e);e&&!1!==e.disabled&&(e.disabled=!1,t.push(e.id))}),setTimeout(()=>{t.forEach(e=>{this.refresh(e)})},15),t}disable(){let t=[];return Array.from(arguments).forEach(e=>{e=this.get(e);e&&!0!==e.disabled&&(e.disabled=!0,t.push(e.id))}),setTimeout(()=>{t.forEach(e=>{this.refresh(e)})},15),t}dragMove(i){if(this.last.reordering){let s=this;var l=this.last.moving,r=this.tabs[l.index],n=h(l.index,1),a=h(l.index,-1),r=query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(r.id));if(0t)return n=this.tabs.indexOf(n),this.tabs.splice(l.index,0,this.tabs.splice(n,1)[0]),l.$tab.before(o.get(0)),l.$tab.css("opacity",0),void Object.assign(this.last.moving,{index:n,divX:-e,x:i.pageX+e,left:l.left+l.divX+e})}if(l.divX<0&&a){o=query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(a.id));let e=parseInt(r.get(0).clientWidth),t=parseInt(o.get(0).clientWidth);e=et&&(n=this.tabs.indexOf(a),this.tabs.splice(l.index,0,this.tabs.splice(n,1)[0]),o.before(l.$tab),l.$tab.css("opacity",0),Object.assign(l,{index:n,divX:e,x:i.pageX-e,left:l.left+l.divX-e}))}function h(e,t){e+=t;let i=s.tabs[e];return i=i&&i.hidden?h(e,t):i}}}mouseAction(e,t,i){var s=this.get(t),l=this.trigger("mouse"+e,{target:t,tab:s,object:s,originalEvent:i});if(!0!==l.isCancelled&&!s.disabled&&!s.hidden){switch(e){case"Enter":this.tooltipShow(t);break;case"Leave":this.tooltipHide(t);break;case"Down":this.initReorder(t,i)}l.finish()}}tooltipShow(t){var i=this.get(t),t=query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(t)).get(0);if(null!=this.tooltip&&!i.disabled&&!this.last.reordering){var s=this.tooltip;let e=i.tooltip;"function"==typeof e&&(e=e.call(this,i)),w2tooltip.show({anchor:t,name:this.name+"_tooltip",html:e,position:s})}}tooltipHide(e){null!=this.tooltip&&w2tooltip.hide(this.name+"_tooltip")}getTabHTML(e){e=this.get(e,!0),e=this.tabs[e];if(null==e)return!1;null==e.text&&null!=e.caption&&(e.text=e.caption),null==e.tooltip&&null!=e.hint&&(e.tooltip=e.hint),null!=e.caption&&console.log("NOTICE: tabs tab.caption property is deprecated, please use tab.text. Tab -> ",e),null!=e.hint&&console.log("NOTICE: tabs tab.hint property is deprecated, please use tab.tooltip. Tab -> ",e);let t=e.text,i=(null==(t="function"==typeof t?t.call(this,e):t)&&(t=""),""),s="";return e.hidden&&(s+="display: none;"),e.disabled&&(s+="opacity: 0.2;"),e.closable&&!e.disabled&&(i=`
    +
    `),` +
    + ${w2utils.lang(t)+i} +
    `}refresh(e){var t=Date.now(),i=("up"==this.flow?query(this.box).addClass("w2ui-tabs-up"):query(this.box).removeClass("w2ui-tabs-up"),this.trigger("refresh",{target:null!=e?e:this.name,object:this.get(e)}));if(!0!==i.isCancelled){if(null==e)for(let e=0;e +
    +
    ${this.right}
    +
    +
    +
    `,query(this.box).attr("name",this.name).addClass("w2ui-reset w2ui-tabs").html(e),0{this.resize()}),this.last.observeResize.observe(this.box),i.finish(),this.refresh(),this.resize(),Date.now()-t)}initReorder(e,n){if(this.reorder){let t=this,i=query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(e)),s=this.get(e,!0),l=query(i.get(0).cloneNode(!0)),r;l.attr("id","#tabs_"+this.name+"_tab_ghost"),this.last.moving={index:s,indexFrom:s,$tab:i,$ghost:l,divX:0,left:i.get(0).getBoundingClientRect().left,parentX:query(this.box).get(0).getBoundingClientRect().left,x:n.pageX,opacity:i.css("opacity")},query(document).off(".w2uiTabReorder").on("mousemove.w2uiTabReorder",function(e){if(!t.last.reordering){if(!0===(r=t.trigger("reorder",{target:t.tabs[s].id,indexFrom:s,tab:t.tabs[s]})).isCancelled)return;w2tooltip.hide(this.name+"_tooltip"),t.last.reordering=!0,l.addClass("moving"),l.css({"pointer-events":"none",position:"absolute",left:i.get(0).getBoundingClientRect().left}),i.css("opacity",0),query(t.box).find(".w2ui-scroll-wrapper").append(l.get(0)),query(t.box).find(".w2ui-tab-close").hide()}t.last.moving.divX=e.pageX-t.last.moving.x,l.css("left",t.last.moving.left-t.last.moving.parentX+t.last.moving.divX+"px"),t.dragMove(e)}).on("mouseup.w2uiTabReorder",function(){query(document).off(".w2uiTabReorder"),l.css({transition:"0.1s",left:t.last.moving.$tab.get(0).getBoundingClientRect().left-t.last.moving.parentX}),query(t.box).find(".w2ui-tab-close").show(),setTimeout(()=>{l.remove(),i.css({opacity:t.last.moving.opacity}),t.last.reordering&&r.finish({indexTo:t.last.moving.index}),t.last.reordering=!1},100)})}}scroll(a,o){return new Promise((e,t)=>{var i=query(this.box).find(".w2ui-scroll-wrapper"),s=i.get(0).scrollLeft,l=i.find(".w2ui-tabs-right").get(0),r=i.parent().get(0).getBoundingClientRect().width,n=s+parseInt(l.offsetLeft)+parseInt(l.clientWidth);switch(a){case"left":{let e=s-r+50;e<=0&&(e=0),i.get(0).scrollTo({top:0,left:e,behavior:o?"atuo":"smooth"});break}case"right":{let e=s+r-50;e>=n-r&&(e=n-r),i.get(0).scrollTo({top:0,left:e,behavior:o?"atuo":"smooth"});break}}setTimeout(()=>{this.resize(),e()},o?0:350)})}scrollIntoView(i,s){return new Promise((e,t)=>{null==i&&(i=this.active),null!=this.get(i)&&(query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(i)).get(0).scrollIntoView({block:"start",inline:"center",behavior:s?"atuo":"smooth"}),setTimeout(()=>{this.resize(),e()},s?0:500))})}resize(){var e=Date.now();if(null!=this.box){var t,i,s,l,r=this.trigger("resize",{target:this.name});if(!0!==r.isCancelled)return(t=query(this.box)).find(".w2ui-scroll-left, .w2ui-scroll-right").hide(),i=t.find(".w2ui-scroll-wrapper").get(0),l=t.find(".w2ui-tabs-right"),(s=t.get(0).getBoundingClientRect().width)<(l=0{window.location.hash=t},1)}e.finish()}}clickClose(e,t){var i=this.get(e);if(null==i||i.disabled)return!1;let s=this.trigger("close",{target:e,object:i,tab:i,originalEvent:t});!0!==s.isCancelled&&(this.animateClose(e).then(()=>{this.remove(e),s.finish(),this.refresh()}),t)&&t.stopPropagation()}animateClose(r){return new Promise((e,t)=>{var i=query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(r)),s=parseInt(i.get(0).clientWidth||0);let l=i.replace(`
    `);setTimeout(()=>{l.css({width:"0px"})},1),setTimeout(()=>{l.remove(),this.resize(),e()},500)})}animateInsert(t,r){return new Promise((i,e)=>{let s=query(this.box).find("#tabs_"+this.name+"_tab_"+w2utils.escapeId(t)),l=query.html(this.getTabHTML(r.id));if(0==s.length)(s=query(this.box).find("#tabs_tabs_right")).before(l),this.resize();else{l.css({opacity:0}),query(this.box).find("#tabs_tabs_right").before(l.get(0));let e=query(this.box).find("#"+l.attr("id")).get(0).clientWidth??0,t=query.html('
    ');s.before(t),l.hide(),t.before(l[0]),setTimeout(()=>{t.css({width:e+"px"})},1),setTimeout(()=>{t.remove(),l.css({opacity:1}).show(),this.refresh(r.id),this.resize(),i()},500)}})}}let w2panels=["top","left","main","preview","right","bottom"];class w2layout extends w2base{constructor(e){super(e.name),this.box=null,this.name=null,this.panels=[],this.last={},this.padding=1,this.resizer=4,this.style="",this.onShow=null,this.onHide=null,this.onResizing=null,this.onResizerClick=null,this.onRender=null,this.onRefresh=null,this.onChange=null,this.onResize=null,this.onDestroy=null,this.panel_template={type:null,title:"",size:100,minSize:20,maxSize:!1,hidden:!1,resizable:!1,overflow:"auto",style:"",html:"",tabs:null,toolbar:null,width:null,height:null,show:{toolbar:!1,tabs:!1},removed:null,onRefresh:null,onShow:null,onHide:null},Object.assign(this,e),Array.isArray(this.panels)||(this.panels=[]),this.panels.forEach((e,t)=>{var i,s,l;this.panels[t]=w2utils.extend({},this.panel_template,e),(w2utils.isPlainObject(e.tabs)||Array.isArray(e.tabs))&&function(e,t,i){var s=e.get(t);null!=s&&null==i&&(i=s.tabs);if(null==s||null==i)return;Array.isArray(i)&&(i={tabs:i});var l=e.name+"_"+t+"_tabs";w2ui[l]&&w2ui[l].destroy();s.tabs=new w2tabs(w2utils.extend({},i,{owner:e,name:e.name+"_"+t+"_tabs"})),s.show.tabs=!0}(this,e.type),(w2utils.isPlainObject(e.toolbar)||Array.isArray(e.toolbar))&&(t=this,e=e.type,i=void 0,null!=(s=t.get(e))&&null==i&&(i=s.toolbar),null!=s)&&null!=i&&(Array.isArray(i)&&(i={items:i}),l=t.name+"_"+e+"_toolbar",w2ui[l]&&w2ui[l].destroy(),s.toolbar=new w2toolbar(w2utils.extend({},i,{owner:t,name:t.name+"_"+e+"_toolbar"})),s.show.toolbar=!0)}),w2panels.forEach(e=>{null==this.get(e)&&this.panels.push(w2utils.extend({},this.panel_template,{type:e,hidden:"main"!==e,size:50}))}),"string"==typeof this.box&&(this.box=query(this.box).get(0)),this.box&&this.render(this.box)}html(l,r,n){let a=this.get(l);var e={panel:l,html:a.html,error:!1,cancelled:!1,removed(e){"function"==typeof e&&(a.removed=e)}};if("function"==typeof a.removed&&(a.removed({panel:l,html:a.html,html_new:r,transition:n||"none"}),a.removed=null),"css"==l)query(this.box).find("#layout_"+this.name+"_panel_css").html(""),e.status=!0;else if(null==a)console.log("ERROR: incorrect panel name. Panel name can be main, left, right, top, bottom, preview or css"),e.error=!0;else if(null!=r){var t=this.trigger("change",{target:l,panel:a,html_new:r,transition:n});if(!0===t.isCancelled)e.cancelled=!0;else{let i="#layout_"+this.name+"_panel_"+a.type;var o=query(this.box).find(i+"> .w2ui-panel-content");let s=0;if(0 .w2ui-panel-content"),t=(e.after('
    '),query(this.box).find(i+"> .w2ui-panel-content.new-panel"));e.css("top",s),t.css("top",s),"object"==typeof r?(r.box=t[0],r.render()):t.hide().html(r),w2utils.transition(e[0],t[0],n,()=>{e.remove(),t.removeClass("new-panel"),t.css("overflow",a.overflow),query(query(this.box).find(i+"> .w2ui-panel-content").get(1)).remove(),query(this.box).removeClass("animating"),this.refresh(l)})}else this.refresh(l);t.finish()}}return e}message(e,t){var i=this.get(e);let s=query(this.box).find("#layout_"+this.name+"_panel_"+i.type),l=s.css("overflow");s.css("overflow","hidden");i=w2utils.message({owner:this,box:s.get(0),after:".w2ui-panel-title",param:e},t);return i&&i.self.on("close:after",()=>{s.css("overflow",l)}),i}confirm(e,t){var i=this.get(e);let s=query(this.box).find("#layout_"+this.name+"_panel_"+i.type),l=s.css("overflow");s.css("overflow","hidden");i=w2utils.confirm({owner:this,box:s.get(0),after:".w2ui-panel-title",param:e},t);return i&&i.self.on("close:after",()=>{s.css("overflow",l)}),i}load(i,s,l){return new Promise((t,e)=>{"css"!=i&&null==this.get(i)||null==s?e():fetch(s).then(e=>e.text()).then(e=>{this.resize(),t(this.html(i,e,l))})})}sizeTo(e,t,i){return null!=this.get(e)&&(query(this.box).find(":scope > div > .w2ui-panel").css("transition",!0!==i?".2s":"0s"),setTimeout(()=>{this.set(e,{size:t})},1),setTimeout(()=>{query(this.box).find(":scope > div > .w2ui-panel").css("transition","0s"),this.resize()},300),!0)}show(e,t){let i=this.trigger("show",{target:e,thisect:this.get(e),immediate:t});var s;if(!0!==i.isCancelled)return null!=(s=this.get(e))&&(!(s.hidden=!1)===t?(query(this.box).find("#layout_"+this.name+"_panel_"+e).css({opacity:"1"}),i.finish(),this.resize()):(query(this.box).addClass("animating"),query(this.box).find("#layout_"+this.name+"_panel_"+e).css({opacity:"0"}),query(this.box).find(":scope > div > .w2ui-panel").css("transition",".2s"),setTimeout(()=>{this.resize()},1),setTimeout(()=>{query(this.box).find("#layout_"+this.name+"_panel_"+e).css({opacity:"1"})},250),setTimeout(()=>{query(this.box).find(":scope > div > .w2ui-panel").css("transition","0s"),query(this.box).removeClass("animating"),i.finish(),this.resize()},300)),!0)}hide(e,t){let i=this.trigger("hide",{target:e,object:this.get(e),immediate:t});var s;if(!0!==i.isCancelled)return null!=(s=this.get(e))&&((s.hidden=!0)===t?(query(this.box).find("#layout_"+this.name+"_panel_"+e).css({opacity:"0"}),i.finish(),this.resize()):(query(this.box).addClass("animating"),query(this.box).find(":scope > div > .w2ui-panel").css("transition",".2s"),query(this.box).find("#layout_"+this.name+"_panel_"+e).css({opacity:"0"}),setTimeout(()=>{this.resize()},1),setTimeout(()=>{query(this.box).find(":scope > div > .w2ui-panel").css("transition","0s"),query(this.box).removeClass("animating"),i.finish(),this.resize()},300)),!0)}toggle(e,t){var i=this.get(e);return null!=i&&(i.hidden?this.show(e,t):this.hide(e,t))}set(e,t){var i=this.get(e,!0);return null!=i&&(w2utils.extend(this.panels[i],t),null==t.html&&null==t.resizable||this.refresh(e),this.resize(),!0)}get(t,i){for(let e=0;e .w2ui-panel-content");return 1!=e.length?null:e[0]}hideToolbar(e){var t=this.get(e);t&&(t.show.toolbar=!1,query(this.box).find("#layout_"+this.name+"_panel_"+e+"> .w2ui-panel-toolbar").hide(),this.resize())}showToolbar(e){var t=this.get(e);t&&(t.show.toolbar=!0,query(this.box).find("#layout_"+this.name+"_panel_"+e+"> .w2ui-panel-toolbar").show(),this.resize())}toggleToolbar(e){var t=this.get(e);t&&(t.show.toolbar?this.hideToolbar(e):this.showToolbar(e))}assignToolbar(e,t){"string"==typeof t&&null!=w2ui[t]&&(t=w2ui[t]);var i=this.get(e),s=(i.toolbar=t,query(this.box).find(e+"> .w2ui-panel-toolbar"));null!=i.toolbar?(0===s.find("[name="+i.toolbar.name+"]").length?i.toolbar.render(s.get(0)):null!=i.toolbar&&i.toolbar.refresh(),(t.owner=this).showToolbar(e),this.refresh(e)):(s.html(""),this.hideToolbar(e))}hideTabs(e){var t=this.get(e);t&&(t.show.tabs=!1,query(this.box).find("#layout_"+this.name+"_panel_"+e+"> .w2ui-panel-tabs").hide(),this.resize())}showTabs(e){var t=this.get(e);t&&(t.show.tabs=!0,query(this.box).find("#layout_"+this.name+"_panel_"+e+"> .w2ui-panel-tabs").show(),this.resize())}toggleTabs(e){var t=this.get(e);t&&(t.show.tabs?this.hideTabs(e):this.showTabs(e))}render(e){var t=Date.now();let o=this;"string"==typeof e&&(e=query(e).get(0));var i=this.trigger("render",{target:this.name,box:e??this.box});if(!0!==i.isCancelled){if(null!=e&&(0"),0
    ';query(this.box).find(":scope > div").append(s)}return query(this.box).find(":scope > div").append('
    '),this.refresh(),this.last.observeResize=new ResizeObserver(()=>{this.resize()}),this.last.observeResize.observe(this.box),i.finish(),setTimeout(()=>{o.last.events={resizeStart:l,mouseMove:n,mouseUp:r},this.resize()},0),Date.now()-t}function l(e,t){o.box&&(t=t||window.event,query(document).off("mousemove",o.last.events.mouseMove).on("mousemove",o.last.events.mouseMove),query(document).off("mouseup",o.last.events.mouseUp).on("mouseup",o.last.events.mouseUp),o.last.resize={type:e,x:t.screenX,y:t.screenY,diff_x:0,diff_y:0,value:0},w2panels.forEach(e=>{var t=query(o.el(e)).find(".w2ui-lock");0{var t=query(o.el(e)).find(".w2ui-lock");"yes"==t.data("locked")?t.removeData("locked"):o.unlock(e)}),0!==o.last.diff_x||0!==o.last.resize.diff_y){var s=o.get("top"),l=o.get("bottom"),r=o.get(o.last.resize.type),i=w2utils.getSize(query(o.box),"width"),n=w2utils.getSize(query(o.box),"height"),a=String(r.size);let e,t;switch(o.last.resize.type){case"top":e=parseInt(r.sizeCalculated)+o.last.resize.diff_y,t=0;break;case"bottom":e=parseInt(r.sizeCalculated)-o.last.resize.diff_y,t=0;break;case"preview":e=parseInt(r.sizeCalculated)-o.last.resize.diff_y,t=(s&&!s.hidden?s.sizeCalculated:0)+(l&&!l.hidden?l.sizeCalculated:0);break;case"left":e=parseInt(r.sizeCalculated)+o.last.resize.diff_x,t=0;break;case"right":e=parseInt(r.sizeCalculated)-o.last.resize.diff_x,t=0}"%"==a.substr(a.length-1)?r.size=Math.floor(100*e/("left"==r.type||"right"==r.type?i:n-t)*100)/100+"%":"-"==String(r.size).substr(0,1)?r.size=parseInt(r.size)-r.sizeCalculated+e:r.size=e,o.resize()}query(o.box).find("#layout_"+o.name+"_resizer_"+o.last.resize.type).removeClass("active"),delete o.last.resize}}function n(i){if(o.box&&(i=i||window.event,null!=o.last.resize)){var s=o.get(o.last.resize.type),l=o.last.resize,r=o.trigger("resizing",{target:o.name,object:s,originalEvent:i,panel:l?l.type:"all",diff_x:l?l.diff_x:0,diff_y:l?l.diff_y:0});if(!0!==r.isCancelled){var n=query(o.box).find("#layout_"+o.name+"_resizer_"+l.type);let e=i.screenX-l.x,t=i.screenY-l.y;var a=o.get("main");switch(n.hasClass("active")||n.addClass("active"),l.type){case"left":s.minSize-e>s.width&&(e=s.minSize-s.width),s.maxSize&&s.width+e>s.maxSize&&(e=s.maxSize-s.width),a.minSize+e>a.width&&(e=a.width-a.minSize);break;case"right":s.minSize+e>s.width&&(e=s.width-s.minSize),s.maxSize&&s.width-e>s.maxSize&&(e=s.width-s.maxSize),a.minSize-e>a.width&&(e=a.minSize-a.width);break;case"top":s.minSize-t>s.height&&(t=s.minSize-s.height),s.maxSize&&s.height+t>s.maxSize&&(t=s.maxSize-s.height),a.minSize+t>a.height&&(t=a.height-a.minSize);break;case"preview":case"bottom":s.minSize+t>s.height&&(t=s.height-s.minSize),s.maxSize&&s.height-t>s.maxSize&&(t=s.height-s.maxSize),a.minSize-t>a.height&&(t=a.minSize-a.height)}switch(l.diff_x=e,l.diff_y=t,l.type){case"top":case"preview":case"bottom":(l.diff_x=0) .w2ui-panel-content")[0],setTimeout(()=>{0 .w2ui-panel-content").length&&(query(l.box).find(t+"> .w2ui-panel-content").removeClass().removeAttr("name").addClass("w2ui-panel-content").css("overflow",e.overflow)[0].style.cssText+=";"+e.style),e.html&&"function"==typeof e.html.render&&e.html.render()},1)):0 .w2ui-panel-content").length&&(query(l.box).find(t+"> .w2ui-panel-content").removeClass().removeAttr("name").addClass("w2ui-panel-content").html(e.html).css("overflow",e.overflow)[0].style.cssText+=";"+e.style);let i=query(l.box).find(t+"> .w2ui-panel-tabs");e.show.tabs?0===i.find("[name="+e.tabs.name+"]").length&&null!=e.tabs?e.tabs.render(i.get(0)):e.tabs.refresh():i.html("").removeClass("w2ui-tabs").hide(),i=query(l.box).find(t+"> .w2ui-panel-toolbar"),e.show.toolbar?0===i.find("[name="+e.toolbar.name+"]").length&&null!=e.toolbar?e.toolbar.render(i.get(0)):e.toolbar.refresh():i.html("").removeClass("w2ui-toolbar").hide(),i=query(l.box).find(t+"> .w2ui-panel-title"),e.title?i.html(e.title).show():i.html("").hide()}else{if(0===query(l.box).find("#layout_"+l.name+"_panel_main").length)return void l.render();l.resize();for(let e=0;ethis.padding?this.resizer:this.padding,query(this.box).find("#layout_"+this.name+"_resizer_top").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px",cursor:"ns-resize"}).off("mousedown").on("mousedown",function(e){e.preventDefault();var t=i.trigger("resizerClick",{target:"top",originalEvent:e});if(!0!==t.isCancelled)return w2ui[i.name].last.events.resizeStart("top",e),t.finish(),!1}))):(query(this.box).find("#layout_"+this.name+"_panel_top").hide(),query(this.box).find("#layout_"+this.name+"_resizer_top").hide()),null!=c&&!0!==c.hidden?(e=0,t=0+(b?f.sizeCalculated+this.padding:0),s=c.sizeCalculated,l=h-(b?f.sizeCalculated+this.padding:0)-(v?m.sizeCalculated+this.padding:0),query(this.box).find("#layout_"+this.name+"_panel_left").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px"}),c.width=s,c.height=l,c.resizable&&(e=c.sizeCalculated-(0===this.padding?this.resizer:0),s=this.resizer>this.padding?this.resizer:this.padding,query(this.box).find("#layout_"+this.name+"_resizer_left").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px",cursor:"ew-resize"}).off("mousedown").on("mousedown",function(e){e.preventDefault();var t=i.trigger("resizerClick",{target:"left",originalEvent:e});if(!0!==t.isCancelled)return w2ui[i.name].last.events.resizeStart("left",e),t.finish(),!1}))):(query(this.box).find("#layout_"+this.name+"_panel_left").hide(),query(this.box).find("#layout_"+this.name+"_resizer_left").hide()),null!=p&&!0!==p.hidden?(e=o-p.sizeCalculated,t=0+(b?f.sizeCalculated+this.padding:0),s=p.sizeCalculated,l=h-(b?f.sizeCalculated+this.padding:0)-(v?m.sizeCalculated+this.padding:0),query(this.box).find("#layout_"+this.name+"_panel_right").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px"}),p.width=s,p.height=l,p.resizable&&(e-=this.padding,s=this.resizer>this.padding?this.resizer:this.padding,query(this.box).find("#layout_"+this.name+"_resizer_right").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px",cursor:"ew-resize"}).off("mousedown").on("mousedown",function(e){e.preventDefault();var t=i.trigger("resizerClick",{target:"right",originalEvent:e});if(!0!==t.isCancelled)return w2ui[i.name].last.events.resizeStart("right",e),t.finish(),!1}))):(query(this.box).find("#layout_"+this.name+"_panel_right").hide(),query(this.box).find("#layout_"+this.name+"_resizer_right").hide()),null!=m&&!0!==m.hidden?(e=0,t=h-m.sizeCalculated,s=o,l=m.sizeCalculated,query(this.box).find("#layout_"+this.name+"_panel_bottom").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px"}),m.width=s,m.height=l,m.resizable&&(t-=0===this.padding?0:this.padding,l=this.resizer>this.padding?this.resizer:this.padding,query(this.box).find("#layout_"+this.name+"_resizer_bottom").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px",cursor:"ns-resize"}).off("mousedown").on("mousedown",function(e){e.preventDefault();var t=i.trigger("resizerClick",{target:"bottom",originalEvent:e});if(!0!==t.isCancelled)return w2ui[i.name].last.events.resizeStart("bottom",e),t.finish(),!1}))):(query(this.box).find("#layout_"+this.name+"_panel_bottom").hide(),query(this.box).find("#layout_"+this.name+"_resizer_bottom").hide()),e=0+(y?c.sizeCalculated+this.padding:0),t=0+(b?f.sizeCalculated+this.padding:0),s=o-(y?c.sizeCalculated+this.padding:0)-(w?p.sizeCalculated+this.padding:0),l=h-(b?f.sizeCalculated+this.padding:0)-(v?m.sizeCalculated+this.padding:0)-(g?u.sizeCalculated+this.padding:0),query(this.box).find("#layout_"+this.name+"_panel_main").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px"}),d.width=s,d.height=l,null!=u&&!0!==u.hidden?(e=0+(y?c.sizeCalculated+this.padding:0),t=h-(v?m.sizeCalculated+this.padding:0)-u.sizeCalculated,s=o-(y?c.sizeCalculated+this.padding:0)-(w?p.sizeCalculated+this.padding:0),l=u.sizeCalculated,query(this.box).find("#layout_"+this.name+"_panel_preview").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px"}),u.width=s,u.height=l,u.resizable&&(t-=0===this.padding?0:this.padding,l=this.resizer>this.padding?this.resizer:this.padding,query(this.box).find("#layout_"+this.name+"_resizer_preview").css({display:"block",left:e+"px",top:t+"px",width:s+"px",height:l+"px",cursor:"ns-resize"}).off("mousedown").on("mousedown",function(e){e.preventDefault();var t=i.trigger("resizerClick",{target:"preview",originalEvent:e});if(!0!==t.isCancelled)return w2ui[i.name].last.events.resizeStart("preview",e),t.finish(),!1}))):(query(this.box).find("#layout_"+this.name+"_panel_preview").hide(),query(this.box).find("#layout_"+this.name+"_resizer_preview").hide());for(let t=0;t .w2ui-panel-";let e=0;q&&(q.title&&(_=query(this.box).find(C+"title").css({top:e+"px",display:"block"}),e+=w2utils.getSize(_,"height")),q.show.tabs&&(_=query(this.box).find(C+"tabs").css({top:e+"px",display:"block"}),e+=w2utils.getSize(_,"height")),q.show.toolbar)&&(q=query(this.box).find(C+"toolbar").css({top:e+"px",display:"block"}),e+=w2utils.getSize(q,"height")),query(this.box).find(C+"content").css({display:"block"}).css({top:e+"px"})}return a.finish(),Date.now()-r}}destroy(){var e=this.trigger("destroy",{target:this.name});if(!0!==e.isCancelled)return null!=w2ui[this.name]&&(0'},add:{type:"button",id:"w2ui-add",text:"Add New",tooltip:"Add new record",icon:"w2ui-icon-plus"},edit:{type:"button",id:"w2ui-edit",text:"Edit",tooltip:"Edit selected record",icon:"w2ui-icon-pencil",batch:1,disabled:!0},delete:{type:"button",id:"w2ui-delete",text:"Delete",tooltip:"Delete selected records",icon:"w2ui-icon-cross",batch:!0,disabled:!0},save:{type:"button",id:"w2ui-save",text:"Save",tooltip:"Save changed records",icon:"w2ui-icon-check"}},this.operators={text:["is","begins","contains","ends"],number:["=","between",">","<",">=","<="],date:["is",{oper:"less",text:"before"},{oper:"more",text:"since"},"between"],list:["is"],hex:["is","between"],color:["is","begins","contains","ends"],enum:["in","not in"]},this.defaultOperator={text:"begins",number:"=",date:"is",list:"is",enum:"in",hex:"begins",color:"begins"},this.operatorsMap={text:"text",int:"number",float:"number",money:"number",currency:"number",percent:"number",hex:"hex",alphanumeric:"text",color:"color",date:"date",time:"date",datetime:"date",list:"list",combo:"text",enum:"enum",file:"enum",select:"list",radio:"list",checkbox:"list",toggle:"list"},this.onAdd=null,this.onEdit=null,this.onRequest=null,this.onLoad=null,this.onDelete=null,this.onSave=null,this.onSelect=null,this.onClick=null,this.onDblClick=null,this.onContextMenu=null,this.onContextMenuClick=null,this.onColumnClick=null,this.onColumnDblClick=null,this.onColumnResize=null,this.onColumnAutoResize=null,this.onSort=null,this.onSearch=null,this.onSearchOpen=null,this.onChange=null,this.onRestore=null,this.onExpand=null,this.onCollapse=null,this.onError=null,this.onKeydown=null,this.onToolbar=null,this.onColumnOnOff=null,this.onCopy=null,this.onPaste=null,this.onSelectionExtend=null,this.onEditField=null,this.onRender=null,this.onRefresh=null,this.onReload=null,this.onResize=null,this.onDestroy=null,this.onStateSave=null,this.onStateRestore=null,this.onFocus=null,this.onBlur=null,this.onReorderRow=null,this.onSearchSave=null,this.onSearchRemove=null,this.onSearchSelect=null,this.onColumnSelect=null,this.onColumnDragStart=null,this.onColumnDragEnd=null,this.onResizerDblClick=null,this.onMouseEnter=null,this.onMouseLeave=null,w2utils.extend(this,e),Array.isArray(this.records)){let i=[];this.records.forEach((e,t)=>{null!=e[this.recid]&&(e.recid=e[this.recid]),null==e.recid&&console.log("ERROR: Cannot add records without recid. (obj: "+this.name+")"),!0===e.w2ui?.summary&&(this.summary.push(e),i.push(t))}),i.sort();for(let e=i.length-1;0<=e;e--)this.records.splice(i[e],1)}Array.isArray(this.columns)&&this.columns.forEach((i,e)=>{i=w2utils.extend({},this.colTemplate,i);e=(this.columns[e]=i).searchable;if(null!=e&&!1!==e&&null==this.getSearch(i.field))if(w2utils.isPlainObject(e))this.addSearch(w2utils.extend({field:i.field,label:i.text,type:"text"},e));else{let e=i.searchable,t="";!0===i.searchable&&(e="text",t='size="20"'),this.addSearch({field:i.field,label:i.text,type:e,attr:t})}}),Array.isArray(this.defaultSearches)&&this.defaultSearches.forEach((e,t)=>{e.id="default-"+t,e.icon??="w2ui-icon-search"});e=this.cache("searches");Array.isArray(e)&&e.forEach(e=>{this.savedSearches.push({id:e.id??"none",text:e.text??"none",icon:"w2ui-icon-search",remove:!0,logic:e.logic??"AND",data:e.data??[]})}),"string"==typeof this.box&&(this.box=query(this.box).get(0)),this.box&&this.render(this.box)}add(t,i){Array.isArray(t)||(t=[t]);let s=0;for(let e=0;ethis.records.length&&(a=this.records.length);for(let i=n;i{this.columns.forEach(i=>{if(i.field==e){let t=w2utils.clone(s);Object.keys(t).forEach(e=>{"function"==typeof t[e]&&(t[e]=t[e](i)),i[e]!=t[e]&&l++}),w2utils.extend(i,t)}})}),0{if(!(e.w2ui&&null!=e.w2ui.parent_recid||t.w2ui&&null!=t.w2ui.parent_recid))return o(e,t);var i=n(e),s=n(t);for(let e=0;es.length?1:i.length{this.status(w2utils.lang("Sorting took ${count} seconds",{count:e/1e3}))},10),e;function n(e){var t;return e.w2ui&&null!=e.w2ui.parent_recid?e.w2ui._path||((t=a.get(e.w2ui.parent_recid))?n(t).concat(e):(console.log("ERROR: no parent record: "+e.w2ui.parent_recid),[e])):[e]}function o(s,l){if(s===l)return 0;for(let i=0;it.constructor.name?s:-s;e&&"object"==typeof e&&(e=e.valueOf()),t&&"object"==typeof t&&(t=t.valueOf());var r={}.toString;switch(e&&"object"==typeof e&&e.toString!=r&&(e=String(e)),t&&"object"==typeof t&&t.toString!=r&&(t=String(t)),"string"==typeof e&&(e=e.toLowerCase().trim()),"string"==typeof t&&(t=t.toLowerCase().trim()),l){case"natural":l=w2utils.naturalCompare;break;case"i18n":l=w2utils.i18nCompare}return"function"==typeof l?l(e,t)*s:t=parseFloat(a)&&parseFloat(c.parseField(l,s.field))<=parseFloat(o)&&r++:"date"==s.type?(h=c.parseField(l,s.field+"_")instanceof Date?c.parseField(l,s.field+"_"):c.parseField(l,s.field),n=w2utils.isDate(h,w2utils.settings.dateFormat,!0),a=w2utils.isDate(a,w2utils.settings.dateFormat,!0),null!=(o=w2utils.isDate(o,w2utils.settings.dateFormat,!0))&&(o=new Date(o.getTime()+864e5)),n>=a&&n=a&&n=a)&&n=":d=!0;case">":case"more":-1!=["int","float","money","currency","percent"].indexOf(s.type)?(n=parseFloat(c.parseField(l,s.field)),a=parseFloat(i.value),(n>a||d&&n===a)&&r++):"date"==s.type?(h=c.parseField(l,s.field+"_")instanceof Date?c.parseField(l,s.field+"_"):c.parseField(l,s.field),n=w2utils.isDate(h,w2utils.settings.dateFormat,!0),a=w2utils.isDate(a,w2utils.settings.dateFormat,!0),(n>a||d&&n===a)&&r++):"time"==s.type?(h=c.parseField(l,s.field+"_")instanceof Date?c.parseField(l,s.field+"_"):c.parseField(l,s.field),n=w2utils.formatTime(h,"hh24:mi"),a=w2utils.formatTime(a,"hh24:mi"),(n>a||d&&n===a)&&r++):"datetime"==s.type&&(h=c.parseField(l,s.field+"_")instanceof Date?c.parseField(l,s.field+"_"):c.parseField(l,s.field),n=w2utils.formatDateTime(h,"yyyy-mm-dd|hh24:mm:ss"),a=w2utils.formatDateTime(w2utils.isDateTime(a,w2utils.settings.datetimeFormat,!0),"yyyy-mm-dd|hh24:mm:ss"),n.length==a.length)&&(n>a||d&&n===a)&&r++;break;case"in":h=i.value,-1===(h=i.svalue?i.svalue:h).indexOf(w2utils.isFloat(t)?parseFloat(t):t)&&-1===h.indexOf(n)||r++;break;case"not in":h=i.value,-1===(h=i.svalue?i.svalue:h).indexOf(w2utils.isFloat(t)?parseFloat(t):t)&&-1===h.indexOf(n)&&r++;break;case"begins":case"begins with":0===n.indexOf(a)&&r++;break;case"contains":0<=n.indexOf(a)&&r++;break;case"null":null==c.parseField(l,s.field)&&r++;break;case"not null":null!=c.parseField(l,s.field)&&r++;break;case"ends":case"ends with":let e=n.lastIndexOf(a);-1!==e&&e==n.length-a.length&&r++}}}if("OR"==c.last.logic&&0!==r||"AND"==c.last.logic&&r==c.searchData.length)return!0;if(l.w2ui?.children&&!0!==l.w2ui?.expanded)for(let t=0;tthis.records.length&&(i=this.records.length-s),0{this.status(w2utils.lang("Search took ${count} seconds",{count:e/1e3}))},10),e}}getRangeData(e,i){var s=this.get(e[0].recid,!0),l=this.get(e[1].recid,!0),r=e[0].column,n=e[1].column,a=[];if(r==n)for(let e=s;e<=l;e++){var t=this.records[e],o=t[this.columns[r].field]||null;a.push(!0!==i?o:{data:o,column:r,index:e,record:t})}else if(s==l){var h=this.records[s];for(let e=r;e<=n;e++){var d=h[this.columns[e].field]||null;a.push(!0!==i?d:{data:d,column:e,index:s,record:h})}}else for(let t=s;t<=l;t++){var u=this.records[t];a.push([]);for(let e=r;e<=n;e++){var c=u[this.columns[e].field];!0!==i?a[a.length-1].push(c):a[a.length-1].push({data:c,column:e,index:t,record:u})}}return a}addRange(s){let e=0,l,r;if("row"!=this.selectType){Array.isArray(s)||(s=[s]);for(let i=0;ithis.last.colStart&&(e=query(this.box).find("#grid_"+this.name+"_rec_"+w2utils.escapeId(u.recid)+' td[col="start"]')),u.columnthis.last.colEnd&&(t=query(this.box).find("#grid_"+this.name+"_rec_"+w2utils.escapeId(c.recid)+' td[col="end"]'),l='"end"');var p=parseInt(query(this.box).find("#grid_"+this.name+"_rec_top").next().attr("index")),f=parseInt(query(this.box).find("#grid_"+this.name+"_rec_bottom").prev().attr("index")),m=parseInt(query(this.box).find("#grid_"+this.name+"_frec_top").next().attr("index")),g=parseInt(query(this.box).find("#grid_"+this.name+"_frec_bottom").prev().attr("index"));0===e.length&&u.indexp&&(e=query(this.box).find("#grid_"+this.name+"_rec_top").next().find('td[col="'+u.column+'"]')),0===t.length&&c.index>f&&u.indexm&&(i=query(this.box).find("#grid_"+this.name+"_frec_top").next().find('td[col="'+u.column+'"]')),0===s.length&&c.index>g&&u.index'+("selection"==d.name?'
    ':"")+""),n=query(this.box).find("#grid_"+this.name+"_f"+d.name)):(n.attr("style",d.style),n.find(".w2ui-selection-resizer").show()),0===s.length&&(0===(s=query(this.box).find("#grid_"+this.name+"_frec_"+w2utils.escapeId(c.recid)+" td:last-child")).length&&(s=query(this.box).find("#grid_"+this.name+"_frec_bottom td:first-child")),n.css("border-right","0px"),n.find(".w2ui-selection-resizer").hide()),null!=u.recid)&&null!=c.recid&&0'+("selection"==d.name?'
    ':"")+""),n=query(this.box).find("#grid_"+this.name+"_"+d.name)):n.attr("style",d.style),0===e.length&&0===(e=query(this.box).find("#grid_"+this.name+"_rec_"+w2utils.escapeId(u.recid)+" td:first-child")).length&&(e=query(this.box).find("#grid_"+this.name+"_rec_top td:first-child")),0!==s.length&&n.css("border-left","0px"),null!=u.recid)&&null!=c.recid&&0{e=this.trigger("resizerDblClick",{target:this.name,originalEvent:e});!0!==e.isCancelled&&e.finish()});let a={target:this.name,originalRange:null,newRange:null};return Date.now()-e;function i(s){var l=r.last.move;if(l&&"expand"==l.type){l.divX=s.screenX-l.x,l.divY=s.screenY-l.y;let e,t,i=s.target;"TD"!=i.tagName.toUpperCase()&&(i=query(i).closest("td")[0]),null!=(t=null!=query(i).attr("col")?parseInt(query(i).attr("col")):t)&&(i=query(i).closest("tr")[0],e=r.records[query(i).attr("index")].recid,l.newRange[1].recid!=e||l.newRange[1].column!=t)&&(s=w2utils.clone(l.newRange),l.newRange=[{recid:l.recid,column:l.column},{recid:e,column:t}],a.detail&&(a.detail.newRange=w2utils.clone(l.newRange),a.detail.originalRange=w2utils.clone(l.originalRange)),!0===(a=r.trigger("selectionExtend",a)).isCancelled?(l.newRange=s,a.detail.newRange=s):(r.removeRange("grid-selection-expand"),r.addRange({name:"grid-selection-expand",range:l.newRange,style:"background-color: rgba(100,100,100,0.1); border: 2px dotted rgba(100,100,100,0.5);"})))}}function s(e){r.removeRange("grid-selection-expand"),delete r.last.move,query("body").off(".w2ui-"+r.name),a.finish&&a.finish()}}}select(){if(0===arguments.length)return 0;let s=0;var l=this.last.selection;this.multiSelect||this.selectNone(!0);let t=Array.from(arguments);Array.isArray(t[0])&&(t=t[0]);var e={target:this.name},e=(1==t.length?(e.multiple=!1,w2utils.isPlainObject(t[0])?e.clicked={recid:t[0].recid,column:t[0].column}:e.recid=t[0]):(e.multiple=!0,e.clicked={recids:t}),this.trigger("select",e));if(!0===e.isCancelled)return 0;if("row"==this.selectType)for(let e=0;e=this.last.range_start&&r+1<=this.last.range_end)&&(e=query(this.box).find("#grid_"+this.name+"_frec_"+w2utils.escapeId(i)),t=query(this.box).find("#grid_"+this.name+"_rec_"+w2utils.escapeId(i))),"row"==this.selectType&&-1==l.indexes.indexOf(r)&&(l.indexes.push(r),e&&t&&(e.addClass("w2ui-selected").find(".w2ui-col-number").addClass("w2ui-row-selected"),t.addClass("w2ui-selected").find(".w2ui-col-number").addClass("w2ui-row-selected"),e.find(".w2ui-grid-select-check").prop("checked",!0)),s++)}}else{var n={};for(let e=0;e=this.last.range_start&&u+1<=this.last.range_end&&(t=query(this.box).find("#grid_"+this.name+"_rec_"+w2utils.escapeId(h)),i=query(this.box).find("#grid_"+this.name+"_frec_"+w2utils.escapeId(h)));var c=l.columns[u]||[];-1==l.indexes.indexOf(u)&&l.indexes.push(u);for(let e=0;ee-t);for(let e=0;ee-t);var f=0 td[col="${h}"]`).removeClass("w2ui-selected w2ui-inactive"),query(this.box).find(`#grid_${this.name}_frec_${w2utils.escapeId(r)} > td[col="${h}"]`).removeClass("w2ui-selected w2ui-inactive");let t=!1,i=!1;var d=this.getSelection();for(let e=0;e{i(t,""),Array.isArray(t.items)&&t.items.forEach(e=>{i(e,t.id+":")})}),this.show.toolbarSave&&(0{this.initSearches(),this.last.search_opened=!0;let t=query(`#w2overlay-${this.name}-search-overlay`);t.data("gridName",this.name).off(".grid-search").on("click.grid-search",()=>{t.find("input, select").each(e=>{e=query(e).data("tooltipName");e&&e.forEach(e=>{w2tooltip.hide(e)})})}),w2utils.bindEvents(t.find("select, input, button"),this);var i=query(`#w2overlay-${this.name}-search-overlay *[rel=search]`);0{t.removeClass("checked"),this.last.search_opened=!1})}}}searchClose(){w2tooltip.hide(this.name+"-search-overlay")}searchFieldTooltip(e,t,i){var e=this.searches[e],s=this.searchData[t];let l=s.operator,r=("less"==(l="more"==l&&"date"==s.type?"since":l)&&"date"==s.type&&(l="before"),""),n=s.value;Array.isArray(s.value)?(s.value.forEach(e=>{r+=`${e.text||e}`}),"date"==s.type&&(r="",s.value.forEach(e=>{r+=`${w2utils.formatDate(e)}`}))):"date"==s.type&&(n=w2utils.formatDateTime(n)),w2tooltip.hide(this.name+"-search-props"),w2tooltip.show({name:this.name+"-search-props",anchor:i,class:"w2ui-white",hideOn:"doc-click",html:` +
    + ${e.label} + ${w2utils.lang(l)} + ${Array.isArray(s.value)?""+r:`${n}`} +
    + +
    +
    `}).then(e=>{query(e.detail.overlay.box).find("#remove").on("click",()=>{this.searchData.splice(""+t,1),this.reload(),this.localSearch(),w2tooltip.hide(this.name+"-search-props")})})}searchSuggest(e,t,i){clearTimeout(this.last.kbd_timer),clearTimeout(this.last.overlay_timer),this.searchShowFields(!0),this.searchClose(),!0===t?w2tooltip.hide(this.name+"-search-suggest"):0${t}`:t}}).select(e=>{var t=this.trigger("searchSelect",{target:this.name,index:e.detail.index,item:e.detail.item});!0===t.isCancelled?e.preventDefault():(e.detail.overlay.hide(),this.last.logic=e.detail.item.logic||"AND",this.last.search="",this.last.label="[Multiple Fields]",this.searchData=w2utils.clone(e.detail.item.data),this.searchSelected=w2utils.clone(e.detail.item,{exclude:["icon","remove"]}),this.reload(),t.finish())}).remove(e=>{let i=e.detail.item,s=this.trigger("searchRemove",{target:this.name,index:e.detail.index,item:i});!0===s.isCancelled?e.preventDefault():(e.detail.overlay.hide(),this.confirm(w2utils.lang('Do you want to delete search "${item}"?',{item:i.text})).yes(e=>{var t=this.savedSearches.findIndex(e=>e.id==i.id);-1!==t&&this.savedSearches.splice(t,1),this.cacheSave("searches",this.savedSearches.map(e=>w2utils.clone(e,{exclude:["remove","icon"]}))),e.detail.self.close(),s.finish()}).no(e=>{e.detail.self.close()}))})):this.last.overlay_timer=setTimeout(()=>{this.searchSuggest(!0)},100))}searchSave(){let e="",t=(this.searchSelected&&(e=this.searchSelected.text),this.savedSearches.findIndex(e=>e.id==this.searchSelected?.id)),s=this.trigger("searchSave",{target:this.name,saveLocalStorage:!0});!0!==s.isCancelled&&this.message({width:350,height:150,body:``,buttons:` + + + `}).open(async i=>{query(i.detail.box).find("input, button").eq(0).val(e),await i.complete,query(i.detail.box).find("#grid-search-cancel").on("click",()=>{this.message()}),query(i.detail.box).find("#grid-search-save").on("click",()=>{var e=query(i.detail.box).find(".w2ui-message .search-name").val();this.searchSelected&&-1!=t?Object.assign(this.savedSearches[t],{id:e,text:e,logic:this.last.logic,data:w2utils.clone(this.searchData)}):this.savedSearches.push({id:e,text:e,icon:"w2ui-icon-search",remove:!0,logic:this.last.logic,data:this.searchData}),this.cacheSave("searches",this.savedSearches.map(e=>w2utils.clone(e,{exclude:["remove","icon"]}))),this.message(),(this.searchSelected?(this.searchSelected.text=e,query(this.box).find(`#grid_${this.name}_search_name .name-text`)):(this.searchSelected={text:e,logic:this.last.logic,data:w2utils.clone(this.searchData)},query(i.detail.box).find(`#grid_${this.name}_search_all`).val(" ").prop("readOnly",!0),query(i.detail.box).find(`#grid_${this.name}_search_name`).show().find(".name-text"))).html(e),s.finish({name:e})}),query(i.detail.box).find("input, button").off(".message").on("keydown.message",e=>{var t=String(query(i.detail.box).find(".w2ui-message-body input").val()).trim();13==e.keyCode&&""!=t&&query(i.detail.box).find("#grid-search-save").trigger("click"),27==e.keyCode&&this.message()}).eq(0).on("input.message",e=>{var t=query(i.detail.box).closest(".w2ui-message").find("#grid-search-save");""===String(query(i.detail.box).val()).trim()?t.prop("disabled",!0):t.prop("disabled",!1)}).get(0).focus()})}cache(e){if(w2utils.hasLocalStorage&&this.useLocalStorage)try{var t=JSON.parse(localStorage.w2ui||"{}");return t[this.stateId||this.name]??={},t[this.stateId||this.name][e]}catch(e){}return null}cacheSave(e,t){if(w2utils.hasLocalStorage&&this.useLocalStorage)try{var i=JSON.parse(localStorage.w2ui||"{}");return i[this.stateId||this.name]??={},i[this.stateId||this.name][e]=t,localStorage.w2ui=JSON.stringify(i),!0}catch(e){delete localStorage.w2ui}return!1}searchReset(e){var t=[];let i=!1;for(let e=0;e=this.searches.length?(this.last.field="",this.last.label=""):(this.last.field=this.searches[e].field,this.last.label=this.searches[e].label)}this.last.multi=!1,this.last.fetch.offset=0,this.last.scrollTop=0,this.last.scrollLeft=0,this.last.selection.indexes=[],this.last.selection.columns={},this.searchClose();l=l.val("").get(0);l?._w2field&&l._w2field.reset(),e||this.reload(),s.finish()}}searchShowFields(e){if(!0===e)w2tooltip.hide(this.name+"-search-fields");else{var l=[];for(let s=-1;s",e),e.label=e.caption),l.push({id:e.field,text:w2utils.lang(e.label),search:e,tooltip:i,disabled:t,checked:e.field==this.last.field})}w2menu.show({type:"radio",name:this.name+"-search-fields",anchor:query(this.box).find("#grid_"+this.name+"_search_name").parent().find(".w2ui-search-down").get(0),items:l,align:"none",hideOn:["doc-click","select"]}).select(e=>{this.searchInitInput(e.detail.item.search.field)})}}searchInitInput(e,t){let i;var s=query(this.box).find("#grid_"+this.name+"_search_all");if("all"==e)i={field:"all",label:w2utils.lang("All Fields")};else if(null==(i=this.getSearch(e)))return;""!=this.last.search?(this.last.label=i.label,this.search(i.field,this.last.search)):(this.last.field=i.field,this.last.label=i.label),s.attr("placeholder",w2utils.lang("Search")+" "+w2utils.lang(i.label||i.caption||i.field,!0))}clear(e){this.total=0,this.records=[],this.summary=[],this.last.fetch.offset=0,this.last.idCache={},this.last.selection={indexes:[],columns:{}},this.reset(!0),e||this.refresh()}reset(e){this.last.scrollTop=0,this.last.scrollLeft=0,this.last.range_start=null,this.last.range_end=null,query(this.box).find(`#grid_${this.name}_records`).prop("scrollTop",0),e||this.refresh()}skip(e,t){this.url?.get??this.url?(this.offset=parseInt(e),this.offset>this.total&&(this.offset=this.total-this.limit),(this.offset<0||!w2utils.isInt(this.offset))&&(this.offset=0),this.clear(!0),this.reload(t)):console.log("ERROR: grid.skip() can only be called when you have remote data source.")}load(e,t){return null==e?(console.log('ERROR: You need to provide url argument when calling .load() method of "'+this.name+'" object.'),new Promise((e,t)=>{t()})):(this.clear(!0),this.request("load",{},e,t))}reload(e){let t=this;var i=this.url?.get??this.url;return t.selectionSave(),i?this.load(i,()=>{t.selectionRestore(),"function"==typeof e&&e()}):(this.reset(!0),this.localSearch(),this.selectionRestore(),"function"==typeof e&&e({status:"success"}),new Promise(e=>{e()}))}prepareParams(i,e){var t=this.dataType??w2utils.settings.dataType;let s=e.body;switch(t){case"HTTPJSON":s={request:s},["PUT","DELETE"].includes(e.method)&&(e.method="POST"),l();break;case"HTTP":["PUT","DELETE"].includes(e.method)&&(e.method="POST"),l();break;case"RESTFULL":["PUT","DELETE"].includes(e.method)?e.headers["Content-Type"]="application/json":l();break;case"JSON":"GET"==e.method?(s={request:s},l()):(e.headers["Content-Type"]="application/json",e.method="POST")}return e.body="string"==typeof e.body?e.body:JSON.stringify(e.body),e;function l(){Object.keys(s).forEach(e=>{let t=s[e];"object"==typeof t&&(t=JSON.stringify(t)),i.searchParams.append(e,t)}),delete e.body}}request(i,e,t,s){let l=this,r,n;var a=new Promise((e,t)=>{r=e,n=t});if(null==e&&(e={}),!(t=t||this.url))return new Promise((e,t)=>{t()});w2utils.isInt(this.offset)||(this.offset=0),w2utils.isInt(this.last.fetch.offset)||(this.last.fetch.offset=0);let o;var h={limit:this.limit,offset:parseInt(this.offset)+parseInt(this.last.fetch.offset),searchLogic:this.last.logic,search:this.searchData.map(e=>{e=w2utils.clone(e);return this.searchMap&&this.searchMap[e.field]&&(e.field=this.searchMap[e.field]),e}),sort:this.sortData.map(e=>{e=w2utils.clone(e);return this.sortMap&&this.sortMap[e.field]&&(e.field=this.sortMap[e.field]),e})};if(0===this.searchData.length&&(delete h.search,delete h.searchLogic),0===this.sortData.length&&delete h.sort,w2utils.extend(h,this.postData),w2utils.extend(h,e),"delete"!=i&&"save"!=i||(delete h.limit,delete h.offset,"delete"==(h.action=i)&&(h[this.recid||"recid"]=this.getSelection())),"load"==i){if(!0===(o=this.trigger("request",{target:this.name,url:t,postData:h,httpMethod:"GET",httpHeaders:this.httpHeaders})).isCancelled)return new Promise((e,t)=>{t()})}else o={detail:{url:t,postData:h,httpMethod:"save"==i?"PUT":"DELETE",httpHeaders:this.httpHeaders}};if(0===this.last.fetch.offset&&this.lock(w2utils.lang(this.msgRefresh),!0),this.last.fetch.controller)try{this.last.fetch.controller.abort()}catch(e){}switch(t=o.detail.url,i){case"save":t?.save&&(t=t.save);break;case"delete":t?.remove&&(t=t.remove);break;default:t=t?.get??t}if(0{null!=e&&(200!=e?.status?u(e??{}):(l.unlock(),e.json().catch(u).then(e=>{this.requestComplete(e,i,s,r,n)})))}),"load"==i&&o.finish(),a;function u(e){var t;"AbortError"!==e?.name&&(l.unlock(),!0!==(t=l.trigger("error",{response:e,lastFetch:l.last.fetch})).isCancelled)&&(e.status&&200!=e.status?l.error(e.status+": "+e.statusText):(console.log("ERROR: Server communication failed.","\n EXPECTED:",{total:5,records:[{recid:1,field:"value"}]},"\n OR:",{error:!0,message:"error message"}),l.requestComplete({error:!0,message:w2utils.lang(this.msgHTTPError),response:e},i,s,r,n)),t.finish())}}requestComplete(e,t,i,s,l){let r=e.error??!1,n=(null==e.error&&"error"===e.status&&(r=!0),this.last.fetch.response=(Date.now()-this.last.fetch.start)/1e3,setTimeout(()=>{this.show.statusResponse&&this.status(w2utils.lang("Server Response ${count} seconds",{count:this.last.fetch.response}))},10),this.last.pull_more=!1,this.last.pull_refresh=!0,"load");"save"==this.last.fetch.action&&(n="save"),"delete"==this.last.fetch.action&&(n="delete");var a=this.trigger(n,{target:this.name,error:r,data:e,lastFetch:this.last.fetch});if(!0===a.isCancelled)l();else{if(r)this.error(w2utils.lang(e.message??this.msgServerError)),l(e);else if("function"==typeof this.parser?"object"!=typeof(e=this.parser(e))&&console.log("ERROR: Your parser did not return proper object"):null==e?e={error:!0,message:w2utils.lang(this.msgNotJSON)}:Array.isArray(e)&&(e={error:r,records:e,total:e.length}),"load"==t){if(null==e.total&&(e.total=-1),null==e.records&&(e.records=[]),e.records.length==this.limit?(l=this.records.length+e.records.length,this.last.fetch.hasMore=l!=this.total):(this.last.fetch.hasMore=!1,this.total=this.offset+this.last.fetch.offset+e.records.length),this.last.fetch.hasMore||query(this.box).find("#grid_"+this.name+"_rec_more, #grid_"+this.name+"_frec_more").hide(),0===this.last.fetch.offset)this.records=[],this.summary=[];else if(-1!=e.total&&parseInt(e.total)!=parseInt(this.total)){let e=this;return this.message(w2utils.lang(this.msgNeedReload)).ok(()=>{delete e.last.fetch.offset,e.reload()}),new Promise(e=>{e()})}w2utils.isInt(e.total)&&(this.total=parseInt(e.total)),e.records&&e.records.forEach(e=>{this.recid&&(e.recid=this.parseField(e,this.recid)),null==e.recid&&(e.recid="recid-"+this.records.length),(!0===e.w2ui?.summary?this.summary:this.records).push(e)}),e.summary&&(this.summary=[],e.summary.forEach(e=>{this.recid&&(e.recid=this.parseField(e,this.recid)),null==e.recid&&(e.recid="recid-"+this.summary.length),this.summary.push(e)}))}else if("delete"==t)return this.reset(),this.reload();(this.url?.get??this.url)||(this.localSort(),this.localSearch()),this.total=parseInt(this.total),0===this.last.fetch.offset?this.refresh():(this.scroll(),this.resize()),"function"==typeof i&&i(e),s(e),a.finish(),this.last.fetch.loaded=!0}}error(e){var t=this.trigger("error",{target:this.name,message:e});!0!==t.isCancelled&&(this.message(e),t.finish())}getChanges(t){var i=[];void 0===t&&(t=this.records);for(let e=0;e{e.error||this.mergeChanges(),s.finish(),"function"==typeof t&&t(e)}):(this.mergeChanges(),s.finish()))}editField(d,u,c,p){let f=this;if(!0===this.last.inEditMode)p&&13==p.keyCode?({index:m,column:g,value:y}=this.last._edit,this.editChange({type:"custom",value:y},m,g,p),this.editDone(m,g,p)):0<(y=query(this.box).find("div.w2ui-edit-box .w2ui-input")).length&&("DIV"==y.get(0).tagName?(y.text(y.text()+c),w2utils.setCursorPosition(y.get(0),y.text().length)):(y.val(y.val()+c),w2utils.setCursorPosition(y.get(0),y.val().length)));else{let o=this.get(d,!0),h=this.getCellEditable(o,u);if(h&&!["checkbox","check"].includes(h.type)){let n=this.records[o],a=this.columns[u];var m=!0===a.frozen?"_f":"_";if(-1!=["list","enum","file"].indexOf(h.type))console.log('ERROR: input types "list", "enum" and "file" are not supported in inline editing.');else{var g=this.trigger("editField",{target:this.name,recid:d,column:u,value:c,index:o,originalEvent:p});if(!0!==g.isCancelled){c=g.detail.value,this.last.inEditMode=!0,this.last.editColumn=u,this.last._edit={value:c,index:o,column:u,recid:d},this.selectNone(!0),this.select({recid:d,column:u});var y=query(this.box).find("#grid_"+this.name+m+"rec_"+w2utils.escapeId(d));let e=y.find('[col="'+u+'"] > div'),t=(this.last._edit.tr=y,this.last._edit.div=e,query(this.box).find("div.w2ui-edit-box").remove(),"row"!=this.selectType&&(query(this.box).find("#grid_"+this.name+m+"selection").attr("id","grid_"+this.name+"_editable").removeClass("w2ui-selection").addClass("w2ui-edit-box").prepend('
    ').find(".w2ui-selection-resizer").remove(),e=query(this.box).find("#grid_"+this.name+"_editable > div:first-child")),h.attr=h.attr??"",h.text=h.text??"",h.style=h.style??"",h.items=h.items??[],null!=n.w2ui?.changes?.[a.field]?w2utils.stripTags(n.w2ui.changes[a.field]):w2utils.stripTags(f.parseField(n,a.field))),i="object"!=typeof(t=null==t?"":t)?t:"",s=(null!=g.detail.prevValue&&(i=g.detail.prevValue),null!=c&&(t=c),null!=a.style?a.style+";":"");"string"==typeof a.render&&["number","int","float","money","percent","size"].includes(a.render.split(":")[0])&&(s+="text-align: right;"),0 div').get(0)),m=`font-family: ${p["font-family"]}; font-size: ${p["font-size"]};`;function w(e){try{var t=getComputedStyle(e),i="DIV"==e.tagName.toUpperCase()?e.innerText:e.value,s=query(f.box).find("#grid_"+f.name+"_editable").get(0),l=`font-family: ${t["font-family"]}; font-size: ${t["font-size"]}; white-space: no-wrap;`,r=w2utils.getStrWidth(i,l);r+20>s.clientWidth&&query(s).css("width",r+20+"px")}catch(e){}}"div"===h.type?(e.addClass("w2ui-editable").html(w2utils.stripSpaces(`
    +
    `+h.text)),(l=e.find("div.w2ui-input").get(0)).innerText="object"!=typeof t?t:"",null!=c?w2utils.setCursorPosition(l,l.innerText.length):w2utils.setCursorPosition(l,0,l.innerText.length)):(e.addClass("w2ui-editable").html(w2utils.stripSpaces(``+h.text)),l=e.find("input").get(0),"number"==h.type&&(t=w2utils.formatNumber(t)),"date"==h.type&&(t=w2utils.formatDate(w2utils.isDate(t,h.format,!0)||new Date,h.format)),l.value="object"!=typeof t?t:"",y=e=>{var t=this.last._edit?.escKey;let i=!1;var s=query(l).data("tooltipName");s&&null!=w2tooltip.get(s[0])?.selected&&(i=!0),!this.last.inEditMode||t||!r.includes(h.type)||e.detail.overlay.anchor?.id!=this.last._edit.input?.id&&"list"!=h.type||(this.editChange(),this.editDone(void 0,void 0,{keyCode:i?13:0}))},new w2field(w2utils.extend({},h,{el:l,selected:t,onSelect:y,onHide:y})),null==c&&l&&l.select()),Object.assign(this.last._edit,{input:l,edit:h}),query(l).off(".w2ui-editable").on("blur.w2ui-editable",e=>{var t,i;this.last.inEditMode&&(t=this.last._edit.edit.type,i=query(l).data("tooltipName"),r.includes(t)&&i||(this.editChange(l,o,u,e),this.editDone()))}).on("mousedown.w2ui-editable",e=>{e.stopPropagation()}).on("click.w2ui-editable",e=>{w.call(l,e)}).on("paste.w2ui-editable",e=>{e.preventDefault();e=e.clipboardData.getData("text/plain");document.execCommand("insertHTML",!1,e)}).on("keyup.w2ui-editable",e=>{w.call(l,e)}).on("keydown.w2ui-editable",i=>{switch(i.keyCode){case 8:"list"!=h.type||l._w2field||i.preventDefault();break;case 9:case 13:i.preventDefault();break;case 27:var e=query(l).data("tooltipName");e&&0{switch(i.keyCode){case 9:var e=i.shiftKey?f.prevCell(o,u,!0):f.nextCell(o,u,!0);null!=e&&(t=f.records[e.index].recid,this.editChange(l,o,u,i),this.editDone(o,u,i),"row"!=f.selectType?(f.selectNone(!0),f.select({recid:t,column:e.colIndex})):f.editField(t,e.colIndex,null,i),i.preventDefault)&&i.preventDefault();break;case 13:{let e=!1;var t=query(l).data("tooltipName");t&&null!=w2tooltip.get(t[0]).selected&&(e=!0),t&&e||(this.editChange(l,o,u,i),this.editDone(o,u,i));break}case 27:{this.last._edit.escKey=!1;let e=f.parseField(n,a.field);null!=n.w2ui?.changes?.[a.field]&&(e=n.w2ui.changes[a.field]),null!=l._prevValue&&(e=l._prevValue),"DIV"==l.tagName?l.innerText=null!=e?e:"":l.value=null!=e?e:"",this.editDone(o,u,i),setTimeout(()=>{f.select({recid:d,column:u})},1);break}}w(l)},1)}),l&&(l._prevValue=i),setTimeout(()=>{this.last.inEditMode&&l&&(l.focus(),clearTimeout(this.last.kbd_timer),(l.resize=w)(l))},50),g.finish({input:l})}}}}}editChange(e,t,i,s){e=e??this.last._edit.input,t=t??this.last._edit.index,i=i??this.last._edit.column,s=s??{};var l=(t<0?this.summary:this.records)[t=t<0?-t-1:t],r=this.columns[i];let n="DIV"==e?.tagName?e.innerText:e.value;var a=e._w2field,o=(a&&("list"==a.type&&(n=a.selected),0!==Object.keys(n).length&&null!=n||(n=""),w2utils.isPlainObject(n)||(n=a.clean(n))),"checkbox"==e.type&&(!1===l.w2ui?.editable&&(e.checked=!e.checked),n=e.checked),this.parseField(l,r.field)),h=l.w2ui?.changes&&l.w2ui.changes.hasOwnProperty(r.field)?l.w2ui.changes[r.field]:o;let d={target:this.name,input:e,recid:l.recid,index:t,column:i,originalEvent:s,value:{new:n,previous:h,original:o}},u=(null!=s.target?._prevValue&&(d.value.previous=s.target._prevValue),0);for(;u<20;){if(u++,"object"!=typeof(n=d.value.new)&&String(o)!=String(n)||"object"==typeof n&&n&&n.id!=o&&("object"!=typeof o||null==o||n.id!=o.id)){if(!0!==(d=this.trigger("change",d)).isCancelled){if(n!==d.detail.value.new)continue;(""!==d.detail.value.new&&null!=d.detail.value.new||""!==h&&null!=h)&&(l.w2ui=l.w2ui??{},l.w2ui.changes=l.w2ui.changes??{},l.w2ui.changes[r.field]=d.detail.value.new),d.finish()}}else if(!0!==(d=this.trigger("restore",d)).isCancelled){if(n!==d.detail.value.new)continue;l.w2ui?.changes&&(delete l.w2ui.changes[r.field],0===Object.keys(l.w2ui.changes).length)&&delete l.w2ui.changes,d.finish()}break}}editDone(t,i,s){if(t=t??this.last._edit.index,i=i??this.last._edit.column,s=s??{},this.advanceOnEdit&&13==s.keyCode){let e=s.shiftKey?this.prevRow(t,i,1):this.nextRow(t,i,1);null==e&&(e=t),setTimeout(()=>{"row"!=this.selectType?(this.selectNone(!0),this.select({recid:this.records[e].recid,column:i})):this.editField(this.records[e].recid,i,null,s)},1)}var e=t<0,l=query(this.last._edit.tr).find('[col="'+i+'"]'),r=this.records[t],n=this.columns[i];this.last.inEditMode=!1,this.last._edit=null,e||(null!=r.w2ui?.changes?.[n.field]?l.addClass("w2ui-changed"):l.removeClass("w2ui-changed"),l.replace(this.getCellHTML(t,i,e))),query(this.box).find("div.w2ui-edit-box").remove(),this.updateToolbar(),setTimeout(()=>{var e=query(this.box).find(`#grid_${this.name}_focus`).get(0);document.activeElement===e||this.last.inEditMode||e.focus()},10)}delete(e){var t=this.trigger("delete",{target:this.name,force:e});if(e&&this.message(),!0!==t.isCancelled){e=t.detail.force;var i=this.getSelection();if(0!==i.length)if(""==this.msgDelete||e){if("object"!=typeof this.url?this.url:this.url.remove)this.request("delete");else if("object"!=typeof i[0])this.selectNone(),this.remove.apply(this,i);else{for(let e=0;e{e.detail.self.close(),this.delete(!0)}).no(e=>{e.detail.self.close()})}}click(l,r){var n=Date.now();let a=null;if(!(1==this.last.cancelClick||r&&r.altKey))if("object"==typeof l&&null!==l&&(a=l.column,l=l.recid),null==r&&(r={}),n-parseInt(this.last.click_time)<350&&this.last.click_recid==l&&"click"==r.type)this.dblClick(l,r);else{this.last.bubbleEl&&(this.last.bubbleEl=null),this.last.click_time=n;n=this.last.click_recid;if(this.last.click_recid=l,null==a&&r.target){let e=r.target;"TD"!=e.tagName&&(e=query(e).closest("td")[0]),null!=query(e).attr("col")&&(a=parseInt(query(e).attr("col")))}var o=this.trigger("click",{target:this.name,recid:l,column:a,originalEvent:r});if(!0!==o.isCancelled){var h=this.getSelection(),d=(query(this.box).find("#grid_"+this.name+"_check_all").prop("checked",!1),this.get(l,!0)),u=[];this.last.sel_ind=d,this.last.sel_col=a,this.last.sel_recid=l,this.last.sel_type="click";let e,i,t,s;if(r.shiftKey&&0h[0].column?(t=h[0].column,a):(t=a,h[0].column);for(let e=t;e<=s;e++)u.push(e)}else e=this.get(n,!0),i=this.get(l,!0);var c=[],p=(e>i&&(n=e,e=i,i=n),this.url?.get?this.url.get:this.url);for(let t=e;t<=i;t++)if(!(0=this.records.length?this.selectNone():this.selectAll())}else if(t.altKey&&(l=this.getColumn(s))&&l.sortable&&this.sort(s,null,!(!t||!t.ctrlKey&&!t.metaKey)),"line-number"==e.detail.field)this.getSelection().length>=this.records.length?this.selectNone():this.selectAll();else{t.shiftKey||t.metaKey||t.ctrlKey||this.selectNone(!0);var l=this.getSelection(),s=this.getColumn(e.detail.field,!0),i=[],r=[];if(0!=l.length&&t.shiftKey){let t=s,i=l[0].column;t>i&&(t=l[0].column,i=s);for(let e=t;e<=i;e++)r.push(e)}else r.push(s);if(!0!==(e=this.trigger("columnSelect",{target:this.name,columns:r})).isCancelled){for(let e=0;e{var e=query(this.box).find(`#grid_${this.name}_focus`).get(0);e&&document.activeElement!=e&&e.focus()},10),e.finish()}blur(e){e=this.trigger("blur",{target:this.name,originalEvent:e});if(!0===e.isCancelled)return!1;this.hasFocus=!1,query(this.box).addClass("w2ui-inactive").find(".w2ui-selected").addClass("w2ui-inactive"),query(this.box).find(".w2ui-selection").addClass("w2ui-inactive"),e.finish()}keydown(c){let p=this,f="object"!=typeof this.url?this.url:this.url.get;if(!0===p.keyboard){var m=p.trigger("keydown",{target:p.name,originalEvent:c});if(!0!==m.isCancelled)if(0t&&p.last.sel_ind!=l?p.unselect(p.records[l].recid):p.select(p.records[t].recid);else if(p.last.sel_ind>t&&p.last.sel_ind!=l){t=l;var i=[];for(let e=0;e{var e=query(p.box).find("#grid_"+p.name+"_focus"),t=e.val();e.val(""),p.editField(n,a[0],t,c)},1)),d&&c.preventDefault&&c.preventDefault(),m.finish()}}}scrollIntoView(e,s,t,i){let l=this.records.length;if(0!==(l=0==this.searchData.length||this.url?l:this.last.searchIds.length)){if(null==e){var r=this.getSelection();if(0===r.length)return;w2utils.isPlainObject(r[0])?(e=r[0].index,s=r[0].column):e=this.get(r[0],!0)}var r=query(this.box).find(`#grid_${this.name}_records`),n=r[0].clientWidth,a=r[0].clientHeight,o=r[0].scrollTop,h=r[0].scrollLeft,d=this.last.searchIds.length;if(0{clearTimeout(this.last.kbd_timer),this.contextMenuClick(i,e)}),clearTimeout(this.last.kbd_timer)),e.preventDefault(),t.finish())}}contextMenuClick(e,t){e=this.trigger("contextMenuClick",{target:this.name,recid:e,originalEvent:t.detail.originalEvent,menuEvent:t,menuIndex:t.detail.index,menuItem:t.detail.item});!0!==e.isCancelled&&e.finish()}toggle(e){var t=this.get(e);if(null!=t)return t.w2ui=t.w2ui??{},!0===t.w2ui.expanded?this.collapse(e):this.expand(e)}expand(e,t){var i=this.get(e,!0);let s=this.records[i];s.w2ui=s.w2ui??{};var l=w2utils.escapeId(e),r=s.w2ui.children;let n;if(Array.isArray(r)){if(!0===s.w2ui.expanded||0===r.length)return!1;if(!0===(n=this.trigger("expand",{target:this.name,recid:e})).isCancelled)return!1;s.w2ui.expanded=!0,r.forEach(e=>{e.w2ui=e.w2ui??{},e.w2ui.parent_recid=s.recid,null==e.w2ui.children&&(e.w2ui.children=[])}),this.records.splice.apply(this.records,[i+1,0].concat(r)),-1!==this.total&&(this.total+=r.length),("object"!=typeof this.url?this.url:this.url.get)||(this.localSort(!0,!0),0 + +
    + + + `),query(this.box).find("#grid_"+this.name+"_frec_"+l).after(` + ${this.show.lineNumbers?'':""} + +
    + + `),!0===(n=this.trigger("expand",{target:this.name,recid:e,box_id:"grid_"+this.name+"_rec_"+e+"_expanded",fbox_id:"grid_"+this.name+"_frec_"+e+"_expanded"})).isCancelled)return query(this.box).find("#grid_"+this.name+"_rec_"+l+"_expanded_row").remove(),query(this.box).find("#grid_"+this.name+"_frec_"+l+"_expanded_row").remove(),!1;i=query(this.box).find("#grid_"+this.name+"_rec_"+e+"_expanded"),r=query(this.box).find("#grid_"+this.name+"_frec_"+e+"_expanded"),t=i.find(":scope div:first-child")[0]?.clientHeight??50;i[0].clientHeight{query(this.box).find("#grid_"+this.name+"_rec_"+e+"_expanded_row").remove(),query(this.box).find("#grid_"+this.name+"_frec_"+e+"_expanded_row").remove(),l.w2ui.expanded=!1,n.finish(),this.resizeRecords()},300)}return!0}sort(i,e,s){var t=this.trigger("sort",{target:this.name,field:i,direction:e,multiField:s});if(!0!==t.isCancelled){if(null!=i){let t=this.sortData.length;for(let e=0;ei&&(i=s[e].column),-1==r.indexOf(s[e].index)&&r.push(s[e].index);r.sort((e,t)=>e-t);for(let e=0;e!!e);e.classList.forEach(e=>{t.includes(e)||i.push(e)}),e.classList.remove(...i),e.classList.add(...o)}}if(u.columns[t].style&&u.columns[t].style!=e.style.cssText&&(e.style.cssText=u.columns[t].style??""),null!=s.w2ui.class){if("string"==typeof s.w2ui.class){let t=["w2ui-odd","w2ui-even","w2ui-record"],i=[];n=s.w2ui.class.split(" ").filter(e=>!!e);l&&r&&(l.classList.forEach(e=>{t.includes(e)||i.push(e)}),l.classList.remove(...i),l.classList.add(...n),r.classList.remove(...i),r.classList.add(...n))}if(w2utils.isPlainObject(s.w2ui.class)&&"string"==typeof s.w2ui.class[a.field]){let t=["w2ui-grid-data"],i=[];h=s.w2ui.class[a.field].split(" ").filter(e=>!!e);e.classList.forEach(e=>{t.includes(e)||i.push(e)}),e.classList.remove(...i),e.classList.add(...h)}}null!=s.w2ui.style&&(l&&r&&"string"==typeof s.w2ui.style&&l.style.cssText!==s.w2ui.style&&(l.style.cssText="height: "+u.recordHeight+"px;"+s.w2ui.style,l.setAttribute("custom_style",s.w2ui.style),r.style.cssText="height: "+u.recordHeight+"px;"+s.w2ui.style,r.setAttribute("custom_style",s.w2ui.style)),w2utils.isPlainObject(s.w2ui.style))&&"string"==typeof s.w2ui.style[a.field]&&e.style.cssText!==s.w2ui.style[a.field]&&(e.style.cssText=s.w2ui.style[a.field])}}}}refreshCell(e,t){var i=this.get(e,!0),t=this.getColumn(t,!0),e=!this.records[i]||this.records[i].recid!=e,s=query(this.box).find(`${e?".w2ui-grid-summary ":""}#grid_${this.name}_data_${i}_`+t);return 0!=s.length&&(s.replace(this.getCellHTML(i,t,e)),!0)}refreshRow(t,i=null){let s=query(this.box).find("#grid_"+this.name+"_frec_"+w2utils.escapeId(t)),l=query(this.box).find("#grid_"+this.name+"_rec_"+w2utils.escapeId(t));if(0{var t=[];for(let e=0;e{var t=query(this.box).find('td[col="'+e.col+'"]:not(.w2ui-head)');w2utils.marker(t,e.search)})},50),this.updateToolbar(),t.finish(),this.resize(),this.addRange("selection"),setTimeout(()=>{this.resize(),this.scroll()},1),this.reorderColumns&&!this.last.columnDrag?this.last.columnDrag=this.initColumnDrag():!this.reorderColumns&&this.last.columnDrag&&this.last.columnDrag.remove(),Date.now()-e}}}refreshSearch(){if(this.multiSearch&&0`);let r=` + +
    `;this.searchData.forEach((i,e)=>{var t=this.getSearch(i.field,!0),s=this.searches[t];let l;if(l=Array.isArray(i.value)?`${i.value.length}`:": "+i.value,s&&"date"==s.type)if("between"==i.operator){let e=i.value[0],t=i.value[1];Number(e)===e&&(e=w2utils.formatDate(e)),Number(t)===t&&(t=w2utils.formatDate(t)),l=`: ${e} - `+t}else{let e=i.value,t=(Number(e)==e&&(e=w2utils.formatDate(e)),i.operator);"more:"==(t="less"==(t="more"==t?"since":t)?"before":t).substr(0,5)&&(t="since"),l=`: ${t} `+e}r+=` + ${s?s.label:""} + ${l} + + `}),r+=` + ${this.show.searchSave?`
    + + `:""} + + `,query(this.box).find(`#grid_${this.name}_searches`).html(r),query(this.box).find(`#grid_${this.name}_search_logic`).html(w2utils.lang("AND"==this.last.logic?"All":"Any"))}else query(this.box).find(".w2ui-grid-toolbar").css("height",this.last.toolbar_height+"px").find(".w2ui-grid-searches").remove();this.searchSelected?(query(this.box).find(`#grid_${this.name}_search_all`).val(" ").prop("readOnly",!0),query(this.box).find(`#grid_${this.name}_search_name`).show().find(".name-text").html(this.searchSelected.text)):(query(this.box).find(`#grid_${this.name}_search_all`).prop("readOnly",!1),query(this.box).find(`#grid_${this.name}_search_name`).hide().find(".name-text").html("")),w2utils.bindEvents(query(this.box).find(`#grid_${this.name}_searches .w2ui-action, #grid_${this.name}_searches button`),this)}refreshBody(){this.scroll();var e=this.getRecordsHTML(),t=this.getColumnsHTML(),e='
    '+e[0]+'
    '+e[1]+'
    '+t[0]+'
    '+t[1]+"
    "+``;let l=query(this.box).find(`#grid_${this.name}_body`,this.box).html(e);t=query(this.box).find(`#grid_${this.name}_records`,this.box),e=query(this.box).find(`#grid_${this.name}_frecords`,this.box);"row"==this.selectType&&(t.on("mouseover mouseout",{delegate:"tr"},e=>{var t=query(e.delegate).attr("recid");query(this.box).find(`#grid_${this.name}_frec_`+w2utils.escapeId(t)).toggleClass("w2ui-record-hover","mouseover"==e.type)}),e.on("mouseover mouseout",{delegate:"tr"},e=>{var t=query(e.delegate).attr("recid");query(this.box).find(`#grid_${this.name}_rec_`+w2utils.escapeId(t)).toggleClass("w2ui-record-hover","mouseover"==e.type)})),w2utils.isIOS?t.append(e).on("click",{delegate:"tr"},e=>{var t=query(e.delegate).attr("recid");this.dblClick(t,e)}):t.add(e).on("click",{delegate:"tr"},e=>{var t=query(e.delegate).attr("recid");"-none-"!=t&&this.click(t,e)}).on("contextmenu",{delegate:"tr"},e=>{var t=query(e.delegate).attr("recid"),i=query(e.target).closest("td"),i=parseInt(i.attr("col")??-1);this.showContextMenu(t,i,e)}).on("mouseover",{delegate:"tr"},e=>{this.last.rec_out=!1;let t=query(e.delegate).attr("index"),i=query(e.delegate).attr("recid");t!==this.last.rec_over&&(this.last.rec_over=t,setTimeout(()=>{delete this.last.rec_out,this.trigger("mouseEnter",{target:this.name,originalEvent:e,index:t,recid:i}).finish()}))}).on("mouseout",{delegate:"tr"},t=>{let i=query(t.delegate).attr("index"),s=query(t.delegate).attr("recid");this.last.rec_out=!0,setTimeout(()=>{let e=()=>{this.trigger("mouseLeave",{target:this.name,originalEvent:t,index:i,recid:s}).finish()};i!==this.last.rec_over&&e(),setTimeout(()=>{this.last.rec_out&&(delete this.last.rec_out,delete this.last.rec_over,e())})})}),l.data("scroll",{lastDelta:0,lastTime:0}).find(".w2ui-grid-frecords").on("mousewheel DOMMouseScroll ",e=>{e.preventDefault();var t=l.data("scroll"),i=l.find(".w2ui-grid-records"),e=null!=typeof e.wheelDelta?-e.wheelDelta:e.detail||e.deltaY,s=i.prop("scrollTop");t.lastDelta+=e,e=Math.round(t.lastDelta),l.data("scroll",t),i.get(0).scroll({top:s+e,behavior:"smooth"})}),t.off(".body-global").on("scroll.body-global",{delegate:".w2ui-grid-records"},e=>{this.scroll(e)}),query(this.box).find(".w2ui-grid-body").off(".body-global").on("click.body-global dblclick.body-global contextmenu.body-global",{delegate:"td.w2ui-head"},e=>{var t=query(e.delegate).attr("col"),i=this.columns[t]??{field:t};switch(e.type){case"click":this.columnClick(i.field,e);break;case"dblclick":this.columnDblClick(i.field,e);break;case"contextmenu":this.show.columnMenu&&(w2menu.show({type:"check",anchor:document.body,originalEvent:e,items:this.initColumnOnOff()}).then(()=>{query("#w2overlay-context-menu .w2ui-grid-skip").off(".w2ui-grid").on("click.w2ui-grid",e=>{e.stopPropagation()}).on("keypress",e=>{13==e.keyCode&&(this.skip(e.target.value),this.toolbar.click("w2ui-column-on-off"))})}).select(e=>{var t=e.detail.item.id;["w2ui-stateSave","w2ui-stateReset"].includes(t)?this[t.substring(5)]():"w2ui-skip"!=t&&this.columnOnOff(e,e.detail.item.id),clearTimeout(this.last.kbd_timer)}),clearTimeout(this.last.kbd_timer)),e.preventDefault()}}).on("mouseover.body-global",{delegate:".w2ui-col-header"},e=>{let t=query(e.delegate).parent().attr("col");this.columnTooltipShow(t,e),query(e.delegate).off(".tooltip").on("mouseleave.tooltip",()=>{this.columnTooltipHide(t,e)})}).on("click.body-global",{delegate:"input.w2ui-select-all"},e=>{e.delegate.checked?this.selectAll():this.selectNone(),e.stopPropagation(),clearTimeout(this.last.kbd_timer)}).on("click.body-global",{delegate:".w2ui-show-children, .w2ui-col-expand"},e=>{e.stopPropagation(),this.toggle(query(e.target).parents("tr").attr("recid"))}).on("click.body-global mouseover.body-global",{delegate:".w2ui-info"},e=>{var t=query(e.delegate).closest("td"),i=t.parent(),s=this.columns[t.attr("col")],l=i.parents(".w2ui-grid-body").hasClass("w2ui-grid-summary");["mouseenter","mouseover"].includes(s.info?.showOn?.toLowerCase())&&"mouseover"==e.type?this.showBubble(i.attr("index"),t.attr("col"),l).then(()=>{query(e.delegate).off(".tooltip").on("mouseleave.tooltip",()=>{w2tooltip.hide(this.name+"-bubble")})}):"click"==e.type&&(w2tooltip.hide(this.name+"-bubble"),this.showBubble(i.attr("index"),t.attr("col"),l))}).on("mouseover.body-global",{delegate:".w2ui-clipboard-copy"},l=>{if(!l.delegate._tooltipShow){let t=query(l.delegate).parent(),i=t.parent();var e=this.columns[t.attr("col")];let s=i.parents(".w2ui-grid-body").hasClass("w2ui-grid-summary");w2tooltip.show({name:this.name+"-bubble",anchor:l.delegate,html:w2utils.lang("string"==typeof e.clipboardCopy?e.clipboardCopy:"Copy to clipboard"),position:"top|bottom",offsetY:-2}).hide(e=>{l.delegate._tooltipShow=!1,query(l.delegate).off(".tooltip")}),query(l.delegate).off(".tooltip").on("mouseleave.tooltip",e=>{w2tooltip.hide(this.name+"-bubble")}).on("click.tooltip",e=>{e.stopPropagation(),w2tooltip.update(this.name+"-bubble",w2utils.lang("Copied")),this.clipboardCopy(i.attr("index"),t.attr("col"),s)}),l.delegate._tooltipShow=!0}}).on("click.body-global",{delegate:".w2ui-editable-checkbox"},e=>{var t=query(e.delegate).data();this.editChange.call(this,e.delegate,t.changeind,t.colind,e),this.updateToolbar()}),0===this.records.length&&this.msgEmpty?query(this.box).find(`#grid_${this.name}_body`).append(`
    ${w2utils.lang(this.msgEmpty)}
    `):0=this.searches.length?(this.last.field="",this.last.label=""):(this.last.field=this.searches[e].field,this.last.label=this.searches[e].label)}if(query(this.box).attr("name",this.name).addClass("w2ui-reset w2ui-grid w2ui-inactive").html('
    "),"row"!=this.selectType&&query(this.box).addClass("w2ui-ss"),0{this.searchInitInput(this.last.field,1==e.length?e[0].value:null)},1)}query(this.box).find(`#grid_${this.name}_footer`).html(this.getFooterHTML()),this.last.state||(this.last.state=this.stateSave(!0)),this.stateRestore(),e&&(this.clear(),this.refresh());let t=!1;for(let e=0;e{this.searchReset()},1)):this.reload(),query(this.box).find(`#grid_${this.name}_focus`).on("focus",e=>{clearTimeout(this.last.kbd_timer),this.hasFocus||this.focus()}).on("blur",e=>{clearTimeout(this.last.kbd_timer),this.last.kbd_timer=setTimeout(()=>{this.hasFocus&&this.blur()},100)}).on("paste",i=>{var s=i.clipboardData||null;if(s){let e=s.items,t=[];for(var l in e=2==e.length&&2==(e=2==e.length&&"file"==e[1].kind?[e[1]]:e).length&&"text/plain"==e[0].type&&"text/html"==e[1].type?[e[1]]:e){l=e[l];if("file"===l.kind){var r=l.getAsFile();t.push({kind:"file",data:r})}else if("string"===l.kind&&("text/plain"===l.type||"text/html"===l.type)){i.preventDefault();let e=s.getData("text/plain");-1!=e.indexOf("\r")&&-1==e.indexOf("\n")&&(e=e.replace(/\r/g,"\n")),t.push({kind:"text/html"==l.type?"html":"text",data:e})}}1===t.length&&"file"!=t[0].kind&&(t=t[0].data),w2ui[this.name].paste(t,i),i.preventDefault()}}).on("keydown",function(e){w2ui[p.name].keydown.call(w2ui[p.name],e)});let c;return query(this.box).off("mousedown.mouseStart").on("mousedown.mouseStart",function(l){if(1==l.which&&("text"==p.last.userSelect&&(p.last.userSelect="",query(p.box).find(".w2ui-grid-body").css("user-select","none")),!("row"==p.selectType&&(query(l.target).parents().hasClass("w2ui-head")||query(l.target).hasClass("w2ui-head"))||p.last.move&&"expand"==p.last.move.type))){if(l.altKey)query(p.box).find(".w2ui-grid-body").css("user-select","text"),p.selectNone(),p.last.move={type:"text-select"},p.last.userSelect="text";else{let e=l.target;var r={x:l.offsetX-10,y:l.offsetY-10};let t=!1;for(;e&&(!e.classList||!e.classList.contains("w2ui-grid"));)e.tagName&&"TD"==e.tagName.toUpperCase()&&(t=!0),e.tagName&&"TR"!=e.tagName.toUpperCase()&&1==t&&(r.x+=e.offsetLeft,r.y+=e.offsetTop),e=e.parentNode;p.last.move={x:l.screenX,y:l.screenY,divX:0,divY:0,focusX:r.x,focusY:r.y,recid:query(l.target).parents("tr").attr("recid"),column:parseInt(("TD"==l.target.tagName.toUpperCase()?query(l.target):query(l.target).parents("td")).attr("col")),type:"select",ghost:!1,start:!0},null==p.last.move.recid&&(p.last.move.type="select-column");let i=l.target,s=query(p.box).find("#grid_"+p.name+"_focus");if(p.last.move){let e=p.last.move.focusX,t=p.last.move.focusY;var n=query(i).parents("table").parent();(n.hasClass("w2ui-grid-records")||n.hasClass("w2ui-grid-frecords")||n.hasClass("w2ui-grid-columns")||n.hasClass("w2ui-grid-fcolumns")||n.hasClass("w2ui-grid-summary"))&&(e=p.last.move.focusX-query(p.box).find("#grid_"+p.name+"_records").prop("scrollLeft"),t=p.last.move.focusY-query(p.box).find("#grid_"+p.name+"_records").prop("scrollTop")),(query(i).hasClass("w2ui-grid-footer")||0{p.last.inEditMode||(["INPUT","TEXTAREA","SELECT"].includes(i.tagName)?i.focus():s.get(0)!==document.active&&s.get(0)?.focus({preventScroll:!0}))},50),p.multiSelect||p.reorderRows||"drag"!=p.last.move.type||delete p.last.move}if(1==p.reorderRows){let e=l.target;var t,i,s,a;"TD"!=e.tagName.toUpperCase()&&(e=query(e).parents("td")[0]),query(e).hasClass("w2ui-col-number")||query(e).hasClass("w2ui-col-order")?(p.selectNone(),p.last.move.reorder=!0,n=query(p.box).find(".w2ui-even.w2ui-empty-record").css("background-color"),t=query(p.box).find(".w2ui-odd.w2ui-empty-record").css("background-color"),query(p.box).find(".w2ui-even td").filter(":not(.w2ui-col-number)").css("background-color",n),query(p.box).find(".w2ui-odd td").filter(":not(.w2ui-col-number)").css("background-color",t),t=p.last.move,i=query(p.box).find(".w2ui-grid-records"),t.ghost||(s=query(p.box).find(`#grid_${p.name}_rec_`+t.recid),a=s.parents("table").find("tr:first-child").get(0).cloneNode(!0),t.offsetY=l.offsetY,t.from=t.recid,t.pos={top:s.get(0).offsetTop-1,left:s.get(0).offsetLeft},t.ghost=query(s.get(0).cloneNode(!0)),t.ghost.removeAttr("id"),t.ghost.find("td").css({"border-top":"1px solid silver","border-bottom":"1px solid silver"}),s.find("td").remove(),s.append(`
    `),i.append('
    '),i.append('
    '),query(p.box).find("#grid_"+p.name+"_ghost").append(a).append(t.ghost)),query(p.box).find("#grid_"+p.name+"_ghost").css({top:t.pos.top+"px",left:t.pos.left+"px"})):p.last.move.reorder=!1}query(document).on("mousemove.w2ui-"+p.name,o).on("mouseup.w2ui-"+p.name,h),l.stopPropagation()}}),this.updateToolbar(),s.finish(),this.last.observeResize=new ResizeObserver(()=>{this.resize()}),this.last.observeResize.observe(this.box),Date.now()-i;function o(t){if(t.target.tagName){var r=p.last.move;if(r&&-1!=["select","select-column"].indexOf(r.type)&&(r.divX=t.screenX-r.x,r.divY=t.screenY-r.y,!(Math.abs(r.divX)<=1&&Math.abs(r.divY)<=1)))if(p.last.cancelClick=!0,1==p.reorderRows&&p.last.move.reorder){let e=query(t.target).parents("tr").attr("recid");(e="-none-"==e?"bottom":e)!=r.from&&(a=query(p.box).find("#grid_"+p.name+"_rec_"+e),query(p.box).find(".insert-before"),a.addClass("insert-before"),r.lastY=t.screenY,r.to=e,a={top:a.get(0)?.offsetTop,left:a.get(0)?.offsetLeft},query(p.box).find("#grid_"+p.name+"_ghost_line").css({top:a.top+"px",left:r.pos.left+"px","border-top":"2px solid #769EFC"})),void query(p.box).find("#grid_"+p.name+"_ghost").css({top:r.pos.top+r.divY+"px",left:r.pos.left+"px"})}else{r.start&&r.recid&&(p.selectNone(),r.start=!1);var n=[],a=("TR"==t.target.tagName.toUpperCase()?query(t.target):query(t.target).parents("tr")).attr("recid");if(null==a){if("row"!=p.selectType&&(!p.last.move||"select"!=p.last.move.type)){var o=parseInt(query(t.target).parents("td").attr("col"));if(isNaN(o))p.removeRange("column-selection"),query(p.box).find(".w2ui-grid-columns .w2ui-col-header, .w2ui-grid-fcolumns .w2ui-col-header").removeClass("w2ui-col-selected"),query(p.box).find(".w2ui-col-number").removeClass("w2ui-row-selected"),delete r.colRange;else{let e=o+"-"+o;r.columno?o+"-"+r.column:e).split("-");for(let e=parseInt(s[0]);e<=parseInt(s[1]);e++)i.push(e);if(r.colRange!=e&&!0!==(c=p.trigger("columnSelect",{target:p.name,columns:i})).isCancelled){null==r.colRange&&p.selectNone();var l=e.split("-");query(p.box).find(".w2ui-grid-columns .w2ui-col-header, .w2ui-grid-fcolumns .w2ui-col-header").removeClass("w2ui-col-selected");for(let e=parseInt(l[0]);e<=parseInt(l[1]);e++)query(p.box).find("#grid_"+p.name+"_column_"+e+" .w2ui-col-header").addClass("w2ui-col-selected");query(p.box).find(".w2ui-col-number").not(".w2ui-head").addClass("w2ui-row-selected"),r.colRange=e,p.removeRange("column-selection"),p.addRange({name:"column-selection",range:[{recid:p.records[0].recid,column:l[0]},{recid:p.records[p.records.length-1].recid,column:l[1]}],style:"background-color: rgba(90, 145, 234, 0.1)"})}}}}else{let l=p.get(r.recid,!0);if(!(null==l||p.records[l]&&p.records[l].recid!=r.recid)){let e=p.get(a,!0);if(null!=e){let i=parseInt(r.column),s=parseInt(("TD"==t.target.tagName.toUpperCase()?query(t.target):query(t.target).parents("td")).attr("col"));isNaN(i)&&isNaN(s)&&(i=0,s=p.columns.length-1),l>e&&(o=l,l=e,e=o);var h,a="ind1:"+l+",ind2;"+e+",col1:"+i+",col2:"+s;if(r.range!=a){r.range=a;for(let t=l;t<=e;t++)if(!(0s&&(h=i,i=s,s=h);for(let e=i;e<=s;e++)p.columns[e].hidden||n.push({recid:p.records[t].recid,column:parseInt(e)})}else n.push(p.records[t].recid);if("row"!=p.selectType){var d=p.getSelection();let e=[];for(let i=0;i{delete p.last.cancelClick},1),!query(t.target).parents().hasClass(".w2ui-head")&&!query(t.target).hasClass(".w2ui-head")){if(i&&-1!=["select","select-column"].indexOf(i.type)){if(null!=i.colRange&&!0!==c.isCancelled){var s=i.colRange.split("-"),l=[];for(let e=0;ee?p.records.splice(e,0,i):p.records.splice(e-1,0,i)),p.sortData=[],query(p.box).find(`#grid_${p.name}_columns .w2ui-col-header`).removeClass("w2ui-col-sorted"),a(),t.finish()}else a()}delete p.last.move,query(document).off(".w2ui-"+p.name)}}function a(){query(p.box).find(`#grid_${p.name}_ghost`).remove(),query(p.box).find(`#grid_${p.name}_ghost_line`).remove(),p.refresh(),delete p.last.move}}}destroy(){var e=this.trigger("destroy",{target:this.name});!0!==e.isCancelled&&(query(this.box).off(),"object"==typeof this.toolbar&&this.toolbar.destroy&&this.toolbar.destroy(),0`+w2utils.lang("records"),i.push({id:"w2ui-skip",text:e,group:!1,icon:"w2ui-icon-empty"})),this.show.saveRestoreState&&i.push({id:"w2ui-stateSave",text:w2utils.lang("Save Grid State"),icon:"w2ui-icon-empty",group:!1},{id:"w2ui-stateReset",text:w2utils.lang("Restore Default State"),icon:"w2ui-icon-empty",group:!1});let t=[];return i.forEach(e=>{e.text=w2utils.lang(e.text),e.checked&&t.push(e.id)}),this.toolbar.set("w2ui-column-on-off",{selected:t,items:i}),i}initColumnDrag(e){if(this.columnGroups&&this.columnGroups.length)throw"Draggable columns are not currently supported with column groups.";let r=this,n={pressed:!1,targetPos:null,columnHead:null},a=(t,e)=>{var i=["w2ui-col-number","w2ui-col-expand","w2ui-col-select"];!0!==e&&i.push("w2ui-head-last");for(let e=0;e{var e=query(r.box).find(".w2ui-grid-ghost");query(r.box).find(".w2ui-intersection-marker").hide(),query(n.ghost).remove(),e.remove(),query(document).off(".colDrag"),n={}};if(e.pageX==n.initialX&&e.pageY==n.initialY)r.columnClick(r.columns[n.originalPos].field,e),s();else{if(!0===(e=r.trigger("columnDragEnd",{originalEvent:e,target:n.columnHead[0],dragData:n})).isCancelled)return!1;t=r.columns[n.originalPos],i=r.columns,n.originalPos!=n.targetPos&&null!=n.targetPos&&(i.splice(n.targetPos,0,w2utils.clone(t)),i.splice(i.indexOf(t),1)),s(),r.refresh(),e.finish({targetColumn:NaN})}}}return query(r.box).off(".colDrag").on("mousedown.colDrag",function(e){if(!n.pressed&&0!==n.numberPreColumnsPresent&&0===e.button){var i,t;if(query(e.target).parents().hasClass("w2ui-head")&&!a(e.target)){if(n.pressed=!0,n.initialX=e.pageX,n.initialY=e.pageY,n.numberPreColumnsPresent=query(r.box).find(".w2ui-head.w2ui-col-number, .w2ui-head.w2ui-col-expand, .w2ui-head.w2ui-col-select").length,n.columnHead=s=query(e.target).closest(".w2ui-head"),n.originalPos=t=parseInt(s.attr("col"),10),!0===(t=r.trigger("columnDragStart",{originalEvent:e,origColumnNumber:t,target:s[0]})).isCancelled)return!1;i=n.columns=query(r.box).find(".w2ui-head:not(.w2ui-head-last)"),query(document).on("mouseup.colDrag",h),query(document).on("mousemove.colDrag",o);var s=r.columns[n.originalPos],s=w2utils.lang("function"==typeof s.text?s.text(s):s.text);n.ghost=query.html(`${s}`)[0],query(document.body).append(n.ghost),query(n.ghost).css({display:"none",left:e.pageX,top:e.pageY,opacity:1,margin:"3px 0 0 20px",padding:"3px","background-color":"white",position:"fixed","z-index":999999}).addClass(".w2ui-grid-ghost"),n.offsets=[];for(let e=0,t=i.length;e + ${this.buttons.search.html} +
    + + + x +
    + +
    + +
    + `,this.toolbar.items.push({id:"w2ui-search",type:"html",html:t,onRefresh:async e=>{await e.complete;var e=query(this.box).find(`#grid_${this.name}_search_all`),t=(w2utils.bindEvents(query(this.box).find(`#grid_${this.name}_search_all, .w2ui-action`),this),w2utils.debounce(e=>{var t=e.target.value;this.liveSearch&&this.last.liveText!=t&&(this.last.liveText=t,this.search(this.last.field,t)),40==e.keyCode&&this.searchSuggest(!0)},250));e.on("change",e=>{this.liveSearch||(this.search(this.last.field,e.target.value),this.searchSuggest(!0,!0,this))}).on("blur",()=>{this.last.liveText=""}).on("keyup",t)}})),Array.isArray(e)&&(t=e.map(e=>e.id),this.show.toolbarAdd&&!t.includes(this.buttons.add.id)&&this.toolbar.items.push(w2utils.extend({},this.buttons.add)),this.show.toolbarEdit&&!t.includes(this.buttons.edit.id)&&this.toolbar.items.push(w2utils.extend({},this.buttons.edit)),this.show.toolbarDelete&&!t.includes(this.buttons.delete.id)&&this.toolbar.items.push(w2utils.extend({},this.buttons.delete)),this.show.toolbarSave&&!t.includes(this.buttons.save.id)&&((this.show.toolbarAdd||this.show.toolbarDelete||this.show.toolbarEdit)&&this.toolbar.items.push({type:"break",id:"w2ui-break2"}),this.toolbar.items.push(w2utils.extend({},this.buttons.save))),e=e.map(e=>this.buttons[e.name]?w2utils.extend({},this.buttons[e.name],e):e)),this.toolbar.items.push(...e),this.toolbar.on("click",e=>{var i=this.trigger("toolbar",{target:e.target,originalEvent:e});if(!0!==i.isCancelled){let t;switch(e.detail.item.id){case"w2ui-reload":if(!0===(t=this.trigger("reload",{target:this.name})).isCancelled)return!1;this.reload(),t.finish();break;case"w2ui-column-on-off":e.detail.subItem?(s=e.detail.subItem.id,["w2ui-stateSave","w2ui-stateReset"].includes(s)?this[s.substring(5)]():"w2ui-skip"!=s&&this.columnOnOff(e,e.detail.subItem.id)):(this.initColumnOnOff(),setTimeout(()=>{query(`#w2overlay-${this.name}_toolbar-drop .w2ui-grid-skip`).off(".w2ui-grid").on("click.w2ui-grid",e=>{e.stopPropagation()}).on("keypress",e=>{13==e.keyCode&&(this.skip(e.target.value),this.toolbar.click("w2ui-column-on-off"))})},100));break;case"w2ui-add":if(!0===(t=this.trigger("add",{target:this.name,recid:null})).isCancelled)return!1;t.finish();break;case"w2ui-edit":{var s=this.getSelection();let e=null;if(1==s.length&&(e=s[0]),!0===(t=this.trigger("edit",{target:this.name,recid:e})).isCancelled)return!1;t.finish();break}case"w2ui-delete":this.delete();break;case"w2ui-save":this.save()}i.finish()}}),this.toolbar.on("refresh",e=>{if("w2ui-search"==e.target){let e=this.searchData;setTimeout(()=>{this.searchInitInput(this.last.field,1==e.length?e[0].value:null)},1)}})}}initResize(){let r=this;query(this.box).find(".w2ui-resizer").off(".grid-col-resize").on("click.grid-col-resize",function(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0,e.preventDefault&&e.preventDefault()}).on("mousedown.grid-col-resize",function(e){e=e||window.event,r.last.colResizing=!0,r.last.tmp={x:e.screenX,y:e.screenY,gx:e.screenX,gy:e.screenY,col:parseInt(query(this).attr("name"))},r.last.tmp.tds=query(r.box).find("#grid_"+r.name+'_body table tr:first-child td[col="'+r.last.tmp.col+'"]'),e.stopPropagation?e.stopPropagation():e.cancelBubble=!0,e.preventDefault&&e.preventDefault();for(let e=0;e{r.resizeRecords(),r.scroll()},100),r.last.tmp.tds.css({width:t}),r.last.tmp.x=e.screenX,r.last.tmp.y=e.screenY))}).on("mouseup.grid-col-resize",function(e){query(document).off(".grid-col-resize"),r.resizeRecords(),r.scroll(),i.finish({originalEvent:e}),setTimeout(()=>{r.last.colResizing=!1},1)})}).on("dblclick.grid-col-resize",function(e){let t=parseInt(query(this).attr("name")),i=r.columns[t],s=0;if(!1===i.autoResize)return!0;e.stopPropagation?e.stopPropagation():e.cancelBubble=!0,e.preventDefault&&e.preventDefault(),query(r.box).find('.w2ui-grid-records td[col="'+t+'"] > div',r.box).each(()=>{var e=this.offsetWidth-this.scrollWidth;e{var t=query(e).get(0).parentNode;query(e).css({height:t.clientHeight+"px","margin-left":t.clientWidth-3+"px"})})}resizeBoxes(){var e=query(this.box).find(`#grid_${this.name}_header`),t=query(this.box).find(`#grid_${this.name}_toolbar`),i=query(this.box).find(`#grid_${this.name}_fsummary`),s=query(this.box).find(`#grid_${this.name}_summary`),l=query(this.box).find(`#grid_${this.name}_footer`),r=query(this.box).find(`#grid_${this.name}_body`);this.show.header&&e.css({top:"0px",left:"0px",right:"0px"}),this.show.toolbar&&t.css({top:0+(this.show.header?w2utils.getSize(e,"height"):0)+"px",left:"0px",right:"0px"}),0 div.w2ui-grid-box"),r=query(this.box).find(`#grid_${this.name}_header`),n=query(this.box).find(`#grid_${this.name}_toolbar`),a=query(this.box).find(`#grid_${this.name}_summary`),o=query(this.box).find(`#grid_${this.name}_fsummary`),h=query(this.box).find(`#grid_${this.name}_footer`),d=query(this.box).find(`#grid_${this.name}_body`),u=query(this.box).find(`#grid_${this.name}_columns`),c=query(this.box).find(`#grid_${this.name}_fcolumns`),p=query(this.box).find(`#grid_${this.name}_records`),f=query(this.box).find(`#grid_${this.name}_frecords`),m=query(this.box).find(`#grid_${this.name}_scroll1`);let g=8*String(this.total).length+10,y=(g<34&&(g=34),null!=this.lineNumberWidth&&(g=this.lineNumberWidth),!1),w=!1,b=0;for(let e=0;e table")[0]?.clientHeight??0)+(y?w2utils.scrollBarSize():0)&&(w=!0),this.fixedBody?(e=l[0]?.clientHeight-(this.show.header?w2utils.getSize(r,"height"):0)-(this.show.toolbar?w2utils.getSize(n,"height"):0)-("none"!=a.css("display")?w2utils.getSize(a,"height"):0)-(this.show.footer?w2utils.getSize(h,"height"):0),d.css("height",e+"px")):(r=(e=w2utils.getSize(u,"height")+w2utils.getSize(query(this.box).find("#grid_"+this.name+"_records table"),"height")+(y?w2utils.scrollBarSize():0))+(this.show.header?w2utils.getSize(r,"height"):0)+(this.show.toolbar?w2utils.getSize(n,"height"):0)+("none"!=a.css("display")?w2utils.getSize(a,"height"):0)+(this.show.footer?w2utils.getSize(h,"height"):0),l.css("height",r+"px"),d.css("height",e+"px"),s.css("height",w2utils.getSize(l,"height")+"px"));let v=this.records.length;n="object"!=typeof this.url?this.url:this.url.get;if(0==this.searchData.length||n||(v=this.last.searchIds.length),this.fixedBody||(w=!1),y||w?(u.find(":scope > table > tbody > tr:nth-child(1) td.w2ui-head-last").css("width",w2utils.scrollBarSize()+"px").show(),p.css({top:(0 table > tbody > tr:nth-child(1) td.w2ui-head-last").hide(),p.css({top:(0=this.recordHeight&&(e-=this.recordHeight,t++),this.fixedBody){for(let e=v;e',l+='',i.show.lineNumbers&&(s+=''),i.show.selectColumn&&(s+=''),i.show.expandColumn&&(s+=''),l+='',i.reorderRows&&(l+='');for(let e=0;ei.last.colEnd)&&!n.frozen||(r='',n.frozen?s+=r:l+=r)}s+=' ',l+=' ',query(i.box).find("#grid_"+i.name+"_frecords > table").append(s),query(i.box).find("#grid_"+i.name+"_records > table").append(l)}let _,q;if(0_&&!0!==C.hidden&&(C.hidden=!0,i=!0),C.gridMinWidth<_)&&!0===C.hidden&&(C.hidden=!1,i=!0)}if(!0===i)return void this.refresh();for(let e=0;eparseInt(E.max)&&(E.sizeCalculated=E.max+"px"),$+=parseInt(E.sizeCalculated))}let z=parseInt(_)-parseInt($);if(0 table > tbody > tr:nth-child(1) td.w2ui-head-last").css("width",w2utils.scrollBarSize()+"px").show();let O=1;this.show.lineNumbers&&(O+=g),this.show.selectColumn&&(O+=26),this.show.expandColumn&&(O+=26);for(let e=0;e table > tbody > tr:nth-child(1) td").add(c.find(":scope > table > tbody > tr:nth-child(1) td")).each(e=>{query(e).hasClass("w2ui-col-number")&&query(e).css("width",g+"px");var t=query(e).attr("col");if(null!=t){if("start"==t){let t=0;for(let e=0;e table > tbody > tr").length&&u.find(":scope > table > tbody > tr:nth-child(1) td").add(c.find(":scope > table > tbody > tr:nth-child(1) td")).html("").css({height:"0",border:"0",padding:"0",margin:"0"}),p.find(":scope > table > tbody > tr:nth-child(1) td").add(f.find(":scope > table > tbody > tr:nth-child(1) td")).each(e=>{query(e).hasClass("w2ui-col-number")&&query(e).css("width",g+"px");var t=query(e).attr("col");if(null!=t){if("start"==t){let t=0;for(let e=0;e table > tbody > tr:nth-child(1) td").add(o.find(":scope > table > tbody > tr:nth-child(1) td")).each(e=>{query(e).hasClass("w2ui-col-number")&&query(e).css("width",g+"px");var t=query(e).attr("col");if(null!=t){if("start"==t){let t=0;for(let e=0;e + ${w2utils.lang("Advanced Search")} + + + + + + `;for(let t=0;t",s),s.label=s.caption);var l=``;i+=` + + + "}}return i+=` + + +
    ${w2utils.lang(s.label)||""}${l}`;let e;switch(s.type){case"text":case"alphanumeric":case"hex":case"color":case"list":case"combo":case"enum":e="width: 250px;",-1!=["hex","color"].indexOf(s.type)&&(e="width: 90px;"),i+=``;break;case"int":case"float":case"money":case"currency":case"percent":case"date":case"time":case"datetime":e="width: 90px;","datetime"==s.type&&(e="width: 140px;"),i+=` + `;break;case"select":i+=``}i+=s.text+"
    + + + + +
    `}getOperators(e,t){let i=this.operators[this.operatorsMap[e]]||[],s=(null!=t&&Array.isArray(t)&&(i=t),"");return i.forEach(e=>{let t=e,i=e;Array.isArray(e)?(t=e[1],i=e[0]):w2utils.isPlainObject(e)&&(t=e.text,i=e.oper),null==t&&(t=e),s+=` +`}),s}initOperator(e){let i;var t=this.searches[e],s=this.getSearchData(t.field),l=query(`#w2overlay-${this.name}-search-overlay`),r=l.find(`#grid_${this.name}_range_`+e);let n=l.find(`#grid_${this.name}_field_`+e),a=l.find(`#grid_${this.name}_field2_`+e);var o=l.find(`#grid_${this.name}_operator_`+e).val();switch(n.show(),r.hide(),o){case"between":r.show();break;case"null":case"not null":n.hide(),n.val(o),n.trigger("change")}switch(t.type){case"text":case"alphanumeric":var h=n[0]._w2field;h&&h.reset();break;case"int":case"float":case"hex":case"color":case"money":case"currency":case"percent":case"date":case"time":case"datetime":n[0]._w2field||(new w2field(t.type,{el:n[0],...t.options}),new w2field(t.type,{el:a[0],...t.options}),setTimeout(()=>{n.trigger("keydown"),a.trigger("keydown")},1));break;case"list":case"combo":case"enum":i=t.options,"list"==t.type&&(i.selected={}),"enum"==t.type&&(i.selected=[]),s&&(i.selected=s.value),n[0]._w2field||(h=new w2field(t.type,{el:n[0],...i}),s&&null!=s.text&&h.set({id:s.value,text:s.text}));break;case"select":i='';for(let e=0;e'+t+""}else i+='"}n.html(i)}}initSearches(){var s=query(`#w2overlay-${this.name}-search-overlay`);for(let t=0;t{w2utils.isPlainObject(e)&&(i[t]=e.oper)}),r&&r.operator&&(e=r.operator);var l=this.defaultOperator[this.operatorsMap[l.type]],l=(-1==i.indexOf(e)&&(e=l),s.find(`#grid_${this.name}_operator_`+t).val(e),this.initOperator(t),s.find(`#grid_${this.name}_field_`+t)),n=s.find(`#grid_${this.name}_field2_`+t);null!=r&&(Array.isArray(r.value)?["in","not in"].includes(r.operator)?l[0]._w2field.set(r.value):(l.val(r.value[0]).trigger("change"),n.val(r.value[1]).trigger("change")):null!=r.value&&l.val(r.value).trigger("change"))}s.find(".w2ui-grid-search-advanced *[rel=search]").on("keypress",e=>{13==e.keyCode&&(this.search(),w2tooltip.hide(this.name+"-search-overlay"))})}getColumnsHTML(){let h=this,e="",t="";var i,s,l;return this.show.columnHeaders&&(t=0 ",h.columnGroups[e]),h.columnGroups[e].text=h.columnGroups[e].caption);""!=h.columnGroups[h.columnGroups.length-1].text&&h.columnGroups.push({text:""});h.show.lineNumbers&&(t+='
     
    ');h.show.selectColumn&&(t+='
     
    ');h.show.expandColumn&&(t+='
     
    ');let r=0;s+=``,h.reorderRows&&(s+='
     
    ');for(let e=0;e",a),a.text=a.caption);let i=0;for(let e=r;e`);var o=w2utils.lang("function"==typeof a.text?a.text(a):a.text);l=``+e+`
    `+`
    `+(o||" ")+"
    "}else{o=w2utils.lang("function"==typeof n.text?n.text(n):n.text);l=``+`
    ${o||" "}
    `+""}a&&a.frozen?t+=l:s+=l}r+=n.span}return t+="",s+=``,[t,s]}(),s=r(!1),e=l[0]+i[0]+s[0],l[1]+i[1]+s[1]):(l=r(!0),e=l[0],l[1])),[e,t];function r(t){let i="",s="",l=(h.show.lineNumbers&&(i+='
    #
    '),h.show.selectColumn&&(i+='
    '+`
    "),h.show.expandColumn&&(i+='
     
    '),0),r=0,n;s+=``,h.reorderRows&&(s+='
     
    ');for(let e=0;e ",o),o.text=o.caption),null==o.size&&(o.size="100%"),e==r&&(n=h.columnGroups[l++]||{},r+=n.span),(eh.last.colEnd)&&!o.frozen||o.hidden||!0===n.main&&!t||(a=h.getColumnCellHTML(e),o&&o.frozen?i+=a:s+=a)}return i+='
     
    ',s+='
     
    ',i+="",s+="",[i,s]}}getColumnCellHTML(t){var i=this.columns[t];if(null==i)return"";var e=!this.reorderColumns||this.columnGroups&&this.columnGroups.length?"":" w2ui-col-reorderable ";let s="";for(let e=0;e'+(!1!==i.resizable?'
    ':"")+'
    '+(a||" ")+"
    "}columnTooltipShow(e,t){var i=query(this.box).find("#grid_"+this.name+"_column_"+e),e=this.columns[e],s=this.columnTooltip;w2tooltip.show({name:this.name+"-column-tooltip",anchor:i.get(0),html:e.tooltip,position:s})}columnTooltipHide(e,t){w2tooltip.hide(this.name+"-column-tooltip")}getRecordsHTML(){let e=this.records.length;var t="object"!=typeof this.url?this.url:this.url.get,t=((e=0==this.searchData.length||t?e:this.last.searchIds.length)>this.vs_start?this.last.show_extra=this.vs_extra:this.last.show_extra=this.vs_start,query(this.box).find(`#grid_${this.name}_records`));let i=Math.floor((t.get(0)?.clientHeight||0)/this.recordHeight)+this.last.show_extra+1;(!this.fixedBody||i>e)&&(i=e);var s=this.getRecordHTML(-1,0);let l=""+s[0],r="
    "+s[1];l+='',r+='';for(let e=0;e
    ',r+=' ',this.last.range_start=0,this.last.range_end=i,[l,r]}getSummaryHTML(){if(0!==this.summary.length){var s=this.getRecordHTML(-1,0);let t=""+s[0],i="
    "+s[1];for(let e=0;ethis.last.scrollLeft&&null==l&&(l=e),t+s-30>this.last.scrollLeft+n&&null==r&&(r=e),t+=s);null==r&&(r=this.columns.length-1)}if(null!=l&&(l<0&&(l=0),r<0&&(r=0),l==r&&(0this.last.colStart)for(let e=this.last.colStart;er;e--)a.find("#grid_"+this.name+"_columns #grid_"+this.name+"_column_"+e).remove(),a.find("#grid_"+this.name+'_records td[col="'+e+'"]').remove(),a.find("#grid_"+this.name+'_summary td[col="'+e+'"]').remove();if(l=l;s--)this.columns[s]&&(this.columns[s].frozen||this.columns[s].hidden)||(e.after(this.getColumnCellHTML(s)),f.each(e=>{var t=query(e).parent().attr("index");let i='';null!=t&&(i=this.getCellHTML(parseInt(t),s,!1)),query(e).after(i)}),g.each(e=>{var t=query(e).parent().attr("index");let i='';null!=t&&(i=this.getCellHTML(parseInt(t),s,!0)),query(e).after(i)}));if(r>this.last.colEnd)for(let s=this.last.colEnd+1;s<=r;s++)this.columns[s]&&(this.columns[s].frozen||this.columns[s].hidden)||(t.before(this.getColumnCellHTML(s)),m.each(e=>{var t=query(e).parent().attr("index");let i='';null!=t&&(i=this.getCellHTML(parseInt(t),s,!1)),query(e).before(i)}),y.each(e=>{var t=query(e).parent().attr("index")||-1,t=this.getCellHTML(parseInt(t),s,!0);query(e).before(t)}));this.last.colStart=l,this.last.colEnd=r}else{this.last.colStart=l,this.last.colEnd=r;var o=this.getColumnsHTML(),w=this.getRecordsHTML(),c=this.getSummaryHTML(),p=a.find(`#grid_${this.name}_columns`);let e=a.find(`#grid_${this.name}_records`);var b=a.find(`#grid_${this.name}_frecords`);let t=a.find(`#grid_${this.name}_summary`);p.find("tbody").html(o[1]),b.html(w[0]),e.prepend(w[1]),null!=c&&t.html(c[1]),setTimeout(()=>{e.find(":scope > table").filter(":not(table:first-child)").remove(),t[0]&&(t[0].scrollLeft=this.last.scrollLeft)},1)}this.resizeRecords()}let v=this.records.length;if(v>this.total&&-1!==this.total&&(v=this.total),0!==(v=0==this.searchData.length||i?v:this.last.searchIds.length)&&0!==d.length&&0!==d.prop("clientHeight")){v>this.vs_start?this.last.show_extra=this.vs_extra:this.last.show_extra=this.vs_start;let e=Math.round(d.prop("scrollTop")/this.recordHeight+1),t=e+(Math.round(d.prop("clientHeight")/this.recordHeight)-1);if(e>v&&(e=v),t>=v-1&&(t=v),query(this.box).find("#grid_"+this.name+"_footer .w2ui-footer-right").html((this.show.statusRange?w2utils.formatNumber(this.offset+e)+"-"+w2utils.formatNumber(this.offset+t)+(-1!=this.total?" "+w2utils.lang("of")+" "+w2utils.formatNumber(this.total):""):"")+(i&&this.show.statusBuffered?" ("+w2utils.lang("buffered")+" "+w2utils.formatNumber(v)+(0this.total&&-1!=this.total&&(i=this.total);var x=d.find("#grid_"+this.name+"_rec_top"),_=d.find("#grid_"+this.name+"_rec_bottom"),q=u.find("#grid_"+this.name+"_frec_top"),C=u.find("#grid_"+this.name+"_frec_bottom"),p=(-1!=String(x.next().prop("id")).indexOf("_expanded_row")&&(x.next().remove(),q.next().remove()),this.total>i&&-1!=String(_.prev().prop("id")).indexOf("_expanded_row")&&(_.prev().remove(),C.prev().remove()),parseInt(x.next().attr("line"))),o=parseInt(_.prev().attr("line"));let e,s,l,r,n;if(p=p-this.last.show_extra+2&&1i))break;s.remove(),l.remove()}e=d.find("#grid_"+this.name+"_rec_top").next(),"bottom"==(r=e.attr("line"))&&(r=i);for(let e=parseInt(r)-1;e>=t;e--)this.records[e-1]&&((l=this.records[e-1].w2ui)&&!Array.isArray(l.children)&&(l.expanded=!1),n=this.getRecordHTML(e-1,e),x.after(n[1]),q.after(n[0]))}k(),setTimeout(()=>{this.refreshRanges()},0);b=(t-1)*this.recordHeight;let a=(v-i)*this.recordHeight;function k(){h.markSearch&&(clearTimeout(h.last.marker_timer),h.last.marker_timer=setTimeout(()=>{var t=[];for(let e=0;e{var t=query(h.box).find('td[col="'+e.col+'"]:not(.w2ui-head)');w2utils.marker(t,e.search)})},50))}a<0&&(a=0),x.css("height",b+"px"),q.css("height",b+"px"),_.css("height",a+"px"),C.css("height",a+"px"),this.last.range_start=t,this.last.range_end=i,Math.floor(d.prop("scrollTop")/this.recordHeight)+Math.floor(d.prop("clientHeight")/this.recordHeight)+10>v&&!0!==this.last.pull_more&&(v
    '),h.last.pull_more=!0,h.last.fetch.offset+=h.limit,h.request("load")}).find("td").html(h.autoLoad?'
    ':'
    '+w2utils.lang("Load ${count} more...",{count:h.limit})+"
    "))}}}getRecordHTML(r,n,a){let o="",h="";var d=this.last.selection;let u;if(-1==r){o+='
    ',h+='',this.show.lineNumbers&&(o+=''),this.show.selectColumn&&(o+=''),this.show.expandColumn&&(o+=''),h+='',this.reorderRows&&(h+='');for(let e=0;e';t.frozen&&!t.hidden?o+=i:t.hidden||ethis.last.colEnd||(h+=i)}o+='',h+=''}else{var c="object"!=typeof this.url?this.url:this.url.get;if(!0!==a){if(0=this.last.searchIds.length)return"";r=this.last.searchIds[r]}else if(r>=this.records.length)return"";u=this.records[r]}else{if(r>=this.summary.length)return"";u=this.summary[r]}if(!u)return"";null==u.recid&&null!=this.recid&&null!=(c=this.parseField(u,this.recid))&&(u.recid=c);let e=!1,t=(-1!=d.indexes.indexOf(r)&&(e=!0),u.w2ui?u.w2ui.style:""),i=(null!=t&&"string"==typeof t||(t=""),u.w2ui?u.w2ui.class:"");if(null!=i&&"string"==typeof i||(i=""),o+='",h+='",this.show.lineNumbers&&(o+='"),this.show.selectColumn&&(o+='"),this.show.expandColumn){let e="";e=!0===u.w2ui?.expanded?"-":"+","none"!=u.w2ui?.expanded&&Array.isArray(u.w2ui.children)&&u.w2ui.children.length||(e="+"),"spinner"==u.w2ui?.expanded&&(e='
    '),o+='"}h+='',this.reorderRows&&(h+='");let s=0,l=0;for(;;){let e=1;var p,f=this.columns[s];if(null==f)break;if(f.hidden)s++,0this.last.colEnd)||f.frozen){if(u.w2ui&&"object"==typeof u.w2ui.colspan){var m=parseInt(u.w2ui.colspan[f.field])||null;if(1=this.columns.length);e++)this.columns[e].hidden&&t++;e=m-t,l=m-1}}var g=this.getCellHTML(r,s,a,e);f.frozen?o+=g:h+=g}s++}}o+='',h+=''}return o+="",h+="",[o,h]}getLineHTML(e){return"
    "+e+"
    "}getCellHTML(i,s,l,e){let r=this,n=this.columns[s];if(null==n)return"";let a=(!0!==l?this.records:this.summary)[i],{value:t,style:o,className:h,attr:d,divAttr:u}=this.getCellValue(i,s,l,!0);var c=-1!==i?this.getCellEditable(i,s):"";let p="max-height: "+parseInt(this.recordHeight)+"px;"+(n.clipboardCopy?"margin-right: 20px":"");var f=!l&&a?.w2ui?.changes&&null!=a.w2ui.changes[n.field],m=this.last.selection;let g=!1,y="";if(-1!=m.indexes.indexOf(i)&&(g=!0),null==e&&(e=a?.w2ui?.colspan&&a.w2ui.colspan[n.field]?a.w2ui.colspan[n.field]:1),0===s&&Array.isArray(a?.w2ui?.children)){let t=0,e=this.get(a.w2ui.parent_recid,!0);for(;;){if(null==e)break;t++;var w=this.records[e].w2ui;if(null==w||null==w.parent_recid)break;e=this.get(w.parent_recid,!0)}if(a.w2ui.parent_recid)for(let e=0;e';var b=0`}if(!0===n.info&&(n.info={}),null!=n.info){let e="w2ui-icon-info",t=("function"==typeof n.info.icon?e=n.info.icon(a,{self:this,index:i,colIndex:s,summary:!!l}):"object"==typeof n.info.icon?e=n.info.icon[this.parseField(a,n.field)]||"":"string"==typeof n.info.icon&&(e=n.info.icon),n.info.style||"");"function"==typeof n.info.style?t=n.info.style(a,{self:this,index:i,colIndex:s,summary:!!l}):"object"==typeof n.info.style?t=n.info.style[this.parseField(a,n.field)]||"":"string"==typeof n.info.style&&(t=n.info.style),y+=``}let v=t,x=(c&&-1!=["checkbox","check"].indexOf(c.type)&&(p+="text-align: center;",v=``,y=""),null==(v=`
    ${y}${String(v)}
    `)&&(v=""),"string"==typeof n.render&&(b=n.render.toLowerCase().split(":"),-1!=["number","int","float","money","currency","percent","size"].indexOf(b[0]))&&(o+="text-align: right;"),a?.w2ui&&("object"==typeof a.w2ui.style&&("string"==typeof a.w2ui.style[s]&&(o+=a.w2ui.style[s]+";"),"string"==typeof a.w2ui.style[n.field])&&(o+=a.w2ui.style[n.field]+";"),"object"==typeof a.w2ui.class)&&("string"==typeof a.w2ui.class[s]&&(h+=a.w2ui.class[s]+" "),"string"==typeof a.w2ui.class[n.field])&&(h+=a.w2ui.class[n.field]+" "),!1);g&&m.columns[i]?.includes(s)&&(x=!0);let _;return n.clipboardCopy&&(_=''),v='
    ",v=-1===i&&!0===l?'":v}clipboardCopy(e,t,i){var s=(i?this.summary:this.records)[e],l=this.columns[t];let r=l?this.parseField(s,l.field):"";"function"==typeof l.clipboardCopy&&(r=l.clipboardCopy(s,{self:this,index:e,colIndex:t,summary:!!i})),query(this.box).find("#grid_"+this.name+"_focus").text(r).get(0).select(),document.execCommand("copy")}showBubble(s,l,r){var n=this.columns[l].info;if(n){let i="";var a=this.records[s],e=query(this.box).find(`${r?".w2ui-grid-summary":""} #grid_${this.name}_data_${s}_${l} .w2ui-info`);if(this.last.bubbleEl&&w2tooltip.hide(this.name+"-bubble"),this.last.bubbleEl=e,null==n.fields){n.fields=[];for(let e=0;e';else{let e=this.getColumn(h[0]),t=(e=null==e?{field:h[0],caption:h[0]}:e)?this.parseField(a,e.field):"";1n.maxLength&&(t=t.substr(0,n.maxLength)+"..."),i+="")}}i+="
    "+(!0!==a?this.getLineHTML(n,u):"")+"'+(!0===a||u.w2ui&&!0===u.w2ui.hideCheckBox?"":'
    ')+"
    '+(!0!==a?`
    ${e}
    `:"")+"
    '+(!0!==a?'
     
    ':"")+"
    "+v+(_&&w2utils.stripTags(v)?_:"")+"
    "+e.text+""+((0===t?"0":t)||"")+"
    "}else if(w2utils.isPlainObject(t)){for(var d in i='',t){var u=t[d];if(""==u||"-"==u||"--"==u||"---"==u)i+='';else{var c=String(u).split(":");let e=this.getColumn(c[0]),t=(e=null==e?{field:c[0],caption:c[0]}:e)?this.parseField(a,e.field):"";1n.maxLength&&(t=t.substr(0,n.maxLength)+"..."),i+="")}}i+="
    "+d+""+((0===t?"0":t)||"")+"
    "}return w2tooltip.show(w2utils.extend({name:this.name+"-bubble",html:i,anchor:e.get(0),position:"top|bottom",class:"w2ui-info-bubble",style:"",hideOn:["doc-click"]},n.options??{})).hide(()=>[this.last.bubbleEl=null])}}getCellEditable(e,t){var i=this.columns[t],s=this.records[e];if(!s||!i)return null;let l=s.w2ui?s.w2ui.editable:null;return!1===l?null:(null!=l&&!0!==l||"function"==typeof(l=0 '}status(i){if(null!=i)query(this.box).find(`#grid_${this.name}_footer`).find(".w2ui-footer-left").html(i);else{let t="";i=this.getSelection();if(0{query(this.box).find("#grid_"+this.name+"_empty_msg").remove(),w2utils.lock(...i)},10)}unlock(e){setTimeout(()=>{query(this.box).find(".w2ui-message").hasClass("w2ui-closing")||w2utils.unlock(this.box,e)},25)}stateSave(e){var t={columns:[],show:w2utils.clone(this.show),last:{search:this.last.search,multi:this.last.multi,logic:this.last.logic,label:this.last.label,field:this.last.field,scrollTop:this.last.scrollTop,scrollLeft:this.last.scrollLeft},sortData:[],searchData:[]};let l;for(let e=0;e{this.stateColProps[e]&&(l=void 0!==i[e]?i[e]:this.colTemplate[e]||null,s[e]=l)}),t.columns.push(s)}for(let e=0;e{s||(0=this.columns.length)return null==(e=this.nextRow(e))?e:this.nextCell(e,-1,i);var s=this.records[e].w2ui,l=this.columns[t],s=s&&s.colspan&&!isNaN(s.colspan[l.field])?parseInt(s.colspan[l.field]):1;if(null==l)return null;if(l&&l.hidden||0===s)return this.nextCell(e,t,i);if(i){l=this.getCellEditable(e,t);if(null==l||-1!=["checkbox","check"].indexOf(l.type))return this.nextCell(e,t,i)}return{index:e,colIndex:t}}prevCell(e,t,i){t-=1;if(t<0)return null==(e=this.prevRow(e))?e:this.prevCell(e,this.columns.length,i);if(t<0)return null;var s=this.records[e].w2ui,l=this.columns[t],s=s&&s.colspan&&!isNaN(s.colspan[l.field])?parseInt(s.colspan[l.field]):1;if(null==l)return null;if(l&&l.hidden||0===s)return this.prevCell(e,t,i);if(i){l=this.getCellEditable(e,t);if(null==l||-1!=["checkbox","check"].indexOf(l.type))return this.prevCell(e,t,i)}return{index:e,colIndex:t}}nextRow(e,t,i){var s=this.last.searchIds;let l=null;if(-1==(i=null==i?1:i))return this.records.length-1;if(e+ithis.records.length)break;e+=i}var r=this.records[e].w2ui,n=this.columns[t],r=r&&r.colspan&&null!=n&&!isNaN(r.colspan[n.field])?parseInt(r.colspan[n.field]):1;l=0===r?this.nextRow(e,t,i):e}return l}prevRow(e,t,i){var s=this.last.searchIds;let l=null;if(-1==(i=null==i?1:i))return 0;if(0<=e-i&&0===s.length||0s[0]){if(e-=i,0{-1==i.indexOf(e)&&-1!=["label","attr","style","text","span","page","column","anchor","group","groupStyle","groupTitleStyle","groupCollapsible"].indexOf(e)&&(t.html[e]=t[e],delete t[e])}),t}function h(t,i){let s=["style","html"];Object.keys(t).forEach(e=>{-1==s.indexOf(e)&&-1!=["span","column","attr","text","label"].indexOf(e)&&t[e]&&!i.html[e]&&(i.html[e]=t[e])})}r=[],Object.keys(e).forEach(i=>{let s=e[i];if("group"==s.type){if(s.text=i,w2utils.isPlainObject(s.fields)){let i=s.fields;s.fields=[],Object.keys(i).forEach(e=>{let t=i[e];t.field=e,s.fields.push(o(t))})}r.push(s)}else if("tab"==s.type){let e={id:i,text:i},t=(s.style&&(e.style=s.style),a.push(e),l(s.fields).fields);t.forEach(e=>{e.html=e.html||{},e.html.page=a.length-1,h(s,e)}),r.push(...t)}else s.field=i,r.push(o(s))})}r.forEach(s=>{if("group"==s.type){let i={group:s.text||"",groupStyle:s.style||"",groupTitleStyle:s.titleStyle||"",groupCollapsible:!0===s.collapsible};Array.isArray(s.fields)&&s.fields.forEach(e=>{let t=w2utils.clone(e);null==t.html&&(t.html={}),w2utils.extend(t.html,i),Array("span","column","attr","label","page").forEach(e=>{null==t.html[e]&&null!=s[e]&&(t.html[e]=s[e])}),null==t.field&&null!=t.name&&(console.log("NOTICE: form field.name property is deprecated, please use field.field. Field ->",s),t.field=t.name),n.push(t)})}else{let e=w2utils.clone(s);null==e.field&&null!=e.name&&(console.log("NOTICE: form field.name property is deprecated, please use field.field. Field ->",s),e.field=e.name),n.push(e)}});return{fields:n,tabs:a}}(r),this.fields=e.fields,!a)&&0e.text()).then(e=>{this.formHTML=e,this.isGenerated=!0,this.box&&this.render(this.box)}):this.formURL||this.formHTML?this.formHTML&&(this.isGenerated=!0):(this.formHTML=this.generateHTML(),this.isGenerated=!0),"string"==typeof this.box&&(this.box=query(this.box).get(0)),this.box&&this.render(this.box)}get(t,i){if(0===arguments.length){var s=[];for(let e=0;ee[t],s)}catch(e){}return e}return this.record[t]}setValue(e,l){if((""===l||null==l||Array.isArray(l)&&0===l.length||w2utils.isPlainObject(l)&&0==Object.keys(l).length)&&(l=null),!this.nestedFields)return this.record[e]=l,!0;try{let s=this.record;return String(e).split(".").map((e,t,i)=>{i.length-1!==t?s=s[e]||(s[e]={},s[e]):s[e]=l}),!0}catch(e){return!1}}getFieldValue(e){let s=this.get(e);if(null!=s){var l=s.el;let t=this.getValue(e);e=this.getValue(e,!0);let i=l.value;["int","float","percent","money","currency"].includes(s.type)&&(i=s.w2field.clean(i)),["radio"].includes(s.type)&&(r=query(l).closest("div").find("input:checked").get(0),i=r?s.options.items[query(r).data("index")].id:null),["toggle","checkbox"].includes(s.type)&&(i=l.checked),-1!==["check","checks"].indexOf(s.type)&&(i=[],0<(r=query(l).closest("div").find("input:checked")).length&&r.each(e=>{e=s.options.items[query(e).data("index")];i.push(e.id)}),Array.isArray(t)||(t=[]));var r=l._w2field?.selected;if(["list","enum","file"].includes(s.type)&&r){var n=r,a=t;if(Array.isArray(n)){i=[];for(let e=0;e{var t=query(e).find(".w2ui-map.key").val(),e=query(e).find(".w2ui-map.value").val();"map"==s.type?i[t]=e:i.push(e)})),{current:i,previous:t,original:e}}}setFieldValue(e,r){let n=this.get(e);if(null!=n){var s=n.el;switch(n.type){case"toggle":case"checkbox":s.checked=!!r;break;case"radio":{r=r?.id??r;let i=query(s).closest("div").find("input");n.options.items.forEach((e,t)=>{e.id===r&&i.filter(`[data-index="${t}"]`).prop("checked",!0)});break}case"check":case"checks":{r=(r=Array.isArray(r)?r:null!=r?[r]:[]).map(e=>e?.id??e);let i=query(s).closest("div").find("input");n.options.items.forEach((e,t)=>{i.filter(`[data-index="${t}"]`).prop("checked",!!r.includes(e.id))});break}case"list":case"combo":let t=r;null==t?.id&&Array.isArray(n.options?.items)&&n.options.items.forEach(e=>{e.id===r&&(t=e)}),t!=r&&this.setValue(n.name,t),"list"==n.type?(n.w2field.selected=t,n.w2field.refresh()):n.el.value=t?.text??r;break;case"enum":case"file":{let s=[...r=Array.isArray(r)?r:null!=r?[r]:[]],l=!1;s.forEach((t,i)=>{null==t?.id&&Array.isArray(n.options.items)&&n.options.items.forEach(e=>{e.id==t&&(s[i]=e,l=!0)})}),l&&this.setValue(n.name,s),n.w2field.selected=s,n.w2field.refresh();break}case"map":case"array":"map"!=n.type||null!=r&&w2utils.isPlainObject(r)||(this.setValue(n.field,{}),r=this.getValue(n.field)),"array"!=n.type||null!=r&&Array.isArray(r)||(this.setValue(n.field,[]),r=this.getValue(n.field));var i=query(n.el).parent().find(".w2ui-map-container");n.el.mapRefresh(r,i);break;case"div":case"custom":query(s).html(r);break;case"html":case"empty":break;default:s.value=r??""}}}show(){var t=[];for(let e=0;e{!function(e){let t=!0;return e.each(e=>{"none"!=e.style.display&&(t=!1)}),t}(query(e).find(".w2ui-field"))?query(e).show():query(e).hide()})}change(){Array.from(arguments).forEach(e=>{e=this.get(e);e.$el&&e.$el.change()})}reload(e){return("object"!=typeof this.url?this.url:this.url.get)&&null!=this.recid?this.request(e):("function"==typeof e&&e(),new Promise(e=>{e()}))}clear(){0!=arguments.length?Array.from(arguments).forEach(e=>{let s=this.record;String(e).split(".").map((e,t,i)=>{i.length-1!==t?s=s[e]:delete s[e]}),this.refresh(e)}):(this.recid=null,this.record={},this.original=null,this.refresh(),this.hideErrors())}error(e){var t=this.trigger("error",{target:this.name,message:e,fetchCtrl:this.last.fetchCtrl,fetchOptions:this.last.fetchOptions});!0!==t.isCancelled&&(setTimeout(()=>{this.message(e)},1),t.finish())}message(e){return w2utils.message({owner:this,box:this.box,after:".w2ui-form-header"},e)}confirm(e){return w2utils.confirm({owner:this,box:this.box,after:".w2ui-form-header"},e)}validate(e){null==e&&(e=!0);var t=[];for(let e=0;e{var i=w2utils.extend({anchorClass:"w2ui-error",class:"w2ui-light",position:"right|left",hideOn:["input"]},t.options);if(null!=t.field){let e=t.field.el;"radio"===t.field.type?e=query(t.field.el).closest("div").get(0):["enum","file"].includes(t.field.type),w2tooltip.show(w2utils.extend({anchor:e,name:`${this.name}-${t.field.field}-error`,html:t.error},i))}}),query(e[0].field.$el).parents(".w2ui-page").off(".hideErrors").on("scroll.hideErrors",e=>{this.hideErrors()}))}hideErrors(){this.fields.forEach(e=>{w2tooltip.hide(`${this.name}-${e.field}-error`)})}getChanges(){let e={};return e=null!=this.original&&"object"==typeof this.original&&0!==Object.keys(this.record).length?function e(t,i,s){if(Array.isArray(t)&&Array.isArray(i))for(;t.length{if(-1!=["list","combo","enum"].indexOf(e.type)){var t={nestedFields:!0,record:s};let i=this.getValue.call(t,e.field);w2utils.isPlainObject(i)&&null!=i.id&&this.setValue.call(t,e.field,i.id),Array.isArray(i)&&i.forEach((e,t)=>{w2utils.isPlainObject(e)&&e.id&&(i[t]=e.id)})}var i;"map"==e.type&&(t={nestedFields:!0,record:s},(t=this.getValue.call(t,e.field))._order)&&delete t._order,"file"==e.type&&(t={nestedFields:!0,record:s},(i=this.getValue.call(t,e.field)??[]).forEach(e=>{delete e.file,delete e.modified}),this.setValue.call(t,e.field,i))}),!0===e&&Object.keys(s).forEach(e=>{this.get(e)||delete s[e]}),s}prepareParams(i,e){var t=this.dataType??w2utils.settings.dataType;let s=e.body;switch(t){case"HTTPJSON":s={request:s},l();break;case"HTTP":l();break;case"RESTFULL":"POST"==e.method?e.headers["Content-Type"]="application/json":l();break;case"JSON":"GET"==e.method?(s={request:s},l()):(e.headers["Content-Type"]="application/json",e.method="POST")}return e.body="string"==typeof e.body?e.body:JSON.stringify(e.body),e;function l(){Object.keys(s).forEach(e=>{let t=s[e];"object"==typeof t&&(t=JSON.stringify(t)),i.searchParams.append(e,t)}),delete e.body}}request(e,i){let s=this,l,r;var n=new Promise((e,t)=>{l=e,r=t});if("function"==typeof e&&(i=e,e=null),null==e&&(e={}),this.url&&("object"!=typeof this.url||this.url.get)){var a={action:"get"},e=(a.recid=this.recid,a.name=this.name,w2utils.extend(a,this.postData),w2utils.extend(a,e),this.trigger("request",{target:this.name,url:this.url,httpMethod:"GET",postData:a,httpHeaders:this.httpHeaders}));if(!0!==e.isCancelled){this.record={},this.original=null,this.lock(w2utils.lang(this.msgRefresh));let t=e.detail.url;if("object"==typeof t&&t.get&&(t=t.get),this.last.fetchCtrl)try{this.last.fetchCtrl.abort()}catch(e){}if(0!=Object.keys(this.routeData).length){var o=w2utils.parseRoute(t);if(0{200!=e?.status?e&&h(e):e.json().catch(h).then(e=>{var t=s.trigger("load",{target:s.name,fetchCtrl:this.last.fetchCtrl,fetchOptions:this.last.fetchOptions,data:e});!0!==t.isCancelled&&(null==e.error&&"error"===e.status&&(e.error=!0),e.record||Object.assign(e,{record:w2utils.clone(e)}),!0===e.error?s.error(w2utils.lang(e.message??this.msgServerError)):s.record=w2utils.clone(e.record),s.unlock(),t.finish(),s.refresh(),s.setFocus(),"function"==typeof i&&i(e),l(e))})}),e.finish(),n;function h(e){var t;"AbortError"!==e.name&&(s.unlock(),!0!==(t=s.trigger("error",{response:e,fetchCtrl:s.last.fetchCtrl,fetchOptions:s.last.fetchOptions})).isCancelled)&&(e.status&&200!=e.status?s.error(e.status+": "+e.statusText):(console.log("ERROR: Server request failed.",e,". ","Expected Response:",{error:!1,record:{field1:1,field2:"item"}},"OR:",{error:!0,message:"Error description"}),s.error(String(e))),t.finish(),r(e))}}}}submit(e,t){return this.save(e,t)}save(e,i){let s=this,l,r;var n=new Promise((e,t)=>{l=e,r=t}),a=("function"==typeof e&&(i=e,e=null),s.validate(!0));if(0===a.length)if(null==e&&(e={}),!s.url||"object"==typeof s.url&&!s.url.save)console.log("ERROR: Form cannot be saved because no url is defined.");else{s.lock(w2utils.lang(s.msgSaving)+' ');a={action:"save"},e=(a.recid=s.recid,a.name=s.name,w2utils.extend(a,s.postData),w2utils.extend(a,e),a.record=w2utils.clone(s.record),s.trigger("submit",{target:s.name,url:s.url,httpMethod:this.method??"POST",postData:a,httpHeaders:s.httpHeaders}));if(!0!==e.isCancelled){let t=e.detail.url;if("object"==typeof t&&t.save&&(t=t.save),s.last.fetchCtrl&&s.last.fetchCtrl.abort(),0{s.unlock(),200!=e?.status?h(e??{}):e.json().catch(h).then(e=>{var t=s.trigger("save",{target:s.name,fetchCtrl:this.last.fetchCtrl,fetchOptions:this.last.fetchOptions,data:e});!0!==t.isCancelled&&(!0===e.error?s.error(w2utils.lang(e.message??this.msgServerError)):s.original=null,t.finish(),s.refresh(),"function"==typeof i&&i(e),l(e))})}),e.finish(),n;function h(e){var t;"AbortError"!==e?.name&&(s.unlock(),!0!==(t=s.trigger("error",{response:e,fetchCtrl:s.last.fetchCtrl,fetchOptions:s.last.fetchOptions})).isCancelled)&&(e.status&&200!=e.status?s.error(e.status+": "+e.statusText):(console.log("ERROR: Server request failed.",e,". ","Expected Response:",{error:!1,record:{field1:1,field2:"item"}},"OR:",{error:!0,message:"Error description"}),s.error(String(e))),t.finish(),r())}}}}lock(e,t){var i=Array.from(arguments);i.unshift(this.box),w2utils.lock(...i)}unlock(e){var t=this.box;w2utils.unlock(t,e)}lockPage(e,t,i){e=query(this.box).find(".page-"+e);return!!e.length&&(w2utils.lock(e,t,i),!0)}unlockPage(e,t){e=query(this.box).find(".page-"+e);return!!e.length&&(w2utils.unlock(e,t),!0)}goto(e){this.page!==e&&(null!=e&&(this.page=e),!0===query(this.box).data("autoSize")&&(query(this.box).get(0).clientHeight=0),this.refresh())}generateHTML(){let s=[],t="",l,r,n,a;for(let e=0;e",h),h.html.label=h.html.caption),null==h.html.label&&(h.html.label=h.field),h.html=w2utils.extend({label:"",span:6,attr:"",text:"",style:"",page:0,column:0},h.html),null==l&&(l=h.html.page),null==r&&(r=h.html.column);let i=``;switch(h.type){case"pass":case"password":i=i.replace('type="text"','type="password"');break;case"checkbox":i=` + `;break;case"check":case"checks":{null==h.options.items&&null!=h.html.items&&(h.options.items=h.html.items);let t=h.options.items;i="",0<(t=Array.isArray(t)?t:[]).length&&(t=w2utils.normMenu.call(this,t,h));for(let e=0;e + +  ${t[e].text} + +
    `;break}case"radio":{i="",null==h.options.items&&null!=h.html.items&&(h.options.items=h.html.items);let t=h.options.items;0<(t=Array.isArray(t)?t:[]).length&&(t=w2utils.normMenu.call(this,t,h));for(let e=0;e + +  ${t[e].text} + +
    `;break}case"select":{i=`";break}case"textarea":i=``;break;case"toggle":i=` +
    `;break;case"map":case"array":h.html.key=h.html.key||{},h.html.value=h.html.value||{},h.html.tabindex_str=o,i=''+(h.html.text||"")+'
    ';break;case"div":case"custom":i='
    '+(h&&h.html&&h.html.html?h.html.html:"")+"
    ";break;case"html":case"empty":i=h&&h.html?(h.html.html||"")+(h.html.text||""):""}if(""!==t&&(l!=h.html.page||r!=h.html.column||h.html.group&&t!=h.html.group)&&(s[l][r]+="\n \n ",t=""),h.html.group&&t!=h.html.group){let e="";h.html.groupCollapsible&&(e=''),n+='\n
    \n
    "+e+w2utils.lang(h.html.group)+'
    \n
    ',t=h.html.group}if(null==h.html.anchor){let e=null!=h.html.span?"w2ui-span"+h.html.span:"",t=""+w2utils.lang("checkbox"!=h.type?h.html.label:h.html.text)+"";h.html.label||(t=""),n+='\n
    \n '+t+("empty"===h.type?i:"\n
    "+i+("array"!=h.type&&"map"!=h.type?w2utils.lang("checkbox"!=h.type?h.html.text:""):"")+"
    ")+"\n
    "}else s[h.html.page].anchors=s[h.html.page].anchors||{},s[h.html.page].anchors[h.html.anchor]='
    '+("empty"===h.type?i:"
    "+w2utils.lang("checkbox"!=h.type?h.html.label:h.html.text,!0)+i+w2utils.lang("checkbox"!=h.type?h.html.text:"")+"
    ")+"
    ";null==s[h.html.page]&&(s[h.html.page]={}),null==s[h.html.page][h.html.column]&&(s[h.html.page][h.html.column]=""),s[h.html.page][h.html.column]+=n,l=h.html.page,r=h.html.column}if(""!==t&&(s[l][r]+="\n
    \n
    "),this.tabs.tabs)for(let e=0;e",d),d.text=d.caption),d.text&&(u.text=d.text),d.style&&(u.style=d.style),d.class&&(u.class=d.class)):(u.text=i,-1!==["save","update","create"].indexOf(i.toLowerCase())?u.class="w2ui-btn-blue":u.class=""),e+='\n ",a++}e+="\n"}n="";for(let i=0;i',!s[i])return console.log(`ERROR: Page ${i} does not exist`),!1;s[i].before&&(n+=s[i].before),n+='
    ',Object.keys(s[i]).sort().forEach((e,t)=>{e==parseInt(e)&&(n+='
    '+(s[i][e]||"")+"\n
    ")}),n+="\n
    ",s[i].after&&(n+=s[i].after),n+="\n",s[i].anchors&&Object.keys(s[i].anchors).forEach((e,t)=>{n=n.replace(e,s[i].anchors[e])})}return n+=e}toggleGroup(e,t){var i,e=query(this.box).find('.w2ui-group-title[data-group="'+w2utils.base64encode(e)+'"]');0!==e.length&&(i=query(e.prop("nextElementSibling")),(t=void 0===t?"none"==i.css("display"):t)?(i.show(),e.find("span").addClass("w2ui-icon-collapse").removeClass("w2ui-icon-expand")):(i.hide(),e.find("span").addClass("w2ui-icon-expand").removeClass("w2ui-icon-collapse")))}action(e,t){var i=this.actions[e];let s=i;w2utils.isPlainObject(i)&&i.onClick&&(s=i.onClick);e=this.trigger("action",{target:e,action:i,originalEvent:t});!0!==e.isCancelled&&("function"==typeof s&&s.call(this,t),e.finish())}resize(){let o=this;var e=this.trigger("resize",{target:this.name});if(!0!==e.isCancelled){let s=query(this.box).find(":scope > div .w2ui-form-header"),l=query(this.box).find(":scope > div .w2ui-form-toolbar"),r=query(this.box).find(":scope > div .w2ui-form-tabs"),n=query(this.box).find(":scope > div .w2ui-page");var t=query(this.box).find(":scope > div .w2ui-page.page-"+this.page+" > div");let a=query(this.box).find(":scope > div .w2ui-buttons");var{headerHeight:i,tbHeight:h,tabsHeight:d}=u();function u(){var e=""!==o.header?w2utils.getSize(s,"height"):0,t=Array.isArray(o.toolbar?.items)&&0("string"!=typeof e&&console.log("ERROR: Arguments in refresh functions should be field names"),this.get(e,!0))).filter((e,t)=>null!=e):(query(this.box).find("input, textarea, select").each(e=>{var t=null!=query(e).attr("name")?query(e).attr("name"):query(e).attr("id"),i=this.get(t);if(i){var s=query(e).closest(".w2ui-page");if(0{query(e).off("click").on("click",function(e){let t=this.value;this.id&&(t=this.id),this.name&&(t=this.name),c.action(t,e)})});for(let e=0;e{t+=``}),s.$el.html(t)}this.W2FIELD_TYPES.includes(s.type)&&(s.w2field=s.w2field??new w2field(w2utils.extend({},s.options,{type:s.type})),s.w2field.render(s.el)),["map","array"].includes(s.type)&&!function(d){let u;d.el.mapAdd=function(e,t,i){var s=(e.disabled?" readOnly ":"")+(e.html.tabindex_str||""),i=` +
    + ${"map"==e.type?` + ${e.html.key.text||""} + `:""} + + ${e.html.value.text||""} +
    `;t.append(i)},d.el.mapRefresh=function(l,r){let n,a,o;var h;"map"==d.type&&(null==(l=w2utils.isPlainObject(l)?l:{})._order&&(l._order=Object.keys(l)),n=l._order),"array"==d.type&&(Array.isArray(l)||(l=[]),n=l.map((e,t)=>t));for(let e=r.find(".w2ui-map-field").length-1;e>=n.length;e--)r.find(`div[data-index='${e}']`).remove();for(let s=0;se.key==t)).length&&(i=h[0].value),a.val(t),o.val(i),!0!==d.disabled&&!1!==d.disabled||(a.prop("readOnly",!!d.disabled),o.prop("readOnly",!!d.disabled))}var e=n.length,t=r.find(`div[data-index='${e}']`),e=(0!==t.length||a&&""==a.val()&&""==o.val()||a&&(!0===a.prop("readOnly")||!0===a.prop("disabled"))||d.el.mapAdd(d,r,e),!0!==d.disabled&&!1!==d.disabled||(t.find(".key").prop("readOnly",!!d.disabled),t.find(".value").prop("readOnly",!!d.disabled)),query(d.el).get(0)?.nextSibling);query(e).find("input.w2ui-map").off(".mapChange").on("keyup.mapChange",function(e){var t=query(e.target).closest(".w2ui-map-field"),i=t.get(0).nextElementSibling,t=t.get(0).previousElementSibling,s=(13==e.keyCode&&((s=u??i)instanceof HTMLElement&&0<(s=query(s).find("input")).length&&s.get(0).focus(),u=void 0),query(e.target).hasClass("key")?"key":"value");38==e.keyCode&&t&&(query(t).find("input."+s).get(0).select(),e.preventDefault()),40==e.keyCode&&i&&(query(i).find("input."+s).get(0).select(),e.preventDefault())}).on("keydown.mapChange",function(e){38!=e.keyCode&&40!=e.keyCode||e.preventDefault()}).on("input.mapChange",function(e){var e=query(e.target).closest("div"),t=e.data("index"),i=e.get(0).nextElementSibling;if(""==e.find("input").val()||i){if(""==e.find("input").val()&&i){let t=!0;query(i).find("input").each(e=>{""!=e.value&&(t=!1)}),t&&query(i).remove()}}else d.el.mapAdd(d,r,parseInt(t)+1)}).on("change.mapChange",function(e){null==c.original&&(0{t._order.push(e.value)}),c.trigger("change",{target:d.field,field:d.field,originalEvent:e,value:{current:t,previous:i,original:s}}));!0!==l.isCancelled&&("map"==d.type&&(t._order=t._order.filter(e=>""!==e),delete t[""]),"array"==d.type&&(t=t.filter(e=>""!==e)),""==query(e.target).parent().find("input").val()&&(u=e.target),c.setValue(d.field,t),d.el.mapRefresh(t,r),l.finish())})}}(s),this.setFieldValue(s.field,this.getValue(s.name))}}return t.finish(),this.resize(),Date.now()-e}}}render(e){var t=Date.now();let i=this;"string"==typeof e&&(e=query(e).get(0));var s=this.trigger("render",{target:this.name,box:e??this.box});if(!0!==s.isCancelled&&(null!=e&&(0'+(""!==this.header?'
    '+w2utils.lang(this.header)+"
    ":"")+' '+this.formHTML+"",e=(query(this.box).attr("name",this.name).addClass("w2ui-reset w2ui-form").html(e),0this.refresh()):this.refresh(),this.last.observeResize=new ResizeObserver(()=>{this.resize()}),this.last.observeResize.observe(this.box),-1!=this.focus){let e=0,t=()=>{0 input, select, textarea, div > label:nth-child(1) > [type=radio]").filter(":not(.file-input)");null==i[e].offsetParent&&i.length>=e;)e++;i[e]&&(t=query(i[e]))}else"string"==typeof e&&(t=query(this.box).find(`[name='${e}']`));return 0 `,arrow:!1,advanced:null,transparent:!0},this.options=w2utils.extend({},e,t),t=this.options;break;case"date":e={format:w2utils.settings.dateFormat,keyboard:!0,autoCorrect:!0,start:null,end:null,blockDates:[],blockWeekdays:[],colored:{},btnNow:!0},this.options=w2utils.extend({type:"date"},e,t),t=this.options,null==query(this.el).attr("placeholder")&&query(this.el).attr("placeholder",t.format);break;case"time":e={format:w2utils.settings.timeFormat,keyboard:!0,autoCorrect:!0,start:null,end:null,btnNow:!0,noMinutes:!1},this.options=w2utils.extend({type:"time"},e,t),t=this.options,null==query(this.el).attr("placeholder")&&query(this.el).attr("placeholder",t.format);break;case"datetime":e={format:w2utils.settings.dateFormat+"|"+w2utils.settings.timeFormat,keyboard:!0,autoCorrect:!0,start:null,end:null,startTime:null,endTime:null,blockDates:[],blockWeekdays:[],colored:{},btnNow:!0,noMinutes:!1},this.options=w2utils.extend({type:"datetime"},e,t),t=this.options,null==query(this.el).attr("placeholder")&&query(this.el).attr("placeholder",t.placeholder||t.format);break;case"list":case"combo":e={items:[],selected:{},url:null,recId:null,recText:null,method:null,interval:350,postData:{},minLength:1,cacheMax:250,maxDropHeight:350,maxDropWidth:null,minDropWidth:null,match:"begins",icon:null,iconStyle:"",align:"both",altRows:!0,renderDrop:null,compare:null,filter:!0,hideSelected:!1,prefix:"",suffix:"",msgNoItems:"No matches",msgSearch:"Type to search...",openOnFocus:!1,markSearch:!1,onSearch:null,onRequest:null,onLoad:null,onError:null},"function"==typeof t.items&&(t._items_fun=t.items),t.items=w2utils.normMenu.call(this,t.items),"list"===this.type&&(query(this.el).addClass("w2ui-select"),!w2utils.isPlainObject(t.selected))&&Array.isArray(t.items)&&t.items.forEach(e=>{e&&e.id===t.selected&&(t.selected=w2utils.clone(e))}),t=w2utils.extend({},e,t),this.options=t,w2utils.isPlainObject(t.selected)||(t.selected={}),this.selected=t.selected,query(this.el).attr("autocapitalize","off").attr("autocomplete","off").attr("autocorrect","off").attr("spellcheck","false"),null!=t.selected.text&&query(this.el).val(t.selected.text);break;case"enum":e={items:[],selected:[],max:0,url:null,recId:null,recText:null,interval:350,method:null,postData:{},minLength:1,cacheMax:250,maxItemWidth:250,maxDropHeight:350,maxDropWidth:null,match:"contains",align:"",altRows:!0,openOnFocus:!1,markSearch:!1,renderDrop:null,renderItem:null,compare:null,filter:!0,hideSelected:!0,style:"",msgNoItems:"No matches",msgSearch:"Type to search...",onSearch:null,onRequest:null,onLoad:null,onError:null,onClick:null,onAdd:null,onNew:null,onRemove:null,onMouseEnter:null,onMouseLeave:null,onScroll:null},"function"==typeof(t=w2utils.extend({},e,t,{suffix:""})).items&&(t._items_fun=t.items),t.items=w2utils.normMenu.call(this,t.items),t.selected=w2utils.normMenu.call(this,t.selected),this.options=t,Array.isArray(t.selected)||(t.selected=[]),this.selected=t.selected;break;case"file":e={selected:[],max:0,maxSize:0,maxFileSize:0,maxItemWidth:250,maxDropHeight:350,maxDropWidth:null,readContent:!0,silent:!0,align:"both",altRows:!0,renderItem:null,style:"",onClick:null,onAdd:null,onRemove:null,onMouseEnter:null,onMouseLeave:null},t=w2utils.extend({},e,t),this.options=t,Array.isArray(t.selected)||(t.selected=[]),this.selected=t.selected,null==query(this.el).attr("placeholder")&&query(this.el).attr("placeholder",w2utils.lang("Attach files by dragging and dropping or Click to Select"))}query(this.el).css("box-sizing","border-box").addClass("w2field w2ui-input").off(".w2field").on("change.w2field",e=>{this.change(e)}).on("click.w2field",e=>{this.click(e)}).on("focus.w2field",e=>{this.focus(e)}).on("blur.w2field",e=>{"list"!==this.type&&this.blur(e)}).on("keydown.w2field",e=>{this.keyDown(e)}).on("keyup.w2field",e=>{this.keyUp(e)}),this.addPrefix(),this.addSuffix(),this.addSearch(),this.addMultiSearch(),this.change(new Event("change"))}else console.log("ERROR: w2field could only be applied to INPUT or TEXTAREA.",this.el)}get(){let e;return e=-1!==["list","enum","file"].indexOf(this.type)?this.selected:query(this.el).val()}set(e,t){-1!==["list","enum","file"].indexOf(this.type)?("list"!==this.type&&t?(Array.isArray(this.selected)||(this.selected=[]),this.selected.push(e),(t=w2menu.get(this.el.id+"_menu"))&&(t.options.selected=this.selected)):(null==e&&(e=[]),t="enum"!==this.type||Array.isArray(e)?e:[e],this.selected=t),query(this.el).trigger("input").trigger("change"),this.refresh()):query(this.el).val(e)}setIndex(e,t){if(-1!==["list","enum"].indexOf(this.type)){var i=this.options.items;if(i&&i[e])return"list"==this.type&&(this.selected=i[e]),"enum"==this.type&&(t||(this.selected=[]),this.selected.push(i[e])),(t=w2menu.get(this.el.id+"_menu"))&&(t.options.selected=this.selected),query(this.el).trigger("input").trigger("change"),this.refresh(),!0}return!1}refresh(){let s=this.options;var e=Date.now(),t=getComputedStyle(this.el);if("list"==this.type){if(query(this.el).parent().css("white-space","nowrap"),this.helpers.prefix&&this.helpers.prefix.hide(),!this.helpers.search)return;null==this.selected&&s.icon?s.prefix=` + + `:s.prefix="",this.addPrefix();let e=query(this.helpers.search_focus);var i=query(e[0].previousElementSibling);e.css({outline:"none"}),""===e.val()?(e.css("opacity",0),i.css("opacity",0),this.selected?.id?(n=this.selected.text,r=this.findItemIndex(s.items,this.selected.id),null!=n&&query(this.el).val(w2utils.lang(n)).data({selected:n,selectedIndex:r[0]})):(this.el.value="",query(this.el).removeData("selected selectedIndex"))):(e.css("opacity",1),i.css("opacity",1),query(this.el).val(""),setTimeout(()=>{this.helpers.prefix&&this.helpers.prefix.hide(),s.icon?(e.css("margin-left","17px"),query(this.helpers.search).find(".w2ui-icon-search").addClass("show-search")):(e.css("margin-left","0px"),query(this.helpers.search).find(".w2ui-icon-search").removeClass("show-search"))},1)),query(this.el).prop("readOnly")||query(this.el).prop("disabled")?setTimeout(()=>{this.helpers.prefix&&query(this.helpers.prefix).css("opacity","0.6"),this.helpers.suffix&&query(this.helpers.suffix).css("opacity","0.6")},1):setTimeout(()=>{this.helpers.prefix&&query(this.helpers.prefix).css("opacity","1"),this.helpers.suffix&&query(this.helpers.suffix).css("opacity","1")},1)}let l=this.helpers.multi;if(["enum","file"].includes(this.type)&&l){let i="";Array.isArray(this.selected)&&this.selected.forEach((e,t)=>{null!=e&&(i+=` +
    + ${"function"==typeof s.renderItem?s.renderItem(e,t,`
      
    `):` + ${e.icon?``:""} +
      
    + ${("enum"===this.type?e.text:e.name)??e.id??e} + ${e.size?` - ${w2utils.formatSize(e.size)}`:""} + `} +
    `)});var r,n=l.find(".w2ui-multi-items");s.style&&l.attr("style",l.attr("style")+";"+s.style),query(this.el).css("z-index","-1"),query(this.el).prop("readOnly")||query(this.el).prop("disabled")?setTimeout(()=>{l[0].scrollTop=0,l.addClass("w2ui-readonly").find(".li-item").css("opacity","0.9").parent().find(".li-search").hide().find("input").prop("readOnly",!0).closest(".w2ui-multi-items").find(".w2ui-list-remove").hide()},1):setTimeout(()=>{l.removeClass("w2ui-readonly").find(".li-item").css("opacity","1").parent().find(".li-search").show().find("input").prop("readOnly",!1).closest(".w2ui-multi-items").find(".w2ui-list-remove").show()},1),0${query(this.el).attr("placeholder")}`)),l.off(".w2item").on("scroll.w2item",e=>{e=this.trigger("scroll",{target:this.el,originalEvent:e});!0!==e.isCancelled&&(w2tooltip.hide(this.el.id+"_preview"),e.finish())}).find(".li-item").on("click.w2item",e=>{var i=query(e.target).closest(".li-item"),s=i.attr("index"),l=this.selected[s];if(!query(i).hasClass("li-search")){e.stopPropagation();let t;if(query(e.target).hasClass("w2ui-list-remove"))query(this.el).prop("readOnly")||query(this.el).prop("disabled")||!0!==(t=this.trigger("remove",{target:this.el,originalEvent:e,item:l})).isCancelled&&(this.selected.splice(s,1),query(this.el).trigger("input").trigger("change"),query(e.target).remove());else if(!0!==(t=this.trigger("click",{target:this.el,originalEvent:e.originalEvent,item:l})).isCancelled){let e=l.tooltip;if("file"===this.type&&(/image/i.test(l.type)&&(e=` +
    + +
    `),e+=` +
    +
    ${w2utils.lang("Name")}:
    +
    ${l.name}
    +
    ${w2utils.lang("Size")}:
    +
    ${w2utils.formatSize(l.size)}
    +
    ${w2utils.lang("Type")}:
    +
    ${l.type}
    +
    ${w2utils.lang("Modified")}:
    +
    ${w2utils.date(l.modified)}
    +
    `),e){let t=this.el.id+"_preview";w2tooltip.show({name:t,anchor:i.get(0),html:e,hideOn:["doc-click"],class:""}).show(e=>{query(`#w2overlay-${t} img`).on("load",function(e){var t=this.clientWidth,i=this.clientHeight;t<300&i<300||(i<=t&&300{var t=query(e.target).closest(".li-item");query(t).hasClass("li-search")||(t=this.selected[query(e.target).attr("index")],!0!==(e=this.trigger("mouseEnter",{target:this.el,originalEvent:e,item:t})).isCancelled&&e.finish())}).on("mouseleave.w2item",e=>{var t=query(e.target).closest(".li-item");query(t).hasClass("li-search")||(t=this.selected[query(e.target).attr("index")],!0!==(e=this.trigger("mouseLeave",{target:this.el,originalEvent:e,item:t})).isCancelled&&e.finish())}),"enum"===this.type?this.helpers.multi.find("input").css({width:"15px"}):this.helpers.multi.find(".li-search").hide(),this.resize()}return Date.now()-e}resize(){var e=this.el.clientWidth,t=getComputedStyle(this.el),i=this.helpers.search,s=this.helpers.multi,l=this.helpers.suffix,r=this.helpers.prefix,i=(i&&query(i).css("width",e),s&&query(s).css("width",e-parseInt(t["margin-left"],10)-parseInt(t["margin-right"],10)),l&&this.addSuffix(),r&&this.addPrefix(),this.helpers.multi);if(["enum","file"].includes(this.type)&&i){query(this.el).css("height","auto");let e=query(i).find(":scope div.w2ui-multi-items").get(0).clientHeight+5;(e=(e=e<20?20:e)>this.tmp["max-height"]?this.tmp["max-height"]:e)e&&(e=s),query(i).css({height:e+"px",overflow:e==this.tmp["max-height"]?"auto":"hidden"}),query(i).css("height",e+"px"),query(this.el).css({height:e+"px"})}this.tmp.current_width=e}reset(){null!=this.tmp&&(query(this.el).css("height","auto"),Array("padding-left","padding-right","background-color","border-color").forEach(e=>{this.tmp&&null!=this.tmp["old-"+e]&&(query(this.el).css(e,this.tmp["old-"+e]),delete this.tmp["old-"+e])}),clearInterval(this.tmp.sizeTimer)),query(this.el).val(this.clean(query(this.el).val())).removeClass("w2field").removeData("selected selectedIndex").off(".w2field"),Object.keys(this.helpers).forEach(e=>{query(this.helpers[e]).remove()}),this.helpers={}}clean(e){var t;return e="number"!=typeof e&&(t=this.options,e=String(e).trim(),["int","float","money","currency","percent"].includes(this.type))?""!==(e="string"==typeof e?(e=t.autoFormat&&(["money","currency"].includes(this.type)&&(e=String(e).replace(t.moneyRE,"")),"percent"===this.type&&(e=String(e).replace(t.percentRE,"")),["int","float"].includes(this.type))?String(e).replace(t.numberRE,""):e).replace(/\s+/g,"").replace(new RegExp(t.groupSymbol,"g"),"").replace(t.decimalSymbol,"."):e)&&w2utils.isFloat(e)?Number(e):"":e}format(e){var t=this.options;if(t.autoFormat&&""!==e){switch(this.type){case"money":case"currency":""!==(e=w2utils.formatNumber(e,t.currencyPrecision,!0))&&(e=t.currencyPrefix+e+t.currencySuffix);break;case"percent":""!==(e=w2utils.formatNumber(e,t.precision,!0))&&(e+="%");break;case"float":e=w2utils.formatNumber(e,t.precision,!0);break;case"int":e=w2utils.formatNumber(e,0,!0)}var i=parseInt(1e3).toLocaleString(w2utils.settings.locale,{useGrouping:!0}).slice(1,2);i!==this.options.groupSymbol&&(e=e.replaceAll(i,this.options.groupSymbol))}return e}change(e){if(-1!==["int","float","money","currency","percent"].indexOf(this.type)){var t=query(this.el).val(),i=this.format(this.clean(query(this.el).val()));if(""!==t&&t!=i)return query(this.el).val(i),e.stopPropagation(),e.preventDefault(),!1}if("color"===this.type){let e=query(this.el).val();"rgb"!==e.substr(0,3).toLowerCase()&&(e="#"+e,8!==(t=query(this.el).val().length))&&6!==t&&3!==t&&(e="");i=query(this.el).get(0).nextElementSibling;query(i).find("div").css("background-color",e),query(this.el).hasClass("has-focus")&&this.updateOverlay()}if(-1!==["list","enum","file"].indexOf(this.type)&&this.refresh(),-1!==["date","time","datetime"].indexOf(this.type)){let e=parseInt(this.el.value);w2utils.isInt(this.el.value)&&3e3{this.updateOverlay()},100)}var t;"file"==this.type&&(t=query(this.el).get(0).previousElementSibling,query(t).addClass("has-focus")),query(this.el).addClass("has-focus")}}blur(e){var i,s=query(this.el).val().trim();if(query(this.el).removeClass("has-focus"),["int","float","money","currency","percent"].includes(this.type)&&""!==s){let e=s,t="";this.isStrValid(s)?(i=this.clean(s),null!=this.options.min&&i= "+this.options.min),null!=this.options.max&&i>this.options.max&&(e=this.options.max,t="Should be <= "+this.options.max)):e="",this.options.autoCorrect&&(query(this.el).val(e).trigger("input").trigger("change"),t)&&(w2tooltip.show({name:this.el.id+"_error",anchor:this.el,html:t}),setTimeout(()=>{w2tooltip.hide(this.el.id+"_error")},3e3))}["date","time","datetime"].includes(this.type)&&this.options.autoCorrect&&""!==s&&(i="date"==this.type?w2utils.isDate:"time"==this.type?w2utils.isTime:w2utils.isDateTime,w2date.inRange(this.el.value,this.options)&&i.bind(w2utils)(this.el.value,this.options.format)||query(this.el).val("").trigger("input").trigger("change")),"enum"===this.type&&query(this.helpers.multi).find("input").val("").css("width","15px"),"file"==this.type&&(s=this.el.previousElementSibling,query(s).removeClass("has-focus")),"list"===this.type&&(this.el.value=this.selected?.text??"")}keyDown(t,i){var e,s=this.options,i=t.keyCode||i&&i.keyCode;let l=!1,r,n,a,o,h,d;if(["int","float","money","currency","percent","hex","bin","color","alphanumeric"].includes(this.type)&&!(t.metaKey||t.ctrlKey||t.altKey||this.isStrValid(t.key??"1",!0)||[9,8,13,27,37,38,39,40,46].includes(t.keyCode)))return t.preventDefault(),t.stopPropagation?t.stopPropagation():t.cancelBubble=!0,!1;if(["int","float","money","currency","percent"].includes(this.type)){if(!s.keyboard||query(this.el).prop("readOnly")||query(this.el).prop("disabled"))return;switch(r=parseFloat(query(this.el).val().replace(s.moneyRE,""))||0,n=s.step,(t.ctrlKey||t.metaKey)&&(n=10*s.step),i){case 38:t.shiftKey||(h=r+n<=s.max||null==s.max?Number((r+n).toFixed(12)):s.max,query(this.el).val(h).trigger("input").trigger("change"),l=!0);break;case 40:t.shiftKey||(h=r-n>=s.min||null==s.min?Number((r-n).toFixed(12)):s.min,query(this.el).val(h).trigger("input").trigger("change"),l=!0)}l&&(t.preventDefault(),this.moveCaret2end())}if(["date","datetime"].includes(this.type)){if(!s.keyboard||query(this.el).prop("readOnly")||query(this.el).prop("disabled"))return;var u=("date"==this.type?w2utils.isDate:w2utils.isDateTime).bind(w2utils),c=("date"==this.type?w2utils.formatDate:w2utils.formatDateTime).bind(w2utils);switch(a=864e5,n=1,(t.ctrlKey||t.metaKey)&&(n=10),(o=u(query(this.el).val(),s.format,!0))||(o=new Date,a=0),i){case 38:t.shiftKey||(10==n?o.setMonth(o.getMonth()+1):o.setTime(o.getTime()+a),d=c(o.getTime(),s.format),query(this.el).val(d).trigger("input").trigger("change"),l=!0);break;case 40:t.shiftKey||(10==n?o.setMonth(o.getMonth()-1):o.setTime(o.getTime()-a),d=c(o.getTime(),s.format),query(this.el).val(d).trigger("input").trigger("change"),l=!0)}l&&(t.preventDefault(),this.moveCaret2end(),this.updateOverlay())}if("time"===this.type){if(!s.keyboard||query(this.el).prop("readOnly")||query(this.el).prop("disabled"))return;n=t.ctrlKey||t.metaKey?60:1,r=query(this.el).val();let e=w2date.str2min(r)||w2date.str2min((new Date).getHours()+":"+((new Date).getMinutes()-1));switch(i){case 38:t.shiftKey||(e+=n,l=!0);break;case 40:t.shiftKey||(e-=n,l=!0)}l&&(t.preventDefault(),query(this.el).val(w2date.min2str(e)).trigger("input").trigger("change"),this.moveCaret2end())}if(["list","enum"].includes(this.type))switch(i){case 8:case 46:"list"==this.type?""==query(this.helpers.search_focus).val()&&(this.selected=null,w2menu.hide(this.el.id+"_menu"),query(this.el).val("").trigger("input").trigger("change")):""==query(this.helpers.multi).find("input").val()&&(w2menu.hide(this.el.id+"_menu"),this.selected.pop(),(e=w2menu.get(this.el.id+"_menu"))&&(e.options.selected=this.selected),this.refresh());break;case 9:case 16:break;case 27:w2menu.hide(this.el.id+"_menu"),this.refresh()}}keyUp(t){if("list"==this.type){let e=query(this.helpers.search_focus);""!==e.val()?query(this.el).attr("placeholder",""):query(this.el).attr("placeholder",this.tmp.pholder),13==t.keyCode?setTimeout(()=>{e.val(""),w2menu.hide(this.el.id+"_menu"),this.refresh()},1):[8,9,16,27,46].includes(t.keyCode)?w2menu.hide(this.el.id+"_menu"):this.updateOverlay(),this.refresh()}var e;"combo"==this.type&&this.updateOverlay(),"enum"==this.type&&(t=this.helpers.multi.find("input"),e=getComputedStyle(t.get(0)),e=w2utils.getStrWidth(t.val(),`font-family: ${e["font-family"]}; font-size: ${e["font-size"]};`),t.css({width:e+15+"px"}),this.resize())}findItemIndex(e,i,s){let l=[];return s=s||[],e.forEach((e,t)=>{e.id===i&&(l=s.concat([t]),this.options.index=[t]),0==l.length&&e.items&&0{e=e.detail.color;query(this.el).val(e).trigger("input").trigger("change")}).liveUpdate(e=>{e=e.detail.color;query(this.helpers.suffix).find(":scope > div").css("background-color","#"+e)})}if(["list","combo","enum"].includes(this.type)){var t;this.el;let s=this.el;if("enum"===this.type&&(t=this.helpers.multi.get(0),s=query(t).find("input").get(0)),"list"===this.type&&(t=this.selected,w2utils.isPlainObject(t)&&0{var t,i;["list","combo"].includes(this.type)?(this.selected=e.detail.item,query(s).val(""),query(this.el).val(this.selected.text).trigger("input").trigger("change"),this.focus({showMenu:!1})):(i=this.selected,(t=e.detail?.item)&&!0!==(e=this.trigger("add",{target:this.el,item:t,originalEvent:e})).isCancelled&&(i.length>=l.max&&0{e=e.detail.date;null!=e&&query(this.el).val(e).trigger("input").trigger("change")})}isStrValid(e,t){let i=!0;switch(this.type){case"int":i=!(!t||!["-",this.options.groupSymbol].includes(e))||w2utils.isInt(e.replace(this.options.numberRE,""));break;case"percent":e=e.replace(/%/g,"");case"float":i=!(!t||!["-","",this.options.decimalSymbol,this.options.groupSymbol].includes(e))||w2utils.isFloat(e.replace(this.options.numberRE,""));break;case"money":case"currency":i=!(!t||!["-",this.options.decimalSymbol,this.options.groupSymbol,this.options.currencyPrefix,this.options.currencySuffix].includes(e))||w2utils.isFloat(e.replace(this.options.moneyRE,""));break;case"bin":i=w2utils.isBin(e);break;case"color":case"hex":i=w2utils.isHex(e);break;case"alphanumeric":i=w2utils.isAlphaNumeric(e)}return i}addPrefix(){var e,t;this.options.prefix&&(t=getComputedStyle(this.el),null==this.tmp["old-padding-left"]&&(this.tmp["old-padding-left"]=t["padding-left"]),this.helpers.prefix&&query(this.helpers.prefix).remove(),query(this.el).before(`
    ${this.options.prefix}
    `),e=query(this.el).get(0).previousElementSibling,query(e).css({color:t.color,"font-family":t["font-family"],"font-size":t["font-size"],height:this.el.clientHeight+"px","padding-top":t["padding-top"],"padding-bottom":t["padding-bottom"],"padding-left":this.tmp["old-padding-left"],"padding-right":0,"margin-top":parseInt(t["margin-top"],10)+2+"px","margin-bottom":parseInt(t["margin-bottom"],10)+1+"px","margin-left":t["margin-left"],"margin-right":0,"z-index":1}),query(this.el).css("padding-left",e.clientWidth+"px !important"),this.helpers.prefix=e)}addSuffix(){if(this.options.suffix||this.options.arrow){let e,t=this;var i=getComputedStyle(this.el),s=(null==this.tmp["old-padding-right"]&&(this.tmp["old-padding-right"]=i["padding-right"]),parseInt(i["padding-right"]||0));this.options.arrow&&(this.helpers.arrow&&query(this.helpers.arrow).remove(),query(this.el).after('
     
    '),e=query(this.el).get(0).nextElementSibling,query(e).css({color:i.color,"font-family":i["font-family"],"font-size":i["font-size"],height:this.el.clientHeight+"px",padding:0,"margin-top":parseInt(i["margin-top"],10)+1+"px","margin-bottom":0,"border-left":"1px solid silver",width:"16px",transform:"translateX(-100%)"}).on("mousedown",function(e){query(e.target).hasClass("arrow-up")&&t.keyDown(e,{keyCode:38}),query(e.target).hasClass("arrow-down")&&t.keyDown(e,{keyCode:40})}),s+=e.clientWidth,query(this.el).css("padding-right",s+"px !important"),this.helpers.arrow=e),""!==this.options.suffix&&(this.helpers.suffix&&query(this.helpers.suffix).remove(),query(this.el).after(`
    ${this.options.suffix}
    `),e=query(this.el).get(0).nextElementSibling,query(e).css({color:i.color,"font-family":i["font-family"],"font-size":i["font-size"],height:this.el.clientHeight+"px","padding-top":i["padding-top"],"padding-bottom":i["padding-bottom"],"padding-left":0,"padding-right":i["padding-right"],"margin-top":parseInt(i["margin-top"],10)+2+"px","margin-bottom":parseInt(i["margin-bottom"],10)+1+"px",transform:"translateX(-100%)"}),query(this.el).css("padding-right",e.clientWidth+"px !important"),this.helpers.suffix=e)}}addSearch(){if("list"===this.type){this.helpers.search&&query(this.helpers.search).remove();let e=parseInt(query(this.el).attr("tabIndex")),t=(isNaN(e)||-1===e||(this.tmp["old-tabIndex"]=e),null!=(e=this.tmp["old-tabIndex"]?this.tmp["old-tabIndex"]:e)&&!isNaN(e)||(e=0),"");var i=` +
    + + +
    `,i=(query(this.el).attr("tabindex",-1).before(i),query(this.el).get(0).previousElementSibling),s=(this.helpers.search=i,this.helpers.search_focus=query(i).find("input").get(0),getComputedStyle(this.el));query(i).css({width:this.el.clientWidth+"px","margin-top":s["margin-top"],"margin-left":s["margin-left"],"margin-bottom":s["margin-bottom"],"margin-right":s["margin-right"]}).find("input").css({cursor:"default",width:"100%",opacity:1,padding:s.padding,margin:s.margin,border:"1px solid transparent","background-color":"transparent"}),query(i).find("input").off(".helper").on("focus.helper",e=>{query(e.target).val(""),this.tmp.pholder=query(this.el).attr("placeholder")??"",this.focus(e),e.stopPropagation()}).on("blur.helper",e=>{query(e.target).val(""),null!=this.tmp.pholder&&query(this.el).attr("placeholder",this.tmp.pholder),this.blur(e),e.stopPropagation()}).on("keydown.helper",e=>{this.keyDown(e)}).on("keyup.helper",e=>{this.keyUp(e)}),query(i).on("click",e=>{query(e.target).find("input").focus()})}}addMultiSearch(){if(["enum","file"].includes(this.type)){query(this.helpers.multi).remove();let e="";var l,r,n=getComputedStyle(this.el),a=w2utils.stripSpaces(` + margin-top: 0px; + margin-bottom: 0px; + margin-left: ${n["margin-left"]}; + margin-right: ${n["margin-right"]}; + width: ${w2utils.getSize(this.el,"width")-parseInt(n["margin-left"],10)-parseInt(n["margin-right"],10)}px; + `);null==this.tmp["min-height"]&&(l=this.tmp["min-height"]=parseInt(("none"!=n["min-height"]?n["min-height"]:0)||0),r=parseInt(n.height),this.tmp["min-height"]=Math.max(l,r)),null==this.tmp["max-height"]&&"none"!=n["max-height"]&&(this.tmp["max-height"]=parseInt(n["max-height"]));let t="",i=(null!=query(this.el).attr("id")&&(t=`id="${query(this.el).attr("id")}_search"`),parseInt(query(this.el).attr("tabIndex"))),s=(isNaN(i)||-1===i||(this.tmp["old-tabIndex"]=i),null!=(i=this.tmp["old-tabIndex"]?this.tmp["old-tabIndex"]:i)&&!isNaN(i)||(i=0),"enum"===this.type&&(e=` +
    +
    + +
    +
    `),"file"===this.type&&(e=` +
    +
    + +
    +
    + +
    +
    `),this.tmp["old-background-color"]=n["background-color"],this.tmp["old-border-color"]=n["border-color"],query(this.el).before(e).css({"border-color":"transparent","background-color":"transparent"}),query(this.el.previousElementSibling));this.helpers.multi=s,query(this.el).attr("tabindex",-1),s.on("click",e=>{this.focus(e)}),s.find("input:not(.file-input)").on("click",e=>{this.click(e)}).on("focus",e=>{this.focus(e)}).on("blur",e=>{this.blur(e)}).on("keydown",e=>{this.keyDown(e)}).on("keyup",e=>{this.keyUp(e)}),"file"===this.type&&s.find("input.file-input").off(".drag").on("click.drag",e=>{e.stopPropagation(),query(this.el).prop("readOnly")||query(this.el).prop("disabled")||this.focus(e)}).on("dragenter.drag",e=>{query(this.el).prop("readOnly")||query(this.el).prop("disabled")||s.addClass("w2ui-file-dragover")}).on("dragleave.drag",e=>{query(this.el).prop("readOnly")||query(this.el).prop("disabled")||s.removeClass("w2ui-file-dragover")}).on("drop.drag",e=>{query(this.el).prop("readOnly")||query(this.el).prop("disabled")||(s.removeClass("w2ui-file-dragover"),Array.from(e.dataTransfer.files).forEach(e=>{this.addFile(e)}),this.focus(e),e.preventDefault(),e.stopPropagation())}).on("dragover.drag",e=>{e.preventDefault(),e.stopPropagation()}).on("change.drag",e=>{void 0!==e.target.files&&Array.from(e.target.files).forEach(e=>{this.addFile(e)}),this.focus(e)}),this.refresh()}}addFile(t){var e=this.options,s=this.selected;let l={name:t.name,type:t.type,modified:t.lastModifiedDate,size:t.size,content:null,file:t},i=0,r=0,n=[],a=(Array.isArray(s)&&s.forEach(e=>{e.name==t.name&&e.size==t.size&&n.push(w2utils.lang('The file "${name}" (${size}) is already added.',{name:t.name,size:w2utils.formatSize(t.size)})),i+=e.size,r++}),0!==e.maxFileSize&&l.size>e.maxFileSize&&n.push(w2utils.lang("Maximum file size is ${size}",{size:w2utils.formatSize(e.maxFileSize)})),0!==e.maxSize&&i+l.size>e.maxSize&&n.push(w2utils.lang("Maximum total size is ${size}",{size:w2utils.formatSize(e.maxSize)})),0!==e.max&&r>=e.max&&n.push(w2utils.lang("Maximum number of files is ${count}",{count:e.max})),this.trigger("add",{target:this.el,file:l,total:r,totalSize:i,errors:n}));if(!0!==a.isCancelled)if(!0!==e.silent&&0")}),console.log("ERRORS (while adding files): ",n);else if(s.push(l),"undefined"!=typeof FileReader&&!0===e.readContent){s=new FileReader;let i=this;s.onload=function(e){var e=e.target.result,t=e.indexOf(",");l.content=e.substr(t+1),i.refresh(),query(i.el).trigger("input").trigger("change"),a.finish()},s.readAsDataURL(t)}else this.refresh(),query(this.el).trigger("input").trigger("change"),a.finish()}moveCaret2end(){setTimeout(()=>{this.el.setSelectionRange(this.el.value.length,this.el.value.length)},0)}}!function(r){function e(){var t,i;t=window,i={w2ui:w2ui,w2utils:w2utils,query:query,w2locale:w2locale,w2event:w2event,w2base:w2base,w2popup:w2popup,w2alert:w2alert,w2confirm:w2confirm,w2prompt:w2prompt,Dialog:Dialog,w2tooltip:w2tooltip,w2menu:w2menu,w2color:w2color,w2date:w2date,Tooltip:Tooltip,w2toolbar:w2toolbar,w2sidebar:w2sidebar,w2tabs:w2tabs,w2layout:w2layout,w2grid:w2grid,w2form:w2form,w2field:w2field},Object.keys(i).forEach(e=>{t[e]=i[e]})}var t=String(void 0).split("?")[1]||"";function i(t,i){var e;if(r.isPlainObject(t)){let e;return"w2form"==i&&(e=new w2form(t),0{let i=r(t).data("w2field");return i,(i=new w2field(s,l)).render(t),i})},r.fn.w2form=function(e){return i.call(this,e,"w2form")},r.fn.w2grid=function(e){return i.call(this,e,"w2grid")},r.fn.w2layout=function(e){return i.call(this,e,"w2layout")},r.fn.w2sidebar=function(e){return i.call(this,e,"w2sidebar")},r.fn.w2tabs=function(e){return i.call(this,e,"w2tabs")},r.fn.w2toolbar=function(e){return i.call(this,e,"w2toolbar")},r.fn.w2popup=function(e){0{w2utils.marker(t,i)})},r.fn.w2tag=function(i,s){return this.each((e,t)=>{null==i&&null==s?w2tooltip.hide():("object"==typeof i?s=i:(s=s??{}).html=i,w2tooltip.show(t,s))})},r.fn.w2overlay=function(i,s){return this.each((e,t)=>{null==i&&null==s?w2tooltip.hide():("object"==typeof i?s=i:s.html=i,Object.assign(s,{class:"w2ui-white",hideOn:["doc-click"]}),w2tooltip.show(t,s))})},r.fn.w2menu=function(i,s){return this.each((e,t)=>{"object"==typeof i&&(s=i),"object"==typeof i?s=i:s.items=i,w2menu.show(t,s)})},r.fn.w2color=function(i,s){return this.each((e,t)=>{t=w2color.show(t,i);"function"==typeof s&&t.select(s)})})}(window.jQuery),function(t,i){if("function"==typeof define&&define.amd)return define(()=>i);if("undefined"!=typeof exports){if("undefined"!=typeof module&&module.exports)return exports=module.exports=i;t=exports}t&&Object.keys(i).forEach(e=>{t[e]=i[e]})}(self,{w2ui:w2ui,w2utils:w2utils,query:query,w2locale:w2locale,w2event:w2event,w2base:w2base,w2popup:w2popup,w2alert:w2alert,w2confirm:w2confirm,w2prompt:w2prompt,Dialog:Dialog,w2tooltip:w2tooltip,w2menu:w2menu,w2color:w2color,w2date:w2date,Tooltip:Tooltip,w2toolbar:w2toolbar,w2sidebar:w2sidebar,w2tabs:w2tabs,w2layout:w2layout,w2grid:w2grid,w2form:w2form,w2field:w2field}); \ No newline at end of file diff --git a/spells.html b/spells.html index 8c3717e..74f68f6 100644 --- a/spells.html +++ b/spells.html @@ -1,6 +1,7 @@ + - + @@ -9,23 +10,69 @@ Spells - fateforge.tools + + + + + + + + + + - - - -

    FateforgeTools

    -

    Spells

    +
    + + + +

    FateforgeTools

    +
    +
    +

    Spells

    + + + +
    +
    + +
    - - - - + + + + + + + +
    + + + + +
    Select a spell from the list to view it here
    +
    + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test-w2ui.html b/test-w2ui.html new file mode 100644 index 0000000..8b13f5b --- /dev/null +++ b/test-w2ui.html @@ -0,0 +1,57 @@ + + + + W2UI Demo: grid/6 + + + + +
    +
    + + + + + + \ No newline at end of file