commit 3a0cf3f14d637b8cebc527d3825f6ea6d12eed7a Author: zhongjin Date: Wed Jan 16 11:59:25 2019 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37aa01f --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/.idea +/node_modules +.git +admin/i18n/flat.txt +admin/i18n/*/flat.txt +test/Ananas64.exe +iob_npm.done +tmp +package-lock.json \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..8f071f6 --- /dev/null +++ b/.npmignore @@ -0,0 +1,12 @@ +gulpfile.js +tasks +node_modules +.idea +.git +/node_modules +test +.travis.yml +appveyor.yml +admin/i18n +iob_npm.done +package-lock.json diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c7a2beb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +os: + - linux + - osx +language: node_js +node_js: + - '4' + - '6' + - '8' + - '10' +before_install: + - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export CC=clang++; export CXX=clang++; export CXXFLAGS=-std=c++11; fi' + - 'if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-4.8; fi' +before_script: + - export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1) + - 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm@5; fi' + - npm -v + - npm install winston@2.3.1 + - 'npm install https://github.com/yunkong2/yunkong2.js-controller/tarball/master --production' +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8f8d28e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2018 Bluefox + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2dffc1f --- /dev/null +++ b/README.md @@ -0,0 +1,304 @@ +![Logo](admin/modbus.png) +# yunkong2.rsonoff +===================== + +[![NPM version](http://img.shields.io/npm/v/yunkong2.rsonoff.svg)](https://www.npmjs.com/package/yunkong2.rsonoff) +[![Downloads](https://img.shields.io/npm/dm/yunkong2.rsonoff.svg)](https://www.npmjs.com/package/yunkong2.rsonoff) + +[![NPM](https://nodei.co/npm/yunkong2.rsonoff.png?downloads=true)](https://nodei.co/npm/yunkong2.rsonoff/) + +Implementation of ModBus Slave and Master for yunkong2. Following types are supported: +- Modbus RTU over serial (master) +- Modbus RTU over TCP (master) +- Modbus TCP (slave, master) + +## Settings +### Partner IP Address +IP address of modbus partner. + +### Port +TCP Port of modbus partner if configured as master (client) or own port if configured as slave(server). + +### Device ID +Modbus Device ID. Important if TCP/Modbus bridge is used. + +### Type +Slave(Server) or Master(Client). + +### Use aliases as address +Normally all registers can have address from 0 to 65535. By using of aliases you can define virtual address fields for every type of registers. Normally: +- discrete inputs are from 10001 to 20000 +- coils are from 1 to 1000 +- input registers are from 30001 to 40000 +- holding registers are from 40001 to 60000 + +Every alias will be mapped internally to address, e.g. 30011 will be mapped to input register 10. and so on. + +### Do not align addresses to word +Normally the coils and the discrete inputs addresses are aligned to 16 bit. Like addresses from 3 to 20 will be aligned to 0 up 32. +If this option is active the addresses will not be aligned. + +### Round Real to +How many digits after comma for float and doubles. + +### Poll delay +Cyclic poll interval (Only relevant for master) + +### Reconnect time +Reconnection interval (Only relevant for master) + +### Pulse time +if pulse used for coils, this define the interval how long is pulse. + +### Max read request length +Maximal length of command READ_MULTIPLE_REGISTERS as number of registers to read. + +Some systems require first "write request" to deliver the data on "read request". +You can force this mode by setting of the "Max read request length" to 1. + +**Notice:** +Some USB Modbus solutions (e.g. based on socat) can have trouble to work with serialport npm module. + +There is a software [**Modbus RTU <-> Modbus RTU over TCP**](http://mbus.sourceforge.net/index.html) gateway to enable using of serial RTU over TCP protocol. + +Both solutions **RTU over TCP** and **TCP** works well. + +## Data types + +- uint16be - Unsigned 16 bit (Big Endian): AABB => AABB +- uint16le - Unsigned 16 bit (Little Endian): AABB => BBAA +- int16be - Signed 16 bit (Big Endian): AABB => AABB +- int16le - Signed 16 bit (Little Endian): AABB => BBAA +- uint32be - Unsigned 32 bit (Big Endian): AABBCCDD => AABBCCDD +- uint32le - Unsigned 32 bit (Little Endian): AABBCCDD => DDCCBBAA +- uint32sw - Unsigned 32 bit (Big Endian Word Swap): AABBCCDD => CCDDAABB +- uint32sb - Unsigned 32 bit (Big Endian Byte Swap): AABBCCDD => DDCCBBAA +- int32be - Signed 32 bit (Big Endian): AABBCCDD => AABBCCDD +- int32le - Signed 32 bit (Little Endian): ABBCCDD => DDCCBBAA +- int32sw - Signed 32 bit (Big Endian Word Swap): AABBCCDD => CCDDAABB +- int32sb - Signed 32 bit (Big Endian Byte Swap): AABBCCDD => DDCCBBAA +- uint64be - Unsigned 64 bit (Big Endian): AABBCCDDEEFFGGHH => AABBCCDDEEFFGGHH +- uint64le - Unsigned 64 bit (Little Endian): AABBCCDDEEFFGGHH => HHGGFFEEDDCCBBAA +- uint8be - Unsigned 8 bit (Big Endian): AA => AA +- uint8le - Unsigned 8 bit (Little Endian): AA => AA +- int8be - Signed 8 bit (Big Endian): AA => AA +- int8le - Signed 8 bit (Little Endian): AA => AA +- floatbe - Float (Big Endian): AABBCCDD => AABBCCDD +- floatle - Float (Little Endian): AABBCCDD => DDCCBBAA +- floatsw - Float (Big Endian Word Swap): AABBCCDD => CCDDAABB +- floatsb - Float (Big Endian Byte Swap): AABBCCDD => DDCCBBAA +- doublebe - Double (Big Endian): AABBCCDDEEFFGGHH => AABBCCDDEEFFGGHH +- doublele - Double (Little Endian): AABBCCDDEEFFGGHH => HHGGFFEEDDCCBBAA +- string - String (Zero-end): ABCDEF\0 => ABCDEF\0 +- stringle - String (Little Endian, Zero-end): BADCFE\0 => ABCDEF\0 + +Following description was copied from [here](http://www.chipkin.com/how-real-floating-point-and-32-bit-data-is-encoded-in-modbus-rtu-messages/) + +The point-to-point Modbus protocol is a popular choice for RTU communications if for no other reason that it’s basic convenience. The protocol itself controls the interactions of each device on a Modbus network, how device establishes a known address, how each device recognizes its messages and how basic information is extracted from the data. In essence, the protocol is the foundation of the entire Modbus network. + +Such convenience does not come without some complications however, and Modbus RTU Message protocol is no exception. The protocol itself was designed based on devices with a 16-bit register length. Consequently, special considerations were required when implementing 32-bit data elements. This implementation settled on using two consecutive 16-bit registers to represent 32 bits of data or essentially 4 bytes of data. It is within these 4 bytes of data that single-precision floating point data can be encoded into a Modbus RTU message. + +### The Importance of Byte Order + +Modbus itself does not define a floating point data type but it is widely accepted that it implements 32-bit floating point data using the IEEE-754 standard. However, the IEEE standard has no clear cut definition of byte order of the data payload. Therefore the most important consideration when dealing with 32-bit data is that data is addressed in the proper order. + +For example, the number 123/456.00 as defined in the IEEE 754 standard for single-precision 32-bit floating point numbers appears as follows: + +![Image1](img/img1.png) + +The affects of various byte orderings are significant. For example, ordering the 4 bytes of data that represent 123456.00 in a “B A D C” sequence in known as a “byte swap”. When interpreted as an IEEE 744 floating point data type, the result is quite different: + +![Image2](img/img2.png) + +Ordering the same bytes in a “C D A B” sequence is known as a “word swap”. Again, the results differ drastically from the original value of 123456.00: + +![Image3](img/img3.png) + +Furthermore, both a “byte swap” and a “word swap” would essentially reverse the sequence of the bytes altogether to produce yet another result: + +![Image4](img/img4.png) + +Clearly, when using network protocols such as Modbus, strict attention must be paid to how bytes of memory are ordered when they are transmitted, also known as the ‘byte order’. + +### Determining Byte Order + +The Modbus protocol itself is declared as a ‘big-Endian’ protocol, as per the Modbus Application Protocol Specification, V1.1.b: + +```Modbus uses a “big-Endian” representation for addresses and data items. This means that when a numerical quantity larger than a single byte is transmitted, the most significant byte is sent first.``` + +Big-Endian is the most commonly used format for network protocols – so common, in fact, that it is also referred to as ‘network order’. + +Given that the Modbus RTU message protocol is big-Endian, in order to successfully exchange a 32-bit datatype via a Modbus RTU message, the endianness of both the master and the slave must considered. Many RTU master and slave devices allow specific selection of byte order particularly in the case of software-simulated units. One must merely insure that both all units are set to the same byte order. + +As a rule of thumb, the family of a device’s microprocessor determines its endianness. Typically, the big-Endian style (the high-order byte is stored first, followed by the low-order byte) is generally found in CPUs designed with a Motorola processor. The little-Endian style (the low-order byte is stored first, followed by the high-order byte) is generally found in CPUs using the Intel architecture. It is a matter of personal perspective as to which style is considered ‘backwards’. + +If, however, byte order and endianness is not a configurable option, you will have to determine the how to interpret the byte. This can be done requesting a known floating-point value from the slave. If an impossible value is returned, i.e. a number with a double-digit exponent or such, the byte ordering will most likely need modification. + +### Practical Help + +The FieldServer Modbus RTU drivers offer several function moves that handle 32-bit integers and 32-bit float values. More importantly, these function moves consider all different forms of byte sequencing. The following table shows the FieldServer function moves that copy two adjacent 16-bit registers to a 32-bit integer value. + +| Function Keyword | Swap Mode | Source Bytes | Target Bytes | +|-------------------|--------------------|-----------------|--------------| +| 2.i16-1.i32 | N/A | [ a b ] [ c d ] | [ a b c d ] | +| 2.i16-1.i32-s | byte and word swap | [ a b ] [ c d ] | [ d c b a ] | +| 2.i16-1.i32-sb | byte swap | [ a b ] [ c d ] | [ b a d c ] | +| 2.i16-1.i32-sw | word swap | [ a b ] [ c d ] | [ c d a b ] | + +The following table shows the FieldServer function moves that copy two adjacent 16-bit registers to a 32-bit floating point value: + +| Function Keyword | Swap Mode | Source Bytes | Target Bytes | +|-------------------|--------------------|-----------------|--------------| +| 2.i16-1.ifloat | N/A | [ a b ] [ c d ] | [ a b c d ] | +| 2.i16-1.ifloat-s | byte and word swap | [ a b ] [ c d ] | [ d c b a ] | +| 2.i16-1.ifloat-sb | byte swap | [ a b ] [ c d ] | [ b a d c ] | +| 2.i16-1.ifloat-sw | word swap | [ a b ] [ c d ] | [ c d a b ] | + +The following table shows the FieldServer function moves that copy a single 32-bit floating point value to two adjacent 16-bit registers: + +| Function Keyword | Swap Mode | Source Bytes | Target Bytes | +|------------------|-------------------|-----------------|----------------| +| 1.float-2.i16 |N/A | [ a b ] [ c d ] | [ a b ][ c d ] | +| 1.float-2.i16-s |byte and word swap | [ a b ] [ c d ] | [ d c ][ b a ] | +| 1.float-2.i16-sb |byte swap | [ a b ] [ c d ] | [ b a ][ d c ] | +| 1.float-2.i16-sw |word swap | [ a b ] [ c d ] | [ c d ][ a b ] | + +Given the various FieldServer function moves, the correct handling of 32-bit data is dependent on choosing the proper one. Observe the following behavior of these FieldServer function moves on the known single-precision decimal float value of 123456.00: + +|16-bit Values | Function Move | Result | Function Move | Result | +|---------------|-------------------|-----------|-------------------|---------------| +|0x2000 0x47F1 | 2.i16-1.float | 123456.00 | 1.float-2.i16 | 0x2000 0x47F1 | +|0xF147 0x0020 | 2.i16-1.float-s | 123456.00 | 1.float-2.i16-s | 0xF147 0X0020 | +|0x0020 0xF147 | 2.i16-1.float-sb | 123456.00 | 1.float-2.i16-sb | 0x0020 0xF147 | +|0x47F1 0x2000 | 2.i16-1.float-sw | 123456.00 | 1.float-2.i16-sw | 0x47F1 0x2000 | + +Notice that different byte and word orderings require the use of the appropriate FieldServer function move. Once the proper function move is selected, the data can be converted in both directions. + +Of the many hex-to-floating point converters and calculators that are available in the Internet, very few actually allow manipulation of the byte and word orders. One such utility is located at www.61131.com/download.htm where both Linux and Windows versions of the utilities can be downloaded. Once installed, the utility is run as an executable with a single dialog interface. The utility presents the decimal float value of 123456.00 as follows: + +![Image5](img/img5.png) + +One can then swap bytes and/or words to analyze what potential endianness issues may exist between Modbus RTU master and slave devices. + +## Test +There are some programs in folder *test' to test the TCP communication: +- Ananas32/64 is slave simulator (only holding registers and inputs, no coils and digital inputs) +- RMMS is master simulator +- mod_RSsim.exe is slave simulator. It can be that you need [Microsoft Visual C++ 2008 SP1 Redistributable Package](https://www.microsoft.com/en-us/download/details.aspx?id=5582) to start it (because of SideBySide error). + +## Changelog +# 2.0.9 (2018-10-11) +* (Bjoern3003) Write registers was corrected + +# 2.0.7 (2018-07-02) +* (bluefox) The server mode was fixed + +# 2.0.6 (2018-06-26) +* (bluefox) rtu-tcp master mode was fixed + +# 2.0.3 (2018-06-16) +* (bluefox) Fixed the rounding of numbers + +# 2.0.2 (2018-06-12) +* (bluefox) The error with blocks reading was fixed +* (bluefox) The block reading for discrete values was implemented + +# 2.0.1 (2018-05-06) +* (bluefox) Added the support of multiple device IDs + +# 1.1.1 (2018-04-15) +* (Apollon77) Optimize reconnect handling + +# 1.1.0 (2018-01-23) +* (bluefox) Little endian strings added +* (Apollon77) Upgrade Serialport Library + +# 1.0.2 (2018-01-20) +* (bluefox) Fixed read of coils + +# 0.5.4 (2017-09-27) +* (Apollon77) Several Fixes + +# 0.5.0 (2017-02-11) +* (bluefox) Create all states each after other + +# 0.4.10 (2017-02-10) +* (Apollon77) Do not recreate all datapoints on start of adapter +* (ykuendig) Multiple optimization and wording fixes + +# 0.4.9 (2016-12-20) +* (bluefox) fix serial RTU + +# 0.4.8 (2016-12-15) +* (Apollon77) update serialport library for node 6.x compatibility + +# 0.4.7 (2016-11-27) +* (bluefox) Use old version of jsmodbus + +# 0.4.6 (2016-11-08) +* (bluefox) backward compatibility with 0.3.x + +# 0.4.5 (2016-10-25) +* (bluefox) better buffer handling on tcp and serial + +# 0.4.4 (2016-10-21) +* (bluefox) Fix write of holding registers + +# 0.4.1 (2016-10-19) +* (bluefox) Support of ModBus RTU over serial and over TCP (only slave) + +# 0.3.11 (2016-08-18) +* (Apollon77) Fix wrong byte count in loop + +# 0.3.10 (2016-02-01) +* (bluefox) fix lost of history settings. + +# 0.3.9 (2015-11-09) +* (bluefox) Use always write_multiple_registers by write of holding registers. + +# 0.3.7 (2015-11-02) +* (bluefox) add special read/write mode if "Max read request length" is 1. + +# 0.3.6 (2015-11-01) +* (bluefox) add cyclic write for holding registers (fix) + +# 0.3.5 (2015-10-31) +* (bluefox) add cyclic write for holding registers + +# 0.3.4 (2015-10-28) +* (bluefox) add doubles and fix uint64 + +# 0.3.3 (2015-10-27) +* (bluefox) fix holding registers + +# 0.3.2 (2015-10-27) +* (bluefox) fix import from text file + +# 0.3.1 (2015-10-26) +* (bluefox) fix error with length of read block (master) +* (bluefox) support of read blocks and maximal length of read request (master) +* (bluefox) can define fields by import + +# 0.3.0 (2015-10-24) +* (bluefox) add round settings +* (bluefox) add deviceID +* (bluefox) slave supports floats, integers and strings + +# 0.2.6 (2015-10-22) +* (bluefox) add different types for inputRegisters and for holding registers ONLY FOR MASTER + +# 0.2.5 (2015-10-20) +* (bluefox) fix names of objects if aliases used + +# 0.2.4 (2015-10-19) +* (bluefox) fix error add new values + +# 0.2.3 (2015-10-15) +* (bluefox) fix error with master + +# 0.2.2 (2015-10-14) +* (bluefox) implement slave +* (bluefox) change addressing model + +# 0.0.1 +* (bluefox) initial commit diff --git a/admin/i18n/de/translations.json b/admin/i18n/de/translations.json new file mode 100644 index 0000000..8f25574 --- /dev/null +++ b/admin/i18n/de/translations.json @@ -0,0 +1,74 @@ +{ + "General": "Allgemein", + "Inputs": "Diskrete Eingänge", + "Do not align addresses to word:": "Die Adressen nicht auf 16 Bits ausrichten:", + "Coils": "Diskrete Ausgänge", + "Input Registers": "Eingangsregister", + "Holding Registers": "Eingangsregister", + "PLC Connection:": "SPS Verbindung:", + "PLC IP Address:": "SPS IP Adresse:", + "PLC Rack:": "SPS Rack:", + "PLC Slot:": "SPS Slot:", + "Round Real to:": "Aufrunden Real auf:", + "Poll delay:": "Poll delay:", + "deviceId": "Slave-ID", + "Reconnect time:": "Reconnect-Zeit:", + "Pulse time:": "Pulsetime:", + "Import symbols file:": "Symboldatei Importieren:", + "Import DB file:": "DB-Datei importieren:", + "Load Symbols": "Lade Symbole", + "Add DB": "DB einfügen", + "Toggle poll": "Poll umschalten", + "Toggle RW": "RW umschalten", + "Toggle WP": "WP umschalten", + "Address": "Adresse", + "Name": "Name", + "Description": "Beschreibung", + "Type": "Typ", + "Unit": "Einheit", + "poll": "poll", + "RW": "RW", + "CW": "CW", + "WP": "WP", + "Role": "Rolle", + "Room": "Raum", + "registers": "Register", + "Device ID:": "Geräte ID:", + "RTU over TCP": "RTU over TCP", + "Data bits:": "Data bits:", + "Stop bits:": "Stop bits:", + "Parity:": "Parity:", + "Read timeout:": "Read timeout:", + "Multi device IDs:": "Mehrere Geräte-IDs:", + "Use aliases as address:": "Aliases benutzen:", + "Max read request length:": "Maximale Lese-Request-Länge:", + "Enable polling of data point": "Zyklische Abfrage vom Datenpunkt", + "Write access allowed": "Schreiben erlaubt", + "Write pulses (true=>false edge)": "Schreibe Pulse (Ja=>Nein Flanke)", + "Connection parameters:": "Verbindungsparameter:", + "Partner IP Address:": "Partner IP Adresse:", + "Port:": "Port:", + "Type:": "Typ:", + "Master": "Master", + "Slave": "Slave", + "Are you sure?": "Sind Sie sicher?", + "Start address:": "Start-Adresse:", + "Text copied to clipboard. Click to close the window": "Text wurde in die Zwischenablage kopiert. Klicke um das Fenster zu schliessen.", + "Export": "Export", + "Import": "Import", + "Close": "Zumachen", + "Export to CSV": "Export in CSV", + "Import from CSV": "Import aus CSV", + "Delete all entries": "Alle Einträge löschen", + "Length": "Länge", + "All entries will be deleted. Are you sure?": "Alle Einträge werden gelöscht. Sind sie sicher?", + "Factor": "Faktor", + "Offset": "Offset", + "Cyclic write": "Zyklisch schreiben", + "TCP/Serial RTU:": "TCP/Serial RTU:", + "TCP": "TCP", + "Serial": "Serial", + "Baud rate:": "Baud rate:", + "Use direct addresses by aliases:": "Direkte Adressen benutzen (bei Aliases):", + "Select port": "Port wählen" +} \ No newline at end of file diff --git a/admin/i18n/en/translations.json b/admin/i18n/en/translations.json new file mode 100644 index 0000000..49ea21d --- /dev/null +++ b/admin/i18n/en/translations.json @@ -0,0 +1,74 @@ +{ + "General": "General", + "Inputs": "Discrete Inputs", + "Do not align addresses to word:": "Do not align addresses to 16 bits:", + "Coils": "Coils", + "Input Registers": "Input Registers", + "Holding Registers": "Holding Registers", + "PLC Connection:": "PLC Connection:", + "PLC IP Address:": "PLC IP Address:", + "PLC Rack:": "PLC Rack:", + "PLC Slot:": "PLC Slot:", + "Round Real to:": "Round real to:", + "Poll delay:": "Poll delay:", + "deviceId": "Slave ID", + "Reconnect time:": "Reconnect time:", + "Pulse time:": "Pulse time:", + "Import symbols file:": "Import symbols file:", + "Import DB file:": "Import DB file:", + "Load Symbols": "Load symbols", + "Add DB": "Add DB", + "Toggle poll": "Toggle poll", + "Toggle RW": "Toggle RW", + "Toggle WP": "Toggle WP", + "Address": "Address", + "Name": "Name", + "Description": "Description", + "Type": "Type", + "Unit": "Unit", + "poll": "poll", + "RW": "RW", + "CW": "CW", + "WP": "WP", + "Role": "Role", + "Room": "Room", + "registers": "registers", + "Device ID:": "Device ID:", + "RTU over TCP": "RTU over TCP", + "Data bits:": "Data bits:", + "Stop bits:": "Stop bits:", + "Parity:": "Parity:", + "Read timeout:": "Read timeout:", + "Multi device IDs:": "Multi device IDs:", + "Use aliases as address:": "Use aliases:", + "Max read request length:": "Max read request length:", + "Enable polling of data point": "Enable polling of data point", + "Write access allowed": "Write access allowed", + "Write pulses (true=>false edge)": "Write pulses (true=>false edge)", + "Connection parameters:": "Connection parameters:", + "Partner IP Address:": "Partner IP Address:", + "Port:": "Port:", + "Type:": "Type:", + "Master": "Master", + "Slave": "Slave", + "Are you sure?": "Are you sure?", + "Start address:": "Start address:", + "Text copied to clipboard. Click to close the window": "Text copied to clipboard. Click to close the window", + "Export": "Export", + "Import": "Import", + "Close": "Close", + "Export to CSV": "Export to CSV", + "Import from CSV": "Import from CSV", + "Delete all entries": "Delete all entries", + "Length": "Length", + "All entries will be deleted. Are you sure?": "All entries will be deleted. Are you sure?", + "Factor": "Factor", + "Offset": "Offset", + "Cyclic write": "Cyclic write", + "TCP/Serial RTU:": "TCP/Serial RTU:", + "TCP": "TCP", + "Serial": "Serial", + "Baud rate:": "Baud rate:", + "Use direct addresses by aliases:": "Use direct addresses by aliases:", + "Select port": "Select port" +} \ No newline at end of file diff --git a/admin/i18n/es/translations.json b/admin/i18n/es/translations.json new file mode 100644 index 0000000..5a888f7 --- /dev/null +++ b/admin/i18n/es/translations.json @@ -0,0 +1,74 @@ +{ + "General": "General", + "Inputs": "Entradas discretas", + "Do not align addresses to word:": "No alinee las direcciones a 16 bits:", + "Coils": "Bobinas", + "Input Registers": "Registros de entrada", + "Holding Registers": "Registros de mantenimiento", + "PLC Connection:": "Conexión de PLC:", + "PLC IP Address:": "Dirección IP del PLC:", + "PLC Rack:": "Estante del PLC:", + "PLC Slot:": "Ranura de PLC:", + "Round Real to:": "Redondo real para:", + "Poll delay:": "Retraso en la encuesta:", + "deviceId": "Slave ID", + "Reconnect time:": "Tiempo de reconexión:", + "Pulse time:": "Tiempo de pulso:", + "Import symbols file:": "Importar archivo de símbolos:", + "Import DB file:": "Importar archivo DB", + "Load Symbols": "Cargar símbolos", + "Add DB": "Agregar DB", + "Toggle poll": "Alternar encuesta", + "Toggle RW": "Alternar RW", + "Toggle WP": "Alternar WP", + "Address": "Dirección", + "Name": "Nombre", + "Description": "Descripción", + "Type": "Tipo", + "Unit": "Unidad", + "poll": "encuesta", + "RW": "RW", + "CW": "CW", + "WP": "WP", + "Role": "Papel", + "Room": "Habitación", + "registers": "registros", + "Device ID:": "ID del dispositivo:", + "RTU over TCP": "RTU sobre TCP", + "Data bits:": "Bits de datos:", + "Stop bits:": "Bits de parada:", + "Parity:": "Paridad:", + "Read timeout:": "Tiempo de espera de lectura:", + "Multi device IDs:": "ID de dispositivos múltiples:", + "Use aliases as address:": "Usa alias:", + "Max read request length:": "Longitud máxima de solicitud de lectura:", + "Enable polling of data point": "Habilitar el sondeo del punto de datos", + "Write access allowed": "Acceso de escritura permitido", + "Write pulses (true=>false edge)": "Escribir pulsos (verdadero => borde falso)", + "Connection parameters:": "Parámetros de conexión:", + "Partner IP Address:": "Dirección IP del socio:", + "Port:": "Puerto:", + "Type:": "Tipo:", + "Master": "Dominar", + "Slave": "Esclavo", + "Are you sure?": "¿Estás seguro?", + "Start address:": "Dirección de inicio:", + "Text copied to clipboard. Click to close the window": "Texto copiado al portapapeles. Haga clic para cerrar la ventana", + "Export": "Exportar", + "Import": "Importar", + "Close": "Cerca", + "Export to CSV": "Exportar a CSV", + "Import from CSV": "Importar desde CSV", + "Delete all entries": "Eliminar todas las entradas", + "Length": "Longitud", + "All entries will be deleted. Are you sure?": "Todas las entradas serán eliminadas. ¿Estás seguro?", + "Factor": "Factor", + "Offset": "Compensar", + "Cyclic write": "Escritura cíclica", + "TCP/Serial RTU:": "TCP / Serial RTU:", + "TCP": "TCP", + "Serial": "De serie", + "Baud rate:": "Velocidad de baudios:", + "Use direct addresses by aliases:": "Use direcciones directas por alias:", + "Select port": "Seleccionar puerto" +} \ No newline at end of file diff --git a/admin/i18n/fr/translations.json b/admin/i18n/fr/translations.json new file mode 100644 index 0000000..67465d9 --- /dev/null +++ b/admin/i18n/fr/translations.json @@ -0,0 +1,74 @@ +{ + "General": "Général", + "Inputs": "Entrées discrètes", + "Do not align addresses to word:": "Ne pas aligner les adresses sur 16 bits:", + "Coils": "Bobines", + "Input Registers": "Registres d'entrée", + "Holding Registers": "Tenir des registres", + "PLC Connection:": "Connexion PLC:", + "PLC IP Address:": "Adresse IP de l'API:", + "PLC Rack:": "Rack PLC:", + "PLC Slot:": "Slot PLC:", + "Round Real to:": "Rond réel à:", + "Poll delay:": "Délai d'interrogation:", + "deviceId": "Slave ID", + "Reconnect time:": "Reconnectez le temps:", + "Pulse time:": "Temps d'impulsion:", + "Import symbols file:": "Importer un fichier de symboles:", + "Import DB file:": "Importer un fichier DB:", + "Load Symbols": "Charger des symboles", + "Add DB": "Ajouter une DB", + "Toggle poll": "Basculer le sondage", + "Toggle RW": "Toggle RW", + "Toggle WP": "Toggle WP", + "Address": "Adresse", + "Name": "prénom", + "Description": "La description", + "Type": "Type", + "Unit": "Unité", + "poll": "sondage", + "RW": "RW", + "CW": "CW", + "WP": "WP", + "Role": "Rôle", + "Room": "Chambre", + "registers": "registres", + "Device ID:": "Reference de l'appareil:", + "RTU over TCP": "RTU sur TCP", + "Data bits:": "Bits de données:", + "Stop bits:": "Bits d'arrêt:", + "Parity:": "Parité:", + "Read timeout:": "Lire le délai d'attente:", + "Multi device IDs:": "ID multi-appareils:", + "Use aliases as address:": "Utiliser des alias:", + "Max read request length:": "Max longueur de la requête de lecture:", + "Enable polling of data point": "Activer l'interrogation du point de données", + "Write access allowed": "Accès en écriture autorisé", + "Write pulses (true=>false edge)": "Écrire des impulsions (true => false edge)", + "Connection parameters:": "Paramètres de connexion:", + "Partner IP Address:": "Adresse IP du partenaire:", + "Port:": "Port:", + "Type:": "Type:", + "Master": "Maîtriser", + "Slave": "Esclave", + "Are you sure?": "Êtes-vous sûr?", + "Start address:": "Adresse de départ:", + "Text copied to clipboard. Click to close the window": "Texte copié dans le presse-papier Cliquez pour fermer la fenêtre", + "Export": "Exportation", + "Import": "Importer", + "Close": "Fermer", + "Export to CSV": "Exporter au format CSV", + "Import from CSV": "Importer à partir du fichier CSV", + "Delete all entries": "Supprimer toutes les entrées", + "Length": "Longueur", + "All entries will be deleted. Are you sure?": "Toutes les entrées seront supprimées. Êtes-vous sûr?", + "Factor": "Facteur", + "Offset": "Décalage", + "Cyclic write": "Écriture cyclique", + "TCP/Serial RTU:": "TCP / Sériel RTU:", + "TCP": "TCP", + "Serial": "En série", + "Baud rate:": "Débit en bauds:", + "Use direct addresses by aliases:": "Utilisez des adresses directes par alias:", + "Select port": "Sélectionnez un port" +} \ No newline at end of file diff --git a/admin/i18n/it/translations.json b/admin/i18n/it/translations.json new file mode 100644 index 0000000..82012c3 --- /dev/null +++ b/admin/i18n/it/translations.json @@ -0,0 +1,74 @@ +{ + "General": "Generale", + "Inputs": "Ingressi discreti", + "Do not align addresses to word:": "Non allineare gli indirizzi a 16 bit:", + "Coils": "bobine", + "Input Registers": "Registri di input", + "Holding Registers": "Holding Registers", + "PLC Connection:": "Connessione PLC:", + "PLC IP Address:": "Indirizzo IP del PLC:", + "PLC Rack:": "Rack PLC:", + "PLC Slot:": "Slot PLC:", + "Round Real to:": "Round reale a:", + "Poll delay:": "Ritardo del sondaggio:", + "deviceId": "ID slave", + "Reconnect time:": "Tempo di riconnessione:", + "Pulse time:": "Tempo di impulso:", + "Import symbols file:": "Importa file simboli:", + "Import DB file:": "Importa file DB:", + "Load Symbols": "Carica simboli", + "Add DB": "Aggiungi DB", + "Toggle poll": "Attiva / disattiva sondaggio", + "Toggle RW": "Attiva / disattiva RW", + "Toggle WP": "Attiva / disattiva WP", + "Address": "Indirizzo", + "Name": "Nome", + "Description": "Descrizione", + "Type": "genere", + "Unit": "Unità", + "poll": "sondaggio", + "RW": "RW", + "CW": "CW", + "WP": "WP", + "Role": "Ruolo", + "Room": "Camera", + "registers": "registri", + "Device ID:": "ID del dispositivo:", + "RTU over TCP": "RTU su TCP", + "Data bits:": "Bit di dati:", + "Stop bits:": "Stop bit:", + "Parity:": "Parità:", + "Read timeout:": "Leggi il timeout:", + "Multi device IDs:": "ID multi dispositivo:", + "Use aliases as address:": "Usa alias:", + "Max read request length:": "Lunghezza massima richiesta di lettura:", + "Enable polling of data point": "Abilita il polling del punto dati", + "Write access allowed": "Accesso in scrittura consentito", + "Write pulses (true=>false edge)": "Scrivi impulsi (true => falso bordo)", + "Connection parameters:": "Parametri di connessione:", + "Partner IP Address:": "Indirizzo IP del partner:", + "Port:": "Porta:", + "Type:": "Genere:", + "Master": "Maestro", + "Slave": "Schiavo", + "Are you sure?": "Sei sicuro?", + "Start address:": "Indirizzo iniziale:", + "Text copied to clipboard. Click to close the window": "Testo copiato negli appunti. Clicca per chiudere la finestra", + "Export": "Esportare", + "Import": "Importare", + "Close": "Vicino", + "Export to CSV": "Esporta in CSV", + "Import from CSV": "Importa da CSV", + "Delete all entries": "Elimina tutte le voci", + "Length": "Lunghezza", + "All entries will be deleted. Are you sure?": "Tutte le voci saranno cancellate. Sei sicuro?", + "Factor": "Fattore", + "Offset": "Compensare", + "Cyclic write": "Scrittura ciclica", + "TCP/Serial RTU:": "RTU TCP / seriale:", + "TCP": "TCP", + "Serial": "Seriale", + "Baud rate:": "Baud rate:", + "Use direct addresses by aliases:": "Usa indirizzi diretti per alias:", + "Select port": "Seleziona porta" +} \ No newline at end of file diff --git a/admin/i18n/nl/translations.json b/admin/i18n/nl/translations.json new file mode 100644 index 0000000..f084d5c --- /dev/null +++ b/admin/i18n/nl/translations.json @@ -0,0 +1,74 @@ +{ + "General": "Algemeen", + "Inputs": "Discrete ingangen", + "Do not align addresses to word:": "Lijn adressen niet uit met 16 bits:", + "Coils": "coils", + "Input Registers": "Invoegregisters", + "Holding Registers": "Registers houden", + "PLC Connection:": "PLC-verbinding:", + "PLC IP Address:": "PLC IP-adres:", + "PLC Rack:": "PLC Rack:", + "PLC Slot:": "PLC-slot:", + "Round Real to:": "Rond echt naar:", + "Poll delay:": "Poll vertraging:", + "deviceId": "Slave ID", + "Reconnect time:": "Reconnect tijd:", + "Pulse time:": "Pulstijd:", + "Import symbols file:": "Symboolbestand importeren:", + "Import DB file:": "DB-bestand importeren:", + "Load Symbols": "Laad symbolen", + "Add DB": "Voeg DB toe", + "Toggle poll": "Poll wisselen", + "Toggle RW": "RW omschakelen", + "Toggle WP": "Wissel WP", + "Address": "Adres", + "Name": "Naam", + "Description": "Beschrijving", + "Type": "Type", + "Unit": "Eenheid", + "poll": "poll", + "RW": "RW", + "CW": "CW", + "WP": "WP", + "Role": "Rol", + "Room": "Kamer", + "registers": "registers", + "Device ID:": "Apparaat ID:", + "RTU over TCP": "RTU via TCP", + "Data bits:": "Databits:", + "Stop bits:": "Stop bits:", + "Parity:": "Pariteit:", + "Read timeout:": "Lees time-out:", + "Multi device IDs:": "ID's voor meerdere apparaten:", + "Use aliases as address:": "Gebruik aliassen:", + "Max read request length:": "Max. Leesverzoeklengte:", + "Enable polling of data point": "Polling van gegevenspunt inschakelen", + "Write access allowed": "Schrijftoegang toegestaan", + "Write pulses (true=>false edge)": "Schrijf pulsen (true => false edge)", + "Connection parameters:": "Verbindingsparameters:", + "Partner IP Address:": "IP-adres van partner:", + "Port:": "Haven:", + "Type:": "Type:", + "Master": "Meester", + "Slave": "Slaaf", + "Are you sure?": "Weet je het zeker?", + "Start address:": "Start adres:", + "Text copied to clipboard. Click to close the window": "Tekst gekopieerd naar klembord. Klik om het venster te sluiten", + "Export": "Exporteren", + "Import": "Importeren", + "Close": "Dichtbij", + "Export to CSV": "Exporteren naar CSV", + "Import from CSV": "Importeren vanuit CSV", + "Delete all entries": "Verwijder alle vermeldingen", + "Length": "Lengte", + "All entries will be deleted. Are you sure?": "Alle inzendingen worden verwijderd. Weet je het zeker?", + "Factor": "Factor", + "Offset": "compenseren", + "Cyclic write": "Cyclisch schrijven", + "TCP/Serial RTU:": "TCP / Serial RTU:", + "TCP": "TCP", + "Serial": "serie-", + "Baud rate:": "Baudrate:", + "Use direct addresses by aliases:": "Gebruik directe adressen op aliassen:", + "Select port": "Selecteer poort" +} \ No newline at end of file diff --git a/admin/i18n/pl/translations.json b/admin/i18n/pl/translations.json new file mode 100644 index 0000000..7ba243f --- /dev/null +++ b/admin/i18n/pl/translations.json @@ -0,0 +1,74 @@ +{ + "General": "Generał", + "Inputs": "Wejścia dyskretne", + "Do not align addresses to word:": "Nie wyrównaj adresów do 16 bitów:", + "Coils": "Cewki", + "Input Registers": "Rejestry wejściowe", + "Holding Registers": "Prowadzenie rejestrów", + "PLC Connection:": "Połączenie PLC:", + "PLC IP Address:": "Adres IP sterownika PLC:", + "PLC Rack:": "Rack PLC:", + "PLC Slot:": "Gniazdo PLC:", + "Round Real to:": "Runda prawdziwa do:", + "Poll delay:": "Opóźnienie ankiety:", + "deviceId": "ID Slave", + "Reconnect time:": "Czas ponownego połączenia:", + "Pulse time:": "Czas impulsu:", + "Import symbols file:": "Importuj plik symboli:", + "Import DB file:": "Importuj plik DB:", + "Load Symbols": "Załaduj symbole", + "Add DB": "Dodaj DB", + "Toggle poll": "Przełącz ankietę", + "Toggle RW": "Przełącz RW", + "Toggle WP": "Przełącz WP", + "Address": "Adres", + "Name": "Nazwa", + "Description": "Opis", + "Type": "Rodzaj", + "Unit": "Jednostka", + "poll": "głosowanie", + "RW": "RW", + "CW": "CW", + "WP": "WP", + "Role": "Rola", + "Room": "Pokój", + "registers": "rejestry", + "Device ID:": "Identyfikator urzadzenia:", + "RTU over TCP": "RTU przez TCP", + "Data bits:": "Bity danych:", + "Stop bits:": "Stop bitów:", + "Parity:": "Parytet:", + "Read timeout:": "Odczyt limitu czasu:", + "Multi device IDs:": "Identyfikatory wielu urządzeń:", + "Use aliases as address:": "Użyj aliasów:", + "Max read request length:": "Maksymalna długość żądania odczytu:", + "Enable polling of data point": "Włącz odpytywanie punktu danych", + "Write access allowed": "Dostęp do zapisu dozwolony", + "Write pulses (true=>false edge)": "Pisz impulsy (true => false edge)", + "Connection parameters:": "Parametry połączenia:", + "Partner IP Address:": "Adres IP partnera:", + "Port:": "Port:", + "Type:": "Rodzaj:", + "Master": "Mistrz", + "Slave": "Niewolnik", + "Are you sure?": "Jesteś pewny?", + "Start address:": "Adres początkowy:", + "Text copied to clipboard. Click to close the window": "Tekst skopiowany do schowka. Kliknij, aby zamknąć okno", + "Export": "Eksport", + "Import": "Import", + "Close": "Blisko", + "Export to CSV": "Eksportuj do pliku CSV", + "Import from CSV": "Importuj z CSV", + "Delete all entries": "Usuń wszystkie wpisy", + "Length": "Długość", + "All entries will be deleted. Are you sure?": "Wszystkie wpisy zostaną usunięte. Jesteś pewny?", + "Factor": "Czynnik", + "Offset": "Offsetowy", + "Cyclic write": "Cykliczny zapis", + "TCP/Serial RTU:": "TCP / Serial RTU:", + "TCP": "TCP", + "Serial": "Seryjny", + "Baud rate:": "Szybkość transmisji:", + "Use direct addresses by aliases:": "Użyj bezpośrednich adresów przez aliasy:", + "Select port": "Wybierz port" +} \ No newline at end of file diff --git a/admin/i18n/pt/translations.json b/admin/i18n/pt/translations.json new file mode 100644 index 0000000..bfae85a --- /dev/null +++ b/admin/i18n/pt/translations.json @@ -0,0 +1,74 @@ +{ + "General": "Geral", + "Inputs": "Entradas discretas", + "Do not align addresses to word:": "Não alinhe endereços para 16 bits:", + "Coils": "Bobinas", + "Input Registers": "Registos de entrada", + "Holding Registers": "Registros de retenção", + "PLC Connection:": "Conexão do PLC:", + "PLC IP Address:": "Endereço IP do PLC:", + "PLC Rack:": "PLC Rack:", + "PLC Slot:": "Slot PLC:", + "Round Real to:": "Round real to:", + "Poll delay:": "Retardo de enquete:", + "deviceId": "Slave ID", + "Reconnect time:": "Reconectar o tempo:", + "Pulse time:": "Tempo de pulso:", + "Import symbols file:": "Arquivo de símbolos de importação:", + "Import DB file:": "Importar arquivo DB:", + "Load Symbols": "Carregar símbolos", + "Add DB": "Adicionar DB", + "Toggle poll": "Alternar pesquisa", + "Toggle RW": "Toggle RW", + "Toggle WP": "Toggle WP", + "Address": "Endereço", + "Name": "Nome", + "Description": "Descrição", + "Type": "Tipo", + "Unit": "Unidade", + "poll": "votação", + "RW": "RW", + "CW": "CW", + "WP": "WP", + "Role": "Função", + "Room": "Quarto", + "registers": "registros", + "Device ID:": "ID de dispositivo:", + "RTU over TCP": "RTU sobre TCP", + "Data bits:": "Bits de dados:", + "Stop bits:": "Parar bits:", + "Parity:": "Paridade:", + "Read timeout:": "Tempo de ler esgotado:", + "Multi device IDs:": "IDs de vários dispositivos:", + "Use aliases as address:": "Use aliases:", + "Max read request length:": "Comprimento máximo do pedido de leitura:", + "Enable polling of data point": "Habilitar a votação do ponto de dados", + "Write access allowed": "Acesso de acesso permitido", + "Write pulses (true=>false edge)": "Escrever pulsos (true => false edge)", + "Connection parameters:": "Parâmetros de conexão:", + "Partner IP Address:": "Endereço IP do parceiro:", + "Port:": "Porta:", + "Type:": "Tipo:", + "Master": "mestre", + "Slave": "Escravo", + "Are you sure?": "Você tem certeza?", + "Start address:": "Endereço de início:", + "Text copied to clipboard. Click to close the window": "Texto copiado para a área de transferência. Clique para fechar a janela", + "Export": "Exportar", + "Import": "Importar", + "Close": "Fechar", + "Export to CSV": "Exportar para CSV", + "Import from CSV": "Importação de CSV", + "Delete all entries": "Eliminar todas as entradas", + "Length": "comprimento", + "All entries will be deleted. Are you sure?": "Todas as entradas serão excluídas. Você tem certeza?", + "Factor": "Fator", + "Offset": "Offset", + "Cyclic write": "Escrita cíclica", + "TCP/Serial RTU:": "TCP / Serial RTU:", + "TCP": "TCP", + "Serial": "Serial", + "Baud rate:": "Taxa de transmissão:", + "Use direct addresses by aliases:": "Use endereços diretos por alias:", + "Select port": "Selecione a porta" +} \ No newline at end of file diff --git a/admin/i18n/ru/translations.json b/admin/i18n/ru/translations.json new file mode 100644 index 0000000..f09929e --- /dev/null +++ b/admin/i18n/ru/translations.json @@ -0,0 +1,74 @@ +{ + "General": "Основное", + "Inputs": "Дискретные входы", + "Do not align addresses to word:": "Не выравнивать адреса до 16 бит:", + "Coils": "Регистры флагов", + "Input Registers": "Регистры входа", + "Holding Registers": "Регистры хранения", + "PLC Connection:": "PLC соединение:", + "PLC IP Address:": "PLC IP адрес:", + "PLC Rack:": "PLC Rack:", + "PLC Slot:": "PLC слот:", + "Round Real to:": "Округлять real до:", + "Poll delay:": "Интервал опроса:", + "deviceId": "Slave ID", + "Reconnect time:": "Reconnect time:", + "Pulse time:": "Pulse time:", + "Import symbols file:": "Ипморт символьных файлов:", + "Import DB file:": "Ипморт DB файлов:", + "Load Symbols": "Загрузить символы", + "Add DB": "Добавить DB", + "Toggle poll": "Изменить poll", + "Toggle RW": "Изменить RW", + "Toggle WP": "Изменить WP", + "Address": "Адрес", + "Name": "Имя", + "Description": "Описание", + "Type": "Тип", + "Unit": "Единицы", + "poll": "poll", + "RW": "RW", + "CW": "CW", + "WP": "WP", + "Role": "Роль", + "Room": "Комната", + "registers": "регистров", + "Device ID:": "ID устройства:", + "RTU over TCP": "RTU over TCP", + "Data bits:": "Data bits:", + "Stop bits:": "Stop bits:", + "Parity:": "Parity:", + "Read timeout:": "Таймаут чтения:", + "Multi device IDs:": "Несколько ID:", + "Use aliases as address:": "Использовать alias:", + "Max read request length:": "Макс. кол-во регистров при чтении:", + "Enable polling of data point": "Постоянный опрос переменной в каждом цикле", + "Write access allowed": "Разрешить запись в переменную", + "Write pulses (true=>false edge)": "Генерировать импульсы (1 => 0)", + "Connection parameters:": "Параметры соединения:", + "Partner IP Address:": "IP адрес партнёра:", + "Port:": "Порт:", + "Type:": "Тип:", + "Master": "Master", + "Slave": "Slave", + "Are you sure?": "Вы уверенны?", + "Start address:": "Начальный адрес:", + "Text copied to clipboard. Click to close the window": "Текст скопирован в буфер обмена. Щелкните мышкой здесь, чтобы закрыть окно", + "Export": "Экспорт", + "Import": "Импорт", + "Close": "Закрыть", + "Export to CSV": "Экспорт в CSV", + "Import from CSV": "Импорт из CSV", + "Delete all entries": "Удалить все элементы", + "Length": "Длина", + "All entries will be deleted. Are you sure?": "Все элементы будут удалены. Вы уверенны?", + "Factor": "Множитель", + "Offset": "Сдвиг", + "Cyclic write": "Писать в каждом цикле", + "TCP/Serial RTU:": "TCP/Serial RTU:", + "TCP": "TCP", + "Serial": "Serial", + "Baud rate:": "Скорость порта:", + "Use direct addresses by aliases:": "Использовать прямые адреса при alias:", + "Select port": "Выберите порт" +} \ No newline at end of file diff --git a/admin/img/plc_back.png b/admin/img/plc_back.png new file mode 100644 index 0000000..9b03ce0 Binary files /dev/null and b/admin/img/plc_back.png differ diff --git a/admin/index.html b/admin/index.html new file mode 100644 index 0000000..0e1d169 --- /dev/null +++ b/admin/index.html @@ -0,0 +1,1372 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Connection parameters:
+ +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
General
ms
ms
ms
ms
registers
registers
+
+
+
+
Start address:
+ + + +
+
+
+
+
+
Start address:
+ + + + + +
+
+
+
+
+
Start address:
+ + + +
+
+
+
+
+
Start address:
+ + + + + +
+
+
+ + + +
+ + diff --git a/admin/lib/css/jsgrid-theme.css b/admin/lib/css/jsgrid-theme.css new file mode 100644 index 0000000..936950b --- /dev/null +++ b/admin/lib/css/jsgrid-theme.css @@ -0,0 +1,205 @@ +/* + * jsGrid v1.0.1 (http://js-grid.com) + * (c) 2015 Artem Tabalin + * Licensed under MIT (https://github.com/tabalinas/jsgrid/blob/master/LICENSE) + */ + +.jsgrid-grid-header, +.jsgrid-grid-body, +.jsgrid-header-row > th, +.jsgrid-filter-row > td, +.jsgrid-insert-row > td, +.jsgrid-edit-row > td { + border: 1px solid #e9e9e9; +} + +.jsgrid-header-row > th { + border-top: 0; +} + +.jsgrid-header-row > th, .jsgrid-filter-row > td, .jsgrid-insert-row > td { + border-bottom: 0; +} + +.jsgrid-header-row > th:first-child, .jsgrid-filter-row > td:first-child, .jsgrid-insert-row > td:first-child { + border-left: none; +} + +.jsgrid-grid-header { + background: #f9f9f9; +} + +.jsgrid-header-sortable:hover { + cursor: pointer; + background: #fcfcfc; +} + +.jsgrid-header-row .jsgrid-header-sort { + background: #c4e2ff; +} + +.jsgrid-header-sort:before { + content: " "; + display: block; + float: left; + width: 0; + height: 0; + border-style: solid; +} + +.jsgrid-header-sort-asc:before { + border-width: 0 5px 5px 5px; + border-color: transparent transparent #009a67 transparent; +} + +.jsgrid-header-sort-desc:before { + border-width: 5px 5px 0 5px; + border-color: #009a67 transparent transparent transparent; +} + +.jsgrid-grid-body { + border-top: none; +} + +.jsgrid-grid-body td { + border: #f3f3f3 1px solid; +} + +.jsgrid-grid-body tr:first-child td { + border-top: none; +} + +.jsgrid-grid-body tr td:first-child { + border-left: none; +} + +.jsgrid-row > td { + background: #fff; +} + +.jsgrid-alt-row > td { + background: #fcfcfc; +} + +.jsgrid-header-row > th { + background: #f9f9f9; +} + +.jsgrid-filter-row > td { + background: #fcfcfc; +} + +.jsgrid-insert-row > td { + background: #e3ffe5; +} + +.jsgrid-edit-row > td { + background: #fdffe3; +} + +.jsgrid-selected-row > td { + background: #c4e2ff; + border-color: #c4e2ff; +} + +.jsgrid-nodata-row td { + background: #fff; +} + +.jsgrid-pager-current-page { + font-weight: bold; +} + +.jsgrid-button + .jsgrid-button { + margin-left: 5px; +} + +.jsgrid-button:hover { + opacity: .5; + transition: opacity 200ms linear; +} + +.jsgrid .jsgrid-button { + width: 16px; + height: 16px; + border: none; + cursor: pointer; + background-image: url(data:image/png;base64,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); + background-repeat: no-repeat; + background-color: transparent; +} + +@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) { + .jsgrid .jsgrid-button { + background-image: url(data:image/png;base64,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); + background-size: 24px 352px; + } +} + +.jsgrid .jsgrid-mode-button { + width: 24px; + height: 24px; +} + +.jsgrid-cancel-button { background-position: 0 0; width: 16px; height: 16px; } +.jsgrid-clear-filter-button { background-position: 0 -40px; width: 16px; height: 16px; } +.jsgrid-delete-button { background-position: 0 -80px; width: 16px; height: 16px; } +.jsgrid-edit-button { background-position: 0 -120px; width: 16px; height: 16px; } +.jsgrid-insert-mode-button { background-position: 0 -160px; width: 24px; height: 24px; } +.jsgrid-insert-button { background-position: 0 -208px; width: 16px; height: 16px; } +.jsgrid-search-mode-button { background-position: 0 -248px; width: 24px; height: 24px; } +.jsgrid-search-button { background-position: 0 -296px; width: 16px; height: 16px; } +.jsgrid-update-button { background-position: 0 -336px; width: 16px; height: 16px; } + + +.jsgrid-load-shader { + background: #ddd; + opacity: .5; + filter: alpha(opacity=50); +} + +.jsgrid-load-panel { + width: 15em; + height: 5em; + background: #fff; + border: 1px solid #e9e9e9; + padding-top: 3em; + text-align: center; +} + +.jsgrid-load-panel:before { + content: ' '; + position: absolute; + top: .5em; + left: 50%; + margin-left: -1em; + width: 2em; + height: 2em; + border: 2px solid #009a67; + border-right-color: transparent; + border-radius: 50%; + -webkit-animation: indicator 1s linear infinite; + animation: indicator 1s linear infinite; +} + +@-webkit-keyframes indicator +{ + from { -webkit-transform: rotate(0deg); } + 50% { -webkit-transform: rotate(180deg); } + to { -webkit-transform: rotate(360deg); } +} + +@keyframes indicator +{ + from { transform: rotate(0deg); } + 50% { transform: rotate(180deg); } + to { transform: rotate(360deg); } +} + +/* old IE */ +.jsgrid-load-panel { + padding-top: 1.5em \ ; +} +.jsgrid-load-panel:before { + display: none \ ; +} diff --git a/admin/lib/css/jsgrid-theme.min.css b/admin/lib/css/jsgrid-theme.min.css new file mode 100644 index 0000000..31e9c8c --- /dev/null +++ b/admin/lib/css/jsgrid-theme.min.css @@ -0,0 +1,7 @@ +/* + * jsGrid v1.0.1 (http://js-grid.com) + * (c) 2015 Artem Tabalin + * Licensed under MIT (https://github.com/tabalinas/jsgrid/blob/master/LICENSE) + */ + +.jsgrid-edit-row>td,.jsgrid-filter-row>td,.jsgrid-grid-body,.jsgrid-grid-header,.jsgrid-header-row>th,.jsgrid-insert-row>td{border:1px solid #e9e9e9}.jsgrid-header-row>th{border-top:0}.jsgrid-filter-row>td,.jsgrid-header-row>th,.jsgrid-insert-row>td{border-bottom:0}.jsgrid-filter-row>td:first-child,.jsgrid-header-row>th:first-child,.jsgrid-insert-row>td:first-child{border-left:none}.jsgrid-grid-header{background:#f9f9f9}.jsgrid-header-sortable:hover{cursor:pointer;background:#fcfcfc}.jsgrid-header-row .jsgrid-header-sort{background:#c4e2ff}.jsgrid-header-sort:before{content:" ";display:block;float:left;width:0;height:0;border-style:solid}.jsgrid-header-sort-asc:before{border-width:0 5px 5px;border-color:transparent transparent #009a67}.jsgrid-header-sort-desc:before{border-width:5px 5px 0;border-color:#009a67 transparent transparent}.jsgrid-grid-body{border-top:none}.jsgrid-grid-body td{border:1px solid #f3f3f3}.jsgrid-grid-body tr:first-child td{border-top:none}.jsgrid-grid-body tr td:first-child{border-left:none}.jsgrid-row>td{background:#fff}.jsgrid-alt-row>td{background:#fcfcfc}.jsgrid-header-row>th{background:#f9f9f9}.jsgrid-filter-row>td{background:#fcfcfc}.jsgrid-insert-row>td{background:#e3ffe5}.jsgrid-edit-row>td{background:#fdffe3}.jsgrid-selected-row>td{background:#c4e2ff;border-color:#c4e2ff}.jsgrid-nodata-row td{background:#fff}.jsgrid-pager-current-page{font-weight:700}.jsgrid-button+.jsgrid-button{margin-left:5px}.jsgrid-button:hover{opacity:.5;transition:opacity 200ms linear}.jsgrid .jsgrid-button{width:16px;height:16px;border:none;cursor:pointer;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-color:transparent}@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2){.jsgrid .jsgrid-button{background-image:url(data:image/png;base64,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);background-size:24px 352px}}.jsgrid .jsgrid-mode-button{width:24px;height:24px}.jsgrid-cancel-button{background-position:0 0;width:16px;height:16px}.jsgrid-clear-filter-button{background-position:0 -40px;width:16px;height:16px}.jsgrid-delete-button{background-position:0 -80px;width:16px;height:16px}.jsgrid-edit-button{background-position:0 -120px;width:16px;height:16px}.jsgrid-insert-mode-button{background-position:0 -160px;width:24px;height:24px}.jsgrid-insert-button{background-position:0 -208px;width:16px;height:16px}.jsgrid-search-mode-button{background-position:0 -248px;width:24px;height:24px}.jsgrid-search-button{background-position:0 -296px;width:16px;height:16px}.jsgrid-update-button{background-position:0 -336px;width:16px;height:16px}.jsgrid-load-shader{background:#ddd;opacity:.5;filter:alpha(opacity=50)}.jsgrid-load-panel{width:15em;height:5em;background:#fff;border:1px solid #e9e9e9;padding-top:3em;text-align:center}.jsgrid-load-panel:before{content:' ';position:absolute;top:.5em;left:50%;margin-left:-1em;width:2em;height:2em;border:2px solid #009a67;border-right-color:transparent;border-radius:50%;-webkit-animation:indicator 1s linear infinite;animation:indicator 1s linear infinite}@-webkit-keyframes indicator{from{-webkit-transform:rotate(0deg)}50%{-webkit-transform:rotate(180deg)}to{-webkit-transform:rotate(360deg)}}@keyframes indicator{from{transform:rotate(0deg)}50%{transform:rotate(180deg)}to{transform:rotate(360deg)}}.jsgrid-load-panel{padding-top:1.5em \}.jsgrid-load-panel:before{display:none \} \ No newline at end of file diff --git a/admin/lib/css/jsgrid.css b/admin/lib/css/jsgrid.css new file mode 100644 index 0000000..33fc780 --- /dev/null +++ b/admin/lib/css/jsgrid.css @@ -0,0 +1,121 @@ +/* + * jsGrid v1.0.1 (http://js-grid.com) + * (c) 2015 Artem Tabalin + * Licensed under MIT (https://github.com/tabalinas/jsgrid/blob/master/LICENSE) + */ + +.jsgrid { + position: relative; + overflow: hidden; + font-size: 1em; +} + +.jsgrid, .jsgrid *, .jsgrid *:before, .jsgrid *:after { + box-sizing: border-box; +} + +.jsgrid input, +.jsgrid textarea, +.jsgrid select { + font-size: 1em; +} + +.jsgrid-grid-header { + overflow-x: hidden; + overflow-y: hidden; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} + +.jsgrid-grid-body { + overflow-x: hidden; + overflow-y: scroll; + -webkit-overflow-scrolling: touch; +} + +.jsgrid-table { + width: 100%; + table-layout: fixed; + border-collapse: collapse; + border-spacing: 0; +} + +.jsgrid-table td { + padding: 0.5em 0.5em; +} + +.jsgrid-table td, +.jsgrid-table th { + box-sizing: border-box; +} + +.jsgrid-align-left { + text-align: left; +} + +.jsgrid-align-center { + text-align: center; +} + +.jsgrid-align-right { + text-align: right; +} + +.jsgrid-header-row > th { + padding: .5em .5em; +} + +.jsgrid-filter-row input, +.jsgrid-filter-row textarea, +.jsgrid-filter-row select, +.jsgrid-edit-row input, +.jsgrid-edit-row textarea, +.jsgrid-edit-row select, +.jsgrid-insert-row input, +.jsgrid-insert-row textarea, +.jsgrid-insert-row select { + width: 90%; + padding: .3em .5em; +} + +.jsgrid-filter-row input[type='checkbox'], +.jsgrid-edit-row input[type='checkbox'], +.jsgrid-insert-row input[type='checkbox'] { + width: auto; +} + +.jsgrid-header-row > th, +.jsgrid-filter-row > td, +.jsgrid-insert-row > td, +.jsgrid-edit-row > td { + text-align: center; +} + +.jsgrid-selected-row td { + cursor: pointer; +} + +.jsgrid-nodata-row td { + padding: .5em 0; + text-align: center; +} + +.jsgrid-header-sort { + cursor: pointer; +} + +.jsgrid-pager { + padding: .5em 0; +} + +.jsgrid-pager-nav-button { + padding: .2em .6em; +} + +.jsgrid-pager-page { + padding: .2em .6em; +} \ No newline at end of file diff --git a/admin/lib/css/jsgrid.min.css b/admin/lib/css/jsgrid.min.css new file mode 100644 index 0000000..6463bb9 --- /dev/null +++ b/admin/lib/css/jsgrid.min.css @@ -0,0 +1,7 @@ +/* + * jsGrid v1.0.1 (http://js-grid.com) + * (c) 2015 Artem Tabalin + * Licensed under MIT (https://github.com/tabalinas/jsgrid/blob/master/LICENSE) + */ + +.jsgrid{position:relative;overflow:hidden;font-size:1em}.jsgrid,.jsgrid *,.jsgrid :after,.jsgrid :before{box-sizing:border-box}.jsgrid input,.jsgrid select,.jsgrid textarea{font-size:1em}.jsgrid-grid-header{overflow-x:hidden;overflow-y:hidden;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.jsgrid-grid-body{overflow-x:hidden;overflow-y:scroll;-webkit-overflow-scrolling:touch}.jsgrid-table{width:100%;table-layout:fixed;border-collapse:collapse;border-spacing:0}.jsgrid-table td{padding:.5em}.jsgrid-table td,.jsgrid-table th{box-sizing:border-box}.jsgrid-align-left{text-align:left}.jsgrid-align-center{text-align:center}.jsgrid-align-right{text-align:right}.jsgrid-header-row>th{padding:.5em}.jsgrid-edit-row input,.jsgrid-edit-row select,.jsgrid-edit-row textarea,.jsgrid-filter-row input,.jsgrid-filter-row select,.jsgrid-filter-row textarea,.jsgrid-insert-row input,.jsgrid-insert-row select,.jsgrid-insert-row textarea{width:90%;padding:.3em .5em}.jsgrid-edit-row input[type=checkbox],.jsgrid-filter-row input[type=checkbox],.jsgrid-insert-row input[type=checkbox]{width:auto}.jsgrid-edit-row>td,.jsgrid-filter-row>td,.jsgrid-header-row>th,.jsgrid-insert-row>td{text-align:center}.jsgrid-selected-row td{cursor:pointer}.jsgrid-nodata-row td{padding:.5em 0;text-align:center}.jsgrid-header-sort{cursor:pointer}.jsgrid-pager{padding:.5em 0}.jsgrid-pager-nav-button,.jsgrid-pager-page{padding:.2em .6em} \ No newline at end of file diff --git a/admin/lib/js/grid.locale-de.js b/admin/lib/js/grid.locale-de.js new file mode 100644 index 0000000..9c07a68 --- /dev/null +++ b/admin/lib/js/grid.locale-de.js @@ -0,0 +1,10 @@ +function translate_de() { + window.jsGrid.Grid.prototype.noDataContent = "Nicht gefunden"; + window.jsGrid.Grid.prototype.deleteConfirm = "Sind Sie sicher?"; + window.jsGrid.Grid.prototype.pagerFormat = "Seiten = {first} {prev} {pages} {next} {last}    {pageIndex} of {pageCount}"; + window.jsGrid.Grid.prototype.pagePrevText = "Vorh."; + window.jsGrid.Grid.prototype.pageNextText = "Nexte"; + window.jsGrid.Grid.prototype.pageFirstText = "Erste"; + window.jsGrid.Grid.prototype.pageLastText = "Letzte"; + window.jsGrid.Grid.prototype.loadMessage = "Bitte; warten..."; +} \ No newline at end of file diff --git a/admin/lib/js/grid.locale-ru.js b/admin/lib/js/grid.locale-ru.js new file mode 100644 index 0000000..3d3fdf0 --- /dev/null +++ b/admin/lib/js/grid.locale-ru.js @@ -0,0 +1,11 @@ +function translate_ru() { + window.jsGrid.Grid.prototype.noDataContent = "Не найдено"; + window.jsGrid.Grid.prototype.deleteConfirm = "Вы уверены?"; + window.jsGrid.Grid.prototype.pagerFormat = "Страницы = {first} {prev} {pages} {next} {last}    {pageIndex} of {pageCount}"; + window.jsGrid.Grid.prototype.pagePrevText = "Пред."; + window.jsGrid.Grid.prototype.pageNextText = "След"; + window.jsGrid.Grid.prototype.pageFirstText = "Первая"; + window.jsGrid.Grid.prototype.pageLastText = "Последняя"; + window.jsGrid.Grid.prototype.loadMessage = "Подождите..."; +} + diff --git a/admin/lib/js/jsgrid.js b/admin/lib/js/jsgrid.js new file mode 100644 index 0000000..c0208bd --- /dev/null +++ b/admin/lib/js/jsgrid.js @@ -0,0 +1,1954 @@ +/* + * jsGrid v1.0.1 (http://js-grid.com) + * (c) 2015 Artem Tabalin + * Licensed under MIT (https://github.com/tabalinas/jsgrid/blob/master/LICENSE) + */ + +(function(window, $, undefined) { + + var JSGRID = "JSGrid", + JSGRID_DATA_KEY = JSGRID, + JSGRID_ROW_DATA_KEY = "JSGridItem", + JSGRID_EDIT_ROW_DATA_KEY = "JSGridEditRow", + + SORT_ORDER_ASC = "asc", + SORT_ORDER_DESC = "desc", + + FIRST_PAGE_PLACEHOLDER = "{first}", + PAGES_PLACEHOLDER = "{pages}", + PREV_PAGE_PLACEHOLDER = "{prev}", + NEXT_PAGE_PLACEHOLDER = "{next}", + LAST_PAGE_PLACEHOLDER = "{last}", + PAGE_INDEX_PLACEHOLDER = "{pageIndex}", + PAGE_COUNT_PLACEHOLDER = "{pageCount}", + + EMPTY_HREF = "javascript:void(0);"; + + var getOrApply = function(value, context) { + if($.isFunction(value)) { + return value.apply(context, $.makeArray(arguments).slice(2)); + } + return value; + }; + + var defaultController = { + loadData: $.noop, + insertItem: $.noop, + updateItem: $.noop, + deleteItem: $.noop + }; + + + function Grid(element, config) { + var $element = $(element); + + $element.data(JSGRID_DATA_KEY, this); + + this._container = $element; + + this.data = []; + this.fields = []; + + this._editingRow = null; + this._sortField = null; + this._sortOrder = SORT_ORDER_ASC; + this._firstDisplayingPage = 1; + + this._init(config); + this.render(); + } + + Grid.prototype = { + width: "auto", + height: "auto", + updateOnResize: true, + + rowClass: $.noop, + rowRenderer: null, + + rowClick: function(args) { + if(this.editing) { + this.editItem($(args.event.target).closest("tr")); + } + }, + + noDataContent: "Not found", + noDataRowClass: "jsgrid-nodata-row", + + heading: true, + headerRowRenderer: null, + headerRowClass: "jsgrid-header-row", + + filtering: false, + filterRowRenderer: null, + filterRowClass: "jsgrid-filter-row", + + inserting: false, + insertRowRenderer: null, + insertRowClass: "jsgrid-insert-row", + + editing: false, + editRowRenderer: null, + editRowClass: "jsgrid-edit-row", + + confirmDeleting: true, + deleteConfirm: "Are you sure?", + + selecting: true, + selectedRowClass: "jsgrid-selected-row", + oddRowClass: "jsgrid-row", + evenRowClass: "jsgrid-alt-row", + + sorting: false, + sortableClass: "jsgrid-header-sortable", + sortAscClass: "jsgrid-header-sort jsgrid-header-sort-asc", + sortDescClass: "jsgrid-header-sort jsgrid-header-sort-desc", + + paging: false, + pagerContainer: null, + pageIndex: 1, + pageSize: 20, + pageButtonCount: 15, + pagerFormat: "Pages: {first} {prev} {pages} {next} {last}    {pageIndex} of {pageCount}", + pagePrevText: "Prev", + pageNextText: "Next", + pageFirstText: "First", + pageLastText: "Last", + pageNavigatorNextText: "...", + pageNavigatorPrevText: "...", + pagerContainerClass: "jsgrid-pager-container", + pagerClass: "jsgrid-pager", + pagerNavButtonClass: "jsgrid-pager-nav-button", + pageClass: "jsgrid-pager-page", + currentPageClass: "jsgrid-pager-current-page", + + pageLoading: false, + + autoload: false, + controller: defaultController, + + loadIndication: true, + loadIndicationDelay: 500, + loadMessage: "Please, wait...", + loadShading: true, + + onRefreshing: $.noop, + onRefreshed: $.noop, + onItemDeleting: $.noop, + onItemDeleted: $.noop, + onItemInserting: $.noop, + onItemInserted: $.noop, + onItemUpdating: $.noop, + onItemUpdated: $.noop, + onDataLoading: $.noop, + onDataLoaded: $.noop, + onOptionChanging: $.noop, + onOptionChanged: $.noop, + onError: $.noop, + + containerClass: "jsgrid", + tableClass: "jsgrid-table", + gridHeaderClass: "jsgrid-grid-header", + gridBodyClass: "jsgrid-grid-body", + + _init: function(config) { + $.extend(this, config); + this._initLoadStrategy(); + this._initController(); + this._initFields(); + this._attachWindowLoadResize(); + this._attachWindowResizeCallback(); + }, + + loadStrategy: function() { + return this.pageLoading + ? new jsGrid.loadStrategies.PageLoadingStrategy(this) + : new jsGrid.loadStrategies.DirectLoadingStrategy(this); + }, + + _initLoadStrategy: function() { + this._loadStrategy = getOrApply(this.loadStrategy, this); + }, + + _initController: function() { + this._controller = $.extend({}, defaultController, getOrApply(this.controller, this)); + }, + + loadIndicator: function(config) { + return new jsGrid.LoadIndicator(config); + }, + + _initFields: function() { + var self = this; + self.fields = $.map(self.fields, function(field) { + if($.isPlainObject(field)) { + var fieldConstructor = (field.type && jsGrid.fields[field.type]) || jsGrid.Field; + field = new fieldConstructor(field); + } + field._grid = self; + return field; + }); + }, + + _attachWindowLoadResize: function() { + $(window).on("load", $.proxy(this._refreshSize, this)); + }, + + _attachWindowResizeCallback: function() { + if(this.updateOnResize) { + $(window).on("resize", $.proxy(this._refreshSize, this)); + } + }, + + _detachWindowResizeCallback: function() { + $(window).off("resize", this._refreshSize); + }, + + option: function(key, value) { + var optionChangingEventArgs, + optionChangedEventArgs; + + if(arguments.length === 1) { + return this[key]; + } + + optionChangingEventArgs = { + option: key, + oldValue: this[key], + newValue: value + }; + this._callEventHandler(this.onOptionChanging, optionChangingEventArgs); + + this._handleOptionChange(optionChangingEventArgs.option, optionChangingEventArgs.newValue); + + optionChangedEventArgs = { + option: optionChangingEventArgs.option, + value: optionChangingEventArgs.newValue + }; + this._callEventHandler(this.onOptionChanged, optionChangedEventArgs); + }, + + _handleOptionChange: function(name, value) { + this[name] = value; + + switch(name) { + case "width": + case "height": + this._refreshSize(); + break; + case "rowClass": + case "rowRenderer": + case "rowClick": + case "noDataText": + case "noDataRowClass": + case "noDataContent": + case "selecting": + case "selectedRowClass": + case "oddRowClass": + case "evenRowClass": + this._refreshContent(); + break; + case "pageButtonCount": + case "pagerFormat": + case "pagePrevText": + case "pageNextText": + case "pageFirstText": + case "pageLastText": + case "pageNavigatorNextText": + case "pageNavigatorPrevText": + case "pagerClass": + case "pagerNavButtonClass": + case "pageClass": + case "currentPageClass": + this._refreshPager(); + break; + case "fields": + this._initFields(); + this.render(); + break; + case "data": + case "editing": + case "heading": + case "filtering": + case "inserting": + case "paging": + this.refresh(); + break; + case "pageLoading": + this._initLoadStrategy(); + this.search(); + break; + case "pageIndex": + this.openPage(value); + break; + case "pageSize": + this.refresh(); + this.search(); + break; + case "editRowRenderer": + case "editRowClass": + this.cancelEdit(); + break; + default: + this.render(); + break; + } + }, + + destroy: function() { + this._detachWindowResizeCallback(); + this._clear(); + this._container.removeData(JSGRID_DATA_KEY); + }, + + render: function() { + this._clear(); + + this._container.addClass(this.containerClass) + .css("position", "relative") + .append(this._createHeader()) + .append(this._createBody()); + + this._pagerContainer = this._createPagerContainer(); + this._loadIndicator = this._createLoadIndicator(); + + this.refresh(); + + return this.autoload ? this.loadData() : $.Deferred().resolve().promise(); + }, + + _createLoadIndicator: function() { + return getOrApply(this.loadIndicator, this, { + message: this.loadMessage, + shading: this.loadShading, + container: this._container + }); + }, + + _clear: function() { + this.cancelEdit(); + + clearTimeout(this._loadingTimer); + + this._pagerContainer && this._pagerContainer.empty(); + + this._container.empty() + .css({ position: "", width: "", height: "" }); + }, + + _createHeader: function() { + var $headerRow = this._headerRow = this._createHeaderRow(), + $filterRow = this._filterRow = this._createFilterRow(), + $insertRow = this._insertRow = this._createInsertRow(); + + var $headerGrid = this._headerGrid = $("").addClass(this.tableClass) + .append($headerRow) + .append($filterRow) + .append($insertRow); + + var $header = this._header = $("
").addClass(this.gridHeaderClass) + .append($headerGrid); + + return $header; + }, + + _createBody: function() { + var $content = this._content = $("
"); + + var $bodyGrid = this._bodyGrid = $("
").addClass(this.tableClass) + .append($content); + + var $body = this._body = $("
").addClass(this.gridBodyClass) + .append($bodyGrid); + + return $body; + }, + + _createPagerContainer: function() { + var pagerContainer = this.pagerContainer || $("
").appendTo(this._container); + return $(pagerContainer).addClass(this.pagerContainerClass); + }, + + _eachField: function(callBack) { + var self = this; + $.each(this.fields, function(index, field) { + return callBack.call(self, field, index); + }); + }, + + _createHeaderRow: function() { + if($.isFunction(this.headerRowRenderer)) { + return $(this.headerRowRenderer()); + } + + var $result = $("
").addClass(this.headerRowClass); + + this._eachField(function(field, index) { + var $th = $("").addClass(this.filterRowClass); + + this._eachField(function(field) { + $("").addClass(this.insertRowClass); + + this._eachField(function(field) { + $("").addClass(this.noDataRowClass) + .append($(""); + this._renderCells($result, item); + } + + $result.addClass(this._getRowClasses(item, itemIndex)) + .data(JSGRID_ROW_DATA_KEY, item) + .on("click", $.proxy(function(e) { + this.rowClick({ + item: item, + itemIndex: itemIndex, + event: e + }); + }, this)); + + if(this.selecting) { + this._attachRowHover($result); + } + + return $result; + }, + + _getRowClasses: function(item, itemIndex) { + var classes = []; + classes.push(((itemIndex + 1) % 2) ? this.oddRowClass : this.evenRowClass); + classes.push(getOrApply(this.rowClass, this, item, itemIndex)); + return classes.join(" "); + }, + + _attachRowHover: function($row) { + var selectedRowClass = this.selectedRowClass; + $row.hover(function() { + $(this).addClass(selectedRowClass); + }, + function() { + $(this).removeClass(selectedRowClass); + } + ); + }, + + _renderCells: function($row, item) { + this._eachField(function(field) { + $row.append(this._createCell(item, field)); + }); + return this; + }, + + _createCell: function(item, field) { + var $result; + var fieldValue = item[field.name]; + + if($.isFunction(field.cellRenderer)) { + $result = $(field.cellRenderer(fieldValue, item)); + } else { + $result = $("").addClass(this.editRowClass); + + this._eachField(function(field) { + $("
").addClass(field.css) + .appendTo($result) + .append(field.headerTemplate ? field.headerTemplate() : "") + .css("width", field.width); + + if(this.sorting && field.sorting) { + $th.addClass(this.sortableClass) + .on("click", $.proxy(function() { + this.sort(index); + }, this)); + } + }); + + return $result; + }, + + _createFilterRow: function() { + if($.isFunction(this.filterRowRenderer)) { + return $(this.filterRowRenderer()); + } + + var $result = $("
").addClass(field.css) + .appendTo($result) + .append(field.filterTemplate ? field.filterTemplate() : "") + .width(field.width); + }); + + return $result; + }, + + _createInsertRow: function() { + if($.isFunction(this.insertRowRenderer)) { + return $(this.insertRowRenderer()); + } + + var $result = $("
").addClass(field.css) + .appendTo($result) + .append(field.insertTemplate ? field.insertTemplate() : "") + .width(field.width); + }); + + return $result; + }, + + _callEventHandler: function(handler, eventParams) { + return handler.call(this, $.extend(eventParams, { + grid: this + })); + }, + + reset: function() { + this._resetSorting(); + this._resetPager(); + this.refresh(); + }, + + _resetPager: function() { + this._firstDisplayingPage = 1; + this._setPage(1); + }, + + _resetSorting: function() { + this._sortField = null; + this._sortOrder = SORT_ORDER_ASC; + this._clearSortingCss(); + }, + + refresh: function() { + this._callEventHandler(this.onRefreshing); + + this.cancelEdit(); + + this._refreshHeading(); + this._refreshFiltering(); + this._refreshInserting(); + this._refreshContent(); + this._refreshPager(); + this._refreshSize(); + + this._callEventHandler(this.onRefreshed); + }, + + _refreshHeading: function() { + this._headerRow.toggle(this.heading); + }, + + _refreshFiltering: function() { + this._filterRow.toggle(this.filtering); + }, + + _refreshInserting: function() { + this._insertRow.toggle(this.inserting); + }, + + _refreshContent: function() { + var $content = this._content; + $content.empty(); + + if(!this.data.length) { + $content.append(this._createNoDataRow()); + return this; + } + + var indexFrom = this._loadStrategy.firstDisplayIndex(); + var indexTo = this._loadStrategy.lastDisplayIndex(); + + for(var itemIndex = indexFrom; itemIndex < indexTo; itemIndex++) { + var item = this.data[itemIndex]; + $content.append(this._createRow(item, itemIndex)); + } + }, + + _createNoDataRow: function() { + var noDataContent = getOrApply(this.noDataContent, this); + return $("
").attr("colspan", this.fields.length).append(noDataContent)); + }, + + _createNoDataContent: function () { + return $.isFunction(this.noDataRenderer) + ? this.noDataRenderer() + : this.noDataText; + }, + + _createRow: function(item, itemIndex) { + var $result; + + if($.isFunction(this.rowRenderer)) { + $result = $(this.rowRenderer(item, itemIndex)); + } else { + $result = $("
").append(field.itemTemplate ? field.itemTemplate(fieldValue, item) : fieldValue); + } + + $result.addClass(field.css) + .width(field.width); + + field.align && $result.addClass("jsgrid-align-" + field.align); + + return $result; + }, + + sort: function(field, order) { + if($.isPlainObject(field)) { + order = field.order; + field = field.field; + } + + this._clearSortingCss(); + this._setSortingParams(field, order); + this._setSortingCss(); + return this._loadStrategy.sort(); + }, + + _clearSortingCss: function() { + this._headerRow.find("th") + .removeClass(this.sortAscClass) + .removeClass(this.sortDescClass); + }, + + _setSortingParams: function(field, order) { + field = this._normalizeSortingField(field); + order = order || ((this._sortField === field) ? this._reversedSortOrder(this._sortOrder) : SORT_ORDER_ASC); + + this._sortField = field; + this._sortOrder = order; + }, + + _normalizeSortingField: function(field) { + if($.isNumeric(field)) { + return this.fields[field]; + } + + if(typeof field === "string") { + return $.grep(this.fields, function (f) { + return f.name === field; + })[0]; + } + + return field; + }, + + _reversedSortOrder: function(order) { + return (order === SORT_ORDER_ASC ? SORT_ORDER_DESC : SORT_ORDER_ASC); + }, + + _setSortingCss: function() { + var fieldIndex = $.inArray(this._sortField, this.fields); + + this._headerRow.find("th").eq(fieldIndex) + .addClass(this._sortOrder === SORT_ORDER_ASC ? this.sortAscClass : this.sortDescClass); + }, + + _sortData: function() { + var sortFactor = this._sortFactor(), + sortField = this._sortField; + + if(sortField) { + this.data.sort(function(item1, item2) { + return sortFactor * sortField.sortingFunc(item1[sortField.name], item2[sortField.name]); + }); + } + }, + + _sortFactor: function() { + return this._sortOrder === SORT_ORDER_ASC ? 1 : -1; + }, + + _itemsCount: function() { + return this._loadStrategy.itemsCount(); + }, + + _pagesCount: function() { + var itemsCount = this._itemsCount(), + pageSize = this.pageSize; + return Math.floor(itemsCount / pageSize) + (itemsCount % pageSize ? 1 : 0); + }, + + _refreshPager: function() { + var $pagerContainer = this._pagerContainer; + $pagerContainer.empty(); + + if(this.paging && this._pagesCount() > 1) { + $pagerContainer.show() + .append(this._createPager()); + } else { + $pagerContainer.hide(); + } + }, + + _createPager: function() { + var pageIndex = this.pageIndex, + pageCount = this._pagesCount(), + pagerParts = this.pagerFormat.split(" "); + + pagerParts = $.map(pagerParts, $.proxy(function(pagerPart) { + var result = pagerPart; + + if(pagerPart === PAGES_PLACEHOLDER) { + result = this._createPages(); + } else if(pagerPart === FIRST_PAGE_PLACEHOLDER) { + result = pageIndex > 1 ? this._createPagerNavButton(this.pageFirstText, 1) : ""; + } else if(pagerPart === PREV_PAGE_PLACEHOLDER) { + result = pageIndex > 1 ? this._createPagerNavButton(this.pagePrevText, pageIndex - 1) : ""; + } else if(pagerPart === NEXT_PAGE_PLACEHOLDER) { + result = pageIndex < pageCount ? this._createPagerNavButton(this.pageNextText, pageIndex + 1) : ""; + } else if(pagerPart === LAST_PAGE_PLACEHOLDER) { + result = pageIndex < pageCount ? this._createPagerNavButton(this.pageLastText, pageCount) : ""; + } else if(pagerPart === PAGE_INDEX_PLACEHOLDER) { + result = pageIndex; + } else if(pagerPart === PAGE_COUNT_PLACEHOLDER) { + result = pageCount; + } + + return $.isArray(result) ? result.concat([" "]) : [result, " "]; + }, this)); + + var $pager = $("
").addClass(this.pagerClass) + .append(pagerParts); + + return $pager; + }, + + _createPages: function() { + var pageCount = this._pagesCount(), + pageButtonCount = this.pageButtonCount, + firstDisplayingPage = this._firstDisplayingPage, + pages = [], + pageNumber; + + if(firstDisplayingPage > 1) { + pages.push(this._createPagerPageNavButton(this.pageNavigatorPrevText, this.showPrevPages)); + } + + for(var i = 0, pageNumber = firstDisplayingPage; i < pageButtonCount && pageNumber <= pageCount; i++, pageNumber++) { + pages.push(pageNumber === this.pageIndex + ? this._createPagerCurrentPage() + : this._createPagerPage(pageNumber)); + } + + if((firstDisplayingPage + pageButtonCount - 1) < pageCount) { + pages.push(this._createPagerPageNavButton(this.pageNavigatorNextText, this.showNextPages)); + } + + return pages; + }, + + _createPagerNavButton: function(text, pageIndex) { + return this._createPagerButton(text, this.pagerNavButtonClass, function() { + this.openPage(pageIndex); + }); + }, + + _createPagerPageNavButton: function(text, handler) { + return this._createPagerButton(text, this.pagerNavButtonClass, handler); + }, + + _createPagerPage: function(pageIndex) { + return this._createPagerButton(pageIndex, this.pageClass, function() { + this.openPage(pageIndex); + }); + }, + + _createPagerButton: function(text, css, handler) { + var $link = $("").attr("href", EMPTY_HREF) + .html(text) + .on("click", $.proxy(handler, this)); + + return $("").addClass(css).append($link); + }, + + _createPagerCurrentPage: function() { + return $("") + .addClass(this.pageClass) + .addClass(this.currentPageClass) + .text(this.pageIndex); + }, + + _refreshSize: function() { + this._refreshHeight(); + this._refreshWidth(); + }, + + _refreshWidth: function() { + var $headerGrid = this._headerGrid, + $bodyGrid = this._bodyGrid, + width = this.width, + scrollBarWidth = this._scrollBarWidth(), + gridWidth; + + if(width === "auto") { + $headerGrid.width("auto"); + gridWidth = $headerGrid.outerWidth(); + width = gridWidth + scrollBarWidth; + } + + $headerGrid.width(""); + $bodyGrid.width(""); + this._header.css("padding-right", scrollBarWidth); + this._container.width(width); + gridWidth = $headerGrid.outerWidth(); + $bodyGrid.width(gridWidth); + }, + + _scrollBarWidth: (function() { + var result; + + return function() { + if(result === undefined) { + var $ghostContainer = $("
"); + var $ghostContent = $("
"); + $ghostContainer.append($ghostContent).appendTo("body"); + var width = $ghostContent.innerWidth(); + $ghostContainer.css("overflow-y", "auto"); + var widthExcludingScrollBar = $ghostContent.innerWidth(); + $ghostContainer.remove(); + result = width - widthExcludingScrollBar; + } + return result; + }; + })(), + + _refreshHeight: function() { + var container = this._container, + pagerContainer = this._pagerContainer, + height = this.height, + nonBodyHeight; + + container.height(height); + + if(height !== "auto") { + height = container.height(); + + nonBodyHeight = this._header.outerHeight(true); + if(pagerContainer.parents(container).length) { + nonBodyHeight += pagerContainer.outerHeight(true); + } + + this._body.outerHeight(height - nonBodyHeight); + } + }, + + showPrevPages: function() { + var firstDisplayingPage = this._firstDisplayingPage, + pageButtonCount = this.pageButtonCount; + + this._firstDisplayingPage = (firstDisplayingPage > pageButtonCount) ? firstDisplayingPage - pageButtonCount : 1; + + this._refreshPager(); + }, + + showNextPages: function() { + var firstDisplayingPage = this._firstDisplayingPage, + pageButtonCount = this.pageButtonCount, + pageCount = this._pagesCount(); + + this._firstDisplayingPage = (firstDisplayingPage + 2 * pageButtonCount > pageCount) + ? pageCount - pageButtonCount + 1 + : firstDisplayingPage + pageButtonCount; + + this._refreshPager(); + }, + + openPage: function(pageIndex) { + if(pageIndex < 1 || pageIndex > this._pagesCount()) + return; + + this._setPage(pageIndex); + this._loadStrategy.openPage(pageIndex); + }, + + _setPage: function(pageIndex) { + var firstDisplayingPage = this._firstDisplayingPage, + pageButtonCount = this.pageButtonCount; + + this.pageIndex = pageIndex; + + if(pageIndex < firstDisplayingPage) { + this._firstDisplayingPage = pageIndex; + } + + if(pageIndex > firstDisplayingPage + pageButtonCount - 1) { + this._firstDisplayingPage = pageIndex - pageButtonCount + 1; + } + }, + + _controllerCall: function(method, param, doneCallback) { + this._showLoading(); + + var controller = this._controller; + if(!controller || !controller[method]) { + throw new Error("controller has no method '" + method + "'"); + } + + return $.when(controller[method](param)) + .done($.proxy(doneCallback, this)) + .fail($.proxy(this._errorHandler, this)) + .always($.proxy(this._hideLoading, this)); + }, + + _errorHandler: function() { + this._callEventHandler(this.onError, { + args: $.makeArray(arguments) + }); + }, + + _showLoading: function() { + clearTimeout(this._loadingTimer); + + this._loadingTimer = setTimeout($.proxy(function() { + this._loadIndicator.show(); + }, this), this.loadIndicationDelay); + }, + + _hideLoading: function() { + clearTimeout(this._loadingTimer); + this._loadIndicator.hide(); + }, + + search: function(filter) { + this._resetSorting(); + this._resetPager(); + return this.loadData(filter); + }, + + loadData: function(filter) { + filter = filter || (this.filtering ? this._getFilter() : {}); + + $.extend(filter, this._loadStrategy.loadParams(), this._sortingParams()); + + this._callEventHandler(this.onDataLoading, { + filter: filter + }); + + return this._controllerCall("loadData", filter, function(loadedData) { + this._loadStrategy.finishLoad(loadedData); + + this._callEventHandler(this.onDataLoaded, { + data: loadedData + }); + }); + }, + + _getFilter: function() { + var result = {}; + this._eachField(function(field) { + if(field.filtering) { + result[field.name] = field.filterValue(); + } + }); + return result; + }, + + _sortingParams: function() { + if(this.sorting && this._sortField) { + return { + sortField: this._sortField.name, + sortOrder: this._sortOrder + }; + } + return {}; + }, + + clearFilter: function() { + var $filterRow = this._createFilterRow(); + this._filterRow.replaceWith($filterRow); + this._filterRow = $filterRow; + return this.search(); + }, + + insertItem: function(item) { + var insertingItem = item || this._getInsertItem(); + + this._callEventHandler(this.onItemInserting, { + item: insertingItem + }); + + return this._controllerCall("insertItem", insertingItem, function(insertedItem) { + insertedItem = insertedItem || insertingItem; + this._loadStrategy.finishInsert(insertedItem); + + this._callEventHandler(this.onItemInserted, { + item: insertedItem + }); + }); + }, + + _getInsertItem: function() { + var result = {}; + this._eachField(function(field) { + if(field.inserting) { + result[field.name] = field.insertValue(); + } + }); + return result; + }, + + clearInsert: function() { + var insertRow = this._createInsertRow(); + this._insertRow.replaceWith(insertRow); + this._insertRow = insertRow; + this.refresh(); + }, + + editItem: function(item) { + var $row = this._rowByItem(item); + if($row.length) { + this._editRow($row); + } + }, + + _rowByItem: function(item) { + if(item.jquery || item.nodeType) + return $(item); + + return this._content.find("tr").filter(function() { + return $.data(this, JSGRID_ROW_DATA_KEY) === item; + }); + }, + + _editRow: function($row) { + if(!this.editing) + return; + + if(this._editingRow) { + this.cancelEdit(); + } + + var item = $row.data(JSGRID_ROW_DATA_KEY), + $editRow = this._createEditRow(item); + + this._editingRow = $row; + $row.hide(); + $editRow.insertAfter($row); + $row.data(JSGRID_EDIT_ROW_DATA_KEY, $editRow); + }, + + _createEditRow: function(item) { + if($.isFunction(this.editRowRenderer)) { + return $(this.editRowRenderer(item, this._itemIndex(item))); + } + + var $result = $("
").addClass(field.css) + .appendTo($result) + .append(field.editTemplate ? field.editTemplate(item[field.name], item) : "") + .width(field.width || "auto"); + }); + + return $result; + }, + + updateItem: function(item, editedItem) { + if(arguments.length === 1) { + editedItem = item; + } + + var $row = item ? this._rowByItem(item) : this._editingRow; + editedItem = editedItem || this._getEditedItem(); + + return this._updateRow($row, editedItem); + }, + + _updateRow: function($updatingRow, editedItem) { + var updatingItem = $updatingRow.data(JSGRID_ROW_DATA_KEY), + updatingItemIndex = this._itemIndex(updatingItem); + + $.extend(updatingItem, editedItem); + + this._callEventHandler(this.onItemUpdating, { + row: $updatingRow, + item: updatingItem, + itemIndex: updatingItemIndex + }); + + return this._controllerCall("updateItem", updatingItem, function(updatedItem) { + updatedItem = updatedItem || updatingItem; + this._finishUpdate($updatingRow, updatedItem, updatingItemIndex); + + this._callEventHandler(this.onItemUpdated, { + row: $updatingRow, + item: updatedItem, + itemIndex: updatingItemIndex + }); + }); + }, + + _itemIndex: function(item) { + return $.inArray(item, this.data); + }, + + _finishUpdate: function($updatedRow, updatedItem, updatedItemIndex) { + this.cancelEdit(); + this.data[updatedItemIndex] = updatedItem; + $updatedRow.replaceWith(this._createRow(updatedItem, updatedItemIndex)); + }, + + _getEditedItem: function() { + var result = {}; + this._eachField(function(field) { + if(field.editing) { + result[field.name] = field.editValue(); + } + }); + return result; + }, + + cancelEdit: function() { + if(!this._editingRow) { + return; + } + + var $row = this._editingRow, + $editRow = $row.data(JSGRID_EDIT_ROW_DATA_KEY); + + $editRow.remove(); + $row.show(); + this._editingRow = null; + }, + + deleteItem: function(item) { + var $row = this._rowByItem(item); + + if(!$row.length) + return; + + var that = this; + if (typeof this.deleteConfirm === 'function') { + this.deleteConfirm(this, $row.data(JSGRID_ROW_DATA_KEY), function (result) { + if (result) { + that._deleteRow($row); + } + }); + } else { + if(this.confirmDeleting && !window.confirm(getOrApply(this.deleteConfirm, this, $row.data(JSGRID_ROW_DATA_KEY)))) + return; + + return this._deleteRow($row); + } + }, + + _deleteRow: function($row) { + var deletingItem = $row.data(JSGRID_ROW_DATA_KEY), + deletingItemIndex = this._itemIndex(deletingItem); + + this._callEventHandler(this.onItemDeleting, { + row: $row, + item: deletingItem, + itemIndex: deletingItemIndex + }); + + return this._controllerCall("deleteItem", deletingItem, function() { + this._loadStrategy.finishDelete(deletingItem, deletingItemIndex); + + this._callEventHandler(this.onItemDeleted, { + row: $row, + item: deletingItem, + itemIndex: deletingItemIndex + }); + }); + } + }; + + $.fn.jsGrid = function(config) { + var args = $.makeArray(arguments), + methodArgs = args.slice(1), + result = this; + + this.each(function() { + var $element = $(this), + instance = $element.data(JSGRID_DATA_KEY), + methodResult; + + if(instance) { + if(typeof config === "string") { + methodResult = instance[config].apply(instance, methodArgs); + if(methodResult !== undefined && methodResult !== instance) { + result = methodResult; + return false; + } + } else { + instance._detachWindowResizeCallback(); + instance._init(config); + instance.render(); + } + } else { + new Grid($element, config); + } + }); + + return result; + }; + + window.jsGrid = { + Grid: Grid, + fields: [] + }; + +}(window, jQuery)); +(function(jsGrid, $, undefined) { + + function LoadIndicator(config) { + this._init(config); + } + + LoadIndicator.prototype = { + + container: "body", + message: "Loading...", + shading: true, + + zIndex: 1000, + shaderClass: "jsgrid-load-shader", + loadPanelClass: "jsgrid-load-panel", + + _init: function(config) { + $.extend(true, this, config); + + this._initContainer(); + this._initShader(); + this._initLoadPanel(); + }, + + _initContainer: function() { + this._container = $(this.container); + }, + + _initShader: function() { + if(!this.shading) + return; + + this._shader = $("
").addClass(this.shaderClass) + .hide() + .css({ + position: "absolute", + top: 0, + right: 0, + bottom: 0, + left: 0, + zIndex: this.zIndex + }) + .appendTo(this._container); + }, + + _initLoadPanel: function() { + this._loadPanel = $("
").addClass(this.loadPanelClass) + .text(this.message) + .hide() + .css({ + position: "absolute", + top: "50%", + left: "50%", + zIndex: this.zIndex + }) + .appendTo(this._container); + }, + + show: function() { + var $loadPanel = this._loadPanel.show(); + + var actualWidth = $loadPanel.outerWidth(); + var actualHeight = $loadPanel.outerHeight(); + + $loadPanel.css({ + marginTop: -actualHeight / 2, + marginLeft: -actualWidth / 2 + }); + + this._shader.show(); + }, + + hide: function() { + this._loadPanel.hide(); + this._shader.hide(); + } + + }; + + jsGrid.LoadIndicator = LoadIndicator; + +}(jsGrid, jQuery)); +(function(jsGrid, $, undefined) { + + function DirectLoadingStrategy(grid) { + this._grid = grid; + } + + DirectLoadingStrategy.prototype = { + + firstDisplayIndex: function() { + var grid = this._grid; + return grid.option("paging") ? (grid.option("pageIndex") - 1) * grid.option("pageSize") : 0; + }, + + lastDisplayIndex: function() { + var grid = this._grid; + var itemsCount = grid.option("data").length; + + return grid.option("paging") + ? Math.min(grid.option("pageIndex") * grid.option("pageSize"), itemsCount) + : itemsCount; + }, + + itemsCount: function() { + return this._grid.option("data").length; + }, + + openPage: function(index) { + this._grid.refresh(); + }, + + loadParams: function() { + return {}; + }, + + sort: function() { + this._grid._sortData(); + this._grid.refresh(); + return $.Deferred().resolve().promise(); + }, + + finishLoad: function(loadedData) { + this._grid.option("data", loadedData); + }, + + finishInsert: function(insertedItem) { + var grid = this._grid; + grid.option("data").push(insertedItem); + grid.refresh(); + }, + + finishDelete: function(deletedItem, deletedItemIndex) { + var grid = this._grid; + grid.option("data").splice(deletedItemIndex, 1); + grid.reset(); + } + }; + + + function PageLoadingStrategy(grid) { + this._grid = grid; + this._itemsCount = 0; + } + + PageLoadingStrategy.prototype = { + firstDisplayIndex: function() { + return 0; + }, + + lastDisplayIndex: function() { + return this._grid.option("data").length; + }, + + itemsCount: function() { + return this._itemsCount; + }, + + openPage: function(index) { + this._grid.loadData(); + }, + + loadParams: function() { + var grid = this._grid; + return { + pageIndex: grid.option("pageIndex"), + pageSize: grid.option("pageSize") + }; + }, + + sort: function() { + return this._grid.loadData(); + }, + + finishLoad: function(loadedData) { + this._itemsCount = loadedData.itemsCount; + this._grid.option("data", loadedData.data); + }, + + finishInsert: function(insertedItem) { + this._grid.search(); + }, + + finishDelete: function(deletedItem, deletedItemIndex) { + this._grid.search(); + } + }; + + jsGrid.loadStrategies = { + DirectLoadingStrategy: DirectLoadingStrategy, + PageLoadingStrategy: PageLoadingStrategy + }; + +}(jsGrid, jQuery)); +(function(jsGrid, $, undefined) { + + var sortStrategies = { + string: function(str1, str2) { + return str1.localeCompare(str2); + }, + + number: function(n1, n2) { + return n1 - n2; + }, + + date: function(dt1, dt2) { + return dt1 - dt2; + }, + + numberAsString: function(n1, n2) { + return parseFloat(n1) - parseFloat(n2); + } + }; + + jsGrid.sortStrategies = sortStrategies; + +}(jsGrid, jQuery)); +(function(jsGrid, $, undefined) { + + function Field(config) { + $.extend(true, this, config); + this.sortingFunc = this._getSortingFunc(); + } + + Field.prototype = { + name: "", + title: "", + css: "", + align: "", + width: 100, + + filtering: true, + inserting: true, + editing: true, + sorting: true, + sorter: "string", // name of SortStrategy or function to compare elements + + headerTemplate: function() { + return this.title || this.name; + }, + + itemTemplate: function(value, item) { + return value; + }, + + filterTemplate: function() { + return ""; + }, + + insertTemplate: function() { + return ""; + }, + + editTemplate: function(value, item) { + this._value = value; + return this.itemTemplate(value, item); + }, + + filterValue: function() { + return ""; + }, + + insertValue: function() { + return ""; + }, + + editValue: function() { + return this._value; + }, + + _getSortingFunc: function() { + var sorter = this.sorter; + + if($.isFunction(sorter)) { + return sorter; + } + + if(typeof sorter === "string") { + return jsGrid.sortStrategies[sorter]; + } + + throw Error("Wrong sorter for the field \"" + this.name + "\"!"); + } + }; + + jsGrid.Field = Field; + +}(jsGrid, jQuery)); +(function(jsGrid, $, undefined) { + + var Field = jsGrid.Field; + + function TextField(config) { + Field.call(this, config); + } + + TextField.prototype = new Field({ + + autosearch: true, + + filterTemplate: function() { + var grid = this._grid, + $result = this.filterControl = this._createTextBox(); + + if(this.autosearch) { + $result.on("keypress", function(e) { + if(e.which === 13) { + grid.search(); + e.preventDefault(); + } + }); + } + + return $result; + }, + + insertTemplate: function() { + var $result = this.insertControl = this._createTextBox(); + return $result; + }, + + editTemplate: function(value) { + var $result = this.editControl = this._createTextBox(); + $result.val(value); + return $result; + }, + + filterValue: function() { + return this.filterControl.val(); + }, + + insertValue: function() { + return this.insertControl.val(); + }, + + editValue: function() { + return this.editControl.val(); + }, + + _createTextBox: function() { + return $("").attr("type", "text"); + } + }); + + jsGrid.fields.text = jsGrid.TextField = TextField; + +}(jsGrid, jQuery)); +(function(jsGrid, $, undefined) { + + var TextField = jsGrid.TextField; + + function NumberField(config) { + TextField.call(this, config); + } + + NumberField.prototype = new TextField({ + + sorter: "number", + align: "right", + + filterValue: function() { + return parseInt(this.filterControl.val() || 0, 10); + }, + + insertValue: function() { + return parseInt(this.insertControl.val() || 0, 10); + }, + + editValue: function() { + return parseInt(this.editControl.val() || 0, 10); + }, + + _createTextBox: function() { + return $("").attr("type", "number"); + } + }); + + jsGrid.fields.number = jsGrid.NumberField = NumberField; + +}(jsGrid, jQuery)); +(function(jsGrid, $, undefined) { + + var TextField = jsGrid.TextField; + + function TextAreaField(config) { + TextField.call(this, config); + } + + TextAreaField.prototype = new TextField({ + + insertTemplate: function() { + var $result = this.insertControl = this._createTextArea(); + return $result; + }, + + editTemplate: function(value) { + var $result = this.editControl = this._createTextArea(); + $result.val(value); + return $result; + }, + + _createTextArea: function() { + return $("