Nápad: JsWriter podobně jako PhpWriter?
- LeonardoCA
- Člen | 296
Chci zjednodušit implementaci některých komponent založených z velké
části na Javascriptu.
Nejprve hlavně datatables.net a
kompletní podporu pro Bootstrap
později třeba JQueryUI a HighCharts
Napadlo mne inspirovat se třídou PhpWriter z nette a napsat JsWriter.
Jde mi hlavně o knihovny, které k inicializaci vyžadují někdy i stránkovou konfiguraci a psaní javascriptu by se mohlo svěřit komponentě…
Myslíte si, že je to dobrý nápad?
Případně máte nějaké tipy jak na to nebo co by knihovna měla umět?
- LeonardoCA
- Člen | 296
Pár dotazů, pokud někdo bude mít nápady.
1. Zatím konfiguraci řeším pomocí šablony inline javascriptu, který si generuje komponenta podle svého nastavení. Inline javascriptu bych se chtěl vyhnout a později zajistím přesun do externího javascriptu. Ale nevím přesně jaká bude nejlepší cesta, protože každá instance komponenty datatable, může mít jinou konfiguraci. A když bude v projektu třeba 50 různých tabulek, stahovat konfiguraci pro všechny najednou nebo raději ke každé stránce připojit krátký javascript pro komponenty na aktuální stránce?
Základní funkční, zatím neúplná konfigurace pro datatables a server side processing vypadá takto:
<script type="text/javascript">
/* <![CDATA[ */
(function($, undefined) {
$.lxoDatatables.add ({$name}, function (datatable) {
$('#'+datatable).dataTable({
//"sDom": "<'row'<'span3'l><'span3'f>r>t<'row'<'span2'i><'span4'p>>",
//"sDom": 'R<"H"lfr>t<"F"ip>',
//"bJQueryUI": true,
//"sPaginationType": "full_numbers",//"two_button"
"sPaginationType": "two_button",//"two_button"
"iDisplayLength": {$iDisplayLength},
"bFilter" : {if $bFilter}true{else}false{/if},
"bStateSave": true,
"iCookieDuration": 60*60*24, // 1 day
"aoColumns": [
{foreach $columns as $c}
{ "bSortable": {if $c->options['bSortable']}true{else}false{/if},
"bSearchable": {if $c->options['bSearchable']}true{else}false{/if},
{if $c->options['sWidth']}"sWidth": {$c->options['sWidth']}{/if},
"bVisible": {if $c->options['bVisible']}true{else}false{/if}
}
{/foreach}
],
"bProcessing": true,
"bServerSide": true,
//"bDeferRender": true,
"bInfo": false,
//"aaSorting": [[0,'desc']],
"sAjaxSource": {link //serverSide},
"oLanguage": {
"sSearch": "Hledej:",
"sInfoEmpty": "",
"sZeroRecords": "Nebyly nalezeny žádné položky",
"sLoadingRecords": "Vyčkejte prosím - nahrávám...",
"sProcessing": "Zpracovám...",
"sInfo": "_START_ - _END_ z _TOTAL_ položek",
"sInfoFiltered": '',// " (bez filtru _MAX_ položek)",
"sLengthMenu": "Zobrazovat _MENU_ položek",
"oPaginate": {
"sFirst" : "První",
"sLast" : "Poslední",
"sPrevious" : "Předchozí",
"sNext" : "Další",
}
},
"fnDrawCallback": function( oSettings ) { // ajaxify any new dom elements and add callback
$.nette.load();
{foreach $jsDrawCallbacks as $dc}{!$dc}{/foreach}
}
})
});
})(jQuery);
$(document).ready(function() {
$.lxoDatatables.init({$name});
});
/* ]]> */
</script>
2. Teď ale řeším jak zjednodušit a zpřehlednit například zápis hodnot konfiguračních parametrů v závislosti na datovém typu:
Např:
"bSearchable": {if $c->options['bSearchable']}true{else}false{/if},
nebo v závislosti na tom, je-li parametr potřeba (nemá defaultní hodnotu)
{if $c->options['sWidth']}"sWidth": {$c->options['sWidth']}{/if},
Mohl bych si definovat třeba makra pro jednotlivé datové typy nebo nějak jinak?
3. Případně, kdybych chtěl vykreslovat celé pole parametrů v cyklu, jak si určit datové typy jednotlivých položek? (Tentýž problém bych řešil i v případě JSgenerátoru). Vytvořit pro pole parametrů php třídu a v ní pomocí anotací označit datové typy jednotlivých položek? A to pak renderovat? To by zase vznikl problém se zanořenými poli.
- akadlec
- Člen | 1326
Ahoj, je to sice trošku jiné řešení než tady máš ty ale konkrétně ty DataTables řeším tak že si presenteru či komponentě jednoduše sestavím objekt/pole s danou konfigurací pro datatables, tu pak přenesu do šablony a tam ji přidám do vybraného elementu (třeba TABLE) do atributu data-settings jako json string. V JS co aktivuje dataTables pak udělám jen vytažení těchto settings a případně je ještě změrguju s default hodnotama co sou natvrdo v js (shodné napříč všemi datatables instancemi) a pak je inituju.
Za cíl teď mám z toho udělat plnohodnou komponentu jako je datagrid apod, aby se dalo v presenteru zadefinovat jaké sloupce tam mají být, buttony atd a pak to vykreslit.
- LeonardoCA
- Člen | 296
Taky jsem o té možnosti uvažoval a z nějakého důvodu jsem zvolil jinou cestu.
Implementaci Datatables jako datagrid už mám skoro hotovou, včetně:
- podpory složených primárních klíčů
- tlačítek
- multicolumn sorting, search
- možnost definování string template pro buňku
- možnosti vkládání komponent do buněk tabulky
- ukládání stavu
- autotable – kdy se předá komponentě jen název tabulky a sloupce se vygenerují automaticky
- server side processing – requesty běží přes nette.ajax.js
Chybí ještě
- podpora asi poloviny ze stovky options, které datatables mají
- hromadné operace /zatím jsem nepotřeboval/
- inline editace /řeším většinou na míru a needituje se všechno/
- podpora jquery ui a vlastních stylů /používám bootstrap/
- podpora pořádných custom filtrů – plánují samostatnou komponentu pro filtry, která se bude dát použít i jinde, třeba na grafy
a hlavně refactoring a clean up, abych to mohl zveřejnit…
- ViPEr*CZ*
- Člen | 817
Teď jsem něco na tu myšlenku zkoušel řešit pro jQuery UI Dialogy. Mám
napsaný plugin, který mi zjednošil kapku psaní dialogů, ale problém je
furt ve znalosti spousty settings a Netbeans IDE mi práci neulehčí, prostě
furt se musím dívat do zdrojáku. Přes PHP komponentu by mě uměl Netbeans
napovídat a zjednodušilo by to život.
Zeptám se… co ti to generuje tady:
{foreach $jsDrawCallbacks as $dc}{!$dc}{/foreach}
- LeonardoCA
- Člen | 296
To je starý kód, už nevím přesně co jsem tím řešil. Asi navěšení js eventů po překreslení tabulky. Nová šablona vypadá takto:
{capture $datatableRendered}
<table id="{$name}" class="datatable table table-bordered table-striped table-condensed table-hover">
</table>
<script>
(function ($, undefined) {
$('#{!$name}').dataTable({
"sDom":"<'row'<'span{$spanLeft}'l><'span{$spanRight}'f>r>t<'row'<'span{$spanLeft}'i><'span{$spanRight}'p>>",
"sPaginationType":"bootstrap",
"aLengthMenu":[
[5, 10, 20, 50],
[5, 10, 20, 50]
],
"iDisplayStart": {(int)$params['iDisplayStart']},
"iDisplayLength": {(int)$params['iDisplayLength']},
"bFilter": {$bFilter},
"bPaginate": {$bPaginate},
"bSort": {$bSort},
"bLengthChange": {$bLengthChange},
"bInfo": {$bInfo},
"sSearch": {$params['sSearch']},
"bDeferRender":true,
{if $sScrollX}"sScrollX": {$sScrollX},{/if}
//"sScrollY": "300px",
//"bScrollInfinite": true,
//"bScrollCollapse": true,
//"sScrollXInner": "110%",
//"iCookieDuration": 60*10,
"aoColumns":[
{foreach $columns as $c}{
"bVisible": {if $c->options['bVisible']}true{else}false{/if},
"bSortable": {if $c->options['bSortable']}true{else}false{/if},
"bSearchable": {if $c->options['bSearchable']}true{else}false{/if},
{if $c->options['sWidth']}"sWidth": {$c->options['sWidth']},{/if}
"sTitle": {$c->options['sTitle']}
}{if !$iterator->last},{/if}{/foreach}
],
"bProcessing":false,
"bServerSide":true,
"sAjaxSource": {link ServerSide},
"sServerMethod":"POST",
"iDeferLoading":[{$iTotalDisplayRecords}, {$iTotalRecords}],
{if !empty($sortingParams)}
"aaSorting":[{foreach $sortingParams as $i => $sorting} [{$sorting['col']}, {$sorting['dir']}]{if !$iterator->last},{/if}{/foreach} ],
{/if}
//"sAjaxDataProp": "dataTablesData.{!$name}.aaData",
"oSearch": {'sSearch': {$params['sSearch']}},
/*"oLanguage": {
"sInfoFiltered": ''
},*/
"bStateSave":false,
"fnStateSave":function (oSettings, oData) {
$.nette.ajax({
url: {link SaveState},
dataType:"json",
data:oData,
type:"POST",
success:function (oData, textStatus, jqXHR) { //console.log('saved');
}
});
},
"fnStateLoad":function (oSettings) {
var o = false;
$.nette.ajax({
type:"POST",
url: {link LoadState },
dataType:"json",
async:false,
success:function (json, textStatus, jqXHR) {
o = json.datatablesLoadState[{$uniqueId}];
}
});
return o;
},
"fnDrawCallback":function (oSettings) {
$.nette.load();
{foreach $jsDrawCallbacks as $dc}{!$dc}{/foreach}
{if $options['editable']}
$('.editable').editable({
showbuttons:false,
onblur:'submit',
source: {link EditableData},
sourceCache:true,
url:function (params) {
$.nette.ajax({
url: {link Editable},
dataType:'json',
data:params,
type:'POST',
success:function (oData, textStatus, jqXHR) {
//console.log(textStatus);
},
error:function (payload) {
//console.log('payload');
//var d = new $.Deferred;
//return d.reject('error message'); //error via deferred object
}
});
}
});
{/if}
{if $options['toggle']}
$(".toggle-checkbox").toggleButtons({
animated:true,
width:90,
height:18,
label:{ enabled:"yes", disabled:"no"},
style:{ enabled:"success", disabled:"warning"},
onChange:function ($el, status, e) {
$.nette.ajax({
type:"POST",
url: {link Toggle},
data:{ id:$el.find("input").attr("id"), value:status},
success:function (data, textStatus, jqXHR) {
}
});
}
});
{/if}
},
"fnServerParams":function (aoData) {
{foreach $aoParams as $paramName => $value}
{if $value != "nobacklink"}
aoData.push({ "name": {$paramName}, "value": {$value} });
{/if}
{/foreach}
},
"fnServerData":function (sSource, aoData, fnCallback, oSettings) {
$.nette.ajax({
type:'POST',
url:sSource,
data:aoData,
success:function (data, textStatus, jqXHR) {
if ("aaData" in data) {
fnCallback(data);
}
}
});
},
"fnInitComplete":function (oSettings, json) {
this.fnSetFilteringDelay({$searchDelay});
},
"aaData": {!$aaData}
})
})(jQuery);
</script>
{/capture}
{!$datatableRendered}
{if $dispSetting}
<pre class="prettyprint linenums pre-scrollable">{$datatableRendered}</pre>
<script>prettyPrint();</script>
{/if}
Tak se dívám, že ten řádek tam mám pořád, nemám čas se teď dívat na co přesně jsem to použil, ale je to na to aby se dal přidat nějaký js callback pro speciální případy, kdy potřebuji ještě nějakým prvků navěsit eventy. To řešení se mi moc nelíbí a budu to pravděpodobně předělávat.
Editoval LeonardoCA (7. 3. 2013 13:51)
- LeonardoCA
- Člen | 296
Bohužel, je to pořád experimentální :-) Jsem psal, že to potřebuje refaktoring.
Btw. co konkrétně se ti nejvíc nelíbí a jak by jsi na to šel, když už jsme u toho?
- Filip Procházka
- Moderator | 4668
- První chyba: proměnné v javascriptu
- Druhá chyba: javascript v šablonách
Ani k jednomu není důvod. Všechna data a přepínače bych vložil do
data-
attributů a podle nich konfiguroval javascript.
- Vojtěch Dobeš
- Gold Partner | 1316
Pravda pravdoucí. Bude to výkonnější a optimalizovanější, ale taky se to i bude dát přečíst :). Z tohohle bolí hlava.
- LeonardoCA
- Člen | 296
souhlas, ve finále bych to chtěl mít v externím js
jeden háček – jak vložit do data atributů custom js callbacky?
Vzpomněl jsem si proč jsem to začal dělat takto. Myšlenka byla, že by se to dalo použít i jako jen takové demo/konfigurátor pro ty co si chtějí jen nakonfigurovat datatables js. A v tom případě, má taková šablona smysl a nebo nějaký js writer, jak jsem se původně ptal…
- Vojtěch Dobeš
- Gold Partner | 1316
Custom callbacky mají být v Javascriptu. Konfigurovat všechno v PHP je
cesta do pekel, časem nebudou konfigurační možnosti stačit a bude třeba
napsat nějaký JsWriter
… jakoby tomu prozřetelnost chtěla,
dneska jsem četl článek, který je někde v jádru tomuto velmi
blízký: http://www.drmaciver.com/…re-we-doing/
- LeonardoCA
- Člen | 296
ok, dík za kritiku… přepíšu to
Ještě dotaz. Má smysl se zabývat tím,když budu mít řekněme 10 různých možných callbacků a na konkrétní tabulce bude potřeba volat jen 2 z nich. Aby se volaly právě jen ty dva? Napadá mne přidat si „data-callbacks“ atribut, kde budou názvy těch callbacků, které je potřeba volat.
- Vojtěch Dobeš
- Gold Partner | 1316
O jaké callbacky kupříkladu jde? Samozřejmě že musí být mezi Javascriptem a PHP nějaké API, kterým mohou komunikovat (prostředkem jsou ideálně ty datové atributy), nicméně čím menší vazba, tím lepší. Rozhodně bude lepší navrhnout nějaké parametry, než aby PHP muselo znát názvy nějakých funkcí v Javascriptu. Ale možná jen plkám, nevím, o jakém kódu mluvím.
- LeonardoCA
- Člen | 296
Právě o ty callbacky které je potřeba volat ve funkci „fnDrawCallback“ – tj. například aktivace toggle a editable bootstrap from twitter pluginů.
Editoval LeonardoCA (7. 3. 2013 15:17)
- akadlec
- Člen | 1326
nechcu vám tady do toho moc kecat, ale to je to co sem psal výše ;) v komponentě sestavím pole/objekt configu co je editovatelný, proženu to json encoderem a v šabloně to bouchnu do data atributu ;) pak v JS kde dělám nějaký init si k tomu dom elementu přistoupím, vytáhnu potřebný data atribut, spojím s defaultníma hodnotama a initnu datatables.
@leonardoCA: callbacky sem taky řešil, ale díky tomu že ty draw, redraw, savestate atd jsou stejné napříč celou appkou tak je mám normálně v tom skriptu co inituje datatables. Ale jinak klobouk dolu pokud to děláš takto hodně uni.