2010-11-04 10 views
5

Sono making a jquery clone per C#. In questo momento ho impostato in modo che ogni metodo sia un metodo di estensione su IEnumerable<HtmlNode>, quindi funziona bene con i progetti esistenti che già utilizzano HtmlAgilityPack. Pensavo di poter andare via senza preservare lo stato ... tuttavia, ho notato che jQuery ha due metodi .andSelf e .end che "pop" gli elementi abbinati più di recente da uno stack interno. Posso imitare questa funzionalità se cambio la mia classe in modo che funzioni sempre sugli oggetti SharpQuery invece che sugli enumerabili, ma c'è ancora un problema.Come progettare la mia API jQuery C# in modo che non sia di confusione da usare?

Con JavaScript, viene fornito automaticamente il documento Html, ma quando si lavora in C# è necessario caricarlo in modo esplicito e, se si desidera, è possibile utilizzare più di un documento. Sembra che quando chiami lo $('xxx') stai essenzialmente creando un nuovo oggetto jQuery e ricominci da zero con uno stack vuoto. In C#, non vorresti farlo, perché non vuoi ricaricare/rimettere il documento dal web. Quindi, invece, lo si carica una volta in un oggetto SharpQuery o in un elenco di HtmlNode (è necessario solo il DocumentNode per iniziare).

nella documentazione jQuery, danno questo esempio

$('ul.first').find('.foo') 
    .css('background-color', 'red') 
.end().find('.bar') 
    .css('background-color', 'green') 
.end(); 

Non ho un metodo di inizializzazione perché non posso sovraccaricare l'operatore (), quindi basta iniziare con sq.Find() invece, che opera sul radice del documento, essenzialmente facendo la stessa cosa. Ma poi le persone cercheranno di scrivere sq.Find() su una riga, e poi su sq.Find() da qualche parte lungo la strada, e (giustamente) si aspettano che funzioni nuovamente nella root del documento ... ma se sto mantenendo lo stato, allora tu Ho appena modificato il contesto dopo la prima chiamata.

Allora ... come dovrei progettare il mio API? Aggiungo un altro metodo Init che dovrebbe iniziare con tutte le query che azzerano lo stack (ma come faccio a forzare l'avvio con quello?) O aggiungere uno Reset() che devono chiamare alla fine della loro riga? Sovraccarico il [] e dico loro di iniziare con quello? Devo dire "dimenticalo, nessuno usa quelle funzioni conservate dallo stato comunque?"

In sostanza, come ti piacerebbe che l'esempio di jQuery fosse scritto in C#?

  1. sq["ul.first"].Find(".foo") ...
    cadute: abusi della proprietà [].

  2. sq.Init("ul.first").Find(".foo") ...
    cadute: Nulla obbliga davvero il programmatore di iniziare con Init, a meno che non aggiungo qualche strano meccanismo "inizializzato"; l'utente potrebbe provare a iniziare con .Find e non ottenere il risultato che si aspettava. Inoltre, Init e Find sono praticamente uguali in ogni caso, tranne che il precedente reimposta anche lo stack.

  3. sq.Find("ul.first").Find(".foo") ... .ClearStack()
    cadute: programmatore può dimenticare di cancellare lo stack.

  4. Impossibile farlo.
    end() non implementato.

  5. Utilizzare due oggetti diversi.
    È possibile utilizzare HtmlDocument come base da cui iniziare tutte le query, quindi ogni metodo restituisce un oggetto SharpQuery che può essere concatenato. In questo modo lo HtmlDocument mantiene sempre lo stato iniziale, ma gli oggetti SharpQuery potrebbero avere stati diversi. Questo purtroppo significa che devo implementare un paio di cose due volte (una volta per HtmlDocument, una volta per l'oggetto SharpQuery).

  6. new SharpQuery(sq).Find("ul.first").Find(".foo") ...
    Le copie costruttore un riferimento al documento, ma ripristina la pila.

+2

IMO 5) è l'opzione migliore. È certamente leggibile e intuitivo che le query inizino da "HtmlDocument" e, come per il lavoro duplicato, si spera che sia possibile implementarlo in modo che solo le firme dei metodi siano duplicate, ma non la logica effettiva. (Concatenare le chiamate ad alcune classi comuni che fanno il lavoro.) Mi piace anche l'opzione 1) e personalmente non la vedo come un "abuso" delle parentesi. :) –

+0

@Kirk: '[]' indica generalmente un'operazione 'O (1)' ... come se i dati fossero già indicizzati. In questo caso, in realtà deve effettuare una ricerca completa. – mpen

+0

Tuttavia, mi sto appoggiando verso (1) ora. In realtà, ho iniziato a codificarlo in questo modo. Ciò impedisce agli utenti di saltare direttamente con '.Find()' perché il contesto non è impostato fino a quando non chiamano '[]'. Ad esempio, '.Find()' semplicemente non troverà nulla perché non ha nulla da cercare. '[]' ripristina il contesto sul nodo del documento e quindi può iniziare la ricerca. – mpen

risposta

4

Penso che il principale ostacolo che stai incontrando qui è che stai cercando di farla franca avendo solo un oggetto SharpQuery per ogni documento. Non è così che funziona jQuery; in generale, gli oggetti jQuery sono immutabili. Quando si chiama un metodo che cambia l'insieme degli elementi (come find o end o add), non altera l'oggetto esistente, ma restituisce uno nuovo:

var theBody = $('body'); 
// $('body')[0] is the <body> 
theBody.find('div').text('This is a div'); 
// $('body')[0] is still the <body> 

(vedere la documentation of end per maggiori informazioni)

SharpQuery dovrebbe funzionare allo stesso modo. Dopo aver creato un oggetto SharpQuery con un documento, le chiamate di metodo devono restituire nuovi oggetti SharpQuery, facendo riferimento a un diverso insieme di elementi dello stesso documento. Ad esempio:

var sq = SharpQuery.Load(new Uri("http://api.jquery.com/category/selectors/")); 
var header = sq.Find("h1"); // doesn't change sq 
var allTheLinks = sq.Find(".title-link") // all .title-link in the whole document; also doesn't change sq 
var someOfTheLinks = header.Find(".title-link"); // just the .title-link in the <h1>; again, doesn't change sq or header 

I vantaggi di questo approccio sono molteplici. Perché sq, header, allTheLinks, ecc. Sono tutti della stessa classe, hai solo un'implementazione di ciascun metodo. Tuttavia ognuno di questi oggetti fa riferimento allo stesso documento, quindi non si dispone di più copie di ogni nodo e le modifiche ai nodi si riflettono su ogni oggetto SharpQuery su quel documento (ad esempio dopo allTheLinks.text("foo"), someOfTheLinks.text() == "foo".).

L'implementazione di end e le altre manipolazioni basate su stack diventano anche semplici. Poiché ogni metodo crea un nuovo oggetto filtrato SharpQuery da un altro, mantiene un riferimento a tale oggetto padre (allTheLinks a header, header a sq). Poi end è semplice come il ritorno di una nuova SharpQuery contenente gli stessi elementi come il genitore, come:

public SharpQuery end() 
{ 
    return new SharpQuery(this.parent.GetAllElements()); 
} 

(o comunque la sintassi scuote.)

Credo che questo approccio sarà ottenere il numero massimo jQuery -come il comportamento, con un'implementazione abbastanza facile.Sicuramente terrò d'occhio questo progetto; È una buona idea.

+0

Ahh..clever! Stavo pensando di restituire un nuovo oggetto, ma poi non sono riuscito a capire come avrei mantenuto lo stack. Mantenere un riferimento al genitore ... così semplice>. < – mpen

+0

Grande aggiornamento: http://sharp-query.googlecode.com/svn/trunk/HtmlAgilityPlus/ Utilizza questo metodo per concatenare ora. – mpen

0

Mi sporgo verso una variante sull'opzione 2. In jQuery $() è una chiamata di funzione. C# non ha funzioni globali, una chiamata di funzione statica è la più vicina. Vorrei usare un metodo che indica che si sta creando un wrapper come ..

SharpQuery.Create("ul.first").Find(".foo") 

non sarei preoccupato accorciando SharpQuery a SQ dal IntelliSense significa che gli utenti non dovranno digitare il tutto (e se hanno resharper hanno solo bisogno di digitare SQ comunque).

+0

Possono chiamarlo come vogliono. 'Sq' sarebbe un'istanza di' SharpQuery'. 'SharpQuery.Create' non funzionerà come metodo statico perché devi prima caricare il documento ... a meno che non vuoi rendere statico il documento, ma puoi lavorare solo con un documento alla volta; una restrizione arbitraria. – mpen

Problemi correlati