Thursday, January 11, 2018

XERO Private Application | XERO Connect Using Private Application Java | Java to Connect to XERO Private Application

At first you have to create an Private Application in XERO. If you didn't yet you can follow the below link:
Connect to Xero Private Application Using Php Application
You already notified that there are two combinations of public/private key file. Public key need to create application and Private key need to sign data before send to XERO. Below is a sample Java script which will connect to XERO private application.

package com.pkm;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.FileReader;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;

/**
 * Created by pritom on 2/01/2018.
 */
public class XeroPrivateApiUtils {
    private static final String NONCE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    private static final String ORGANIZATION_URL = "https://api.xero.com/api.xro/2.0/Organisation";

    private String sbs;
    private String path;
    private String method;
    private Map<Object, Object> configs = new HashMap<>();
    private Map<Object, Object> params = new HashMap<>();
    private Map<Object, Object> sign = new HashMap<>();
    private String KEY_FILE = null;
    private String CONSUMER_KEY = null;
    private String CONSUMER_SECRET = null;

    public static void main(String[] args) throws Exception {
        XeroPrivateApiUtils xero = new XeroPrivateApiUtils();
        HttpJavaClient.Response response = xero.execute("GET", XeroPrivateApiUtils.ORGANIZATION_URL, new HashMap(), new HashMap());
        System.out.println(response.toString());
    }

    private XeroPrivateApiUtils() {
        this.KEY_FILE = "private.key";
        this.CONSUMER_KEY = "IKAEAI8BA..........1MAOR9XBEHU";
        this.CONSUMER_SECRET = "STXPEDM..........0FWMPLIDGB6DS";

        putToMap(this.configs, "consumer_key", CONSUMER_KEY);
        putToMap(this.configs, "consumer_secret", CONSUMER_SECRET);
        putToMap(this.configs, "core_version", "2.0");
        putToMap(this.configs, "payroll_version", "1.0");
        putToMap(this.configs, "file_version", "1.0");
        putToMap(this.configs, "application_type", "Private");
    }

    HttpJavaClient.Response execute(
            String method, String path,
            Map<Object, Object> configs,
            Map<Object, Object> params
    ) throws Exception {
        this.method = method;
        this.path = path;
        this.params = params;
        putToMap(this.configs, configs);
        this.buildParameters();
        this.sign();

        Map<Object, Object> headers = new HashMap<>();
        putToMap(headers, "Accept", "application/json");
        if (this.method.equalsIgnoreCase("get")) {
            return HttpJavaClient.doGet((String) this.sign.get("signed_url"), headers);
        }
        return null;
    }

    private void sign() throws Exception {
        putToMap(this.params, "oauth_signature", this.generateSignature());
        putToMap(this.sign, "parameters", this.params);
        putToMap(this.sign, "signature", escape((String) this.params.get("oauth_signature")));
        putToMap(this.sign, "signed_url", this.path + "?" + normalizeParameters(true));
        putToMap(this.sign, "sbs", this.sbs);
    }

    private String generateSignature() throws Exception {
        switch ((String) this.params.get("oauth_signature_method")) {
            case "RSA-SHA1":
                this.sbs = escape(this.method.toUpperCase());
                this.sbs += "&" + escape(this.path);
                this.sbs += "&" + escape(this.normalizeParameters(false));
                RSAPrivateKey privateKey = getPrivateKey(KEY_FILE);
                Signature sign = Signature.getInstance("SHA1withRSA");
                sign.initSign(privateKey);
                sign.update(this.sbs.getBytes("UTF-8"));
                return encodeBase64(sign.sign());
            case "HMAC-SHA1":
                String secret = this.configs.get("consumer_secret") + "&";
                if (this.configs.get("access_token_secret") != null) {
                    secret = secret + this.configs.get("access_token_secret");
                }
                this.sbs = escape(this.method.toUpperCase());
                this.sbs += "&" + escape(this.path);
                this.sbs += "&" + escape(this.normalizeParameters(false));
                return hmacSha(secret, this.sbs);
            default:
                throw new Exception("Undefined signature method");
        }
    }

    private static String hmacSha(String key, String value) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(keySpec);
        byte[] result = mac.doFinal(value.getBytes());
        return encodeBase64(result);
    }

    public static String encodeBase64(String o) {
        BASE64Encoder base64Encoder = new BASE64Encoder();
        return base64Encoder.encodeBuffer(o.getBytes()).replace("\r\n", "").replace("\n", "");
    }

    private static String encodeBase64(byte[] o) {
        BASE64Encoder base64Encoder = new BASE64Encoder();
        return base64Encoder.encodeBuffer(o).replace("\r\n", "").replace("\n", "");
    }

    private static RSAPrivateKey getPrivateKey(String fileName) throws Exception {
        byte[] encoded = decodeBase64(getKey(fileName));
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
        return (RSAPrivateKey) kf.generatePrivate(keySpec);
    }

    private static byte[] decodeBase64(String o) throws Exception {
        BASE64Decoder decoder = new BASE64Decoder();
        return decoder.decodeBuffer(o);
    }

    private String normalizeParameters(Boolean filter) throws Exception {
        String normalized = "";
        for (Object o : this.params.entrySet()) {
            Map.Entry me2 = (Map.Entry) o;
            String key = me2.getKey().toString();
            Object value = me2.getValue();
            Boolean allow = !(key.equalsIgnoreCase("xml") && filter) && !key.endsWith("_secret");
            if (allow) {
                normalized += escape(key) + "=" + escape(value.toString()) + "&";
            }
        }
        return normalized.length() > 0 ? normalized.substring(0, normalized.length() - 1) : normalized;
    }

    private static String escape(String context) throws Exception {
        context = rawUrlEncode(context);
        context = stringReplace("+", "%20", context);
        context = stringReplace("!", "%21", context);
        context = stringReplace("*", "%2A", context);
        context = stringReplace("\'", "%27", context);
        context = stringReplace("(", "%28", context);
        context = stringReplace(")", "%29", context);
        return context;
    }

    private static String rawUrlEncode(String context) throws Exception {
        return URLEncoder.encode(context, "UTF-8");
    }

    private static String rawUrlDecode(String context) throws Exception {
        return URLDecoder.decode(context, "UTF-8");
    }

    private static String stringReplace(String search, String fill, String context) {
        return context.replace(search, fill);
    }

    private void buildParameters() {
        putToMap(this.params, "oauth_nonce", shuffle(5));
        putToMap(this.params, "oauth_token", CONSUMER_KEY);
        putToMap(this.params, "oauth_version", "1.0");
        putToMap(this.params, "oauth_timestamp", "" + ((System.currentTimeMillis()) / 1000));
        putToMap(this.params, "oauth_consumer_key", CONSUMER_KEY);
        putToMap(this.params, "oauth_signature_method", "RSA-SHA1");
        this.params = new TreeMap(this.params); //Sorted map by key
    }

    private static String shuffle(Integer length) {
        List<Character> characters = new ArrayList<Character>();
        for(char c : NONCE.toCharArray()){
            characters.add(c);
        }
        StringBuilder output = new StringBuilder(length);
        while(length != 0) {
            int randPicker = (int) (Math.random() * characters.size());
            output.append(characters.get(randPicker));
            length --;
        }
        return output.toString();
    }

    private static String getKey(String filename) throws Exception {
        String key = "";
        BufferedReader br = new BufferedReader(new FileReader(filename));
        String line;
        while ((line = br.readLine()) != null) {
            if (!line.startsWith("-")) {
                key += line + "\n";
            }
        }
        br.close();
        return key;
    }

    private static void putToMap(Map<Object, Object> map, Map fromMap) {
        for (Object key : fromMap.keySet().toArray()) {
            putToMap(map, key.toString(), fromMap.get(key));
        }
    }

    private static void putToMap(Map<Object, Object> map, Object key, Object value) {
        map.put(key, value);
    }

    private static void println(Object o) {
        System.out.println("" + o);
    }

    private static void exception() throws Exception {
        throw new Exception();
    }
}
You can download a sample Private Key File from this link
And output? Obviously as below:
{
  "Id": "966aed9c-....-4d86-....-c59a6b34eb2c",
  "Status": "OK",
  "ProviderName": "Dev Private",
  "DateTimeUTC": "\/Date(1515683933534)\/",
  "Organisations": [
    {
      "APIKey": "WXP9HDM..........ILMSZHZIUSIA5",
      "Name": "Dev  Organisation",
      "LegalName": "Dev  Organisation",
      "PaysTax": true,
      "Version": "AU",
      "OrganisationType": "COMPANY",
      "BaseCurrency": "AUD",
      "CountryCode": "AU",
      "IsDemoCompany": false,
      "OrganisationStatus": "ACTIVE",
      "FinancialYearEndDay": 30,
      "FinancialYearEndMonth": 6,
      "SalesTaxBasis": "ACCRUALS",
      "SalesTaxPeriod": "QUARTERLY1",
      "DefaultSalesTax": "Tax Exclusive",
      "DefaultPurchasesTax": "Tax Inclusive",
      "CreatedDateUTC": "\/Date(1514794572000)\/",
      "OrganisationEntityType": "COMPANY",
      "Timezone": "AUSEASTERNSTANDARDTIME",
      "ShortCode": "!wnfkR",
      "OrganisationID": "91de7ce4-....-48e8-....-74573ab8b4e0",
      "LineOfBusiness": "tests xero integration",
      "Addresses": [],
      "Phones": [],
      "ExternalLinks": [],
      "PaymentTerms": {}
    }
  ]
}

Saturday, January 6, 2018

XERO API Integration | XERO Public API Integration | XERO Connect Through Public API

1. Login to https://app.xero.com/Application to create an Public application
2. Now copy "Consumer Key" and "Consumer Secret", which will be needed to connect to XERO. The first step is to get a request token from using above "Consumber Key" and "Consumer Secret", then we will redirect user to XERO login screen, where user can authorize our application and redirect back to our main application with a code, we will then use the code to get access_token and refresh_token. We will store access_token and refresh_token for further use. Below scrip will help us to get Request_Token from XERO end.

<?php
session_start();
include_once "connector.php";

$callback_postfix = "xero-public-api-integration/callback.php?state=".uniqid();
$xero_connector = new XeroConnector(array(
    "oauth_callback" => "http://localhost:81/me/".$callback_postfix
));
$response = $xero_connector->execute(
    "GET", "https://api.xero.com/oauth/RequestToken"
);
if ($response["code"] == 200) {
    $parsed = $xero_connector->extract_params($response["body"]);
    $_SESSION["token"] = $parsed;
    XeroConnector::prettyPrint($parsed);
    $url = "https://api.xero.com/oauth/Authorize?oauth_token=".$parsed["oauth_token"];
    echo '<p>Authorize: <a href="'.$url.'">'.$url.'</a></p>';
}
XeroConnector::prettyPrint($response);
3. After redirected to XERO you will asked to login and then authorize application. (If you are already logged in, then ask for authorize the app)
After authorize you will redirected back you as Callback URL, with a oauth_token and oauth_verifier, you can then process those information to get access_token and refresh_token. Script and result is below respectively.

<?php
session_start();
include_once "connector.php";

XeroConnector::prettyPrint($_SESSION["token"]);
XeroConnector::prettyPrint($_REQUEST);

if (isset($_REQUEST ['oauth_verifier'])) {
    $configs = array(
        "access_token" => $_SESSION["token"]["oauth_token"],
        "access_token_secret" => $_SESSION["token"]["oauth_token_secret"]
    );
    $params = array(
        'oauth_verifier' => $_REQUEST ['oauth_verifier'],
        'oauth_token' => $_REQUEST ['oauth_token']
    );

    $xero_connector = new XeroConnector($params, $configs);
    $response = $xero_connector->execute("GET", "https://api.xero.com/oauth/AccessToken");
    if ($response["code"] == 200) {
        $parsed = $xero_connector->extract_params($response["body"]);
        $_SESSION["access_token"] = $parsed["oauth_token"];
        $_SESSION["oauth_token_secret"] = $parsed["oauth_token_secret"];
        XeroConnector::prettyPrint($parsed);
        echo '<p><a href="organization.php">View Organization Details</a></p>';
    }
    else {
        XeroConnector::prettyPrint($response);
    }
}
Response (Access token and Refresh token collected from XERO end)

Array
(
    [oauth_token] => N0UTBYDEZQFAOLPZ6JZDVJSOBQAJUS
    [oauth_token_secret] => KEOOMKNTNZYAQBNO4WYGCZ3SI33WYO
    [oauth_callback_confirmed] => true
)
Array
(
    [state] => 5a5051b091146
    [oauth_token] => N0UTBYDEZQFAOLPZ6JZDVJSOBQAJUS
    [oauth_verifier] => 7403017
    [org] => u-HyWGrAfasyv8hO$VlheW
)
Array
(
    [oauth_token] => NPTKFCAGC6KXXGU6RWOLIF6RDSUGGO
    [oauth_token_secret] => EWXBV0NWBGLBCIM0ZDSEKDUGOMCWC8
    [oauth_expires_in] => 1800
    [xero_org_muid] => u-HyWGrAfasyv8hO$VlheW
)
View Organization Details
Now you have access_token and refresh_token, below script will help to get organization details.
<?php
session_start();
include_once "connector.php";

$configs = array(
    "oauth_token" => $_SESSION["access_token"],
    "access_token_secret" => $_SESSION["oauth_token_secret"]
);
$params = array(
    "oauth_token" => $_SESSION["access_token"]
);

echo "<h3><a href='xero.php'>Start process again</a></h3>";

$xero_connector = new XeroConnector($params, $configs);
$response = $xero_connector->execute("GET", "https://api.xero.com/api.xro/2.0/Organisation");
if ($response["code"] == 200) {
    $parsed = json_decode($response["body"]);
    XeroConnector::prettyPrint($parsed);
}
XeroConnector::prettyPrint($response);
And organization details is as below:
stdClass Object
(
    [Id] => e8904090-....-46d1-....-829af831fb30
    [Status] => OK
    [ProviderName] => My Public App Rose
    [DateTimeUTC] => /Date(1515213767839)/
    [Organisations] => Array
        (
            [0] => stdClass Object
                (
                    [APIKey] => DZQNXBMG.............YFWIS0ZME
                    [Name] => Mine Test
                    [LegalName] => Mine Test
                    [PaysTax] => 1
                    [Version] => GLOBAL
                    [OrganisationType] => COMPANY
                    [BaseCurrency] => BDT
                    [CountryCode] => BD
                    [IsDemoCompany] => 
                    [OrganisationStatus] => ACTIVE
                    [FinancialYearEndDay] => 31
                    [FinancialYearEndMonth] => 12
                    [DefaultSalesTax] => Tax Exclusive
                    [DefaultPurchasesTax] => Tax Exclusive
                    [CreatedDateUTC] => /Date(1513332449000)/
                    [OrganisationEntityType] => COMPANY
                    [Timezone] => BANGLADESHSTANDARDTIME
                    [ShortCode] => !yXp1C
                    [OrganisationID] => 817ce4e3-....-4da6-....-8ad8fdfbeba2
                    [LineOfBusiness] => Marketing Software
                    [Addresses] => Array
                        (
                        )

                    [Phones] => Array
                        (
                        )

                    [ExternalLinks] => Array
                        (
                        )

                    [PaymentTerms] => stdClass Object
                        (
                        )

                )

        )

)
And finally, below is the Connector.php script:
<?php
class XeroConnector
{
    private $headers = array();
    private $parameters = array();
    private $configs = array(
        "consumer_key" => "8J3YWIAOE4XFH..........FPDB54R",
        "consumer_secret" => "7UGYHJZYITL...........B1FXCMTV",
        "core_version" => "2.0",
        "payroll_version" => "1.0",
        "file_version" => "1.0",
        "application_type" => "Public",
        "user_agent" => "Trial #3"
    );

    function __construct($parameters, $configs = array())
    {
        $this->parameters = $parameters;
        $this->configs = array_merge($this->configs, $configs);
    }

    function execute($method, $target, $post_body = "")
    {
        $this->post_body = $post_body;
        $this->method = strtoupper($method);
        $this->path = $target;
        $this->nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
        $this->buildParameters();
        $this->sign();
        return $this->curlRequest($this->sign["signed_url"]);
    }

    private function sign()
    {
        $this->parameters["oauth_signature"] = $this->generateSignature();
        $this->sign = array(
            'parameters' => $this->parameters,
            'signature' => $this->escape($this->parameters["oauth_signature"]),
            'signed_url' => $this->path . '?' . $this->normalizeParameters('true'),
            'header' => $this->getHeaderString(),
            'sbs' => $this->sbs
        );
    }

    private function getHeaderString($args = array())
    {
        $result = 'OAuth ';

        foreach ($this->parameters as $pName => $pValue) {
            if (strpos($pName, 'oauth_') !== 0)
                continue;
            if (is_array($pValue)) {
                foreach ($pValue as $val) {
                    $result .= $pName . '="' . $this->escape($val) . '", ';
                }
            }
            else {
                $result .= $pName . '="' . $this->escape($pValue) . '", ';
            }
        }
        return preg_replace('/, $/', '', $result);
    }

    private function generateSignature()
    {
        switch ($this->parameters['oauth_signature_method']) {
            case 'RSA-SHA1':
                $private_key = openssl_pkey_get_private($this->readFile($this->configs['rsa_private_key']));
                $this->sbs = $this->escape($this->method) . "&" . $this->escape($this->path) . "&" . $this->escape($this->normalizeParameters());
                openssl_sign($this->sbs, $signature, $private_key);
                openssl_free_key($private_key);
                return base64_encode($signature);
            case 'PLAINTEXT':
                return urlencode($this->configs["consumer_secret"]);
            case 'HMAC-SHA1':
                $secret = $this->configs["consumer_secret"]."&";
                if (isset($this->configs["access_token_secret"])) {
                    $secret = $secret . $this->configs["access_token_secret"];
                }
                $this->sbs = $this->escape($this->method) . '&' . $this->escape($this->path) . '&' . $this->escape($this->normalizeParameters());
                return base64_encode(hash_hmac('sha1', $this->sbs, $secret, true));
            default:
                throw new Exception('Unknown signature method');
        }
    }

    private function normalizeParameters($filter = 'false')
    {
        $elements = array();
        ksort($this->parameters);
        foreach ($this->parameters as $paramName => $paramValue) {
            if ($paramName == 'xml') {
                if ($filter == "true")
                    continue;
            }
            if (preg_match('/\w+_secret/', $paramName))
                continue;
            if (is_array($paramValue)) {
                sort($paramValue);
                foreach ($paramValue as $element)
                    array_push($elements, $this->escape($paramName) . '=' . $this->escape($element));
                continue;
            }
            array_push($elements, $this->escape($paramName) . '=' . $this->escape($paramValue));

        }
        return join('&', $elements);
    }

    private function readFile($file_path)
    {
        $fp = fopen($file_path, "r");
        $file_contents = fread($fp, 8192);
        fclose($fp);
        return $file_contents;
    }

    private function escape($string)
    {
        if ($string === 0)
            return 0;
        if (empty($string))
            return '';
        if (is_array($string))
            throw new Exception('Array passed to escape()');

        $string = rawurlencode($string);
        $string = str_replace('+', '%20', $string);
        $string = str_replace('!', '%21', $string);
        $string = str_replace('*', '%2A', $string);
        $string = str_replace('\'', '%27', $string);
        $string = str_replace('(', '%28', $string);
        $string = str_replace(')', '%29', $string);
        return $string;
    }

    private function buildParameters()
    {
        $parameters = array();
        $parameters["oauth_nonce"] = $this->getNonce(5);
        $parameters["oauth_version"] = "1.0";
        $parameters["oauth_timestamp"] = time();
        $parameters["oauth_consumer_key"] = $this->configs["consumer_key"];
        $parameters["oauth_signature_method"] = "HMAC-SHA1";
        $this->parameters = array_merge($parameters, $this->parameters);
    }

    private function getNonce($length = 5)
    {
        $result = '';
        $cLength = strlen($this->nonce_chars);
        for ($i = 0; $i < $length; $i++) {
            $rnum = rand(0, $cLength);
            $result .= substr($this->nonce_chars, $rnum, 1);
        }
        return $result;
    }

    public static function prettyPrint($o)
    {
        echo "<pre>";
        print_r($o);
        echo "</pre>";
    }

    public function extract_params($body)
    {
        $kvs = explode ( '&', $body );
        $decoded = array ();
        foreach ( $kvs as $kv ) {
            $kv = explode ( '=', $kv, 2 );
            $kv [0] = $this->safe_decode ( $kv [0] );
            $kv [1] = $this->safe_decode ( $kv [1] );
            $decoded [$kv [0]] = $kv [1];
        }
        return $decoded;
    }

    private function safe_encode($data) {
        if (is_array ( $data )) {
            return array_map ( array (
                $this,
                'safe_encode'
            ), $data );
        }
        else if (is_scalar ( $data )) {
            return str_ireplace ( array (
                '+',
                '%7E'
            ), array (
                ' ',
                '~'
            ), rawurlencode ( $data ) );
        }
        else {
            return '';
        }
    }

    private function safe_decode($data)
    {
        if (is_array ( $data )) {
            return array_map ( array (
                $this,
                'safe_decode'
            ), $data );
        }
        else if (is_scalar ( $data )) {
            return rawurldecode ( $data );
        }
        else {
            return '';
        }
    }

    private function curlRequest($url)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_VERBOSE, true);

        $this->headers['Accept'] = 'application/json';
        switch ($this->method) {
            case "GET":
                $this->content_length = 0;
                break;
            case 'POST':
                $this->headers['Content-Length'] = strlen($this->post_body);
                curl_setopt($ch, CURLOPT_POST, TRUE);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $this->post_body);
                $this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
                break;
            case 'PUT' :
                $this->headers['Content-Length'] = strlen($this->post_body);
                $fh = tmpfile();
                fwrite($fh, $this->post_body);
                rewind($fh);
                curl_setopt($ch, CURLOPT_PUT, true);
                curl_setopt($ch, CURLOPT_INFILE, $fh);
                curl_setopt($ch, CURLOPT_INFILESIZE, $this->headers ['Content-Length']);
                $this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
                break;
            default :
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method);
        }

        if (count($this->headers) > 0) {
            $headers = array();
            foreach ($this->headers as $k => $v) {
                $headers [] = trim($k . ': ' . $v);
            }
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        }

        $response = curl_exec($ch);
        if ($response === false) {
            $response = 'Curl error: ' . curl_error($ch);
            $code = 1;
        }
        else {
            $code = curl_getinfo ($ch, CURLINFO_HTTP_CODE);
        }
        $info = curl_getinfo ($ch);

        curl_close($ch);

        return array(
            'body' => $response, 'code' => $code, 'info' => $info
        );
    }
}

Friday, January 5, 2018

Regex Domain Name Validation | Regex URL Validation


Domain Name Validation (Regex-101 Link)
^((https?):\/\/)?([a-zA-Z0-9_][-_a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9]{1,20})$



URL Validation (Regex-101 Link)
^((https?):\/\/)?([a-z]*\.)+([a-z]+)(?:\/)?([a-z]+\/?)*(?:\?([a-z]+\=?[a-z]*&?)+)?(#[a-z]*)*

JQuery | Resizable Table Example | Table Column Resizing | Resizable Table Columns Using jQuery UI | Resizable table columns with jQuery

Core Part of Code I am Using to Resize Table Columns with Percentage Value

var table = $("#table-css-border-1"), tw = table.width();
table.find("tr").each(function() {
 var tr = $(this), c = 0;
  tr.find("td, th").each(function() {
   var te = $(this), ew = ((te.width() * 100) / tw);
   te.css("width", ew + "%").addClass("resizable column-" + (++c));
    te.data("cc", ".column-" + c);
    te.data("nc", ".column-" + (c + 1));
  });
 tr.find("td:last-child, th:last-child").removeClass("resizable");
});

var r = $("#table-css-border-1 .resizable"), mw = 50;
r.resizable({
  handles: 'e',
  maxWidth: mw,
  start: function(e, ui) {
   var td = $(ui.element);
    var nd = td.next() || td.previous();
    r.resizable( "option", "maxWidth", gw(td) + gw(nd) - mw);
    table.find(td.data("cc")).css("width", "");
    table.find(td.data("nc")).css("width", "");
  },
  stop: function(e, ui) {
   var td = $(ui.element);
    var nd = td.next() || td.previous();
    table.find(td.data("cc")).css("width", (((td.width()+5) * 100)/tw)+"%");
    table.find(td.data("nc")).css("width", ((nd.width() * 100)/tw)+"%");
  }
});
function gw(e) {
 return parseFloat(e.css("width").replace(/px/g, ''));
}



JSFiddle link to play

Get Latest Record In Each MySQL Group | How to select the first/least/max row per group in SQL | Select max, min, last row for each group in SQL without a subquery | MySQL - How To Get Top N Rows per Each Group

First approach (LEFT JOIN)


SELECT s.Name,c1.Id AS Max_Score_ID,c1.Score as Max_Score
FROM Students s
LEFT JOIN Scores c1 ON (c1.Student=s.Id)
LEFT JOIN Scores c2 ON (
  c1.Student=c2.Student 
  AND (c1.Score<c2.Score OR (c1.Score=c2.Score AND c1.Id<c2.Id))
)
WHERE c2.Score IS NULL
ORDER BY c1.Score DESC;

Execution Plan: First image shows statistics, an index is used when performing the above SQL, example created in MySQL-Fiddle. And second image for large data size, its around 8 million rows, and it takes a small amount of time to execute.





Now we will go for second approach (GROUP_CONCAT WITH SUBSTRING_INDEX)



SELECT s.Name,
CAST(SUBSTRING_INDEX(
  GROUP_CONCAT(c1.Id order by c1.Score desc),',',1
) AS DECIMAL(10, 2)) AS Max_Score_ID,
CAST(SUBSTRING_INDEX(
  GROUP_CONCAT(c1.Score order by c1.Score desc),',',1
) AS DECIMAL(10, 2)) as Max_Score
FROM Students s
LEFT JOIN Scores c1 ON (c1.Student=s.Id)
GROUP BY s.Id
ORDER BY Max_Score DESC;

Execution plan: First image show statistics taken from MySQL-Fiddle, used group-concat and substring-index both to calculate (min/max) value and/or other column of the table. Second image show that its also good time effective as it also takes a small amount of time to get min/max value and table size around 8 million rows.






MySQL Fiddle Link