帮酷LOGO
0 0 评论
  • 显示原文与译文双语对照的内容
文章标签:Contain  教程  COM  component  组件  ELM  
Tutorial on Container Components in Elm

  • 源代码名称:elm-checkerboardgrid-tutorial
  • 源代码网址:http://www.github.com/TheSeamau5/elm-checkerboardgrid-tutorial
  • elm-checkerboardgrid-tutorial源代码文档
  • elm-checkerboardgrid-tutorial源代码下载
  • Git URL:
    git://www.github.com/TheSeamau5/elm-checkerboardgrid-tutorial.git
  • Git Clone代码到本地:
    git clone http://www.github.com/TheSeamau5/elm-checkerboardgrid-tutorial
  • Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/TheSeamau5/elm-checkerboardgrid-tutorial
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
    
  • 棋盘网格教程

    在本教程中,我们将看到如何在Elm中创建一个方格网格,其中每个单元包含一个独立的,自更新的计数器。

    [Image of Checkerboard Grid of Counters] (/checkerboard )

    goal了解容器组件以及如何处理嵌套组件的布局和更新等问题,从而进一步了解 Elm体系结构

    在本教程中你会学到很多。 具体来说,你将学习:

    • Elm建筑的基础
    • 如何在 elm-html中制作简单的计数器组件
    • 如何制作网格组件
    • 地址的工作方式
    • 如何处理嵌套操作和更新
    • 什么是上下文
    • 如何在上下文之间转换以确保你的组件尽可能的通用

    要求

    本教程使用 ElmElm html。 虽然熟悉两者是首选的,但我将尽可能少地假定。 请查阅Elm文档,如果有什么混淆或者陌生的话。 尽管在开始本教程之前,先阅读 Elm体系结构插件,这一点很重要。 通过CSS的知识有帮助。

    因此,假设以下库:

    此外,假设以下导入

    importHtmlexposing (Html)importHtml.AttributesimportHtml.EventsimportSignalexposing (Address)importListimportWindowimportColorexposing (Color)

    以下 helper 代码有助于代码的可读性和可用性

    infixl 2=>(=>)=(,)type alias Vector={ x :Float, y :Float}toRgbaString:Color->StringtoRgbaString color =let {red, green, blue, alpha}=Color.toRgb color
     in"rgba("++ toString red ++", "++ toString green ++", "++ toString blue ++", "++ toString alpha ++")"

    最后,假设网格组件位于一个名为 Grid.elm的文件中,该文件定义 Grid 模块

    moduleGridwhere

    计数器组件驻留在一个名为 Counter.elm的文件中,该文件定义 Counter 模块

    moduleCounterwhere

    并且我们可以在一个名为 Main.elm的单独文件中尝试,以下导入

    importGridimportCounter

    建模问题

    我们知道我们的目标是有一个网格和一组计数器。 我们可以使用这些信息将我们的问题分成两个小问题: 网格和计数器。 计数器很简单所以我们从那个开始。

    计数器组件

    在基本级别上,使用 elm-html的组件可以建模如下:

    initial:Stateupdate:Action->State->Stateview:AddressAction->State->Html
    • initial 指组件开始时的初始状态。 对于计数器,初始状态为 0.
    • update 是一个函数,它根据某些操作更新组件的状态
    • view 是如何将组件视为 HTML。 Address 部分指的是,当单击或者停止时,UI将需要发送回复操作,因这里地址将允许这里事件。 我们稍后再回到这个点。

    计数器的状态非常简单,它只是一个整数

    type alias State=Int

    初始状态为 0

    initial:Stateinitial =0

    计数器可以是递增的或者递减

    type Action=Increment|Decrement

    给出了这些操作,我们可以按照如下方式更新计数器

    update:Action->State->Stateupdate action state =case action ofIncrement-> state +1Decrement-> state -1

    现在,要查看一个计数器,我们需要的是两个按钮和一些文本,当前的计数器值将

    view:AddressAction->State->Htmlview address state =Html.div
     [][Html.button -- The increment button[Html.Events.onClick address Increment][Html.text "+"],Html.button -- The decrement button [Html.Events.onClick address Decrement][Html.text "-"],Html.span -- The text with the current counter value[][Html.text (toString state)]]

    注意地址:

    Address 定义将发送操作的位置。 例如直线

    Html.Events.onClick address Increment

    当单击按钮时,Address 将被发送操作 Increment

    地址只是邮箱的一部分。

    type alias Mailbox a ={ address :Address a
     , signal :Signal a
     }

    而且,每当将消息发送到地址时,相应的信号就会被更新。

    就是这样,这个组件完全定义了。 我们所需要的是两个函数和两个类型定义。

    另外,如果你想尝试这个组件,你可以使用 StartApp,并添加导入行。

    importStartApp

    然后,在中,你可以说,

    main =StartApp.start
     { model = initial
     , view = view
     , update = update
     }

    ,你有一个工作柜台。

    网格组件

    现在我们有了一个工作台面,让我们来做一个网格。 计数器不同,网格主要是用来容纳多个组件。 因这里,在定义网格时,我们需要尽可以能的一般,从而对网格中的组件类型进行零假设。

    网格工作的方式是它是一个单元格集合,所有相等的维度。 这些细胞水平排列和垂直形成行列,如在棋盘中。

    所以,首先,网格的状态。 我们知道网格将有一个子状态的列表,( 在我们的例子中计数器状态)。 结果,你可以使用以下信息完全定义网格:

    • 单元格高度
    • 列数
    • 网格宽度

    因此,网格的状态可以表示为:

    type alias State childState ={ children :List childState
     , cellHeight :Float, numCols :Int, gridWidth :Float}

    对于动作,我们知道每个子元素都将是独立的,因这里我们需要以某种方式识别动作。 我们知道每个子元素在列表中都是不同的索引,所以我们可以使用索引来标识这个操作

    type Action childAction
     =ChildActionInt childAction

    至于更新,我们需要采用函数来将子组件更新为输入,以便适当地使用它。

    update: (childAction->childState->childState) ->ActionchildAction->StatechildState->StatechildStateupdate updateChild action state =case action ofChildAction n childAction ->let-- We only update if the index of the child state matches that of the action updateN index childState =if n == index
     then updateChild childAction childState
     else childState
     in{ state | children <-List.indexedMap updateN state.children }

    同样,对于视图,我们需要将子组件视为输入来查看子组件以便适当地查看子组件

    view: (AddresschildAction->childState->Html) ->Address (ActionchildAction) ->StatechildState->Htmlview viewChild address state =let-- Get the dimensions of the grid gridDims :Vector gridDims = gridSize state
     -- Get the dimensions of an individual cell cellDims :Vector cellDims = cellSize state
     -- The CSS styles for the grid containerStyle =["position"=>"absolute","top"=>"0px","left"=>"0px","width"=> toString gridDims.x ++"px","height"=> toString gridDims.y ++"px"]-- Function to view an individual cell at a given index-- viewN : Int -> childState -> Html viewN index childState =let-- The left or x-position of the cell left = cellDims.x * toFloat (index % state.numCols)-- The top or y-position of the cell top = cellDims.y * toFloat (index // state.numCols)-- The CSS styles for the cell-- Hint: Try adding a border here to see the cell childContainerStyle = 
     ["position"=>"absolute","left"=> toString left ++"px","top"=> toString top ++"px","width"=> toString cellDims.x ++"px","height"=> toString cellDims.y ++"px"]-- Make a forwarding address for the child at the given index childAddress =Signal.forwardTo address (ChildAction index)in-- We simply wrap the child in an container divHtml.div
     [Html.Attributes.style childContainerStyle ][ viewChild childAddress childState ]in-- Wrap the whole thing in a div-- And view each child with the `viewN` function defined 上面Html.div
     [Html.Attributes.style containerStyle ](List.indexedMap viewN state.children )

    helper 函数 gridSizecellSize的定义如下:

    -- Get the size of a gridgridSize:StatechildState->VectorgridSize state =let numChildren =List.length state.children
     numRows = numChildren // state.numCols
     gridHeight = state.cellheight *(toFloat numRows)in{ x = state.gridWidth
     , y = gridHeight
     }-- Get the size of each individual cell of a gridcellSize:StatechildState->VectorcellSize state ={ x = state.gridWidth /(toFloat state.numCols), y = state.cellHeight
     }

    注意地址:

    由于每个个人组件都需要一个地址,但我们只给出一个地址,从我们给定地址的地址转发。

    为此,我们使用 Signal.forwardTo 函数,该函数具有以下签名

    Signal.forwardTo :Address a ->(b -> a)->Address b

    就是这样,我们定义了网格组件。 我们有自己的状态。行动。视图和更新。

    让我们试试。我们需要做的就是初始化我们的网格,我们已经完成了。

    importStartAppimportGridimportCounterimportListinitial:Grid.StateCounter.Stateinitial ={ children =List.repeat 64Counter.initial
     , cellHeight =50, numCols =8, gridWidth =400}main =StartApp.start
     { model = initial
     , update =Grid.update Counter.update
     , view =Grid.view Counter.view
     }

    修改我们的应用程序

    现在我们有了网格和计数器工作,我们可以尝试修改它们。 最初我们希望网格看起来像一个棋盘。 这意味着我们需要设置单个网格单元的background 颜色。 这里外,如果我们离开单个单元格的文本 black,文本将不会出现在 black background 中。 因此,我们需要修改每个计数器的文本颜色。

    为此我们需要一些上下文。

    命令行上下文

    上下文是我们传递给视图的附加参数,它包含与组件显示相关的信息。 如果要知道计数器有多少空间,我们需要计数器,我们需要了解和文本颜色。

    type alias Context={ viewport :Vector, textColor :Color, backgroundColor :Color 
     }

    从这里,我们需要修改计数器的视图函数,使它的具有这里类型签名:

    view:AddressAction->State->Html

    对于这里类型签名:

    view:Context->AddressAction->State->Html

    现在我们有了新的类型签名,我们可以修改视图函数的实现:

    -- The counter will have three parts-- The top third will be the increment button-- The middle third will be the text-- The bottom third will be the decrement buttonview:Context->AddressAction->State->Htmlview context address state =let-- The font size depends on the viewport-- Responsive design for the win fontSize =(min context.viewport.x context.viewport.y)/3-- The width of each section width = context.viewport.x
     -- The height of the viewport height = context.viewport.y
     -- The height of each section sectionHeight = height /3-- The CSS for the container containerStyle =["position"=>"absolute","top"=>"0px","left"=>"0px","width"=> toString width ++"px","height"=> toString height ++"px","background-color"=> toRgbaString context.backgroundColor
     ]-- The CSS for the increment button incrementButtonStyle =["position"=>"absolute","top"=>"0px","left"=>"0px","width"=> toString width ++"px","height"=> toString sectionHeight ++"px","color"=> toRgbaString context.textColor
     ,"cursor"=>"pointer","font-size"=> toString fontSize ++"px","text-align"=>"center","-webkit-user-select"=>"none"]-- The CSS for the decrement button decrementButtonStyle =["position"=>"absolute","top"=> toString (2* sectionHeight)++"px","left"=>"0px","width"=> toString width ++"px","height"=> toString sectionHeight ++"px","color"=> toRgbaString context.textColor
     ,"cursor"=>"pointer","font-size"=> toString fontSize ++"px","text-align"=>"center","-webkit-user-select"=>"none"]-- The CSS for the text textStyle =["position"=>"absolute","top"=> toString sectionHeight ++"px","left"=>"0px","width"=> toString width ++"px","height"=> toString sectionHeight ++"px","color"=> toRgbaString context.textColor
     ,"font-size"=> toString fontSize ++"px","text-align"=>"center"]inHtml.div
     [Html.Attributes.style containerStyle ][Html.div -- We're changing buttons to divs for aesthetics reasons[Html.Events.onClick address Increment,Html.Attributes.style incrementButtonStyle
     ][Html.text "+"],Html.div
     [Html.Events.onClick address Decrement,Html.Attributes.style decrementButtonStyle
     ][Html.text "-"],Html.span
     [Html.Attributes.style textStyle ][Html.text (toString state)]]

    可以看到,代码的大部分都是专门用于CSS的。 视图功能的实际内容基本上没有变化。 注意,由于美观原因,我已经将按钮改为 div。 考虑不要在现实生活中做这样的事情,因为按钮是预先打包好。

    现在我们已经做到了,我们需要修改网格的视图功能。 首先,由于新引入的上下文,这甚至不会编译。 因此,网格函数的视图将需要动态生成上下文。 也就是说,网格不应该明确地生成精确的上下文。 好的网格的目标是尽可能保持一般的状态。 因此,网格将产生自己的上下文,我们将把它转换为计数器的上下文。

    从查看角度来看,网格只需告诉一个单元格的行和列,以及单元格视口的大小。

    type alias Context={ viewport :Vector, row :Int, column :Int}

    我们可以从网格状态生成,如下所示:

    generateContext:Int->State->ContextgenerateContext index state =let column = index % state.numCols
     row = index // state.numCols
     viewport = cellSize state
     in{ viewport = viewport
     , row = row
     , column = column
     }

    现在,我们需要在两个上下文之间转换函数。

    -- Convert a grid context to a counter contexttoCounterContext:Grid.Context->Counter.ContexttoCounterContext gridContext =let isBlack =(gridContext.row %2==0)==(gridContext.column %2==0)(textColor, backgroundColor)=if isBlack
     then(Color.white,Color.black)else(Color.black,Color.white)in{ viewport = gridContext.viewport
     , textColor = textColor
     , backgroundColor = backgroundColor
     }

    现在我们可以修改网格组件视图函数的代码来处理上下文。

    具体而言,我们需要更改类型签名:

    view: (AddresschildAction->childState->Html) ->Address (ActionchildAction) ->StatechildState->Html

    到以下类型签名:

    view: (Context->AddresschildAction->childState->Html) ->Address (ActionchildAction) ->StatechildState->Html

    我们可以按如下方式实现视图功能:

    view: (Context->AddresschildAction->childState->Html) ->Address (ActionchildAction) ->StatechildState->Htmlview viewChild address state =let-- Get the dimensions of the grid gridDims :Vector gridDims = gridSize state
     -- Get the dimensions of an individual cell cellDims :Vector cellDims = cellSize state
     -- The CSS styles for the grid containerStyle =["position"=>"absolute","top"=>"0px","left"=>"0px","width"=> toString gridDims.x ++"px","height"=> toString gridDims.y ++"px"]-- Function to view an individual cell at a given index-- viewN : Int -> childState -> Html viewN index childState =let-- The left or x-position of the cell left = cellDims.x * toFloat (index % state.numCols)-- The top or y-position of the cell top = cellDims.y * toFloat (index // state.numCols)-- The CSS styles for the cell-- Hint: Try adding a border here to see the cell childContainerStyle =["position"=>"absolute","left"=> toString left ++"px","top"=> toString top ++"px","width"=> toString cellDims.x ++"px","height"=> toString cellDims.y ++"px"]-- Make a forwarding address for the child at the given index childAddress =Signal.forwardTo address (ChildAction index)-- We generate our context here context = generateContext index state
     in-- We simply wrap the child in an container divHtml.div
     [Html.Attributes.style childContainerStyle ][ viewChild context childAddress childState ]-- And we simply pass the child context as a parameterin-- Wrap the whole thing in a div-- And view each child with the `viewN` function defined 上面Html.div
     [Html.Attributes.style containerStyle ](List.indexedMap viewN state.children )

    现在,如果要查看更改,只需按如下所示修改 main:

    importStartAppimportGridimportCounterimportListimportColorinitial:Grid.StateCounter.Stateinitial ={ children =List.repeat 64Counter.initial
     , cellHeight =50, numCols =8, gridWidth =400}-- As implemented 上面toCounterContext:Grid.Context->Counter.ContexttoCounterContext gridContext =...main =StartApp.start
     { model = initial
     , update =Grid.update Counter.update
     , view =Grid.view (toCounterContext >>Counter.view)}-- Note that converting contexts is just a matter of function composition

    还有,我们有一个棋盘格,每个部件都是独立的。

    的结论与进一步探索

    希望本教程能够为你提供如何在tmodel中使用组件以及修改代码的容易性。 Elm可以让编写简单。可以扩展。可以维护的代码变得非常容易,本教程尝试在可以维护性和可以扩展性。

    现在你知道如何在组件中制作一个网格和插头,为什么不尝试制作自己的组件。 如果你希望调整网格大小的能力? 如果要将网格放置在另一个网格中,请执行下列操作:? 在那里需要做什么修改?



    文章标签:COM  component  教程  Contain  组件  ELM  

    Copyright © 2011 HelpLib All rights reserved.    知识分享协议 京ICP备05059198号-3  |  如果智培  |  酷兔英语