Skip to content
This repository
Browse code

[feature] widgets: added a bootstrap widget for generating bootstrapp…

…ed HTML chunks
  • Loading branch information...
commit 660d902a2328c36f4994f3746f5087af4d05901a 1 parent d686276
Frederic Ye authored October 25, 2011

Showing 1 changed file with 605 additions and 0 deletions. Show diff stats Hide diff stats

  1. 605  stdlib/widgets/bootstrap/bootstrap.opa
605  stdlib/widgets/bootstrap/bootstrap.opa
... ...
@@ -0,0 +1,605 @@
  1
+/*
  2
+    Copyright © 2011 MLstate
  3
+
  4
+    This file is part of OPA.
  5
+
  6
+    OPA is free software: you can redistribute it and/or modify it under the
  7
+    terms of the GNU Affero General Public License, version 3, as published by
  8
+    the Free Software Foundation.
  9
+
  10
+    OPA is distributed in the hope that it will be useful, but WITHOUT ANY
  11
+    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  12
+    FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
  13
+    more details.
  14
+
  15
+    You should have received a copy of the GNU Affero General Public License
  16
+    along with OPA.  If not, see <http://www.gnu.org/licenses/>.
  17
+*/
  18
+
  19
+/**
  20
+ * Bootstrapped widgets library.
  21
+ *
  22
+ * @author Frederic Ye 2010
  23
+ * @category WIDGET
  24
+ * @destination PUBLIC
  25
+ * @stability STABLE
  26
+ * @version 0.9
  27
+ */
  28
+
  29
+/**
  30
+ * {1 About this module}
  31
+ *
  32
+ * This module is for generating Bootstrapped HTML chunks.
  33
+ *
  34
+ * {1 Where should I start?}
  35
+ *
  36
+ * You can find documentation and examples at http://bootstrap.opalang.org
  37
+ *
  38
+ * {1 TODO}
  39
+ *
  40
+ * - Forms
  41
+ * - JS
  42
+ */
  43
+
  44
+package stdlib.widgets.bootstrap
  45
+
  46
+/**
  47
+ * {1 Types defined in this module}
  48
+ */
  49
+
  50
+type WBootstrap.Grid.column =
  51
+  { span: int // 1-16
  52
+    offset: option(int) // 1-16
  53
+    content: xhtml } /
  54
+  { third: int // 1-2
  55
+    offset: option(int) // 1-2
  56
+    content: xhtml }
  57
+
  58
+type WBootstrap.List.description =
  59
+  {title: xhtml} /
  60
+  {description: xhtml}
  61
+
  62
+type WBootstrap.Media.media = {
  63
+  href: option(string)
  64
+  onclick: Dom.event -> void
  65
+  content: xhtml
  66
+}
  67
+
  68
+type WBootstrap.Navigation.elt =
  69
+  {active: xhtml onclick: (Dom.event -> void) href: option(string)} /
  70
+  {inactive: xhtml onclick: (Dom.event -> void) href: option(string)} /
  71
+  {disabled: xhtml onclick: (Dom.event -> void) href: option(string)} /
  72
+  {custom_li: xhtml} /
  73
+  {divider}
  74
+
  75
+type WBootstrap.Table.elt = xhtml
  76
+
  77
+type WBootstrap.Navigation.page_nav_elt = WBootstrap.Media.media
  78
+
  79
+type WBootstrap.Button.size =
  80
+  {normal} / {small} / {large}
  81
+
  82
+type WBootstrap.Message.content = {
  83
+  title: string
  84
+  description: xhtml
  85
+}
  86
+
  87
+WBootstrap = {{
  88
+
  89
+  /**
  90
+   * Add a title attribute to an xhtml chunk.
  91
+   * No verification on wether the xhtml supports title attribute
  92
+   */
  93
+  add_title(t:string, x:xhtml) = Xhtml.add_attribute("title", t, x)
  94
+
  95
+  /**
  96
+   * Add/Update ths class attribute of an xhtml chunk, by appending a certain class.
  97
+   * No verification on wether the xhtml supports class attribute
  98
+   */
  99
+  update_class(c:string, x:xhtml) = Xhtml.update_attribute("class", c, x)
  100
+
  101
+  /**
  102
+   * Add a pull-right class to an xhtml chunk
  103
+   */
  104
+  pull_right(x:xhtml) = update_class("pull-right", x)
  105
+
  106
+  /**
  107
+   * Add an optional href attribute to an xhtml
  108
+   */
  109
+  @private add_href_opt(href:option(string), x:xhtml) =
  110
+    match href
  111
+    {some=h} -> Xhtml.add_attribute("href", h, x)
  112
+    {none} -> x
  113
+
  114
+  Grid = {{
  115
+
  116
+    /**
  117
+     * Create a row
  118
+     *
  119
+     * @param columns a list of WBootstrap.Grid.column
  120
+     * @see WBootstrap.Grid.column for column restrictions
  121
+     */
  122
+    row(columns:list(WBootstrap.Grid.column)) =
  123
+      <div>{
  124
+        @toplevel.List.map(column ->
  125
+          match column
  126
+          ~{span offset content} ->
  127
+            offset = match offset
  128
+                     {some=ofs} -> " offset{ofs}"
  129
+                     {none} -> ""
  130
+            <div class="span{span}{offset}">{content}</div>
  131
+          ~{third offset content} ->
  132
+            offset = match offset
  133
+                     {some=1} -> " offset-one-third"
  134
+                     {some=2} -> " offset-two-thirds"
  135
+                     _ -> ""
  136
+            match third with
  137
+            1 -> <div class="span-one-third{offset}">{content}</div>
  138
+            2 -> <div class="span-two-thirds{offset}">{content}</div>
  139
+            _ -> <></>
  140
+        , columns)
  141
+      }</div> |> update_class("row", _)
  142
+
  143
+  }}
  144
+
  145
+  Layout = {{
  146
+
  147
+    /**
  148
+     * Create a fixed Layout
  149
+     */
  150
+    fixed(content:xhtml) =
  151
+      Div.container(content)
  152
+
  153
+    /**
  154
+     * Create a fluid Layout
  155
+     */
  156
+    fluid(sidebar:xhtml, content:xhtml) =
  157
+      Div.container_fluid(
  158
+        <div class="sidebar">{sidebar}</div>
  159
+        <div class="content">{content}</div>
  160
+      )
  161
+
  162
+  }}
  163
+
  164
+  Typography = {{
  165
+
  166
+    /**
  167
+     * Create a header, with an optional sub-header
  168
+     * @param level 1 <= level <= 6
  169
+     */
  170
+    header(level:int, sub_content:option(xhtml), content:xhtml) =
  171
+      sub = match sub_content
  172
+            {some=s} -> <>{" "}</><small>{s}</small>
  173
+            {none} -> <></>
  174
+            end
  175
+      match level
  176
+      1 -> <h1>{content}{sub}</h1>
  177
+      2 -> <h2>{content}{sub}</h2>
  178
+      3 -> <h3>{content}{sub}</h3>
  179
+      4 -> <h4>{content}{sub}</h4>
  180
+      5 -> <h5>{content}{sub}</h5>
  181
+      6 -> <h6>{content}{sub}</h6>
  182
+      _ -> <></>
  183
+
  184
+    /**
  185
+     * Create an HTML5 address
  186
+     */
  187
+    address(name:xhtml, address:xhtml) =
  188
+      <address>
  189
+        <strong>{name}</strong>
  190
+        <br/>
  191
+        {address}
  192
+      </address>
  193
+
  194
+    /**
  195
+     * Create a blockquote
  196
+     */
  197
+    blockquote(content:xhtml, author:xhtml) =
  198
+      <blockquote>
  199
+        <p>{content}</p>
  200
+        <small>{author}</small>
  201
+      </blockquote>
  202
+
  203
+    /**
  204
+     * Create a preformatted text, compatible with google-code-prettify
  205
+     */
  206
+    prettyprint(s:string, linenums:bool, lang:option(string)) =
  207
+      lang = match lang
  208
+             {some=l} -> " lang-{l}"
  209
+             {none} -> ""
  210
+      <pre>{Xhtml.of_string(s)}</pre>
  211
+      |> update_class("prettyprint{lang}", _)
  212
+      |> (match linenums
  213
+          {false} -> identity
  214
+          {true} -> update_class("linenums", _))
  215
+
  216
+  }}
  217
+
  218
+  List = {{
  219
+
  220
+    /**
  221
+     * Create an unordered list
  222
+     */
  223
+    unordered(list:list(xhtml)) =
  224
+      <ul>{
  225
+        @toplevel.List.map(e -> <li>{e}</li>, list)
  226
+      }</ul>
  227
+
  228
+    /**
  229
+     * Create an unordered and unstyled list
  230
+     */
  231
+    unstyled(list:list(xhtml)) =
  232
+      unordered(list) |> update_class("unstyled", _)
  233
+
  234
+    /**
  235
+     * Create an ordered list
  236
+     */
  237
+    ordered(list:list(xhtml)) =
  238
+      <ol>{
  239
+        @toplevel.List.map(e -> <li>{e}</li>, list)
  240
+      }</ol>
  241
+
  242
+    /**
  243
+     * Create an description list
  244
+     */
  245
+    description(list:list(WBootstrap.List.description)) =
  246
+      <dl>{
  247
+        @toplevel.List.map(e -> match e
  248
+          {title=t} -> <dt>{t}</dt>
  249
+          {description=d} -> <dd>{d}</dd>
  250
+        , list)
  251
+      }</dl>
  252
+
  253
+  }}
  254
+
  255
+  Label = {{
  256
+
  257
+    make_label(content:string) =
  258
+      <span>{content}</span> |> update_class("label", _)
  259
+
  260
+    success = update_class("success", _)
  261
+    warning = update_class("warning", _)
  262
+    important = update_class("important", _)
  263
+    notice = update_class("notice",_)
  264
+
  265
+    /**
  266
+     * Create a label
  267
+     */
  268
+    make(lb_text, lb_class) =
  269
+      lb = make_label(lb_text)
  270
+      lb = match lb_class
  271
+          {default} -> lb
  272
+          {success} -> lb |> success(_)
  273
+          {warning} -> lb |> warning(_)
  274
+          {important} -> lb |> important(_)
  275
+          {notice} -> lb |> notice(_)
  276
+      lb
  277
+
  278
+  }}
  279
+
  280
+  Media = {{
  281
+
  282
+    /**
  283
+     * Create a media grid
  284
+     */
  285
+    grid(imgs:list(WBootstrap.Media.media)) =
  286
+      list =
  287
+        @toplevel.List.map(
  288
+          media ->
  289
+            content = media.content
  290
+            <a onclick={media.onclick}>
  291
+              {content}
  292
+            </a> |> add_href_opt(media.href, _)
  293
+        , imgs)
  294
+      List.unordered(list) |> update_class("media-grid", _)
  295
+
  296
+  }}
  297
+
  298
+  Table = {{
  299
+
  300
+    @private gen_head(elts:list(WBootstrap.Table.elt)) =
  301
+      <thead><tr>{
  302
+        @toplevel.List.map(e -> <th>{e}</th>, elts)
  303
+      }</tr></thead>
  304
+
  305
+    @private gen_body(lines:list(list(WBootstrap.Table.elt))) =
  306
+      <tbody>{
  307
+        @toplevel.List.map(
  308
+          line ->
  309
+            <tr>{
  310
+              @toplevel.List.map(
  311
+                e -> <td>{e}</td>
  312
+              , line)
  313
+            }</tr>
  314
+        , lines)
  315
+      }</tbody>
  316
+
  317
+    /**
  318
+     * Create a table
  319
+     */
  320
+    table(head:list(WBootstrap.Table.elt), body:list(list(WBootstrap.Table.elt))) =
  321
+      <table>
  322
+        {gen_head(head)}
  323
+        {gen_body(body)}
  324
+      </table>
  325
+
  326
+    /**
  327
+     * Create a zebra stripped table
  328
+     */
  329
+    zebra_stripped(h, b) = table(h, b) |> update_class("zebra-striped", _)
  330
+
  331
+  }}
  332
+
  333
+  Form = {{
  334
+
  335
+    // TODO
  336
+
  337
+    classic(content:xhtml) =
  338
+      <form action="javascript:void(0);">{content}</form>
  339
+
  340
+    stacked(content:xhtml) =
  341
+      <form action="javascript:void(0);" class="form-stacked">{content}</form>
  342
+
  343
+    Fieldset = {{
  344
+
  345
+      make(legend:option(xhtml), elements:list(xhtml)) =
  346
+        <>
  347
+          {match legend {some=l} -> <legend>{l}</legend> {none} -> <></>}
  348
+          {@toplevel.List.map(
  349
+            e -> <div class="clearfix">{e}</div>
  350
+           , elements)}
  351
+        </>
  352
+
  353
+    }}
  354
+
  355
+  }}
  356
+
  357
+  Button = {{
  358
+
  359
+    make_button(text:string, callback:(Dom.event -> void)) =
  360
+      <button onclick={callback}>{text}</button>
  361
+      |> update_class("btn", _) // CAUTION: cannot use class="btn" (see on top)
  362
+
  363
+    make_no_propagation_button(text:string, callback:(Dom.event -> void)) =
  364
+      <button onclick={callback} options:onclick={[{stop_propagation}]}>{text}</button>
  365
+      |> update_class("btn", _) // CAUTION: cannot use class="btn" (see on top)
  366
+
  367
+    make_link(text:string, href:option(string), callback:(Dom.event -> void)) =
  368
+      <a onclick={callback}>{text}</a>
  369
+      |> update_class("btn", _)
  370
+      |> add_href_opt(href, _)
  371
+
  372
+    make_input(text:string, callback:(Dom.event -> void)) =
  373
+      <input type="button" value="{text}" onclick={callback}/>
  374
+      |> update_class("btn", _)
  375
+
  376
+    primary = update_class("primary", _)
  377
+    info = update_class("info", _)
  378
+    success = update_class("success", _)
  379
+    danger = update_class("danger",_)
  380
+    large = update_class("large", _)
  381
+    small = update_class("small", _)
  382
+    disabled = update_class("disabled", _) // FIXME: incomplete on buttons and inputs (see below)
  383
+
  384
+    /**
  385
+     * Create a button
  386
+     */
  387
+    make(bt_type, bt_options_list) =
  388
+
  389
+      bt = match bt_type
  390
+           ~{button callback} ->
  391
+             make_button(button, callback)
  392
+           ~{link href callback} ->
  393
+             make_link(link, href, callback)
  394
+           ~{input callback} ->
  395
+             make_input(input, callback)
  396
+
  397
+      bt_options = @toplevel.List.fold_right(
  398
+                     opts, opt ->
  399
+                       match opt
  400
+                       {primary} -> {opts with class={primary}}
  401
+                       {info} -> {opts with class={info}}
  402
+                       {success} -> {opts with class={success}}
  403
+                       {danger} -> {opts with class={danger}}
  404
+                       {small} -> { opts with size={small} }
  405
+                       {large} -> { opts with size={large} }
  406
+                       {disabled} -> { opts with disabled=true }
  407
+                   , bt_options_list, {class={default} size={normal}:WBootstrap.Button.size disabled=false})
  408
+
  409
+      bt = match bt_options.class
  410
+          {default} -> bt
  411
+          {primary} -> bt |> primary(_)
  412
+          {info} -> bt |> info(_)
  413
+          {success} -> bt |> success(_)
  414
+          {danger} -> bt |> danger(_)
  415
+
  416
+      bt = match bt_options.size
  417
+           {normal} -> bt
  418
+           {small} -> bt |> small(_)
  419
+           {large} -> bt |> large(_)
  420
+
  421
+      bt = match bt_options.disabled
  422
+           {false} -> bt
  423
+           {true} ->
  424
+             match bt_type
  425
+             {link=_ ...} -> bt |> disabled(_)
  426
+             _ -> bt |> disabled(_) |> Xhtml.update_attribute("disabled", "disabled", _)
  427
+             end
  428
+
  429
+      bt
  430
+
  431
+
  432
+  }}
  433
+
  434
+  Navigation = {{
  435
+
  436
+    @private nav_elt_to_xhtml =
  437
+      | {active=e ~onclick ~href} ->
  438
+        <li class="active">
  439
+          {<a onclick={onclick}>{e}</a>
  440
+          |> add_href_opt(href, _)}
  441
+        </li>
  442
+      | {inactive=e ~onclick ~href} ->
  443
+        <li>
  444
+          {<a onclick={onclick}>{e}</a>
  445
+          |> add_href_opt(href, _)}
  446
+        </li>
  447
+      | {disabled=e ~onclick ~href} ->
  448
+        <li class="disabled">
  449
+          {<a onclick={onclick}>{e}</a>
  450
+          |> add_href_opt(href, _)}
  451
+        </li>
  452
+      | {divider} -> <li class="divider"></li>
  453
+      | {~custom_li} -> custom_li
  454
+
  455
+    /**
  456
+     * Create a topbar
  457
+     */
  458
+    topbar(content:xhtml) =
  459
+      <div data-scrollspy="scrollspy">
  460
+        <div class="topbar-inner">{content}</div>
  461
+      </div> |> update_class("topbar", _)
  462
+
  463
+    /**
  464
+     * Create a brand link
  465
+     */
  466
+    brand(brand:xhtml, href:option(string), callback:(Dom.event -> void)) =
  467
+      <a class="brand" onclick={callback}>{brand}</a>
  468
+      |> add_href_opt(href, _)
  469
+
  470
+    /**
  471
+     * Create a dropdown li (for use with WBootstrap.List)
  472
+     */
  473
+    dropdown_li(toggle:xhtml, href:option(string), list:list(WBootstrap.Navigation.elt)) =
  474
+      a = <a class="dropdown-toggle">{toggle}</a>
  475
+          |> add_href_opt(href, _)
  476
+      <li class="dropdown" data-dropdown="dropdown">
  477
+        {a}
  478
+        <ul class="dropdown-menu">{
  479
+          @toplevel.List.map(nav_elt_to_xhtml, list)
  480
+        }</ul>
  481
+      </li>
  482
+
  483
+    @private make_tabs(cl:string, tabs:list(WBootstrap.Navigation.elt)) =
  484
+      <ul>{
  485
+        @toplevel.List.map(nav_elt_to_xhtml, tabs)
  486
+      }</ul> |> update_class(cl, _)
  487
+
  488
+    nav(l) = make_tabs("nav", l)
  489
+    tabs(l) = make_tabs("tabs", l) |> Xhtml.add_attribute("data-tabs", "tabs", _)
  490
+    pills(l) = make_tabs("pills", l) |> Xhtml.add_attribute("data-pills", "pills", _)
  491
+
  492
+    /**
  493
+     * Create a breadcrumb
  494
+     */
  495
+    breadcrumb(path:list(WBootstrap.Navigation.elt), sep:xhtml) =
  496
+      <ul class="breadcrumb">{
  497
+        list = @toplevel.List.map(nav_elt_to_xhtml, path)
  498
+        XmlConvert.of_list_using(<></>, <></>, <span class="divider">{sep}</span>, list)
  499
+      }</ul>
  500
+
  501
+    /**
  502
+     * Create a pagination
  503
+     */
  504
+    pagination(pages:list(WBootstrap.Navigation.elt), prev:WBootstrap.Navigation.page_nav_elt, next:WBootstrap.Navigation.page_nav_elt) =
  505
+      list = @toplevel.List.map(nav_elt_to_xhtml, pages)
  506
+      is_disabled(l) = if @toplevel.List.is_empty(l) || (match @toplevel.List.head(l) {active=_ href=_ onclick=_} -> true _ -> false) then "disabled" else ""
  507
+      prev_disabled = is_disabled(pages)
  508
+      next_disabled = is_disabled(@toplevel.List.rev(pages))
  509
+      <div class="pagination">
  510
+        <ul>
  511
+          <li class="prev {prev_disabled}">
  512
+            {<a onclick={prev.onclick}>{prev.content}</a>
  513
+             |> add_href_opt(prev.href, _)}
  514
+          </li>
  515
+          {list}
  516
+          <li class="next {next_disabled}">
  517
+            {<a onclick={next.onclick}>{next.content}</a>
  518
+             |> add_href_opt(next.href, _)}
  519
+          </li>
  520
+        </ul>
  521
+      </div>
  522
+
  523
+  }}
  524
+
  525
+  Message = {{
  526
+
  527
+    @private gen_make_alert(closable:bool, content:WBootstrap.Message.content, more:xhtml) =
  528
+      id = Dom.fresh_id()
  529
+      <div id=#{id}>
  530
+        {match closable
  531
+         {true} -> <a class="close" onclick={_->Dom.remove(#{id})}>x</a>
  532
+         {false} -> <></>}
  533
+        <p>
  534
+          {if content.title == "" then <></> else <strong>{content.title}</strong>}
  535
+          {content.description}
  536
+        </p>
  537
+        {more}
  538
+      </div> |> update_class("alert-message", _)
  539
+
  540
+    make_alert(closable:bool, content:WBootstrap.Message.content) =
  541
+      gen_make_alert(closable, content, <></>)
  542
+
  543
+    // TODO: handle alert-actions
  544
+    make_block(closable:bool, actions:option(xhtml), content:WBootstrap.Message.content) =
  545
+      more = match actions
  546
+             {some=a} -> <div class="alert-actions">{a}</div>
  547
+             {none} -> <></>
  548
+      gen_make_alert(closable, content, more) |> update_class("block-message", _)
  549
+
  550
+    warning = update_class("warning", _)
  551
+    error = update_class("error", _)
  552
+    success = update_class("success", _)
  553
+    info = update_class("info", _)
  554
+
  555
+    /**
  556
+     * Create a message (alert or block)
  557
+     */
  558
+    make(msg_type, msg_class) =
  559
+
  560
+      msg = match msg_type
  561
+            ~{alert closable} -> make_alert(closable, alert)
  562
+            ~{block actions closable} -> make_block(closable, actions, block)
  563
+
  564
+      msg = match msg_class
  565
+            {default} -> msg
  566
+            {warning} -> msg |> warning(_)
  567
+            {error} -> msg |> error(_)
  568
+            {success} -> msg |> success(_)
  569
+            {info} -> msg |> info(_)
  570
+
  571
+      msg
  572
+
  573
+  }}
  574
+
  575
+  Div = {{
  576
+
  577
+    container(content:xhtml) =
  578
+      <div>{content}</div> |> update_class("container", _)
  579
+
  580
+    container_fluid(content:xhtml) =
  581
+      <div>{content}</div> |> update_class("container-fluid", _)
  582
+
  583
+    content(content:xhtml) =
  584
+      <div>{content}</div> |> update_class("content", _)
  585
+
  586
+    page_header(level:int, title:string, subtitle:option(string)) =
  587
+      sub = match subtitle
  588
+            {some=s} -> <>{" "}</><small>{s}</small>
  589
+            {none} -> <></>;
  590
+      <div>{Typography.header(level, some(sub), <>{title}</>)}</div>
  591
+      |> update_class("page-header", _)
  592
+
  593
+    inner(content:xhtml) =
  594
+      <div>{content}</div> |> update_class("inner", _)
  595
+
  596
+    well(content:xhtml) =
  597
+      <div>{content}</div> |> update_class("well", _)
  598
+
  599
+  }}
  600
+
  601
+  Span = {{
  602
+
  603
+  }}
  604
+
  605
+}}

0 notes on commit 660d902

Please sign in to comment.
Something went wrong with that request. Please try again.