Una part fonamental del desenvolupament web és guardar les dades. En la part client això consisteix en enviar les dades a la part servidora, en el cas que ens ocupa els ASHX.
Com tots els formularis de l'aplicació web envien les dades de la mateixa manera, tots fan servir la mateixa funció: SubmitFrm.
Aquesta funció és força senzilla i fa servir la serialització de jQuery per empaquetar els camps d'un formulari i posteriorment enviar-los al servidor amb Ajax. Normalment la URL on s'envia porta informat el paràmetre "action" amb el valor "update". D'aquesta manera la part servidora fa un UpdateData que invoca a un UPD de la clsDades (bé de fet de la clase derivada corresponent).
A vegades també és necessari efectuar alguna operació un cop s'han guardat les dades. Per exemple recarregar el grid perque refresqui els valors o bé posar el valor de la clau primària generada en un "insert" a la variable que toca. Per això estan els paràmetres pPostSubmit i pFunctionPostSubmit. El primer és un booleà que indica si s'ha de fer algo més després de guardar les dades i el segon és la funció que s'ha de cridar.
Normalment aquesta funció SubmitFrm es crida des del botó Guardar.
function SubmitFrm(pForm, pURL, pPostSubmit, pFunctionPostSubmit) {
if (pPostSubmit === undefined) pPostSubmit = false;
//El formulari ha de validar per enviarif ($(pForm).validate().form()) {
//Serialitzacióvar frmdata = $(pForm).serialize();
//Ajax
$.ajax({
type: "POST",
url: pURL,
data: frmdata,
dataType: "text",
contentType: "application/x-www-form-urlencoded; charset=ISO-8859-1",
success: function (data, textStatus) {
if (data.indexOf("ERROR:") != -1) {
$("#ui-dialog-title-dialogErr").html("Error guardant les dades");
$("#errmsg").html(data);
$("#dialogErr").dialog("open");
}
else {
$("#ui-dialog-title-dialogOK").html("Dades guardades correctament");
$("#okmsg").html("Dades guardades correctament.");
$("#dialogOK").dialog("open");
//Crida a la funció PostSubmitif (pPostSubmit) {
if (pFunctionPostSubmit !== undefined) pFunctionPostSubmit(data);
}
}
}
});
}
}
Una funció PostSubmit d'exemple podria ser:
function ClassificacioPostInsert(pData) {
evPK_Classificacio = pData;
jQuery("#grdClassificacions").jqGrid().trigger("reloadGrid");
ClassificacioUpdate(pData);
}
Aquesta funció actualitza la variable global que té la clau primaria (PK), refresca el grid i posa el formulari en mode "actualitzar". Dins del paràmetre pData arriba la PK que és l'únic que envia un Insertar desde la part servidora. Una funció NNNNNNNPostUpdate és més senzilla ja que només té la línia que refresca el grid.
#04/02/2014 11:54 Programació Javascript Autor: Alex Canalda
Aquest cap de setmana passat hem visitat el castell de Cardona. Quan vam visitar la mina de sal no ens va donar temps a visitar el castell i per això ho hem pogut fer aquest diumenge.
Ens hem afegit a la visita guiada que comença a les 13h, val 6EUR/persona i dura 1h 30m. El cert és que està molt bé, la guia ho explica tot de forma molt detallada, amb dates, fets, i el perque de tot plegat. Val molt la pena.
A Cardona hi ha un "Parador Nacional" que ocupa molt tros del castell, per sort estava tancat per vacances (del personal suposo) i només estàvem els que fèiem la visita guiada, com a punt negatiu no vam poder veure res de l'espai que ocupa el Parador.
En la visita es puja a la torre de guaita, allí expliquen que aquella torre tenia 25m més d'alçada que l'actual, però que es amb l'aparició de l'artilleria es va reduir l'alçada. També la torre servia de presó medieval on tenia una ocupació mitjana de 10 presoners i una esperança de vida de 30 dies (cal veure el lloc per entendre perque, i no té desaigües...). El castell data del S.X, any 900 i pico, i era un castell de frontera amb al-Andalus, es a dir el territori àrab començava de Cardona cap al sud.
Durant la visita es comenten varis punts, la torre, el pati del palau ducal, l'església i la casamata. Un cop es surt de la torre s'explica que amb la gran quantitat de diners que sorgien de l'explotació de la mina de sal els comptes de Cardona volien una palau on viure millor, per això van fer el palau ducal (que només es veu per fora). El palau tenia accés a aigua de la cisterna per un forat directe i accés a l'església per una plataforma elevada (per tal de no barrejar-se amb la gent normal).
L'església és impressionant, la mida dóna idea del poder dels nobles, van trigar 30 anys en fer-la. La guia explica que tot estava pintat amb frescos per dins, aquests frescos servien per explicar al poble analfabet el cristianisme. Però que l'ús militar que va tenir posteriorment a l'ús religiós els va malmetre. L'església durant l'ús militar es va dividir en dos pisos amb una estructura de fusta per convertir-la en allotjament de soldats (fins a 700), en la guerra civil va ser presó republicana i franquista. Llavors clar, tots els frescos es van fer malbé, encara que queda algun rescatat que està al MNAC. També és curiós que a la cripta no hi ha enterrat ningú, en realitat eren les oficines del mestre d'obres/arquitecte de l'església, ja que és una versió en miniatura de la mateixa. També dins l'església queda reflectit la diferència de poders, el més alt correspon a la noblesa, després l'altar (poder diví), les primeres files per la burgesia (artesans, etc...) i al darrera de tot els camperols.
La casamata és una bateria d'artilleria del S. XVIII restaurada, allí la guia explica la guerra de successió i el paper que va jugar Cardona. Explica el setge més important que va tenir el castell al 1711 i que va aconseguir aguantar, encara que va patir de manca d'aigua al contaminar-se les cisternes. La fortalesa no va caure en mans de l'enemic borbònic i un exèrcit austriacista va trencar el setge guanyant una batalla força sagnant. I no va ser fins al 18 de setembre de 1714 que no es va rendir un cop caiguda Barcelona.
Dins del desenvolupament web, en el javascript de la part client, cal carregar dades en un formulari per editar-les. És aleshores quan entra en joc aquesta funció.
Aquesta funció és relativament senzilla, el primer que fa és netejar els controls del formulari, després fa una crida Ajax a la part servidora, normalment a un ASHX amb el paràmetre "action" amb el valor "data". Això fa que la part servidora faci una crida a la funció LoadData (de la part servidora) i obtingui les dades. Aquestes es serialitzen al ASHX i s'envien al client, on es deserialitzen col·locant els valors corresponents als controls del formulari.
Cal recordar que perque aquesta deserialització funcioni i cada control rebi el valor que li pertoca cal que el control tingui el mateix nom que el camp del JSON. Val més una imatge que mil paraules:
Un cop deserialitzades les dades el formulari s'ha de posar en mode "actualitzar", de tal manera que a l'apretar el botó "Guardar" faci una actualització contra el ASHX corresponent. Com sempre el codi:
function LoadClassificacioData(pPK_Classificacio) {
//Actualització variable global que guarda la clau primària (PK)
evPK_Classificacio = pPK_Classificacio;
//Neteja del formulari
$('#frmDetall :input')
.not(':button, :submit, :reset, :checkbox')
.val('')
.removeAttr('selected');
$("#frmDetall :checkbox").removeAttr('checked');
//Recuperació
$.ajax({
type: "GET",
url: "classificacions.ashx?action=data&PK_Classificacio=" + pPK_Classificacio,
success: function(data, textStatus) {
if (!data.ERROR) {
var config = {};
config.isPHPnaming = false;
config.overwrite = true;
//Deserialització
$('#frmDetall').deserialize(data, config);
//Mode update
ClassificacioUpdate();
}
else {
alert("ERROR: " + data.ERROR);
}
}
});
}
#03/02/2014 11:12 Programació Javascript Autor: Alex Canalda
En la part client, un cop carregat l'HTML el que cal fer és assignar-li events i crear els controls que han d'anar al formulari. Això només cal fer-ho un cop. Per això poso una funció CreateNNNNNEvents (on NNNNN és el nom de l'entitat que estem tractant, o el del formulari). Aquesta funció no té paràmetres, i es crida des del document.ready. Un exemple de document.ready, on també es crea el menú:
<script type="text/javascript">
$(document).ready(function() {
ddsmoothmenu.init({
mainmenuid: "smoothmenu1", //menu DIV id
orientation: 'h', //Horizontal or vertical menu: Set to "h" or "v"
classname: 'ddsmoothmenu', //class added to menu's outer DIV
contentsource: "markup"//"markup" or ["container_id", "path_to_menu_file"]
});
CreateClassificacioEvents();
});
</script>
Ara cal veure un exemple de la creació de controls, primer la càrrega de combos:
function CreateClassificacioEvents() {
LoadCombos("frmFiltre", "PK_Arxiu", "", false, undefined, undefined, true, '');
LoadCombos("frmDetall", "afegirTAAD", "", true, undefined, undefined, true, '');
//... TO BE CONTINUED...
La funció LoadCombos està definida en el helper.js, i només ella soleta mereix un post apart.
El que sí es pot comentar en aquesta crida és que només es carrega un combo per cada formulari, si un formulari en té més d'un aleshores es posen separats per guions. El màxim que he vist han estat 9 combos en un formulari. En el post de la funció LoadCombos s'especifiquen cadascun dels seus paràmetres. Un cop carregats els combos cal posar la resta de controls, per exemple els calendaris:
Aquests calendaris estan localitzats (tenen un idioma en concret, el "ca"-català), però no tenen la part de l'hora. Si es volen amb hora cal veure el post dels calendaris amb hora.
Aquesta és la instanciació d'un grid. Es defineixen les columnes, el ASHX d'on es treuen les dades amb l'acció "loadgrid" de la part servidora. L'event important és quan es fa clic en una fila, "onSelectRow", la funció LoadNNNNNNData recuperarà les dades amb Ajax per omplir el formulari detall (en el post de la part client es veu quin és aquest formulari).
Definim un autocomplete, els autocomplete normalment tenen una action autocompleteNNNN en el ASHX corresponent. Un cop a la part servidora habitualment es fa servir un GET que tindrà un paràmetre que farà un LIKE a la BBDD, per evitar que es saturi el propi control té una cache, i per evitar que retorni massa resultats ajustem la propietat minChars.
Per costum (no per res) poso els events click dels botons al final. El botó d'eliminar registre fa el mateix a tots els formularis, la funció DeleteElem está al helper.js.
Una cosa que s'espera d'un formulari de cerca és que al apretar "enter, intro, return" el formulari es posi en marxa. El codi assigna a tots els inputs del formulari de filtrar l'event corresponent per fer això. Finalment es posa el formulari en mode inserció perque no té cap registre carregat, quan es faci clic en algun element del grid aleshores es passa a mode edició.
Aquests events i controls són els més típics, però cada formulari és un cas i el més habitual és hi hagi moltes més coses definides. Per exemple un multiple upload de fitxers (imatges).
#29/01/2014 16:36 Programació Javascript Autor: Alex Canalda
Dins de les aplicacions web, el Javascript de la part client és el que més costa de fer. I és cert, és la que acostuma a donar problemes. Els problemes que m'acostumo a trobar són incompatibilitats entre navegadors. Això amb el jQuery es mitiga força, però igualment algun problema es troba. De moment no he trobat cap que sigui irresoluble, simplement són una molèstia amb la que s'ha de viure.
Dins del Javascript hi ha dues parts diferenciades, una que crea els events i controls en l'esquelet HTML i una altra part que gestiona el funcionament del formulari un cop està en marxa.
Hi ha controls força senzills, que amb una línia es creen, altres necessiten una bona parrafada (per exemple un grid). Tota la creació de controls normalment es realitza en la funció que anomeno "CreateNNNNEvents", on NNNNN és el nom de la pantalla on està el Javascript. Aquesta funció només s'executa una única vegada. Un cop creats els controls el que acostuma a passar és que es modifiquin, per exemple si es filtra un grid, si el valor d'un combo restringeix els valors d'un altra combo, etc...
Hi ha tot un conjunt de funcions importants que com són sempre les mateixes, estan agrupades en el helper.js. Aquestes funcions són les encarregades de serialitzar/deserialitzar les dades per enviar-les a la part servidora, carregar els combos (es a dir invocar al comboloader i deserialitzar els resultats posant-los als combos corresponents).
#29/01/2014 13:05 Programació Javascript Autor: Alex Canalda
Aquesta és la setena i última novel·la de la serie del Reis Maleits.
L'estil és molt diferent, està explicada en primera persona. Tots els personatges de les anteriors novel·les han mort (en batalla o de vells) i aquí explica la història un cardenal del Perigord que viatja cap a Metz per reunir-se amb l'emperador Alemany.
Spoilers: La història que explica el cardenal tracta principalment del regnat de Juan II. Pel que explica va ser un rei desastrós, tant políticament com militarment. La seva gestió va ser nefasta per França i els anglesos van guanyar molt terreny durant el seu regnat. Explica diferents episodis, un sopar on capturen uns quants nobles i els executen (cosa mal vista sense judici), un setge a un castell sense importància mentre altres assumptes requereixen més atenció (amb uns resultats molt dolents) i com a culminació de tanta ineptitud la batalla de Poitiers (1356) on Juan II perd contra una armada anglesa inferior i a sobre el fan presoner.
El llibre acaba quan el cardenal arriba a Metz, i acaba bruscament, sembla com si a aquest llibre li falti una continuació. Falta algo on es vegin les conseqüències d'aquell regnat desastrós. I veient només el material disponible a la Wikipedia, hi ha per varis llibres més.
A part del tema del final, és una lectura molt recomanable.
Dins del desenvolupament web, hi ha la part més fàcil que és muntar en HTML el formulari que es farà servir per editar dades. L'HTML pertany al que s'anomena part client. Quan es genera l'HTML que dóna estructura a la part client no es posen estils (excepte casos concrets), ni events, ni dades. El que obtenim és un HTML senzill d'entendre i de mantenir, sense barrejar conceptes.
Normalment, tota l'aplicació web fa servir els mateixos fitxers de Javascript i els mateixos CSS, això juntament amb la validació de seguretat ho poso en una MasterPage.
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Blog.Master.cs" Inherits="Blog.Master.Blog" %>
<%
if (Session["IdLogin"] == null) Response.Redirect("login.htm");
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<headrunat="server">
<meta http-equiv="X-UA-Compatible" content="IE=8" />
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<link rel="icon"href="~/img/favicon.gif"type="image/x-icon" />
<!-- CSS UI/Grid -->
<link rel="stylesheet"type="text/css"href="~/css/blitzer/jquery-ui-1.8.14.custom.css" />
<link rel="stylesheet"type="text/css"href="~/css/grid/ui.jqgrid.css" />
<!-- CSS Autocomplete-->
<link rel="stylesheet"type="text/css"href="~/css/jquery.autocomplete.css" />
<link rel="stylesheet"type="text/css"href="~/css/thickbox.css" />
<!-- CSS Menu-->
<link rel="stylesheet"type="text/css"href="~/css/ddsmoothmenu.css" />
<!-- CSS general -->
<link rel="stylesheet"type="text/css"href="~/css/Manteniment.css" />
<!-- CSS dateTime picker addon -->
<link rel="stylesheet"type="text/css"href="~/css/jquery-ui-timepicker-addon.css" />
<!-- jQuery Base -->
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/jquery-1.6.2.js")%>"></script>
<!-- Scripts del UI -->
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/jquery-ui-1.8.14.custom.min.js")%>"></script>
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/ui.datepicker-ca.js")%>"></script>
<!-- Scripts del Autocomplete -->
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/jquery.bgiframe.min.js")%>"></script>
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/jquery.ajaxQueue.js")%>"></script>
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/thickbox-compressed.js")%>"></script>
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/jquery.autocomplete.js")%>"></script>
<!-- Scripts del grid -->
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/grid/i18n/grid.locale-cat.js")%>"></script>
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/grid/jquery.jqGrid.min.js")%>"></script>
<!-- Script del Menu -->
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/ddsmoothmenu.js")%>"></script>
<!-- Script de Deserialitzacio -->
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/deserialize.js")%>"></script>
<!-- Script del Validador -->
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/jquery-validate/jquery.validate.js")%>"></script>
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/jquery-validate/localization/messages_ca.js")%>"></script>
<!-- Script del DateTime picker addon -->
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/jquery-ui-timepicker-addon.js")%>"></script>
<!-- Script del Helper -->
<scripttype="text/javascript"src="<%=ResolveUrl("~/js/helper.js")%>"></script>
<styletype="text/css">
#capcalera td
{
padding: 5px 5px 5px 5px;
}
</style>
<!-- PLACEHOLDER DEL HEAD --><asp:ContentPlaceHolder ID="head"runat="server"></asp:ContentPlaceHolder>
</head>
<body>
<tableid="capcalera"border="0"cellspacing="0"style="width: 100%;">
<tr>
<td><ahref="<%=ResolveUrl("~/Blog.aspx")%>">
<imgsrc="~/img/laughing_man_p.jpg"alt="Manteniment del blog"runat="server" /></a></td>
<tdid="logout">Manteniment del Blog (<ahref="<%=ResolveUrl("~/login.ashx")%>?action=logout">Sortir</a>)</td>
</tr>
</table>
<!-- PLACEHOLDER DEL BODY --><asp:ContentPlaceHolder ID="Contingut"runat="server"></asp:ContentPlaceHolder>
</body>
</html>
Aquesta MasterPage permet que tota l'aplicació tingui la mateixa política de seguretat (excepte la pantalla de login) i també tingui una estructura uniforme. Si l'aplicació té un menú també es posa a la MasterPage. Hi ha dos "placeholders" (en negreta al codi), un pel cap ("head") i una altra pel cos ("body"). Les pàgines filles ompliran aquests dos "forats" amb el seu propi contingut, es a dir cada pàgina farà servir la MasterPage i només tindrà un apartat "head" i un "body", sense declarar res més. Això facilita molt el manteniment, per exemple si es vol canviar una versió d'un component només fent la substitució a la MasterPage hi ha prou. Veiem un exemple del head:
En el head va ben poca cosa, però important. El titol de la pàgina, el seu fitxer Javascript particular que gestionarà la pàgina, i la crida a la funció que crea els events. Aquesta funció que crea els events només es crida quan la pàgina ja està carregada. Ara toca un exemple del body, que és força net:
Hi ha detalls MOLT importants que comentar sobre un HTML que sembla tan inofensiu. La forma de maquetar amb taules o div, això ja va a gust del consumidor, donat que les empreses no són donades a fer servir navegadors moderns amb taules és més quadrat, però això ja és una discusió que queda fora d'aquest post.
Els detalls rellevants a comentar són:
Perque la deserialització funcioni el camp ha de tenir el mateix nom que a la BBDD. Bé, de fet no és obligatori però sí recomanable. Si no acaba sent un galimaties.
És important que tots els camps dins d'un formulari tinguin un camp id únic.
Els checkbox han de tenir value=True, així es deserialitzen correctament.
Els combos no tenen dades, es carreguen amb el comboloader.ashx a la part servidora i el LoadCombos al Javascript.
Els grids a l'HTML venen representats per una taula i un div. Queda petit en temps de disseny però és un grid ben gran.
Els calendaris no apareixen aquí, es creen per javascript.
Els botons es defineixen sense events, es fan per javascript.
#27/01/2014 12:11 Programació HTML/CSS Autor: Alex Canalda
Tot és qüestió d'ampla de banda, des de l'ADSL a qualsevol tipus de comunicació, entre elles les connexions dels discs. El SATA3 que semblava que donaria per molt amb els seus 6Gb/s ara resulta que amb els SSD no dóna l'abast. Per això hi ha SSDs que enlloc de ser SATA ja són una altra cosa, PCI-Express i similars.
Ara arriba el moment d'apropar al màxim les dades al processador. I quin lloc millor que posar un bus DDR3 o similar... I posar els SSDs en els DIMMs? Fantàstics! Dit i fet!
Sandisk ha tret (de moment pel mercat empresarial) uns DIMMS fets amb memòria flash (nom de marketing ULLtraDIMM) i que tenen condensadors prou grans que quan s'envà la corrent tenen suficient càrrega per gravar les últimes dades dels buffers a la memòria flash. Suposo que serà una tecnologia encara força cara però apunta per on aniran els trets.
Dins del desenvolupament d'aplicacions web, ja hem tractat la part de BBDD, la part servidora amb els ASHX i la clsDades. Ara cal començar amb la part client.
Sempre m'ha molestat la barreja que es fa en la part client entre les diferents parts d'una pàgina web. Tal com ho he muntat he aconseguit separar la presentació (HTML+CSS), els events (jQuery i Javascript) i les dades (Ajax pel transport i JSON).
Amb aquesta separació m'he simplificat la vida i millorat la eficiència (per la xarxa no viatja HTML amb dades empotrades, només JSON), el HTML només viatja el primer cop que es carrega. Si cal canviar colors, tocar HTML o CSS, que cal modificar el comportament, tocar Javascript, que cal tocar dades, aleshores ja depen, però normalment JSON, ASHX i llestos.
Pel que fa a components de .NET, per mi estan morts. Són una castanya, suposo que amb el temps han millorat però recordo que eren difícils de fer servir... Així que faig servir uns altres:
El autocomplete, encara que està deprecat al principi el de jQuery no limitava els resultats i per això el vaig mantenir un temps. Ara ja sí es poden limitar els resultats del autocomplete als de la llista (similar un combobox) i faig servir el de jQueryUI.
Un menú horitzontal (encara que admet un setup vertical)
Un deserialitzador fet en jQuery
El validador de formularis del jQuery
Al fer servir aquests components també té una altra avantatja. És que si algun dia la part servidora deixa de ser .NET i és PHP o qualsevol altra tecnologia, mentre es respecti l'estructura del JSON, almenys la part client seguirà funcionant.
Un petit formulari quedaria com la següent imatge:
Aquest és un formulari tipus, poden variar moltes coses, per exemple que el detall sigui molt gran i necessiti una pantalla apart, que el filtre enlloc d'un camp tingui 19, que el grid el vulguin molt gran, que hagin combos depenents, grids depenents un de l'altra, etc... Tot es pot fer. També té una avantatja que és que no necessita mantenir el ViewState de .NET, estalviant molt tràfic de dades, només viatja el JSON de les poques dades del formulari, el JSON del grid, etc... Així que ara el que cal es veure les parts que he comentat en els seus corresponents posts.