La majoria de formularis tenen "desplegables" en el desenvolupament web, o "combos" o "select", depenent del programador es dóna un nom o un altra, jo els dic de qualsevol de les opcions, segons el dia. Però tots volen dir el mateix. Ja he explicat que en la part client que els "select" no tenen els valors carregats en el HTML (a menys que siguin un Sí/No, o Blanc/Negre...), que és posteriorment que es carreguen les dades. És durant la creació dels controls que es posen els valors. Quan hi ha "selects" dels que s'anomenen en cascada (el valor d'un primer "select" determina les opcions d'un segon) també hi ha crides a aquesta funció. En la part servidora hi ha un ASHX que es dedica només a carregar els "selects", el comboloader.ashx. És lògic doncs que a la part client, al Javascript hi hagi una funció homònima que s'encarregui d'enviar peticions i rebre les respostes posant als "select" les seves opcions corresponents. La funció es diu LoadCombos, i té un munt de paràmetres, va començar amb pocs i a mesura que ha passat per projectes s'han anat afegint.
pFrm: Identificador del formulari on estan els combos.
pCombos: Identificadors dels "select" que s'han de carregar. Aquests noms han de coincidir amb els que tingui el comboloader.ashx. Si hi ha més d'un van separats per guions.
pURLExtra: Alguns combos han de tenir els seus valors restringits per algun valor, es posa aquí en el format "&nom=valor" (pot ser més llarga "&nom1=valor1&nom2=valor2..., important el detall del "&" inicial).
pAsync: indica si es poden carregar els combos de forma asíncrona. Això passa en la funció CrearteNNNNEvents, que a vegades s'han de carregar els combos per diferents formularis, aleshores si no es vol que les crides es solapin (lleig) s'ha de posar a false.
pValorDefecte: Es un JSON que indica els valors per defecte de diferents combos, té un aspecte com {"filtre_Comunitat":"3"} i es poden posar els diferents valors per defecte dels diferents combos.
pPostLoadFunction: funció que s'executa després de carregar els combos. Posar "undefined" si no es vol.
pOptionBuit: booleà que indica si als combos s'ha de posar una primera opció que estigui buida.
pValorOptionBuit: valor que té l'opció buida, normalment ''
Com sempre el codi:
var lastFrm = "";
var lastCombos = "";
var lastValorsDefecte = null;
function LoadCombos(pFrm, pCombos, pURLExtra, pAsync, pValorDefecte, pPostLoadFunction, pOptionBuit, pValorOptionBuit) {
if (pURLExtra === undefined) pURLExtra = "";
if (pAsync === undefined) pAsync = true;
if (pOptionBuit === undefined) pOptionBuit = false;
lastFrm = pFrm;
lastCombos = pCombos;
if (pValorDefecte !== undefined) lastValorsDefecte = eval('(' + pValorDefecte + ')');
else lastValorsDefecte = null;
$.ajax({
type: "GET",
async: pAsync,
dataType: 'json',
url: "comboloader.ashx?combos=" + pCombos + pURLExtra,
success: function(data, textStatus) {
if (!data.ERROR) {
var ComboID = '';
var options = '';
var ComboOBJ = null;
jQuery.each(data, function(i, val) {
ComboID = val.ComboID;
options = '';
if (val.Items) {
if (pOptionBuit) options = '<option value="' + pValorOptionBuit + '"></option>';
jQuery.each(val.Items, function(j, ItemVal) {
options += '<option value="' + ItemVal.Val + '">' + ItemVal.Disp + '</option>';
});
//Es més eficient fer-ho així però en IE7 no funciona bé.//$("#" + lastFrm + " #" + ComboID).html(options);
$("#" + lastFrm).find("#" + ComboID).html(options);
if (lastValorsDefecte != null) {
for (var member in lastValorsDefecte) {
if (member == ComboID) $("#" + lastFrm).find("#" + ComboID).val(lastValorsDefecte[member]);
}
}
}
else {
$("#" + lastFrm).find("#" + ComboID).html('');
}
});
if (pPostLoadFunction !== undefined) pPostLoadFunction(data);
}
else {
$("#ui-dialog-title-dialogErr").html("Error carregant combos");
$("#errmsg").html(data.ERROR);
$("#dialogErr").dialog("open");
}
}
});
}
Unes invocacions d'exemple podrien ser, aquesta primera és complicada, carrega un munt de combos al formulari filtre, i el combo de les comarques es filtra per la comunitat 3, i clar el combo comunitats ha de tenir aquest valor per defecte:
Un exemple de combos encadenats pot ser com la següent, s'assigna a l'event "canvi" del combo origen i estaria a la funció "createNNNNNEvents" corresponent:
Al llarg dels últims posts de desenvolupament web, de la part client, m'he anat referint a l'estat del formulari. És hora d'aclarir que és. El formulari, tal com està plantejat pot estar en dos estats. El primer és l'estat "actualitzar" ("update"), on el formulari té carregades dades, s'editen i un cop s'acaba amb el botó "Guardar" s'envien a la part servidora (al ASHX corresponent) per guardar-les. Al guardar les dades seguim en aquest estat.
L'altra estat és el "insertar" on el formulari està buit (sense dades), s'omple i s'envien al ASHX per guardar-les. En aquesta operació es recupera del ASHX la clau primària generada per en futures edicions fer actualitzacions ja que el formulari passa a l'estat "actualitzar".
Quan des de l'estat "actualitzar" es fa una eliminació, el formulari es neteja i passa a l'estat "insertar".
El botó "Nou" (per crear un nou registre) també posa al formulari en l'estat "insertar".
Diagrama d'estats.
Cada un d'aquests estats té una funció Javascript que posa els controls del formulari com els correspon. Veiem uns exemples, mode insert:
function ClassificacioInsert() {
//Neteja del formulari
$('#frmDetall').validate().resetForm();
$('#frmDetall :input')
.not(':button, :submit, :reset, :checkbox')
.val('')
.removeAttr('selected');
$("#frmDetall :checkbox").removeAttr('checked');
//Reasignació del boto Guardar, amb action INSERT
$("#frmDetall #btnSave").unbind('click');
$("#frmDetall #btnSave").click(function ()
{ SubmitFrm('#frmDetall', 'classificacions.ashx?action=insert', true, ClassificacioPostInsert);
});
//El boto Eliminar desapareix en un alta nova
$("#frmDetall #btnDelete").hide();
//Exemple de valor per defecte d'un controlvar Avui = new Date();
$("#frmDetall #DataAlta").val(Avui.getDate() + "/" + (Avui.getMonth() + 1) + "/" + Avui.getFullYear());
//Altres botons es deshabiliten
$("#frmDetall #btnDeleteEspecifica").attr('disabled', '');
$("#frmDetall #btnAfegirEspecifica").attr('disabled', '');
$("#frmDetall #autoEspecifica").attr('disabled', '');
$("#frmDetall #autoEspecifica_TC").attr('disabled', '');
$("#frmDetall #btnDeleteTAAD").attr('disabled', '');
$("#frmDetall #btnAfegirTAAD").attr('disabled', '');
$("#frmDetall #afegirTAAD").attr('disabled', '');
$('#PK_TAAD').val('');
$('#afegirTAAD').html('');
//Es neteja la clau primària, quan es guardi el registre//ja es posara el valor correcte
$('#PK_Classificacio').val('');
evPK_Classificacio = "";
}
Posar en mode "actualitzar" pot tenir una funció com aquesta:
function ClassificacioUpdate(pID) {
//Es netegen validacions pendents
$('#frmDetall').validate().resetForm();
//Si arriba la PK informada es posa a la variableif (pID !== undefined) $("#frmDetall #PK_Classificacio").val(pID);
//S'activen alguns controls (això depen de la lògica de negoci)
$("#frmDetall #btnDeleteEspecifica").removeAttr('disabled');
$("#frmDetall #btnAfegirEspecifica").removeAttr('disabled');
$("#frmDetall #autoEspecifica").removeAttr('disabled');
$("#frmDetall #autoEspecifica_TC").removeAttr('disabled');
//Es mostra el botó d'Eliminar
$("#frmDetall #btnDelete").show();
//Es posa el guardar en "update"
$("#frmDetall #btnSave").unbind('click');
$("#frmDetall #btnSave").click(function() {
SubmitFrm('#frmDetall', 'classificacions.ashx?action=update', true, ClassificacioPostUpdate);
});
}
#06/02/2014 11:40 Programació Javascript Autor: Alex Canalda
Dins les aplicacions web, ha d'haver-hi alguna forma d'enviar les peticions d'eliminació de registre de la part client a la part servidora (als ASHX). En Javascript faig servir una crida Ajax de jQuery per fer aquesta tasca. De fet crec que és la part més senzilla de tot plegat.
En el post de la creació de controls ja hi ha el codi que assigna al botó "Eliminar" la funció que s'encarrega de fer la eliminació (valgui la redundància). Aquí es tracta d'explicar en detall la funció DeleteElem.
DeleteElem rep varis paràmetres:
pForm: El identificador del formulari on està el registre que es vol eliminar
pURL: la URL que ha d'invocar en la part servidora, normalment conté el paràmetre action amb el valor "delete"
pID: la clau primària que serveix per invocar l'eliminació. Si està composta per diferents camps cal posar-los separats per un guió "-" i s'han de correspondre al camps del formulari amb el mateix nom.
pPostDelete: un booleà que indica si ha de cridar a una funció posterior a l'eliminació.
pFunctionPostDelete: la funció posterior a la eliminació.
pMissatge: un missatge que surt per pantalla un cop s'ha eliminat (per defecte surt un lacònic "Element eliminat correctament").
Com sempre el codi:
function DeleteElem(pForm, pURL, pID, pPostDelete, pFunctionPostDelete, pMissatge) {
var Missatge = "Element eliminat correctament";
if (pPostDelete === undefined) pPostDelete = false;
if (pMissatge !== undefined) Missatge = pMissatge;
var PK = pID.split("-");
var PKURL = "";
jQuery.each(PK, function(i, val) {
PKURL += "&" + val + "=" + $("#" + val).val();
});
$.ajax({
type: "GET",
url: pURL + PKURL,
success: function (data, textStatus) {
if (data.indexOf("ERROR:") != -1) {
$("#ui-dialog-title-dialogErr").html("Error eliminant dades");
$("#errmsg").html(data);
$("#dialogErr").dialog("open");
}
else {
$("#ui-dialog-title-dialogOK").html("Element eliminat");
$("#okmsg").html(Missatge);
$("#dialogOK").dialog("open");
if (pPostDelete) {
if (pFunctionPostDelete !== undefined) pFunctionPostDelete(data);
}
}
}
});
}
Normalment la funció postDelete el que fa és netejar el formulari i posar-lo en mode inserció. Seria algo així:
function BlogPostDelete(pData) {
jQuery("#grdPosts").jqGrid().trigger("reloadGrid");
PostsInsert();
}
#05/02/2014 17:45 Programació Javascript Autor: Alex Canalda
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
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.
Línies de temps i accions
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:
Cada camp del JSON al camp corresponent del formulari.
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).
Parts del Javascript
#29/01/2014 13:05 Programació Javascript Autor: Alex Canalda
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
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).
Separació per capes
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:
Un formulari tipus.
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.
Sempre que escric un CREATE TABLE on hi ha la definició d'un Index em sorgeix el dubte de quina diferència hi ha entre un Clustered i un Non-Clustered.
Per entendre-ho cal veure que és un index: els indexos són objectes de la BBDD que ajuden en la cerca de dades dins de la BBDD. Conforme les taules es van fent grans, els indexos es fan indispensables per fer-hi cerques. Tenen una estructura en forma d'arbre, on hi ha una arrel i moltes fulles que són el nivell final. Quan es fa una cerca es mira la clau de l'index per tal fer el menor nombre de lectures possibles fins a arribar a les fulles.
Estructura d'un index
De moment hem vist el que és un índex, ara cal veure la diferència, que està en el format de les fulles. En un index de tipus clustered, les dades de la taula es troben a la mateixa fulla, per tan un cop arriba la lectura de la fulla ja es tenen les dades disponibles.
Index clustered
En canvi en un index Non-Clustered el que hi ha a les fulles és un punter a les dades, cal fer un "bookmark lookup" (una lectura extra) per arribar fins les dades.
Index Non-clustered
Òbviament els index Non-clustered tenen un rendiment inferior a un Clustered donat que tenen que fer una lectura més. Des de SQL Server 2005, es permet posar dins d'un index Non-Clustered camps que no formen part de la clau de l'index per tal d'evitar fer aquesta lectura extra i millorar el rendiment. Respecte com s'inclouen camps a un index, les limitacions sobre incloure camps, ... hi ha un article de la Technet que ho explica, amb exemples.
Tampoc es poden definir tots els indexos com Clustered, ja que només es permet un únic index d'aquest tipus en una taula. La raó és que en realitat els registres estan guardats a disc juntament amb l'index, ordenats seguint l'index i clar, no es pot tenir dos ordres físics alhora.
Punts a tenir en compte al fer servir indexos:
Els camps IDENTITY que siguin clau primària (PK) normalment són els que defineixen el index clustered. Això és per evitar el page split o fragmentació de la taula.
Per cerques de camps concrets que es repeteixen sovint, sense que siguin la PK, es millor fer servir un Non-Clustered. Si els resultats ho permeten es pot afegir algun camp resultat a les fulles.
La PK acostuma a ser una bona candidata per fer un Clustered Index, però si la PK no es fa servir gaire (per exemple factures on s'accedeix per la data de la factura) potser es pot plantejar de fer servir un altra. Però això ja varia a cada cas.
Per cerques compostes (es a dir que tinguin varis camps) cal fer servir Non-Clustered.
#22/01/2014 15:33 Programació SQLServer Autor: Alex Canalda