Nápad: JsWriter podobně jako PhpWriter?

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
LeonardoCA
Člen | 296
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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…

akadlec
Člen | 1326
+
0
-

No tak to seš na tom cca tak jak já ;) Taky dělám implementaci DataTables ale asi né tak do hloubky jako ty. Já jsem to postavil nad Niftyho datagridem. Všechny možnosti co datatables umi tam dávat nehodlám, protože to stejnak nevyužiju a málo kdo to využije ;)

ViPEr*CZ*
Člen | 817
+
0
-

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
+
0
-

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)

Filip Procházka
Moderator | 4668
+
0
-

Z „nové šablony“ je mi smutno, tobě ne?

LeonardoCA
Člen | 296
+
0
-

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
+
0
-
  • 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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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.