Jump to content

CSS in BEM notation


Recommended Posts

  • Moderators
I never liked CSS much but it's difficult to ignore completely so I decided to give it more of a go.
 
About a year ago I posted a couple of bare-bones but usable components, see http://forums.smartmobilestudio.com/index.php?/topic/3710-some-more-simple-components/?hl=component .
These were largely unstyled though.
 
This post focuses on css styling in BEM notation, using the treeview example above.
 
For BEM notation explanation see f.i. https://css-tricks.com/bem-101/
Where it boils down to is that the notation of components (Blocks), its sub-components (Elements) and any variations of these elements (Modifiers) are separated in a parent-child relationship and have a distinct syntax. In this syntax Elements have a double underscore prefix and modifiers have a double hyphen prefix.
 
The Treeview component (Block) consists of a Panel (Element) as a container for the tree. This panel features one or more tree-branches (Sub-Elements) and each branch level can have a different appearance (Modifier).
 
The css structure, with some example properties, then looks like :

.TTreeView {                                //block
}
 
.TTreeView__Panel {                         //element
   border: 1px solid red;
   background-color: white;
   border-radius: 6px;
   box-shadow: 0 5px 10px rgba(93, 93, 93, 0.5);
}
 
.TTreeView__Panel__Branch {                 //sub-element
   font-size: 13px;
}
 
.TTreeView__Panel__Branch--Level1 {         //sub-element variation
   color: teal;
}
 
.TTreeView__Panel__Branch--Level2 {         //sub-element variation
   color: red;
}
 
.TTreeView__Panel__Branch--Level3 {         //sub-element variation
   color: purple;
}

 
 
The relationship with this styling in code is

on the component TTreeView level :

//  Self.CSSClasses.Add('TTreeView');     //superfluous as TTreeView (pascal) is auto-mapped to .TTreeView (css)

 
On the main Element level (Panel)

  Panel1 := TW3Panel.Create(self);
  Panel1.CSSClasses.Add('TTreeView__Panel');

 
On the sub-element / variation level :    // add the parent element + the variation itself

    Level1.Label := TW3Label.Create(Panel1);
    Level1.Label.CSSClasses.Add('TTreeView__Panel__Branch    TTreeView__Panel__Branch--Level1');  

etc
 
 
For completeness sake the modified treeview component code :


unit TreeView;
 
/******************************************************************************
LynkFS / Nico Wouterse 28 - 04 - 2016
 
The TreeView component consists of
- a W3Panel as the main container for the tree, dynamically resized
- a W3Label for the branches of the tree
- each branch has an expanded / not expanded icon, based on Font Awesome
- each lowest level branch can optionally execute a procedure when clicked
 
The component is by default styled according to the css theme of the project.
Styling can be modified by overriding the following css classes :
 
The objects CSS classes in BEM notation are
.TTreeView                               default empty
.TTreeView__Panel                        default W3Panel
.TTreeView__Panel__Branch                default W3Label
.TTreeView__Panel__Branch--Level1        default Panel__Branch
.TTreeView__Panel__Branch--Level2        default Panel__Branch
.TTreeView__Panel__Branch--Level3        default Panel__Branch
 
*/
 
interface
 
uses
  System.Types, SmartCL.System, SmartCL.Components, SmartCL.Controls.Panel,
  System.Colors, SmartCL.Application, SmartCL.Controls.Label, SmartCL.Fonts,
  SmartCL.FileUtils;
 
type
 
  TBranch3 = Class(TObject)
  public
    BranchStr : String;
    Label : TW3Label;
  end;
 
  TBranch2 = Class(TObject)
  public
    Branch3 : Array of TBranch3;
    BranchStr : String;
    Label : TW3Label;
    Expanded : Boolean;
  end;
 
  TBranch1 = Class(TObject)
  public
    Branch2 : Array of TBranch2;
    BranchStr : String;
    Label : TW3Label;
    Expanded : Boolean;
  end;
 
  TBranch0 = Class(TObject)
  public
    Branch1 : Array of TBranch1;
  end;
 
  TTreeView = class(TW3CustomControl)
  private
    Panel1 : TW3Panel;
    Level0 : TBranch0;
    Level1 : TBranch1;
    Level2 : TBranch2;
    Level3 : TBranch3;
  protected
    procedure InitializeObject; override;
    procedure FinalizeObject; override;
    procedure Resize; override;
    Procedure ShowTree;
  public
    Procedure AddBranch (Level: integer; Text: string; ClickProc: procedure);
  end;
 
implementation
 
{ TreeView }
 
procedure TTreeView.InitializeObject;
begin
  inherited;
//  Self.CSSClasses.Add('TTreeView');     //superfluous
 
  Panel1 := TW3Panel.Create(self);
  Panel1.CSSClasses.Add('TTreeView__Panel');
 
  Level3 := TBranch3.Create;
  Level2 := TBranch2.Create;
  Level1 := TBranch1.Create;
  Level0 := TBranch0.Create;
 
  Handle.ReadyExecute( procedure ()
    begin
      ShowTree;
    end);
end;
 
Procedure TTreeView.AddBranch (Level: integer; Text: string; ClickProc : procedure);
begin
//
  If Level = 1 then begin
    Level1.Destroy;
    Level1 := TBranch1.Create;
    Level1.BranchStr := Text;
    Level1.Label := TW3Label.Create(Panel1);
    Level1.Label.CSSClasses.Add('TTreeView__Panel__Branch TTreeView__Panel__Branch--Level1');
    Level1.Label.Caption := Text;
    Level1.Label.InnerHTML := #'<i class="fa fa-caret-square-o-right"></i> '+Level1.BranchStr;
    Level1.Expanded := false;
    Level1.Label.OnClick := procedure (Sender: TObject)
      begin
        For var i := 0 to Level0.Branch1.Count -1 do begin
          If Level0.Branch1[i].Label = Sender then begin
            If Level0.Branch1[i].Branch2.Count > 0
              then begin
                Level0.Branch1[i].Expanded :=
                  not Level0.Branch1[i].Expanded;
                If Level0.Branch1[i].Expanded = false then
                  Level0.Branch1[i].Label.InnerHTML := #'<i class="fa fa-caret-square-o-right"></i> '+Level0.Branch1[i].BranchStr
                else
                  Level0.Branch1[i].Label.InnerHTML := #'<i class="fa fa-caret-square-o-down"></i> '+Level0.Branch1[i].BranchStr;
                ShowTree;
              end else begin
                If assigned(ClickProc) then ClickProc;
              end;
          end;
        end;
      end;
 
    Level0.Branch1.Add(Level1);
  end;
//
  If Level = 2 then begin
    Level2.Destroy;
    Level2 := TBranch2.Create;
    Level2.BranchStr := Text;
    Level2.Label := TW3Label.Create(Panel1);
    Level2.Label.CSSClasses.Add('TTreeView__Panel__Branch TTreeView__Panel__Branch--Level2');
    Level2.Label.Caption := Text;
    Level2.Label.InnerHTML := #'<i class="fa fa-caret-square-o-right"></i> '+Level2.BranchStr;
    Level2.Expanded := false;
    Level2.Label.OnClick := procedure (Sender: TObject)
      begin
        For var i := 0 to Level0.Branch1.Count -1 do begin
          For var j := 0 to Level0.Branch1[i].Branch2.Count -1 do begin
            If Level0.Branch1[i].Branch2[j].Label = Sender then begin
              If Level0.Branch1[i].Branch2[j].Branch3.Count > 0 then begin
                Level0.Branch1[i].Branch2[j].Expanded :=
                  not Level0.Branch1[i].Branch2[j].Expanded;
                If Level0.Branch1[i].Branch2[j].Expanded = false then
                  Level0.Branch1[i].Branch2[j].Label.InnerHTML := #'<i class="fa fa-caret-square-o-right"></i> '+Level0.Branch1[i].Branch2[j].BranchStr
                else
                  Level0.Branch1[i].Branch2[j].Label.InnerHTML := #'<i class="fa fa-caret-square-o-down"></i> '+Level0.Branch1[i].Branch2[j].BranchStr;
                ShowTree;
              end else begin
                If assigned(ClickProc) then ClickProc;
              end;
            end;
          end;
        end;
      end;
    Level1.Branch2.Add(Level2);
  end;
//
  If Level = 3 then begin
    Level3.Destroy;
    Level3 := TBranch3.Create;
    Level3.BranchStr := Text;
    Level3.Label := TW3Label.Create(Panel1);
    Level3.Label.CSSClasses.Add('TTreeView__Panel__Branch TTreeView__Panel__Branch--Level3');
    Level3.Label.Caption := Text;
    Level3.Label.InnerHTML := #'<i class="fa fa-sign-out"></i> '+Level3.BranchStr;
    Level3.Label.OnClick := procedure (Sender: TObject)
      begin
        If assigned(ClickProc) then ClickProc;
      end;
    Level2.Branch3.Add(Level3);
  end;
 
end;
 
Procedure TTreeView.ShowTree;
var
  line : integer := 5;
  maxlen : integer := 0;
  mObj:TW3FontDetector;
  W : integer;
begin
  TW3Storage.LoadCSS('stylesheet',
    'http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css');
 
  mObj:=TW3FontDetector.Create;
 
  For var i := 0 to Level0.Branch1.Count -1 do begin
    For var j := 0 to Level0.Branch1[i].Branch2.Count -1 do begin
      Level0.Branch1[i].Branch2[j].Label.Left := application.display.Width;
      For var k := 0 to Level0.Branch1[i].Branch2[j].Branch3.Count -1 do begin
        Level0.Branch1[i].Branch2[j].Branch3[k].Label.Left := application.display.Width;
      end;
    end;
  end;
 
  For var i := 0 to Level0.Branch1.Count -1 do begin
    line := line + 22;
    Level0.Branch1[i].Label.SetBounds(5,line-22,self.width-5,22);
    W := mObj.MeasureText(mObj.getFontInfo(self.handle),
           Level0.Branch1[i].Label.Caption).tmWidth;
    if W > maxlen then maxlen := W;
 
    If Level0.Branch1[i].Branch2.Count = 0 then
      Level0.Branch1[i].Label.InnerHTML := #'<i class="fa fa-sign-out"></i> '+Level0.Branch1[i].BranchStr;
 
    If Level0.Branch1[i].Expanded = true then begin
      For var j := 0 to Level0.Branch1[i].Branch2.Count -1 do begin
        line := line + 22;
        Level0.Branch1[i].Branch2[j].Label.SetBounds(15,line-22,self.width-15,22);
        W := mObj.MeasureText(mObj.getFontInfo(self.handle),
             Level0.Branch1[i].Branch2[j].Label.Caption).tmWidth;
        if W > maxlen then maxlen := W;
 
        If Level0.Branch1[i].Branch2[j].Branch3.Count = 0 then
          Level0.Branch1[i].Branch2[j].Label.InnerHTML := #'<i class="fa fa-sign-out"></i> '+Level0.Branch1[i].Branch2[j].BranchStr;
 
        If Level0.Branch1[i].Branch2[j].Expanded = true then begin
          For var k := 0 to Level0.Branch1[i].Branch2[j].Branch3.Count -1 do begin
            line := line + 22;
            Level0.Branch1[i].Branch2[j].Branch3[k].Label.SetBounds(25,line-22,self.width-25,22);
            W := mObj.MeasureText(mObj.getFontInfo(self.handle),
                 Level0.Branch1[i].Branch2[j].Branch3[k].Label.Caption).tmWidth;
            if W > maxlen then maxlen := W;
          end;
        end;
      end;
    end;
  end;
 
  maxlen := maxlen + 15;
  Panel1.SetBounds(0,0,maxlen,line+5);
 
end;
 
procedure TTreeView.FinalizeObject;
begin
  Level0.Free;
  Level1.Free;
  Level2.Free;
  Level3.Free;
 
  Panel1.Free;
  Panel1 := nil;
 
  inherited;
end;
 
procedure TTreeView.Resize;
begin
  inherited;
//
end;
 
 
end.
 

 
 
 
and the calling code


unit Form1;
 
interface
 
uses
  SmartCL.System, SmartCL.Components, SmartCL.Forms, SmartCL.Application,
  TreeView;
 
type
  TForm1 = class(TW3Form)
  private
    {$I 'Form1:intf'}
  protected
    procedure InitializeForm; override;
    procedure InitializeObject; override;
    procedure Resize; override;
    Procedure ClickSalamander;
    Procedure ClickEagle;
    TreeView1 : TTreeView;
  end;
 
implementation
 
{ TForm1 }
 
procedure TForm1.InitializeForm;
begin
  inherited;
  // this is a good place to initialize components
end;
 
procedure TForm1.InitializeObject;
begin
  inherited;
  {$I 'Form1:impl'}
 
  TreeView1 := TTreeView.Create(self);
 
//
//  AddBranch parameters : level, text, clickproc
//
 
  TreeView1.AddBranch(1,'All reptiles in the world',nil);
    TreeView1.AddBranch(2,'Lizard family',nil);
      TreeView1.AddBranch(3,'Salamander',ClickSalamander);
    TreeView1.AddBranch(2,'Snake family',nil);
    TreeView1.AddBranch(2,'Bird family',nil);
      TreeView1.AddBranch(3,'Eagle',ClickEagle);
      TreeView1.AddBranch(3,'Canary',nil);
 
  TreeView1.AddBranch(1,'All mammals in the world',nil);
    TreeView1.AddBranch(2,'Equine family',nil);
      TreeView1.AddBranch(3,'Horse',nil);
      TreeView1.AddBranch(3,'Zebra',nil);
    TreeView1.AddBranch(2,'Bovine family',nil);
      TreeView1.AddBranch(3,'Cow',nil);
    TreeView1.AddBranch(2,'Canine family',nil);
      TreeView1.AddBranch(3,'Lassie',nil);
      TreeView1.AddBranch(3,'Rintintin',nil);
 
  TreeView1.AddBranch(1,'Other animals',nil);
    TreeView1.AddBranch(2,'ask Noah',nil);
 
  TreeView1.SetBounds(20,20,application.display.width-40,application.display.height-40);
end;
 
procedure TForm1.Resize;
begin
  inherited;
end;
 
procedure TForm1.ClickSalamander;
begin
  showmessage('Executing Salamander Proc');
end;
 
procedure TForm1.ClickEagle;
begin
  showmessage('Executing Eagle Proc');
end;
 
initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end.
 

 
.
Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...