Making WordPress.org

Changeset 3699


Ignore:
Timestamp:
07/20/2016 04:51:48 PM (10 years ago)
Author:
kovshenin
Message:

WordCamp Budgets: Another pass at the new budget tool.

Location:
sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments
Files:
1 added
4 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments/css/wordcamp-budgets.css

    r2878 r3699  
    182182    }
    183183
     184/* Budget Tool */
     185.wcb-budget-tool .left {
     186    width: 50%;
     187    float: left;
     188}
     189
     190.wcb-budget-tool .right {
     191    width: 50%;
     192    float: left;
     193}
     194
     195.wcb-budget-container {
     196    width: 100%;
     197    table-layout: fixed;
     198    white-space:nowrap;
     199    text-align: left;
     200    border-collapse: collapse;
     201    background: white;
     202    margin: 12px 0;
     203}
     204
     205.wcb-budget-container,
     206.wcb-budget-container td,
     207.wcb-budget-container th {
     208    overflow: hidden;
     209    text-overflow: ellipsis;
     210    border: solid 1px #ccc;
     211    height: 30px;
     212    line-height: 30px;
     213}
     214
     215.wcb-budget-container th {
     216    background: #f8f8f8;
     217}
     218
     219.wcb-budget-container td,
     220.wcb-budget-container th {
     221    vertical-align: top;
     222    padding: 0 4px;
     223}
     224
     225.wcb-budget-container tr.has-changed td {
     226    background: #f7ecdc;
     227}
     228
     229.wcb-budget-container tr.is-new td {
     230    background: #dcf7e0;
     231}
     232
     233.wcb-budget-container .wcb-entry td.editable {
     234    padding: 0;
     235}
     236
     237.wcb-budget-container .wcb-entry input,
     238.wcb-budget-container .wcb-entry select {
     239    float: left;
     240    padding: 0 4px;
     241    width: 100%;
     242    border: 0;
     243    margin: 0;
     244    background: transparent;
     245    -webkit-appearance: none;
     246    -moz-appearance: none;
     247    appearance: none;
     248    border: none;
     249    box-shadow: none;
     250    border-radius: 0;
     251    height: 30px;
     252    line-height: 30px;
     253    font-size: inherit;
     254    cursor: default;
     255}
     256
     257.wcb-budget-container .wcb-entry td.focused {
     258    background: #e8f1f7;
     259    border: double 1px #999;
     260}
     261
     262.wcb-budget-container tr.is-new td.focused {
     263    background: #ecf7ee;
     264}
     265
     266.wcb-budget-container .wcb-entry input:focus {
     267    cursor: text;
     268}
     269
     270.wcb-budget-container .dashicons {
     271    color: inherit;
     272    text-decoration: none;
     273    line-height: 30px;
     274    color: #aaa;
     275}
     276
     277.wcb-budget-container .actions {
     278    text-align: right;
     279}
     280
     281.wcb-budget-container tr:hover .dashicons {
     282    color: #444;
     283}
     284
     285.wcb-budget-container .amount {
     286    text-align: right;
     287}
     288
     289.wcb-budget-container .link {
     290    float: right;
     291    margin: -4px 4px 4px 0;
     292    text-align: right;
     293    font-size: 10px;
     294    color: #aaa;
     295    line-height: 10px;
     296}
     297
     298.wcb-budget-container .wcb-entry td.focused .link-toggle {
     299    opacity: 1;
     300}
     301
     302.wcb-budget-container .wcb-entry td.focused .link span {
     303    opacity: 0;
     304}
     305
     306.wcb-budget-container .link-toggle {
     307    opacity: 0;
     308    position: absolute;
     309    text-decoration: none;
     310    color: #444;
     311}
     312
     313.wcb-budget-container .link-toggle .dashicons {
     314    font-size: 16px;
     315    padding-left: 2px;
     316    padding-right: 2px;
     317    padding-top: 1px;
     318}
     319
     320.wcb-budget-container .link-toggle .link-value {
     321    position: absolute;
     322    background: red;
     323    width: 30px;
     324    opacity: 0;
     325}
     326
     327.wcb-expense-placeholder,
     328.wcb-income-placeholder {
     329    color: #aaa;
     330    font-style: italic;
     331    cursor: pointer;
     332}
     333
     334.wcb-budget-summary,
     335.wcb-budget-summary td,
     336.wcb-budget-summary th {
     337    border-left: none;
     338}
     339
     340.wcb-negative {
     341    color: red;
     342}
     343
     344.wcb-budget-summary .inspire {
     345    color: #aaa;
     346}
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments/includes/budget-tool.php

    r3302 r3699  
    99        add_submenu_page( 'wordcamp-budget', __( 'WordCamp Budget', 'wordcamporg' ), __( 'Budget', 'wordcamporg' ), 'manage_options', 'wordcamp-budget' );
    1010        add_action( 'wcb_render_budget_page', array( __CLASS__, 'render' ) );
     11        register_setting( 'wcb_budget_noop', 'wcb_budget_noop', array( __CLASS__, 'validate' ) );
     12    }
     13
     14    public static function validate( $noop ) {
     15        if ( empty( $_POST['_wcb_budget_data'] ) )
     16            return;
     17
     18        if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'wcb_budget_noop-options' ) )
     19            return;
     20
     21        $budget = get_option( 'wcb_budget', array() );
     22
     23        // TODO: Add prelim, approved, current logic here.
     24        $data = json_decode( wp_unslash( $_POST['_wcb_budget_data'] ), true );
     25        $valid_attributes = array( 'type', 'category', 'amount', 'note', 'link', 'name', 'value' );
     26        foreach ( $data as &$item ) {
     27            $_item = array();
     28            foreach ( $item as $key => $value ) {
     29                if ( ! in_array( $key, $valid_attributes ) )
     30                    continue;
     31
     32                if ( $key == 'amount' )
     33                    $value = round( floatval( $value ), 2 );
     34
     35                $_item[ $key ] = $value;
     36            }
     37
     38            $item = $_item;
     39            print_r( $item );
     40        }
     41
     42        $budget['current'] = $data;
     43        $budget['updated'] = time();
     44        update_option( 'wcb_budget', $budget, 'no' );
     45        return;
    1146    }
    1247
     
    1449        $screen = get_current_screen();
    1550        if ( $screen->id == 'toplevel_page_wordcamp-budget' ) {
    16             wp_enqueue_script( 'backbone' );
     51            wp_enqueue_script( 'wcb-budget-tool',
     52                plugins_url( 'javascript/budget-tool.js', __DIR__ ),
     53                array( 'backbone', 'jquery', 'underscore' ), 1 , true );
    1754        }
    1855    }
    1956
     57    private static function _get_default_budget() {
     58        return array(
     59            array( 'type' => 'meta', 'name' => 'attendees', 'value' => 300 ),
     60            array( 'type' => 'meta', 'name' => 'days', 'value' => 2 ),
     61            array( 'type' => 'meta', 'name' => 'tracks', 'value' => 4 ),
     62            array( 'type' => 'meta', 'name' => 'speakers', 'value' => 25 ),
     63            array( 'type' => 'meta', 'name' => 'volunteers', 'value' => 10 ),
     64            array( 'type' => 'meta', 'name' => 'currency', 'value' => 'USD' ),
     65            array( 'type' => 'meta', 'name' => 'ticket-price', 'value' => 20.00 ),
     66
     67            array( 'type' => 'income', 'category' => 'other', 'note' => 'Tickets Income', 'amount' => 0, 'link' => 'ticket-price-x-attendees' ),
     68            array( 'type' => 'income', 'category' => 'other', 'note' => 'Community Sponsorships', 'amount' => 4300 ),
     69            array( 'type' => 'income', 'category' => 'other', 'note' => 'Local Sponsorships', 'amount' => 7000 ),
     70            array( 'type' => 'income', 'category' => 'other', 'note' => 'Microsponsors', 'amount' => 500 ),
     71
     72            array( 'type' => 'expense', 'category' => 'venue', 'note' => 'Venue', 'amount' => 7500 ),
     73            array( 'type' => 'expense', 'category' => 'venue', 'note' => 'Wifi Costs', 'amount' => 300, 'link' => 'per-day' ),
     74            array( 'type' => 'expense', 'category' => 'other', 'note' => 'Comped Tickets', 'amount' => 300 ),
     75            array( 'type' => 'expense', 'category' => 'audio-visual', 'note' => 'Video recording', 'amount' => 500 ),
     76            array( 'type' => 'expense', 'category' => 'audio-visual', 'note' => 'Projector rental', 'amount' => 300 ),
     77            array( 'type' => 'expense', 'category' => 'audio-visual', 'note' => 'Livestream', 'amount' => 200 ),
     78            array( 'type' => 'expense', 'category' => 'signage-badges', 'note' => 'Printing', 'amount' => 800 ),
     79            array( 'type' => 'expense', 'category' => 'signage-badges', 'note' => 'Badges', 'amount' => 8.21, 'link' => 'per-attendee' ),
     80            array( 'type' => 'expense', 'category' => 'food-beverage', 'note' => 'Snacks', 'amount' => 300 ),
     81            array( 'type' => 'expense', 'category' => 'food-beverage', 'note' => 'Lunch', 'amount' => 2350 ),
     82            array( 'type' => 'expense', 'category' => 'food-beverage', 'note' => 'Coffee', 'amount' => 500 ),
     83            array( 'type' => 'expense', 'category' => 'swag', 'note' => 'T-shirts', 'amount' => 780 ),
     84            array( 'type' => 'expense', 'category' => 'speaker-event', 'note' => 'Speakers Dinner', 'amount' => 20, 'link' => 'per-speaker' ),
     85        );
     86    }
     87
    2088    public static function render() {
     89        $budget = get_option( 'wcb_budget' );
     90        $budget = ! empty( $budget['current'] ) ? $budget['current'] : self::_get_default_budget();
     91
     92        if ( ! $inspire_urls = get_site_transient( 'wcb-inspire-urls' ) ) {
     93            $urls = array( 'https://jawordpressorg.github.io/wapuu/wapuu-archive/original-wapuu.png' );
     94            $r = wp_remote_get( 'https://jawordpressorg.github.io/wapuu-api/v1/wapuu.json' );
     95            if ( ! is_wp_error( $r ) && wp_remote_retrieve_response_code( $r ) == 200 ) {
     96                $body = json_decode( wp_remote_retrieve_body( $r ), true );
     97                $maybe_urls = wp_list_pluck( wp_list_pluck( $body, 'wapuu' ), 'src' );
     98                if ( count( $maybe_urls ) > 0 ) {
     99                    $inspire_urls = $maybe_urls;
     100                }
     101            }
     102
     103            set_site_transient( 'wcb-inspire-urls', $inspire_urls, 30 * DAY_IN_SECONDS );
     104        }
     105
    21106        require( dirname( __DIR__ ) . '/views/budget-tool/main.php' );
    22107    }
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments/includes/wordcamp-budgets.php

    r3306 r3699  
    161161            plugins_url( 'css/wordcamp-budgets.css', __DIR__ ),
    162162            $soft_deps,
    163             4
     163            5
    164164        );
    165165    }
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments/views/budget-tool/main.php

    r3302 r3699  
    1 <?php
    2     $budget = array(
    3         array( 'type' => 'meta', 'name' => 'attendees', 'value' => 300 ),
    4         array( 'type' => 'meta', 'name' => 'days', 'value' => 2 ),
    5         array( 'type' => 'meta', 'name' => 'tracks', 'value' => 4 ),
    6         array( 'type' => 'meta', 'name' => 'speakers', 'value' => 25 ),
    7         array( 'type' => 'meta', 'name' => 'volunteers', 'value' => 10 ),
    8         array( 'type' => 'meta', 'name' => 'currency', 'value' => 'USD' ),
    9         array( 'type' => 'meta', 'name' => 'ticket-price', 'value' => 20.00 ),
     1<script>
     2window.wcb = window.wcb || {models:{}, input:[]};
     3wcb.input = <?php echo json_encode( $budget ); ?>;
     4wcb.urls = <?php echo json_encode( $inspire_urls ); ?>;
     5</script>
    106
    11         array( 'type' => 'income', 'category' => 'other', 'note' => 'Tickets Income', 'amount' => 3500 ),
    12         array( 'type' => 'income', 'category' => 'other', 'note' => 'Community Sponsorships', 'amount' => 4300 ),
    13         array( 'type' => 'income', 'category' => 'other', 'note' => 'Local Sponsorships', 'amount' => 7000 ),
    14         array( 'type' => 'income', 'category' => 'other', 'note' => 'Microsponsors', 'amount' => 500 ),
     7<div class="wrap wcb-budget-tool">
     8    <h2 class="nav-tab-wrapper wp-clearfix">
     9        <a href="#" class="nav-tab nav-tab-active">Preliminary Budget</a>
     10        <!--<a href="#" class="nav-tab">Working Budget</a>-->
     11    </h2>
    1512
    16         array( 'type' => 'expense', 'category' => 'venue', 'note' => 'Venue', 'amount' => 7500 ),
    17         array( 'type' => 'expense', 'category' => 'venue', 'note' => 'Wifi Costs', 'amount' => 300, 'link' => 'per-day' ),
    18         array( 'type' => 'expense', 'category' => 'other', 'note' => 'Comped Tickets', 'amount' => 300 ),
    19         array( 'type' => 'expense', 'category' => 'audio-visual', 'note' => 'Video recording', 'amount' => 500 ),
    20         array( 'type' => 'expense', 'category' => 'audio-visual', 'note' => 'Projector rental', 'amount' => 300 ),
    21         array( 'type' => 'expense', 'category' => 'audio-visual', 'note' => 'Livestream', 'amount' => 200 ),
    22         array( 'type' => 'expense', 'category' => 'signage-badges', 'note' => 'Printing', 'amount' => 800 ),
    23         array( 'type' => 'expense', 'category' => 'signage-badges', 'note' => 'Badges', 'amount' => 8.21, 'link' => 'per-attendee' ),
    24         array( 'type' => 'expense', 'category' => 'food-beverage', 'note' => 'Snacks', 'amount' => 300 ),
    25         array( 'type' => 'expense', 'category' => 'food-beverage', 'note' => 'Lunch', 'amount' => 2350 ),
    26         array( 'type' => 'expense', 'category' => 'food-beverage', 'note' => 'Coffee', 'amount' => 500 ),
    27         array( 'type' => 'expense', 'category' => 'swag', 'note' => 'T-shirts', 'amount' => 780 ),
    28         array( 'type' => 'expense', 'category' => 'speaker-event', 'note' => 'Speakers Dinner', 'amount' => 20, 'link' => 'per-speaker' ),
    29     );
    30 ?>
    31 <script>var wcb_data = <?php echo json_encode( $budget ); ?>;</script>
     13    <p style="max-width: 800px;">Welcome to your WordCamp budget, it's time to crunch some numbers! When you're done with the preliminary budget, hit the "Submit for Approval" button below – a WordCamp deputy will be notified and will review your work. If you're having trouble with these numbers, or if you have any questions, don't hesitate to reach out to your mentor or Central.</p>
    3214
    33 <div class="wrap">
    34     <h1>WordCamp Budget</h1>
    35     <table id="wcb-budget-container">
     15    <div class="left">
     16        <h2>Event Data</h2>
     17        <table class="wcb-budget-container">
     18            <tbody>
     19                <tr class="wcb-group-header">
     20                    <th style="width: 50%;">Name</th>
     21                    <th style="width: 50%;">Value</th>
     22                </tr>
     23                <tr class="wcb-meta-placeholder" style="display: none;">
     24                    <td colspan="2"></td>
     25                </tr>
     26            </tbody>
     27        </table>
     28    </div>
     29    <div class="right">
     30        <h2>Summary</h2>
     31        <div class="wcb-summary-placeholder"></div>
     32    </div>
     33
     34    <div class="clear"></div>
     35
     36    <h2>Expenses</h2>
     37    <table class="wcb-budget-container">
    3638        <tbody>
    3739            <tr class="wcb-group-header">
    38                 <th colspan="4">Event Data</th>
    39             </tr>
    40             <tr class="wcb-meta-placeholder" style="display: none;">
    41                 <td colspan="4"></td>
    42             </tr>
    43             <tr class="wcb-group-header">
    44                 <th style="width: 200px;">Category</th>
    45                 <th>Detail</th>
    46                 <th style="width: 200px;" class="amount">Amount</th>
    47                 <th style="width: 100px;"></th>
    48             </tr>
    49             <tr class="wcb-group-header">
    50                 <th colspan="4">Expenses</th>
     40                <th style="width: 25%;">Category</th>
     41                <th style="width: 25%;">Detail</th>
     42                <th style="width: 25%;" class="amount">Amount</th>
     43                <th style="width: 25%;"></th>
    5144            </tr>
    5245            <tr class="wcb-expense-placeholder">
    5346                <td colspan="4">New Expense Item</td>
    5447            </tr>
     48        </tbody>
     49    </table>
     50
     51    <h2>Income</h2>
     52    <table class="wcb-budget-container">
     53        <tbody>
    5554            <tr class="wcb-group-header">
    56                 <th colspan="4">Income</th>
     55                <th style="width: 25%;">Category</th>
     56                <th style="width: 25%;">Detail</th>
     57                <th style="width: 25%;" class="amount">Amount</th>
     58                <th style="width: 25%;"></th>
    5759            </tr>
     60
    5861            <tr class="wcb-income-placeholder">
    5962                <td colspan="4">New Income Item</td>
     
    6265    </table>
    6366
    64     <p class="submit">
    65         <?php submit_button( 'Save Budget', 'primary', 'submit', false ); ?>
    66         <a href="#" class="button">Cancel Changes</a>
    67     </p>
     67    <form class="wcb-submit-form" action="options.php" method="post">
     68        <?php settings_fields( 'wcb_budget_noop' ); ?>
     69        <input type="hidden" name="_wcb_budget_data" value="<?php echo esc_attr( json_encode( $budget ) ); ?>" />
     70
     71        <p class="submit">
     72            <?php submit_button( 'Save Draft', 'secondary', 'wcb-budget-save-draft', false ); ?>
     73            <a href="<?php echo admin_url( 'admin.php?page=wordcamp-budget' ); ?>" class="button">Cancel Changes</a>
     74            <?php submit_button( 'Submit for Approval', 'primary', 'wcb-budget-submit', false ); ?>
     75        </p>
     76    </form>
    6877</div>
    6978
    70 <style>
    71 #wcb-budget-container {
    72     width: 100%;
    73     table-layout: fixed;
    74     white-space:nowrap;
    75     text-align: left;
    76     border-collapse: collapse;
    77     background: white;
    78     margin: 12px 0;
    79 }
    80 
    81 #wcb-budget-container,
    82 #wcb-budget-container td,
    83 #wcb-budget-container th {
    84     overflow: hidden;
    85     text-overflow: ellipsis;
    86     border: solid 1px #ccc;
    87     height: 30px;
    88     line-height: 30px;
    89 }
    90 
    91 #wcb-budget-container th {
    92     background: #f8f8f8;
    93 }
    94 
    95 #wcb-budget-container td,
    96 #wcb-budget-container th {
    97     vertical-align: top;
    98     padding: 0 4px;
    99 }
    100 
    101 #wcb-budget-container tr.has-changed td {
    102     background: #f7ecdc;
    103 }
    104 
    105 #wcb-budget-container tr.is-new td {
    106     background: #dcf7e0;
    107 }
    108 
    109 #wcb-budget-container .wcb-entry-editor td {
    110     background: #d5e7f4;
    111 }
    112 
    113 #wcb-budget-container .wcb-entry-editor td.editable {
    114     padding: 0;
    115 }
    116 
    117 #wcb-budget-container .wcb-entry-editor input,
    118 #wcb-budget-container .wcb-entry-editor select {
    119     float: left;
    120     padding: 0 4px;
    121     width: 100%;
    122     border: 0;
    123     margin: 0;
    124     background: transparent;
    125     -webkit-appearance: none;
    126     -moz-appearance: none;
    127     appearance: none;
    128     border: none;
    129     box-shadow: none;
    130     border-radius: 0;
    131     height: 30px;
    132     line-height: 30px;
    133     font-size: inherit;
    134 }
    135 
    136 #wcb-budget-container .wcb-entry-editor input:focus {
    137     background: #e8f1f7;
    138 }
    139 
    140 #wcb-budget-container .dashicons {
    141     color: inherit;
    142     text-decoration: none;
    143     line-height: 30px;
    144     color: #aaa;
    145 }
    146 
    147 #wcb-budget-container .actions {
    148     text-align: right;
    149 }
    150 
    151 #wcb-budget-container tr:hover .dashicons {
    152     color: #444;
    153 }
    154 
    155 #wcb-budget-container .amount {
    156     text-align: right;
    157 }
    158 
    159 #wcb-budget-container .link-value {
    160     display: block;
    161     clear: left;
    162     color: #aaa;
    163     margin-top: -12px;
    164 }
    165 
    166 #wcb-budget-container .link-toggle {
    167     position: absolute;
    168     text-decoration: none;
    169     color: #444;
    170 }
    171 
    172 #wcb-budget-container .link-toggle .dashicons {
    173     font-size: 16px;
    174     padding-left: 2px;
    175     padding-right: 2px;
    176     padding-top: 1px;
    177 }
    178 
    179 .wcb-expense-placeholder,
    180 .wcb-income-placeholder {
    181     color: #aaa;
    182     font-style: italic;
    183     cursor: pointer;
    184 }
    185 </style>
    186 
     79<script type="text/template" id="wcb-tmpl-summary">
     80    <tbody>
     81        <tr class="wcb-group-header">
     82            <th style="width: 50%;"></th>
     83            <th style="width: 50%;"></th>
     84        </tr>
     85        <tr>
     86            <td>Income</td>
     87            <td class="amount">{{data.income.toFixed(2)}}</td>
     88        </tr>
     89        <tr>
     90            <td>Expenses</td>
     91            <td class="amount">{{data.expenses.toFixed(2)}}</td>
     92        </tr>
     93        <tr>
     94            <td>Variance</td>
     95            <td class="amount <# if (data.variance < 0) { #>wcb-negative<# } #>">{{data.variance.toFixed(2)}}</td>
     96        </tr>
     97        <tr>
     98            <td>Cost Per Person Per Day</td>
     99            <td class="amount">{{data.per_person.toFixed(2)}}</td>
     100        </tr>
     101        <tr>
     102            <td></td>
     103            <td></td>
     104        </tr>
     105        <tr>
     106            <td></td>
     107            <td></td>
     108        </tr>
     109        <tr>
     110            <td></td>
     111            <td class="amount">
     112                <# if (data.variance < 0) { #>
     113                <a href="#" target="_blank" class="inspire">inspire me</a>
     114                <# } #>
     115            </td>
     116        </tr>
     117    </tbody>
     118</script>
    187119<script type="text/template" id="wcb-tmpl-entry">
    188     <# if (data.type == 'meta' ) { #>
    189         <td>{{wcb.metaLabels[data.name]}}</td>
    190         <td>{{data.value}}</td>
    191         <td></td>
    192         <td class="actions">
    193             <a href="#" class="edit"><span class="dashicons dashicons-edit"></span></a>
    194         </td>
    195     <# } else { #>
    196         <td>{{wcb.categories[data.category]}}</td>
    197         <td>{{data.note}}</td>
    198         <td class="amount">
    199             <span>{{data.realAmount.toFixed(2)}}</span>
    200             <# if (data.link) { #>
    201             <!--<span class="link-value">{{data.amount.toFixed(2)}} per attendee</span>-->
    202             <# } #>
    203         </td>
    204         <td class="actions">
    205             <a href="#" class="edit"><span class="dashicons dashicons-edit"></span></a>
    206             <a href="#" class="delete"><span class="dashicons dashicons-trash"></span></a>
    207         </td>
    208     <# } #>
    209 </script>
    210 <script type="text/template" id="wcb-tmpl-entry-editor">
    211120    <# if (data.type == 'meta' ) { #>
    212121        <td>{{wcb.metaLabels[data.name]}}</td>
     
    214123            <input class="value" type="text" value="{{data.value}}" />
    215124        </td>
    216         <td></td>
    217         <td class="actions">
    218             <a href="#" class="save"><span class="dashicons dashicons-yes"></span></a>
    219             <a href="#" class="cancel"><span class="dashicons dashicons-no-alt"></span></a>
    220         </td>
    221125    <# } else { #>
     126
     127        <# if (data.type == 'expense') { #>
    222128        <td class="editable">
    223129            <select class="category">
     
    227133            </select>
    228134        </td>
     135        <# } else { #>
     136        <td>
     137            Income
     138            <input type="hidden" class="category" value="{{data.category}}" />
     139        </td>
     140        <# } #>
     141
    229142        <td class="editable">
    230143            <input class="note" type="text" value="{{data.note}}" />
    231144        </td>
    232145        <td class="editable">
    233             <a href="#" class="link-toggle"><span class="dashicons dashicons-admin-links"></span> {{data.link}}</a>
    234             <input class="amount" type="text" value="{{data.amount.toFixed(2)}}" />
     146            <div class="link-toggle">
     147                <select class="link-value">
     148                    <option value="" <#if(!data.link){#>selected<#}#>>none</option>
     149                    <# _.each(wcb.linkData, function(item, k) { #>
     150                    <option value="{{k}}" <# if (data.link==k) { #>selected<# } #>>{{{item.label}}}</option>
     151                    <# }); #>
     152                </select>
     153                <span class="dashicons dashicons-admin-links"></span>
     154            </div>
     155            <input class="amount" type="text" value="{{data.realAmount.toFixed(2)}}" />
     156
     157            <# if (data.link) { #>
     158                <div class="link">
     159                    <# if (data.linkHasValue) { #>
     160                    <span>{{data.amount.toFixed(2)}}</span>
     161                    <# } #>
     162
     163                    {{{data.linkLabel}}}
     164                </div>
     165            <# } #>
    235166        </td>
    236167        <td class="actions">
    237             <a href="#" class="save"><span class="dashicons dashicons-yes"></span></a>
    238             <a href="#" class="cancel"><span class="dashicons dashicons-no-alt"></span></a>
     168            <a href="#" class="delete"><span class="dashicons dashicons-trash"></span></a>
    239169        </td>
    240170    <# } #>
    241171</script>
    242 <script type="text/template" id="wcb-tmpl-entry-link">
    243     <td colspan="2"></td>
    244     <td class="editable">
    245         <select class="link-value">
    246             <option value="" <#if(!data.link){#>selected<#}#>>None</option>
    247             <option value="per-attendee" <#if(data.link=='per-attendee'){#>selected<#}#>>Per Attendee</option>
    248             <option value="per-speaker" <#if(data.link=='per-speaker'){#>selected<#}#>>Per Speaker</option>
    249             <option value="per-day" <#if(data.link=='per-day'){#>selected<#}#>>Per Day</option>
    250         </select>
    251     </td>
    252     <td></td>
    253 </script>
    254 
    255 <script>
    256 window.wcb = window.wcb || {models:{}};
    257 
    258 (function($){
    259     $(document).on('budget-tool-render.wordcamp-budgets', function() {
    260         var $container = $('#wcb-budget-container tbody'),
    261             $income = $container.find('.wcb-income-placeholder'),
    262             $expense = $container.find('.wcb-expense-placeholder'),
    263             $meta = $container.find('.wcb-meta-placeholder');
    264 
    265         var template_options = {
    266             evaluate:    /<#([\s\S]+?)#>/g,
    267             interpolate: /\{\{\{([\s\S]+?)\}\}\}/g,
    268             escape:      /\{\{([^\}]+?)\}\}(?!\})/g,
    269             variable:    'data'
    270         };
    271 
    272         var Category = Backbone.Model.extend({
    273             defaults: {
    274                 label: 'Category',
    275                 value: 'category'
    276             }
    277         });
    278 
    279         var Categories = Backbone.Collection.extend({
    280             model: Category
    281         });
    282 
    283         var Entry = Backbone.Model.extend({
    284             defaults: {
    285                 type: 'expense',
    286                 category: 'other',
    287                 amount: 0,
    288                 note: '',
    289                 new: false,
    290                 link: null,
    291 
    292                 // metadata
    293                 name: '',
    294                 value: null
    295             },
    296 
    297             initialize: function() {
    298                 this._realAmount = this.getRealAmount();
    299                 this._attr = _.clone(this.attributes);
    300             },
    301 
    302             getRealAmount: function() {
    303                 if (!this.get('link'))
    304                     return this.get('amount');
    305 
    306                 if (this.get('link') === 'per-attendee') {
    307                     var attendees = wcb.table.collection.findWhere({type: 'meta', name: 'attendees'}).get('value');
    308                     return this.get('amount') * attendees;
    309                 }
    310 
    311                 if (this.get('link') === 'per-speaker') {
    312                     var speakers = wcb.table.collection.findWhere({type: 'meta', name: 'speakers'}).get('value');
    313                     return this.get('amount') * speakers;
    314                 }
    315 
    316                 if (this.get('link') === 'per-day') {
    317                     var days = wcb.table.collection.findWhere({type: 'meta', name: 'days'}).get('value');
    318                     return this.get('amount') * days;
    319                 }
    320 
    321                 return 0;
    322             },
    323 
    324             hasChanged: function() {
    325                 // console.log(this._attr);
    326                 // console.log(this.attributes);
    327                 // console.log(this._realAmount)
    328                 var changed = _.isEqual(this._attr, this.attributes) && this._realAmount == this.getRealAmount()
    329                 return !changed;
    330             },
    331 
    332             editStart: function() {
    333                 this.trigger('edit-start.wordcamp-budgets');
    334             }
    335         });
    336 
    337         var EntryView = Backbone.View.extend({
    338             className: 'wcb-entry',
    339             tagName: 'tr',
    340 
    341             events: {
    342                 'dblclick td': 'editStart',
    343                 'click .edit': 'editStart',
    344                 'click .delete': 'delete',
    345             },
    346 
    347             initialize: function() {
    348                 this.model.bind('change', this.render, this);
    349                 this.model.bind('destroy', this.remove, this);
    350                 this.model.bind('edit-start.wordcamp-budgets', this.editStart, this);
    351             },
    352 
    353             render: function() {
    354                 var data = this.model.toJSON();
    355                 data.realAmount = this.model.getRealAmount();
    356 
    357                 this.template = _.template($('#wcb-tmpl-entry').html(), null, template_options);
    358                 this.$el.html(this.template(data));
    359                 this.$el.toggleClass('has-changed', this.model.hasChanged() && ! this.model.get('new'));
    360                 this.$el.toggleClass('is-new', this.model.get('new'));
    361                 return this;
    362             },
    363 
    364             editStart: function() {
    365                 if (this.editing)
    366                     return false;
    367 
    368                 this.editing = true;
    369                 this.editor = new EntryEditorView({model: this.model});
    370                 this.editor.on('edit-save.wordcamp-budgets', this.editSave, this);
    371                 this.editor.on('edit-cancel.wordcamp-budgets', this.editCancel, this);
    372 
    373                 this.$el.after(this.editor.render().el);
    374                 this.$el.hide();
    375                 return false;
    376             },
    377 
    378             editSave: function() {
    379                 this.clearSelection.apply(this);
    380                 this.model = this.editor.model;
    381                 this.editor.remove();
    382                 this.$el.show();
    383                 this.editing = false;
    384             },
    385 
    386             editCancel: function() {
    387                 this.clearSelection.apply(this);
    388                 this.editor.remove();
    389                 this.$el.show();
    390                 this.editing = false;
    391             },
    392 
    393             clearSelection: function() {
    394                 if (window.getSelection) {
    395                     if (window.getSelection().empty) {
    396                         window.getSelection().empty();
    397                     } else if (window.getSelection().removeAllRanges) {
    398                         window.getSelection().removeAllRanges();
    399                     }
    400                 } else if (document.selection) {
    401                     document.selection.empty();
    402                 }
    403             },
    404 
    405             delete: function() {
    406                 this.model.destroy();
    407                 return false;
    408             }
    409         });
    410 
    411         var EntryEditorView = Backbone.View.extend({
    412             className: 'wcb-entry wcb-entry-editor',
    413             tagName: 'tr',
    414 
    415             events: {
    416                 'click .save': 'editSave',
    417                 'click .cancel': 'editCancel',
    418                 'click .link-toggle': 'linkToggle',
    419                 'keyup': 'keyup'
    420             },
    421 
    422             render: function() {
    423                 this.template = _.template($('#wcb-tmpl-entry-editor').html(), null, template_options);
    424                 this.$el.html(this.template(this.model.toJSON()));
    425                 return this;
    426             },
    427 
    428             editSave: function() {
    429                 this.model.set('amount', parseFloat(this.$el.find('.amount').val()));
    430                 this.model.set('note', this.$el.find('.note').val());
    431                 this.model.set('category', this.$el.find('.category').val());
    432                 this.model.set('value', this.$el.find('.value').val() || null);
    433 
    434                 if (this.linkView)
    435                     this.model.set('link', this.linkView.$el.find('.link-value').val() || null);
    436 
    437                 this.trigger('edit-save.wordcamp-budgets', this);
    438                 if (this.linkView)
    439                     this.linkView.remove();
    440 
    441                 return false;
    442             },
    443 
    444             editCancel: function() {
    445                 this.trigger('edit-cancel.wordcamp-budgets', this);
    446                 if (this.linkView)
    447                     this.linkView.remove();
    448 
    449                 return false;
    450             },
    451 
    452             keyup: function(e) {
    453                 if (e.keyCode == 27) {
    454                     return this.editCancel.apply(this);
    455                 } else if (e.keyCode == 13) {
    456                     return this.editSave.apply(this);
    457                 }
    458             },
    459 
    460             linkToggle: function(e) {
    461                 if (!this.linkView) {
    462                     this.linkView = new EntryLinkView({model: this.model});
    463                     this.$el.after(this.linkView.render().el);
    464                 } else {
    465                     this.linkView.$el.toggle();
    466                 }
    467 
    468                 return false;
    469             }
    470         });
    471 
    472         var EntryLinkView = Backbone.View.extend({
    473             className: 'wcb-entry wcb-entry-editor wcb-entry-link',
    474             tagName: 'tr',
    475 
    476             events: {
    477 
    478             },
    479 
    480             render: function() {
    481                 this.template = _.template($('#wcb-tmpl-entry-link').html(), null, template_options);
    482                 this.$el.html(this.template(this.model.toJSON()));
    483                 return this;
    484             }
    485         })
    486 
    487         var Entries = Backbone.Collection.extend({
    488             model: Entry
    489         });
    490 
    491         var EntriesView = Backbone.View.extend({
    492             tagName: 'table',
    493             className: 'wcb-table',
    494 
    495             initialize: function() {
    496                 this.collection.bind('add', this.addOne, this);
    497                 this.collection.bind('change reset', this.refresh, this);
    498             },
    499 
    500             refresh: function(model) {
    501                 if (model.get('type') == 'meta') {
    502                     this.render.apply(this);
    503                 }
    504 
    505                 return this;
    506             },
    507 
    508             addOne: function(item) {
    509                 var view = new EntryView({model: item});
    510                 switch (view.model.get('type')) {
    511                     case 'expense':
    512                         var $c = $expense;
    513                         break;
    514                     case 'income':
    515                         var $c = $income;
    516                         break;
    517                     case 'meta':
    518                     default:
    519                         var $c = $meta;
    520                 }
    521 
    522                 $c.before(view.render().el);
    523             },
    524 
    525             render: function() {
    526                 $container.find('.wcb-entry').remove();
    527                 this.collection.each(this.addOne, this);
    528                 return this;
    529             }
    530         });
    531 
    532         wcb.categories = {
    533             'venue': 'Venue',
    534             'audio-visual': 'Audio Visual',
    535             'after-party': 'After Party',
    536             'camera-shipping': 'Camera Shipping',
    537             'food-beverage': 'Food & Beverage',
    538             'office-supplies': 'Office Supplies',
    539             'signage-badges': 'Signage & Badges',
    540             'speaker-event': 'Speaker Event',
    541             'swag': 'Swag',
    542             'other': 'Other'
    543         };
    544 
    545         wcb.metaLabels = {
    546             'attendees': 'Attendees',
    547             'days': 'Days',
    548             'tracks': 'Tracks',
    549             'speakers': 'Speakers',
    550             'volunteers': 'Volunteers',
    551             'currency': 'Currency',
    552             'ticket-price': 'Ticket Price'
    553         };
    554 
    555         var table = new EntriesView({collection: new Entries()});
    556 
    557         $income.on('click', function() {
    558             table.collection.add(new wcb.models.Entry({
    559                 type: 'income',
    560                 amount: 0,
    561                 note: 'New Income Item',
    562                 category: 'other',
    563                 new: true
    564             })).editStart();
    565             return false;
    566         });
    567 
    568         $expense.on('click', function() {
    569             table.collection.add(new wcb.models.Entry({
    570                 type: 'expense',
    571                 amount: 0,
    572                 note: 'New Expense Item',
    573                 category: 'other',
    574                 new: true
    575             })).editStart();
    576             return false;
    577         });
    578 
    579         wcb.models.Entry = Entry;
    580         wcb.table = table;
    581     });
    582 }(jQuery));
    583 
    584 jQuery(document).ready(function(){
    585     jQuery(document).trigger('budget-tool-render.wordcamp-budgets');
    586 
    587     _.each(wcb_data, function(i){
    588         wcb.table.collection.add(new wcb.models.Entry(i));
    589     });
    590 });
    591 </script>
Note: See TracChangeset for help on using the changeset viewer.