BorisMoore / jsrender

A lightweight, powerful and highly extensible templating engine. In the browser or on Node.js, with or without jQuery.
http://www.jsviews.com
MIT License
2.68k stars 340 forks source link

Performance Optimization #312

Closed markibanez closed 7 years ago

markibanez commented 7 years ago

Hi @BorisMoore,

Big fan of your work.

I was just wondering if I can still optimize my template further. Loading 600 entries takes around 12 seconds so I feel like I'm doing something wrong. Thanks for your time.

<table>
   <thead>
      <tr>
         <th>&nbsp;</th>
         <th>
            <a href="javascript:void(0);" data-toggle="entry-id-dropdown">
               # <i class="fa fa-toggle-down"></i> 
            </a>
            <div id="entry-id-dropdown" class="dropdown-pane" data-dropdown data-close-on-click="true">
               <div class="row">
                  <div class="small-6 columns">
                     <a href="javascript:void(0);" onclick="SortEntries('EntryID', 1);">Sort Up</a>
                  </div>
                  <div class="small-6 columns text-right">
                     <a href="javascript:void(0);" onclick="SortEntries('EntryID', 2);">Sort Down</a>
                  </div>
               </div>
            </div>
         </th>
         <th>
            <a href="javascript:void(0);" data-toggle="created-dropdown">
               Created
               <i class="fa fa-toggle-down"></i> 
            </a>
            <div id="created-dropdown" class="dropdown-pane" data-dropdown data-close-on-click="true">
               <div class="row">
                  <div class="small-6 columns">
                     <a href="javascript:void(0);" onclick="SortEntries('CreateDate', 1);">Sort Up</a>
                  </div>
                  <div class="small-6 columns text-right">
                     <a href="javascript:void(0);" onclick="SortEntries('CreateDate', 2);">Sort Down</a>
                  </div>                                                                                                                            
               </div>
               <div class="dropdown-pane-divider"></div>
               <div class="row">
                  <div class="small-12 columns">
                     Filter <br />
                     <select data-link="{:StaticKeys[1]^Filter^Value:strToInt}">
                        {{for DateFilters}}
                        <option value="{{:#index}}">{{:Label}}</option>
                        {{/for}}
                     </select>
                  </div>
               </div>
               <div class="row" data-link="class{merge:StaticKeys[1]^Filter^Value != 9 toggle='hide'}">
                  <div class="small-6 columns">
                     <input type="text" data-link="{:StaticKeys[1]^Filter^Amount trigger=true:strToInt}" />
                  </div>
                  <div class="small-6 columns">
                     <select data-link="{:StaticKeys[1]^Filter^Units:}">
                        {{for PastTimePeriods}}
                        <option value="{{:#data}}">{{:#data}}</option>
                        {{/for}}
                     </select>
                  </div>
               </div>
               <div class="row" data-link="class{merge:StaticKeys[1]^Filter^Value != 10 toggle='hide'}">
                  <div class="small-6 columns">
                     <input type="text" class="date-input" data-link="{dateToFormattedDate:StaticKeys[1]^Filter^Start trigger=false:strToDate}" />
                  </div>
                  <div class="small-6 columns">
                     <input type="text" class="date-input" data-link="{dateToFormattedDate:StaticKeys[1]^Filter^End trigger=false:strToDate}" />
                  </div>
               </div>
               <div class="row">
                  <div class="small-12 columns">
                     <a class="button secondary" href="javascript:void(0);" onclick="FilterEntries();">Apply</a>
                  </div>
               </div>
            </div>
         </th>
         {^{for FormFields}}
         <th data-link="class{merge:!show toggle='hidden'} 
            class{merge:TemplateFieldID == 18 toggle='hidden'}
            class{merge:~root.Constants.Numerics.indexOf(TemplateFieldID) > -1 toggle='cell-align-right'}">
            <a href="javascript:void(0);" data-toggle="{{:FieldID + '-dropdown'}}">
               {^{:FieldLabel}}   
               <i class="fa fa-toggle-down"></i>                                                                                                                     
            </a>
            <div id="{{:FieldID + '-dropdown'}}" class="dropdown-pane" data-dropdown data-close-on-click="true">
               {^{if ~root.Constants.NonSortableTypes.indexOf(TemplateFieldID) == -1}}
               <div class="row">
                  <div class="small-6 columns text-left">
                     <a href="javascript:void(0);" onclick="{{:'SortEntries(' + FieldID + ', 1);'}}">Sort Up</a>
                  </div>
                  <div class="small-6 columns text-right">
                     <a href="javascript:void(0);" onclick="{{:'SortEntries(' + FieldID + ', 2);'}}">Sort Down</a>
                  </div>
               </div>
               <div class="dropdown-pane-divider"></div>
               {{/if}}       

               {^{if ~root.Constants.DateFilterable.indexOf(TemplateFieldID) > -1}}                                                     
               <div class="row">
                  <div class="small-12 columns">
                     Filter <br />
                     <select data-link="{:Filter^Value:strToInt}">
                        {{for ~root.DateFilters}}
                        <option value="{{:#index}}">{{:Label}}</option>
                        {{/for}}
                     </select>
                  </div>
               </div>
               <div class="row" data-link="class{merge:Filter^Value != 9 toggle='hide'}">
                  <div class="small-6 columns">
                     <input type="text" data-link="{:Filter^Amount trigger=true:strToInt}" />
                  </div>
                  <div class="small-6 columns">
                     <select data-link="{:Filter^Units:}">
                        {{for ~root.PastTimePeriods}}
                        <option value="{{:#data}}">{{:#data}}</option>
                        {{/for}}
                     </select>
                  </div>
               </div>
               <div class="row" data-link="class{merge:Filter^Value != 10 toggle='hide'}">
                  <div class="small-6 columns">
                     <input type="text" class="date-input" data-link="{dateToFormattedDate:Filter^Start trigger=false:strToDate}" />
                  </div>
                  <div class="small-6 columns">
                     <input type="text" class="date-input" data-link="{dateToFormattedDate:Filter^End trigger=false:strToDate}" />
                  </div>
               </div>                                                            
               {{/if}}

               {^{if ~root.Constants.DistinctFilterable.indexOf(TemplateFieldID) > -1 && Filter.Options.length > 0}}
               <div class="row">
                  <div class="small-6 columns">
                     Filter 
                  </div>
                  <div class="small-6 columns">
                     <a href="javascript:void(0);" class="float-right">None</a>
                     <span class="float-right">&nbsp;&nbsp;</span>
                     <a href="javascript:void(0);" class="float-right">All</a>
                  </div>
               </div>
               <div class="row distinct-options">
                  {{for Filter.Options}}
                  <div class="small-12 columns">
                     <label><input type="checkbox" data-link="{:selected:}" /> {{:label}}</label>
                  </div>
                  {{/for}}
               </div>
               {{/if}}

               <div class="row">
                  <div class="small-12 columns">
                     <label><input type="checkbox" data-link="{:Filter^IncludeEmpty:}" /> Include Blanks</label>
                  </div>
               </div>

               <div class="row">
                  <div class="small-12 columns">
                     <a class="button secondary" href="javascript:void(0);" onclick="FilterEntries();">Apply</a>
                  </div>
               </div>
            </div>
         </th>
         {{/for}}
      </tr>
   </thead>
   <tbody>
      {^{for ~root.Entries}}
      <tr onclick="{{:'SetCurrentEntryIndex(' + EntryID + ')'}}" 
         data-link="class{merge:selected toggle='selected_row'} class{merge:Deleted toggle='deleted'} class{merge:!show || (!~root^ShowDeletedEntries && Deleted) || (!~root^ShowPartialEntries && IsPartial) toggle='hidden'}">

         <td>
            <span class="entries_selector" onclick="{{:'ToggleEntrySelection(' + EntryID + ', event)'}}">
               <i class="fa fa-align-justify fa-1x"></i>
            </span>
         </td>

         <td>        
            <a href="javascript:void(0);" onclick="{{:'SetCurrentEntryIndex(' + EntryID + ')'}}">                                                
               {{:EntryID}}
            </a>
         </td>    

         <td>
            <a href="javascript:void(0);" onclick="{{:'SetCurrentEntryIndex(' + EntryID + ')'}}">
               {{GlobalizeDateTime:CreateDate}}
            </a>
         </td> 

         {{props #data ~fks=~root.FormKeys ~cs=~root.Constants ~eid=EntryID}}
            {{if prop.tid}}                                                            
            <td class="{{:~cs.Numerics.indexOf(prop.tid) > -1 ? 'cell-align-right' : ''}}"
               data-link="class{merge:!prop.show || prop.tid == 18 toggle='hidden'}">
               <a onclick="{{:'SetCurrentEntryIndex(' + ~eid + ')'}}" 
                     href="{{:~cs.FileUploadTypes.indexOf(prop.tid) == -1 ? 'javascript:void(0);' : prop.value}}"
                     class="{{:~cs.ImageUploadTypes.indexOf(prop.tid) > -1 toggle='th'}}">

                  {{if ~cs.ImageUploadTypes.indexOf(prop.tid) > -1}}
                  <img alt="" class="entry_image" src="{{:prop.value || '/static/images/noImageUploaded.png'}}" />

                  {{else ~cs.FileUploadTypes.indexOf(prop.tid) > -1}}
                  <span>
                     {^{:prop.value}}
                  </span>

                  {{else ~cs.SignatureTypes.indexOf(prop.tid) > -1}}
                  <img alt="" class="sig_entry_image {{:!prop.value ? 'hidden' : ''}}" src="{{:prop.value || ''}}" />

                  {{else ~cs.Number.indexOf(prop.tid) > -1}}
                  <span class="truncate entry-table-span">
                     {^{formattedWholeNumber:prop.value}}
                  </span>                                                                

                  {{else prop.tid == 9}}
                  <span class="truncate entry-table-span">
                     {^{strToDateCultured:prop.value}}
                  </span> 

                  {{else}}
                  <span class="truncate entry-table-span">
                     {^{:prop.value}}
                  </span> 
                  {{/if}}                              
               </a>    
            </td>

            {{/if}}                                                  

         {{/props}}      

      </tr>                                       
      {{/for}}
   </tbody>
</table>
BorisMoore commented 7 years ago

Hi Mark,

Can you provide some sample data, or better, create a jsfiddle using this template?

I assume you are only using data-link or {^{... data binding in places where your data is going to be modified observably, right?

BTW it might be better to create a JsViews issue to replace this one, since the perf is going to include the initially data-linking, not just the rendering... so it may relate more to JsViews.

markibanez commented 7 years ago

Closing this and creating an issue in JsViews