O enigma do seletor de etiquetas do Gmail - existe uma maneira melhor de fazer isso?

9

Estamos no meio de implementar uma funcionalidade de rotulagem exatamente como o gmail para nossa webapp - você pode selecionar as postagens (caixas de seleção) e selecionar quais rótulos aplicar / excluir de uma lista suspensa de 'rótulos' conjunto de caixas de seleção). O problema é "como fazer isso?" Eu tenho uma solução e antes de abordá-lo dessa maneira eu quero obter uma opinião sobre se é o caminho certo e se pode ser simplificado usando certas construções jquery / javascript que eu não poderia estar ciente de. Eu não sou um JavaScript / jQuery pro por qualquer meio, ainda. :)

Vamos: M = {conjunto de mensagens} N = {Conjunto de etiquetas} M_N = muitas ou muitas relações entre M e N, ou seja, o conjunto de posts que possuem pelo menos um rótulo de N

Saída: dados um conjunto de postagens 'selecionadas' e o conjunto de rótulos 'selecionados' obtém uma matriz de itens para o JSON com os seguintes valores:

  • Post_id, Label_id, ação {add, delete}

Aqui está a abordagem que eu propus (ingênuo ou ótimo, não sei):

  1. Obter o número atual de postagens selecionadas: var selectionCount = 5 (por exemplo, 5 postagens selecionadas)
  2. Capture o seguinte conjunto de dados para cada item na seleção:
 Label_id | numberOfLabelsInSelection| currentStateToShow |   newState
      4   |            3             |    partialTick     |  ticked (add)
      10  |            5             |      ticked        |  none (delete)
      12  |            1             |    partialTick     |  partialTick (ignore)
      14  |            0             |       none         |  ticked (add)

Basicamente, a estrutura de dados acima é apenas capturar as condições de exibição, ou seja, 5 posts são selecionados em geral e apenas dois têm o rótulo "x", então a lista de marcadores deve mostrar uma "marca parcial" na caixa de seleção. posts tem um rótulo "y", em seguida, o menu suspenso mostra um "tick completo". Os rótulos que não estão no conjunto selecionado são apenas desmarcados, mas só podem alternar para uma marca de seleção ou 'nenhum', mas não para um estado parcial (ou seja, ativado / desativado apenas. O partialTick tem três estados, por assim dizer: on / off / partial)

A coluna 'newState' é basicamente o que foi selecionado. A ação de saída é baseada no estado anterior (isto é, currentStateToShow):

  • parcial para marcar implica adicionar rótulo a todas as postagens que não têm esse rótulo
  • marcado para nenhum implica excluir esse marcador de todas as postagens
  • parcial para nenhum implica excluir somente os marcadores das postagens selecionadas
  • none to ticked implica adicionar novo marcador a todas as postagens
  • parcial a parcial implica ignorar, ou seja, nenhuma alteração .

Então, posso iterar esse conjunto e decidir enviar os seguintes dados para o servidor:

| Post_id | Label_id | Action |
|   99    |     4    |   add  |
|   23    |    10    | delete |
 ...

e assim por diante.

Então, qual é o problema? Bem, isso é muito complicado! O Javascript não tem realmente a estrutura de dados do mapa (não é?) E isso implicaria em muitas iterações sequenciais e verificar cada coisa e, em seguida, ter muitos if-else para determinar o valor do newState.

Não estou procurando "como codificar", mas o que posso fazer para facilitar minha vida? Existe algo lá fora que eu já possa usar? A lógica está correta ou é um pouco complicada demais? Alguma sugestão sobre como atacar o problema ou algumas estruturas de dados embutidas (ou uma biblioteca externa) que poderiam tornar as coisas menos difíceis? Amostras de código: P?

Estou trabalhando com javascript / jquery + AJAX e restlet / java / mysql e estarei enviando uma estrutura de dados JSON para isso, mas estou bastante confuso com este problema. Não parece tão fácil quanto eu inicialmente pensei que fosse (quero dizer, eu achei que era "mais fácil" do que o que estou enfrentando agora:)

Inicialmente, pensei em enviar todos os dados para o servidor e realizar tudo isso no back-end. Mas depois que uma confirmação é recebida, eu ainda preciso atualizar o front end de uma forma similar, então eu estava "de volta à estaca zero", por assim dizer, pois teria que repetir a mesma coisa no front end para decidir quais marcadores esconder e qual mostrar. Por isso, pensei que seria melhor apenas fazer a coisa toda do lado do cliente.

Eu estou supondo que este seja um fácil 100-150 + linhas de código javascript / jquery como por minha 'expertise' por assim dizer, talvez fora ... mas é por isso que estou aqui: D

PS: consultei este post e a demonstração Como posso implementar um seletor de etiquetas no estilo do Gmail? Mas essa demo é apenas para um post de cada vez e pode ser feito facilmente. Meu problema é agravado devido ao conjunto de seleção com essas seleções parciais, etc,

    
por PhD 10.07.2011 в 20:38
fonte

1 resposta

5

Algoritmo

Eu acho que o algoritmo faz sentido.

Embora haja necessidade de muitos ifs elses para calcular a ação de saída? Por que não adicionar um marcador marcado a TODAS as postagens? Certamente, você não pode adicionar um marcador à mesma postagem duas vezes. Eu duvido que isso prejudicaria o desempenho… Especialmente se você ajustar os dados JSON para todas as postagens alteradas em uma solicitação de qualquer maneira (isso depende se o seu back-end suporta PUTting múltiplos objetos de uma vez).

Bata a complexidade com o MVC

Sobre como isso poderia ser menos complexo: eu acho que a organização do código é um grande problema aqui.

Existe algo que você pode usar: sugiro que você verifique as bibliotecas que implementam algum tipo de abordagem MVC em JavaScript (por exemplo, Backbone.js ). Você acabará tendo algumas classes e sua lógica se encaixará em pequenos métodos nessas classes. Sua lógica de armazenamento de dados será tratada por classes "model" e exibirá a lógica por "views". Isso é mais sustentável e testável.

(Por favor, verifique estas duas apresentações impressionantes no tópico, se você ainda não o fez: Construindo grandes aplicações jQuery , Funcionalidade centrada na organização do código .

O problema é que a refatoração do código existente pode levar algum tempo, e é difícil acertar desde a primeira vez. Além disso, isso afeta toda a arquitetura do lado do cliente, então talvez não seja o que você queria.

Exemplo

Se eu tivesse uma tarefa parecida, pegaria o Backbone.js e faria algo assim (pseudocódigo / CoffeeScript; esse exemplo não é bom nem completo , o objetivo é dar uma ideia básica de abordagem baseada em classes em geral):

apply_handler: ->
    # When user clicks Apply button
    selectedPosts = PostManager.get_selected()
    changedLabels = LabelManager.get_changed()
    for label in changedLabels
        for post in selectedPosts
            # Send your data to the server:
            # | post.id | label.id | label.get_action() |
            # Or use functionality provided by Backbone for that. It can handle
            # AJAX requests, if your server-side is RESTful.


class PostModel
    # Post data: title, body, etc.

    labels: <list of labels that this post already contains>
    checked: <true | false>
    view: <PostView instance>

class PostView
    model: <PostModel instance>
    el: <corresponding li element>

    handle_checkbox_click: ->
        # Get new status from checkbox value.
        this.model.checked = $(el).find('.checkbox').val()
        # Update labels representation.
        LabelManager.update_all_initial_states()

class PostManager
    # All post instances:
    posts: <list>

    # Filter posts, returning list containing only checked ones:
    get_selected: -> this.posts.filter (post) -> post.get('checked') == true


class LabelModel
    # Label data: name, color, etc.

    initialState: <ticked | partialTick | none>
    newState: <ticked | partialTick | none>
    view: <LabelView instance>

    # Compute output action:
    get_action: ->
        new = this.newState
        if new == none then 'DELETE'
        if new == partialTick then 'NO_CHANGE'
        if new == ticked then 'ADD'

class LabelView
    model: <LabelModel instance>
    el: <corresponding li element>

    # Get new status from checkbox value.
    handle_checkbox_click: ->
        # (Your custom implementation depends on what solution are you using for 
        # 3-state checkboxes.)
        this.model.newState = $(this.el).find('.checkbox').val()

    # This method updates checked status depending on how many selected posts
    # are tagged with this label.
    update_initial_state: ->
        label = this.model
        checkbox = $(this.el).find('.checkbox')
        selectedPosts = PostManager.get_selected()
        postCount = selectedPosts.length

        # How many selected posts are tagged with this label:
        labelCount = 0
        for post in selectedPosts
            if label in post.labels
                labelCount += 1

        # Update checkbox value
        if labelCount == 0
            # No posts are tagged with this label
            checkbox.val('none')
        if labelCount == postCount
            # All posts are tagged with this label
            checkbox.val('ticked')
        else
            # Some posts are tagged with this label
            checkbox.val('partialTick')

        # Update object status from checkbox value
        this.initialState = checkbox.val()

class LabelManager
    # All labels:
    labels: <list>

    # Get labels with changed state:
    get_changed: ->
        this.labels.filter (label) ->
            label.get('initialState') != label.get('newState')

    # Self-explanatory, I guess:
    update_all_initial_states: ->
        for label in this.labels
            label.view.update_initial_state()

Opa, parece muito código. Se o exemplo não estiver claro, sinta-se à vontade para fazer perguntas.

( Atualização apenas para esclarecer: você pode fazer exatamente o mesmo em JavaScript. Você cria classes chamando extend() métodos de objetos fornecidos pelo Backbone. mais rápido para digitar dessa forma.)

Você provavelmente diria que é ainda mais complexo do que a solução inicial. Eu diria: essas classes geralmente estão em arquivos separados [1], e quando você está trabalhando em alguma parte (digamos, a representação da etiqueta no DOM), você normalmente só lida com uma delas ( LabelView ). Além disso, confira as apresentações mencionadas acima.

[1] Sobre a organização do código, consulte o projeto "brunch" abaixo.

Como o exemplo acima funcionaria:

  1. O usuário seleciona algumas postagens:

    • Clique no gerenciador na visualização de postagem:
      1. alterna o status verificado da postagem.
      2. faz todos os estados de atualização do LabelManager de todos os rótulos.
  2. O usuário seleciona um marcador:

    • O manipulador de cliques na exibição de marcadores alterna o status da etiqueta.
  3. O usuário clica em "Aplicar":

    • apply_handler() : para cada um dos marcadores alterados, emita a ação apropriada para cada postagem selecionada.

Backbone.js

Atualizar em resposta a um comentário

Bem, o Backbone não é muito mais do que um par de classes e objetos base (veja fonte anotada ).

Mas eu gosto mesmo assim.

  • Oferece convenções bem pensadas para organização de código.

    É muito parecido com um framework: basicamente você pode pegá-lo e se concentrar em sua estrutura de informação, representação e lógica de negócios, em vez de "onde eu coloco isto ou aquilo para que eu não acabe com pesadelo de manutenção" ". No entanto, não é um framework , o que significa que você ainda tem muita liberdade para fazer o que quiser (incluindo fotografar-se no pé), mas também tem que tomar algumas decisões de design sozinho. / p>

  • Ele economiza uma boa quantidade de código clichê.

    Por exemplo, se você tiver uma API RESTful fornecida pelo back-end, poderá mapeá-la para os modelos Backbone e ela fará todo o trabalho de sincronização para você: por exemplo, se você salvar uma nova Model instance - & gt; Emite uma solicitação POST para o Collection url, se você atualizar o objeto existente - & gt; Emite um pedido PUT para o URL deste objeto específico. (A carga útil do pedido é o JSON dos atributos do modelo que você definiu usando o método set() .) Portanto, tudo o que você precisa fazer é configurar os URLs e chamar o método save() no modelo quando precisar salvá-lo. fetch() quando você precisa obter seu estado do servidor. Ele usa jQuery.ajax() nos bastidores para realizar solicitações reais de AJAX.

Algumas referências

  • Introdução ao Backbone.js (não oficial mas legal) (quebrado)

  • O exemplo ToDos

    Não tome isso como um exemplo "oficial" do Backbone.js, embora seja referenciado pelos documentos. Por um lado, ele não usa roteadores, que foram introduzidos posteriormente. Em geral, eu diria que é um bom exemplo de um pequeno aplicativo criado no Backbone, mas se você estiver trabalhando em algo mais complexo (o que você faz), provavelmente acabará com algo um pouco diferente.

  • Enquanto você faz isso, não deixe de conferir o brunch . Basicamente, ele fornece um modelo de projeto, empregando CoffeeScript, Backbone.js, Underscore.js, Stitch, Eco e Stylus.

    Graças à estrita estrutura do projeto e ao uso de require() , ela impõe as convenções de organização de código de nível mais alto do que o Backbone.js faz sozinho. (Você basicamente não precisa pensar não apenas em qual classe colocar seu código, mas também em qual arquivo colocar essa classe e onde colocar esse arquivo no sistema de arquivos.) Entretanto, se você não é um "convencional" tipo de pessoa, então você provavelmente vai odiar isso. Eu gosto disso.

    O que é ótimo é que ele também fornece uma maneira de construir facilmente tudo isso. Você apenas executa brunch watch , começa a trabalhar no código e cada vez que você salva as alterações, ele compila e constrói todo o projeto (leva menos de um segundo) em um diretório build , concatenando (e provavelmente minimizando) todo o javascript resultante em um arquivo. Ele também executa o servidor mini Express.js em localhost:8080 , o que reflete imediatamente as alterações.

Perguntas relacionadas
por Tony 11.07.2011 / 03:43
fonte