Z Project

Z UI Library design keynotes

While constructing new JavaScript Z core, you have to take into consideration some keystone aspects of UI workflow.

Working within form

How controls will work within <form> tag. What will happen on form submit. Does controls have proper name attribute, or it will be handled by script.

Find Control

How you can find control itself, its dependent elements. Control is a wide area collection of DOM elements belong to different parent nodes. They can be neighbors, or not.

Value

Control has a server value and visual value. It's common they are the same, but sometimes they differ.

Control states

Generally, control has states, which are:

  • Read only
  • Error
  • Editable
  • Printable
  • Sample

There are conversions between them. Each control is wide area collection of dom elements, so conversion of control states leads to understanding the way of control finding.

Events

There are many events which occurs in control workflow.

  • Save
  • Choose

They all must be easily described and handled.

Modularization

Control behavior, its transformation of states etc., must be described with single module

Sample conception

Control has a sample state, and has ability to understand some condition to become real instance of sample. Sample state : Sample is always invisible, sample is a source for clone

Mutual dependence of controls

Control's value and visibility depend on other controls. So, it intercepts onload event of page for first load and onchange event for value change.

Z Templater

Templates, generated by new templater have ability of atomic refresh of its parts on client side. This ability mainly defines a templater structure.

Template data

Generally, any kind of data from any source on server side will be transformed by server and placed into template. Each request-able part of template depends on some set of values of fields and expressions.

Minimal addressable part will be field of table and its address will be:

  • Table name
  • Primary key
  • Field name

There are several types of data in query:

  • Field of table
  • Expression, that are using several fields from different tables
  • Aggregate functions, like SUM, MAX, COUNT, etc.

It's clear, that only field of table can be updated to new value, and all of them could be requested. Nevertheless, expressions and aggregates couldn't be requested using table name. They must be requested with query contains them.

Field Expression Aggregate
query with pk filter * *
query with used fields only * * *
write to field with pk filter *
full query * * *

Template placeholders

{//full query
  'get' : 'query=Q1'
}
<div>
[[@div $Q1 
  address($Q1) : 
  SELECT 
    a.pk1, 
    a.pk2, 
    a.F1, 
    a.F1 + b.F3 as querysum, 
    MAX(a.F1) as maxx, 
    b.pk, b.F3 
  FROM T1 a JOIN T2 b ON a.rel=b.pk]]
  {//record
    'get' : 'query=Q1 & pk=pk1;pk2'
  }

Simple field:

  <p>
  [[Q1.a.F1
    address($Q1, $Q1.a.pk1, $Q1.a.pk2, $Q1.a.F1)
    depends($Q1.a.F1)
    origin(table'T1',$Q1.a.pk1, $Q1.a.pk2, $Q.a.F1)
    ->[] : $Q1.a.F1]]
  {    
    'put' : 'table=T1 & pk=pk1;pk2 & name=F1'
    'get' : 'query=Q1 & pk=pk1;pk2 & name=F1'
    'column' : 'query=Q1 & name=F1'
  }
  //[[Q1.a.F1 : $Q1.a.F1]]
  </p>

Field under relation:

  <p>
  [[Q1.b.F3
    address($Q1, $Q1.a.pk1, $Q1.a.pk2, $Q1.b.F3)
    depends($Q1.b.F3)
    origin(table'T2', $Q1.b.pk, $Q1.b.F3)
    ->[] : $Q1.b.F3]]
  {
    'put' : 'table=T2 & pk=pk & name=F3',
    'get' : 'query=Q1 & pk=pk1;pk2 & name=F3'
    'column' : 'query=Q1 & name=F3'
  }
  //[[Q1.b.F3 : $Q1.b.F3]]
  </p>

PHP expression:

  <p>
  [[Q1.localsum 
    address($Q1, $Q1.a.pk1, $Q1.a.pk2, $Q1.localsum)
    depends($Q1.a.F1, $Q1.b.F3)
    ->[] : $Q1.a.F1 + $Q1.b.F3]]
  {
    'get' : 'query=Q1 & pk=pk1;pk2 & name=localsum'
    'column' : 'query=Q1 & name=localsum'
  }
  //[[Q1.localsum : $Q1.a.F1 + $Q1.b.F3]]
  </p>

SQL expression:

  <p>
  [[Q1-:querysum 
    address($Q1, $Q1.a.pk1, $Q1.a.pk2, $Q1-:querysum)
    depends($Q1-:querysum)
    ->[] : $Q1-:querysum]]
  {
    'get' : 'query=Q1 & pk=pk1;pk2 & name=querysum'
    'column' : 'query=Q1 & name=querysum'
  }
  //[[Q1-:querysum : $Q1-:querysum ]]
  </p>
  //You can write any PHP code. End it with ';' otherwise it will become output
  [[$sum += $Q1.a.F1;]]

SQL aggregate:

  <p>
  [[Q1.maxx
    address($Q1, $Q1-:maxx)
    depends($Q1-:maxx)
    ->[] : $Q1-:maxx]]
  {
    'get' : 'query=Q1 & name=maxx'
    'column' : 'query=Q1 & name=maxx'
  }
  //[[Q1.maxx : $Q1-:maxx]]
  </p>

Subquery:

{//full query
  'get' : 'query=Q2 & parent=Q1.pk1;Q1.pk2'
}
<div>[[@div $Q2 
  address($Q1, $Q1.a.pk1, $Q1.a.pk2, $Q2) : 
  SELECT 
    a.pk1, 
    a.pk2, 
    a.H1, 
    a.H2, 
    a.H1+a.H2 as h1h2, 
    ext.F1+a.H1 as q1q2 
  FROM T3 WHERE ext.pk1=a.rel1 and ext.pk2=a.rel2]]
  {//record
    'get' : 'query=Q2 & parent=Q1.pk1;Q1.pk2 & pk=pk1;pk2'
  }
  <p>
  [[Q2.h1h2 
    address($Q2, $Q2.a.pk1, $Q2.a.pk2, $Q2-:h1h2)
    depends($Q2-:h1h2)
    ->[][] : $Q-:h1h2]]
  {
    'get' : 'query=Q2 & parent=Q1.pk1;Q1.pk2 & pk=pk1;pk2 & name=h1h2'
    'column' : 'query=Q2 & parent=Q1.pk1;Q1.pk2 & name=h1h2'
  }
  //[[Q2.h1h2 : $Q2-:h1h2]]
  </p>

Simple field in subquery depends on $Q2, its pk, and due $Q2 $Q1 dependence, it depends on $Q1 and its pk too.

  <p>
  [[Q2.a.H2 
    address($Q2, $Q2.a.pk1, $Q2.a.pk2, $Q2.a.H2)
    depends($Q2.a.H2) 
    origin(table'T3', $Q2.a.pk1, $Q2.a.pk2, $Q2.a.H2)
    ->[][] : $Q2.a.H2]]
  {
    'put' : 'table=T3 & pk=pk1;pk2 & name=H2',
    'get' : 'query=Q2 & parent=Q1.pk1;Q1.pk2 & pk=pk1;pk2 & name=H2'
    'column' : 'query=Q2 & parent=Q1.pk1;Q1.pk2 & name=H2'
  }
  //[[Q2.a.H2 : $Q2.a.H2]]
  </p>

Cross-query SQL expressions:

  <p>
  [[Q2.q1q2 
    address($Q2, $Q2.a.pk1, $Q2.a.pk2, $Q2-:q1q2)
    depends($Q2-:q1q2)
    ->[][] : $Q2-:q1q2]]
  {
    'get' : 'query=Q2 & parent=Q1.pk1;Q1.pk2 & pk=pk1;pk2 & name=q1q2'
    'column' : 'query=Q2 & parent=Q1.pk1;Q1.pk2 & name=q1q2'
  }
  //[[Q2.q1q2 : $Q2-:q1q2]]
  </p>

PHP expressions in subquery:

    <p>
    [[Q2.q1q2local
      address($Q2, $Q2.a.pk1, $Q2.a.pk2, $Q2.q1q2local)
      depends($Q1.a.F1, $Q2.a.H1)
      ->[][] : Q1.a.F1 + Q2.a.H1]]
    {
      'get' : 'query=Q2 & parent=Q1.pk1;Q1.pk2 & pk=pk1;pk2 & name=q1q2local'
      'column' : 'query=Q2 & parent=Q1.pk1;Q1.pk2 & name=q1q2local'
    }
    //[[Q2.q1q2local : Q1.a.F1 + Q2.a.H1]]  
    </p>

Sub-sub query:

{//full query
  'get' : 'query=Q3 & parent=Q1.pk1;Q1.pk2;Q2.pk1;Q2.pk2'
}
<div>
  [[@div $Q3 
    address($Q2, $Q2.a.pk1, $Q2.a.pk2, $Q3) 
    : SELECT a.pk,a.G1 FROM T4 WHERE ext.pk1=a.rel1 AND ext.pk2=a.rel2]]
  {//record
    'get' : 'query=Q3 & parent=Q2.pk1;Q2.pk2 & pk=pk'
  }
  <p>
  [[Q3.a.G1 
    address($Q3, $Q3.a.pk, $Q3.a.G1)
    depends($Q3.a.G1) 
    origin(table'T4', $Q3.a.pk, $Q3.a.G1)
    ->[][][] : $Q3.a.G1]]
  {
    'put' : 'table=T4 & pk=pk & name=G1'
    'get' : 'query=Q3 & parent=Q2.pk1;Q2.pk2 & pk=pk & name=G1'
    'column' : 'query=Q3 & parent=Q2.pk1;Q2.pk2 & name=G1'
  }
  </p>
</div>

Expression located inside loop will generate array and located out of loop, wont generate array, only variable.

  </div>
  [[growingsum
    address($Q1, $Q1.a.pk1, $Q1.a.pk2, growingsum)
    depends($Q1.a.F1)
    ->[] : $sum]]
  {
    'get' : 'query=Q1 & pk=pk1;pk2 & name=growingsum'
    'column' : 'query=Q1 & name=growingsum'
  } 
</div>
[[sum
  address(sum) 
  depends($Q1.a.F1)
  ->var : $sum]]
{
  'get' : 'name=sum'
}

Request resource

Resource address defines:

  • Where to get requested data on server side
  • Where to insert requested data on client side

Requested address is a subject to change by server, due server's exclusive knowledge of returned data source and structure. So, address of requested data sometimes is not equal to address of returned data and will be supplied with server response.

When you request resource, you must supply address information. When you request:

  • Q1 - all fields with Q1 in its address will be recalculated and sent back. Generally it means execution of query Q1.
  • Q1, Q1.pk - you trigger execution of query Q1 with filter on Q1.pk and all fields with corresponding addresses will be recalculated and sent back in appropriate structure.
  • Q1, Q1.a.F1, then all fields depending on Q1.a.F1 will be recalculated within query Q1 and field set of query will be defined by dependencies on Q1.a.F1
  • Q1, Q1.pk, Q1.a.F1, you trigger both of Q1.pk and Q1.a.F1 cases.

Note: When query $Q contains aggregate(of SQL or PHP source) field, any request of Q.pk leads to request of $Q otherwise value of aggregate will be wrong.

For subquery Q2, you must always supply Q1.pk. Further interactions with Q2 are similar to Q1. The same way for deeper queries - you must supply pks of higher level queries.

Address resolve

Address defines a tree-like structure of template data. So, when you request some branch, you will get its leafs. When you request root, you will get whole tree.

Generally, address defines single or a set of fields and their environment.

Address request subject filter
Field field
Query all content
Query, Primary key all content primary key
Query, Field field
Query, Primary key, Field field primary key
Parent, Query (subquery) all content parent key
etc.

Primary key in address adds filter to executable query.

Field defines field set in executable query.

Parent key is a primary key of parent query and will be added to subquery as a filter.

Dependency resolve

Each field may depend on other field. This dependence is defined by address of field it uses.

So, after address resolve, we get a list of fields, which we must extend with additional required fields. Then we must execute again address resolve to mark any unmarked used query executable, fill them with right filters and field sets.

Address transformation

Due aggregate fields existence, addresses like Query, Primary key must be transformed to Query, in other words filtering query with primary key leads to wrong result. So, address of result may be different to address of request and must be supplied with server response.

Resource response

Generated data structure depends on location relative to loops.

loop Q1 {
  Q1.output1
  Q1.output2
  loop Q2 {
    Q2.output3
    loop Q3 {
      Q3.output4
    }
  }
}
output5
var data = {
 Q1 : Array(
  output1, output2, Q2 : Array( output3, Q3 : Array(output4) )
 ),
 output5
}