浏览代码

* Add extjsdataset

michael 7 年之前
父节点
当前提交
e24a9c6b04
共有 2 个文件被更改,包括 365 次插入1 次删除
  1. 364 0
      packages/fcl-db/extjsdataset.pas
  2. 1 1
      packages/fcl-db/pas2js_fcldb.lpk

+ 364 - 0
packages/fcl-db/extjsdataset.pas

@@ -0,0 +1,364 @@
+unit ExtJSDataset;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, db, JS, jsondataset;
+
+type
+  { TExtJSJSONDataSet }
+
+  // Base for ExtJS datasets. It handles MetaData conversion.
+  TExtJSJSONDataSet = Class(TBaseJSONDataset)
+  Private
+    FFields : TJSArray;
+    FIDField: String;
+    FRoot: String;
+  Protected
+    // Data proxy support
+    Procedure InternalOpen; override;
+    function DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean; override;
+    Function DataPacketReceived(ARequest: TDataRequest) : Boolean; override;
+    Function GenerateMetaData : TJSObject;
+    function ConvertDateFormat(S: String): String; virtual;
+    Procedure MetaDataToFieldDefs; override;
+    procedure InitDateTimeFields; override;
+    function StringToFieldType(S: String): TFieldType;virtual;
+    function GetStringFieldLength(F: TJSObject; AName: String; AIndex: Integer): integer; virtual;
+  Public
+    // Can be set directly if the dataset is closed.
+    Property MetaData;
+    // Can be set directly if the dataset is closed. If metadata is set, it must match the data.
+    Property Rows;
+    // Root of data array in data packet
+    property Root : String Read FRoot Write FRoot;
+    // property IDField
+    property IDField : String Read FIDField Write FIDField;
+  end;
+
+  { TExtJSJSONObjectDataSet }
+  // Use this dataset for data where the data is an array of objects.
+  TExtJSJSONObjectDataSet = Class(TExtJSJSONDataSet)
+  Protected
+    Function CreateFieldMapper : TJSONFieldMapper; override;
+  end;
+
+  { TExtJSJSONArrayDataSet }
+  // Use this dataset for data where the data is an array of arrays.
+  TExtJSJSONArrayDataSet = Class(TExtJSJSONDataSet)
+  Protected
+    Function CreateFieldMapper : TJSONFieldMapper; override;
+  end;
+
+implementation
+
+{ TExtJSJSONDataSet }
+
+Function  TExtJSJSONDataSet.StringToFieldType(S : String) : TFieldType;
+
+begin
+  if (s='int') then
+    Result:=ftLargeInt
+  else if (s='float') then
+    Result:=ftFloat
+  else if (s='boolean') then
+    Result:=ftBoolean
+  else if (s='date') then
+    Result:=ftDateTime
+  else if (s='string') or (s='auto') or (s='') then
+    Result:=ftString
+  else
+    if MapUnknownToStringType then
+      Result:=ftString
+    else
+      Raise EJSONDataset.CreateFmt('Unknown JSON data type : %s',[s]);
+end;
+
+Function  TExtJSJSONDataSet.GetStringFieldLength(F : TJSObject; AName : String; AIndex : Integer) : integer;
+
+Var
+  I,L : Integer;
+  D : JSValue;
+
+begin
+  Result:=0;
+  D:=F.Properties['maxlen'];
+  if Not jsIsNan(toNumber(D)) then
+    begin
+    Result:=Trunc(toNumber(D));
+    if (Result<=0) then
+      Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s',[AName])
+    end
+  else
+    begin
+    For I:=0 to Rows.Length-1 do
+      begin
+      D:=FieldMapper.GetJSONDataForField(Aname,AIndex,Rows[i]);
+      if isString(D) then
+        begin
+        l:=Length(String(D));
+        if L>Result then
+          Result:=L;
+        end;
+      end;
+    end;
+  if (Result=0) then
+    Result:=20;
+end;
+
+procedure TExtJSJSONDataSet.MetaDataToFieldDefs;
+
+Var
+  A : TJSArray;
+  F : TJSObject;
+  I,J,FS : Integer;
+  N,idf : String;
+  ft: TFieldType;
+  D : JSValue;
+
+begin
+  FieldDefs.Clear;
+  D:=Metadata.Properties['fields'];
+  if Not IsArray(D) then
+    Raise EJSONDataset.Create('Invalid metadata object');
+  A:=TJSArray(D);
+  For I:=0 to A.Length-1 do
+    begin
+    If Not isObject(A[i]) then
+      Raise EJSONDataset.CreateFmt('Field definition %d in metadata is not an object',[i]);
+    F:=TJSObject(A[i]);
+    D:=F.Properties['name'];
+    If Not isString(D) then
+      Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
+    N:=String(D);
+    D:=F.Properties['type'];
+    If IsNull(D) or isUndefined(D) then
+      ft:=ftstring
+    else If Not isString(D) then
+      begin
+      Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
+      end
+    else
+      begin
+      ft:=StringToFieldType(String(D));
+      end;
+    if (ft=ftString) then
+      fs:=GetStringFieldLength(F,N,I)
+    else
+      fs:=0;
+    FieldDefs.Add(N,ft,fs);
+    end;
+  FFields:=A;
+end;
+
+procedure TExtJSJSONDataSet.InternalOpen;
+
+Var
+  I : integer;
+
+begin
+  inherited InternalOpen;
+  Writeln('Checking ID field ',IDField, ' as key field');
+  for I:=0 to Fields.Count-1 do
+    If SameText(Fields[i].FieldName,IDField) then
+      begin
+      Fields[i].ProviderFlags:=Fields[i].ProviderFlags+[pfInKey];
+      Writeln('Setting ID field ',IDField, ' as key field');
+      end;
+end;
+
+function TExtJSJSONDataSet.DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean;
+
+Var
+  D : JSValue;
+  O : TJSObject;
+  A : TJSArray;
+  I,RecordIndex : Integer;
+  FN : String;
+
+begin
+  Result:=True;
+  if anUpdate.OriginalStatus=usDeleted then
+    exit;
+  D:=anUpdate.ServerData;
+  If isNull(D) then
+    exit;
+  if not isNumber(AnUpdate.Bookmark.Data) then
+    exit(False);
+  RecordIndex:=Integer(AnUpdate.Bookmark.Data);
+  If isString(D) then
+    O:=TJSOBject(TJSJSON.Parse(String(D)))
+  else if isObject(D) then
+    O:=TJSOBject(D)
+  else
+    Exit(False);
+  if Not isArray(O[Root]) then
+    exit(False)
+  A:=TJSArray(O[Root]);
+  If A.Length=1 then
+    begin
+    O:=TJSObject(A[0]);
+    For I:=0 to Fields.Count-1 do
+      begin
+      if O.hasOwnProperty(Fields[i].FieldName) then
+        FieldMapper.SetJSONDataForField(Fields[i],Rows[RecordIndex],O[FN]);
+      end;
+    end;
+end;
+
+function TExtJSJSONDataSet.DataPacketReceived(ARequest: TDataRequest): Boolean;
+
+Var
+  O : TJSObject;
+  A : TJSArray;
+
+begin
+  Result:=False;
+  If isNull(aRequest.Data) then
+    exit;
+  If isString(aRequest.Data) then
+    O:=TJSOBject(TJSJSON.Parse(String(aRequest.Data)))
+  else if isObject(aRequest.Data) then
+    O:=TJSOBject(aRequest.Data)
+  else
+    DatabaseError('Cannot handle data packet');
+  if (Root='') then
+    root:='rows';
+  if (IDField='') then
+    idField:='id';
+  if O.hasOwnProperty('metaData') and isObject(o['metaData']) then
+    begin
+    if not Active then // Load fields from metadata
+      metaData:=TJSObject(o['metaData']);
+    // We must always check this one...
+    if metaData.hasOwnProperty('root') and isString(metaData['root']) then
+      Root:=string(metaData['root']);
+    if metaData.hasOwnProperty('idField') and isString(metaData['idField']) then
+      IDField:=string(metaData['idField']);
+    end;
+  if O.hasOwnProperty(Root) and isArray(o[Root]) then
+    begin
+    A:=TJSArray(o[Root]);
+    Result:=A.Length>0;
+    AddToRows(A);
+    end;
+end;
+
+function TExtJSJSONDataSet.GenerateMetaData: TJSObject;
+
+Var
+  F : TJSArray;
+  O : TJSObject;
+  I,M : Integer;
+  T : STring;
+
+begin
+  Result:=TJSObject.New;
+  F:=TJSArray.New;
+  Result.Properties['fields']:=F;
+  For I:=0 to FieldDefs.Count -1 do
+    begin
+    O:=New(['name',FieldDefs[i].name]);
+    F.push(O);
+    M:=0;
+    case FieldDefs[i].DataType of
+      ftfixedchar,
+      ftString:
+        begin
+        T:='string';
+        M:=FieldDefs[i].Size;
+        end;
+      ftBoolean: T:='boolean';
+      ftDate,
+      ftTime,
+      ftDateTime: T:='date';
+      ftFloat: t:='float';
+      ftInteger,
+      ftAutoInc,
+      ftLargeInt : t:='int';
+    else
+      Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[Ord(FieldDefs[i].DataType)]);
+    end; // case
+    O.Properties['type']:=t;
+    if M<>0 then
+      O.Properties['maxlen']:=M;
+    end;
+  Result.Properties['root']:='rows';
+end;
+
+Function TExtJSJSONDataSet.ConvertDateFormat(S : String) : String;
+
+{ Not handled: N S w z W t L o O P T Z c U MS }
+
+begin
+  Result:=StringReplace(S,'y','yy',[rfReplaceall]);
+  Result:=StringReplace(Result,'Y','yyyy',[rfReplaceall]);
+  Result:=StringReplace(Result,'g','h',[rfReplaceall]);
+  Result:=StringReplace(Result,'G','hh',[rfReplaceall]);
+  Result:=StringReplace(Result,'F','mmmm',[rfReplaceall]);
+  Result:=StringReplace(Result,'M','mmm',[rfReplaceall]);
+  Result:=StringReplace(Result,'n','m',[rfReplaceall]);
+  Result:=StringReplace(Result,'D','ddd',[rfReplaceall]);
+  Result:=StringReplace(Result,'j','d',[rfReplaceall]);
+  Result:=StringReplace(Result,'l','dddd',[rfReplaceall]);
+  Result:=StringReplace(Result,'i','nn',[rfReplaceall]);
+  Result:=StringReplace(Result,'u','zzz',[rfReplaceall]);
+  Result:=StringReplace(Result,'a','am/pm',[rfReplaceall,rfIgnoreCase]);
+  Result:=LowerCase(Result);
+end;
+
+procedure TExtJSJSONDataSet.InitDateTimeFields;
+
+Var
+  F : TJSObject;
+  FF : TField;
+  I,J : Integer;
+  Fmt : String;
+  D : JSValue;
+
+begin
+  If (FFields=Nil) then
+    Exit;
+  For I:=0 to FFields.Length-1 do
+    begin
+    F:=TJSObject(FFields[i]);
+    D:=F.Properties['type'];
+    if isString(D) and (String(D)='date') then
+      begin
+      D:=F.Properties['dateFormat'];
+      if isString(D) then
+         begin
+         FMT:=ConvertDateFormat(String(D));
+         FF:=FindField(String(F.Properties['name']));
+         if (FF<>Nil) and (FF.DataType in [ftDate,ftTime,ftDateTime]) and (FF.FieldKind=fkData) then
+           begin
+           if FF is TJSONDateField then
+             TJSONDateField(FF).DateFormat:=Fmt
+           else if FF is TJSONTimeField then
+             TJSONTimeField(FF).TimeFormat:=Fmt
+           else if FF is TJSONDateTimeField then
+             TJSONDateTimeField(FF).DateTimeFormat:=Fmt;
+           end;
+         end;
+      end;
+    end;
+end;
+
+{ TExtJSJSONArrayDataSet }
+
+function TExtJSJSONArrayDataSet.CreateFieldMapper: TJSONFieldMapper;
+begin
+  Result:=TJSONArrayFieldMapper.Create;
+end;
+
+{ TExtJSJSONObjectDataSet }
+
+function TExtJSJSONObjectDataSet.CreateFieldMapper: TJSONFieldMapper;
+begin
+  Result:=TJSONObjectFieldMapper.Create;
+end;
+
+end.
+

+ 1 - 1
packages/fcl-db/pas2js_fcldb.lpk

@@ -35,7 +35,7 @@
         <UnitName Value="RestConnection"/>
       </Item4>
       <Item5>
-        <Filename Value="extjsdataset.pp"/>
+        <Filename Value="extjsdataset.pas"/>
         <UnitName Value="extjsdataset"/>
       </Item5>
     </Files>