Knockout: Obter acesso ao elemento de um componente

Criado em 13 ago. 2014  ·  6Comentários  ·  Fonte: knockout/knockout

Oi!
Estamos usando os novos (e excelentes) componentes knockout e nos deparamos com uma situação em que precisamos acessar o elemento DOM para o componente. Mas o elemento parece ser passado apenas quando se usa o método createViewModel , e não quando viewModel é um método. Eu queria saber qual é o raciocínio por trás disso? Não tenho certeza qual é a diferença entre usar viewModel e createViewModel , é viewModel apenas uma abreviação de createViewModel ? Nesse caso, parece que deve obter o elemento DOM.

Comentários muito úteis

Um caso de uso MUITO BOM que estou tentando resolver, onde o acesso ao elemento pai seria desejável é este:

Eu tenho uma barra de ferramentas e essa barra de ferramentas pode ter injetado vários componentes diferentes (código Pesudo)

<toolbar>
  <icon></icon>
  <icon></icon>
  <singlespace></singlespace>
  <menutab></menutab>
  <menutab></menutab>
  <menutab></menutab>
  <spaceconsumer></spaceconsumer>
  <icon></icon>
  <icon></icon>
</toolbar>

No meu caso específico, dentro da marcação da minha barra de ferramentas tenho o seguinte:

<div class="menubarContainer" data-bind="foreach: menubarComponents">
  <div data-bind='component: { name: controlType , params: $data}'></div>
</div>

Como você pode ver, esta é uma ligação em loop muito padrão, que resulta em uma div para cada componente, definida na matriz observável 'menubarComponents'.

Do jeito que está, funciona e funciona bem em geral.

No entanto, eu (e suspeito que muitos outros) tenho usado o novo modelo de 'caixa flexível' muito mais recentemente.

O Flexbox simplifica bastante os layouts complexos e você pode, por exemplo, adicionar o seguinte estilo a uma barra de ferramentas:

.menubarContainer{
  display: flex;
}


e que, em seguida, coloque automaticamente QUALQUER div contido dentro desse div, sob o controle de item flexível.

novamente, no meu caso específico, estou tentando fazer o seguinte

<menubar style="display: flex;">
  <icon></icon>
  <icon></icon>
  <singlespace style="flex: 0 0 40px;"></singlespace>
  <menutab></menutab>
  <menutab></menutab>
  <menutab></menutab>
  <spaceconsumer style="flex: 1;"></spaceconsumer>
  <icon></icon>
  <icon></icon>
</menubar>

Observe os estilos em alguns dos elementos desta vez?

O componente de espaço único ocupará exatamente 40 pixels de espaço utilizável, criando um espaço de 40px entre os ícones e as guias de menu, enquanto o 'consumidor de espaço' usará o espaço restante após esses 40px mais os tamanhos de todos os outros componentes foram deduzidos.

O resultado é que você acaba com uma barra de ferramentas muito bem definida, que tem 2 ícones à esquerda, 40px de espaço, 3 guias baseadas em texto e 2 ícones à direita da barra com espaço devidamente equilibrado entre eles e o elementos esquerdos.

novamente, tudo isso funciona maravilhosamente, mas, infelizmente, aqui está a picada na cauda ....

quando você define "display: flex" em um elemento pai, o layout filho flex APENAS se aplica a filhos imediatos, que no caso deste post significa o div com o data-bind contendo o componente nele. (O display flex está no div que tem o for-each)

Como eu gostaria de controlar os tamanhos e espaçamento das caixas flexíveis dos componentes que estou inserindo (a ideia é que eu possa configurar minha barra de menus reutilizável definindo uma matriz de componentes, mais provavelmente carregados via json ou passados ​​nos params), idealmente, precisamos de uma maneira de dizer, aqui está o meu elemento, por favor, adicione esta regra de estilo css em seu contêiner.

Eu contornei isso em alguns casos, onde o contêiner flex tinha um ID nele e consegui usar '#id > div.blah' no pai, mas no caso que destaquei, Eu não posso simplesmente colocar uma regra extra na div do componente porque isso se aplicaria a todos os componentes de nível inferior que pudessem ser inseridos na barra de ferramentas.

Então, em resumo, mesmo que não possamos ter uma maneira de chegar ao elemento pai (e eu entendo as razões para isso) não seria pelo menos uma ideia fornecer um mecanismo onde nossos componentes possam pelo menos aplicar algum css regras para isso.

Shawty

Todos 6 comentários

Podemos querer garantir que os documentos sejam claros sobre isso, mas é definitivamente como projetado.

O raciocínio é que, quase sempre, as pessoas _não deveriam_ estar interferindo no elemento diretamente do viewmodel (isso prejudica todo o MVVM) - é para isso que servem as ligações! Está deliberadamente por trás de uma camada extra de abstração, embora permaneça possível via createViewModel se você realmente quiser :)

Bons pontos, mas acho que muitas vezes você cria um elemento/componente personalizado para envolver coisas do DOM, não apenas para criar componentes grandes que chamam ligações. Eu estou supondo que alguns componentes não terão seus próprios modelos de visão, eles apenas atuarão como wrappers em torno de um modelo e mutações DOM.

Eu precisava disso também. Adoro o novo recurso de componentes e queria usá-lo para carregar várias seções do nosso SPA de médio/grande porte. Eu encontrei uma solução alternativa que parece estar funcionando até agora.

Explicação: Embora todas as seções de nossa página pudessem ser adaptadas ao modelo de componente, nem todas elas também queriam usar knockout. Alguns já foram codificados com knockout, chamando applyBindings internamente, etc. enquanto outras seções não precisam ter knockout. Essas últimas seções já têm outra solução de vinculação de dados ou estão fazendo algo muito específico e não querem nenhuma vinculação de dados. Esses componentes querem controle total sobre sua renderização e seu elemento dom em geral. É por isso que eles precisam de acesso ao elemento dom, como Backbone ou this.$.em Polymer (se bem entendi.

No momento, montei um carregador que injeta o elemento no construtor do View Model. Espero que não seja muito frágil em relação a futuros lançamentos de KO.

ko.components.loaders.unshift({
    // Could this be accomplished cleaner with loadComponent?
    loadViewModel: function (name, viewModelConfig, callback) {
        if (typeof viewModelConfig === "function") {
            var viewModelConstructor = {
                createViewModel: function (params, componentInfo) {
                    return new viewModelConfig(params, componentInfo.element)
                }
            };
            ko.components.defaultLoader.loadViewModel(name, viewModelConstructor, callback);
        } else {
            callback(null);
        }
    }
});

Também criou a ligação típica "stopBinding":

ko.bindingHandlers.stopBinding = {
    init: function () {
        return { controlsDescendantBindings: true };
    }
};

Meu componente que quer ser carregado, mas na verdade não quer usar KO internamente. Quer controle total

define(function (require) {

    var wrapperTemplate = require('text!./comp-non-ko-wrapper.html'),
        template = require('text!./comp-non-ko.html'),
        $ = require('jquery')
        _ = require('underscore');

    var ViewModel = function (params, element) {
        this.$el= $(element).find('.wrapper');
        this.template = _.template(template);
        this.render();
    }

    ViewModel.prototype.render = function () {
        // Render my custom, non ko template
        this.$el.html(this.template());
    }

    return {
        viewModel: ViewModel,
        template: wrapperTemplate
    };
});

Meu modelo de componente / wrapper (observe o stopBinding):
comp-non-ko-wrapper.html

<div data-bind='stopBinding' class='wrapper'>
</div>

Meu modelo real (o data-bind é intencionalmente ignorado, esse é o teste)
comp-non-ko.html

<!-- (Just making sure these bindings aren't picked up) -->
<div>Hello <span data-bind="text:name"></span>
</div>

Eu sei que este segmento é antigo, mas um ponto que quero trazer até o design é que existem alguns casos em que os componentes Knockout precisam ter visuais muito complexos que não podem ser controlados de forma simples (ou eficiente) a partir das propriedades do modelo de visualização, ainda também não faz sentido estar dentro do escopo global de associações personalizadas, pois elas não serão usadas fora desse componente. Um exemplo pertinente seria usar qualquer uma das visualizações SVG de d3.js . Esse problema provavelmente poderia ser resolvido sem ter que quebrar o padrão de design MVVM se permitíssemos definições de associações personalizadas em nível de componente.

Um caso de uso MUITO BOM que estou tentando resolver, onde o acesso ao elemento pai seria desejável é este:

Eu tenho uma barra de ferramentas e essa barra de ferramentas pode ter injetado vários componentes diferentes (código Pesudo)

<toolbar>
  <icon></icon>
  <icon></icon>
  <singlespace></singlespace>
  <menutab></menutab>
  <menutab></menutab>
  <menutab></menutab>
  <spaceconsumer></spaceconsumer>
  <icon></icon>
  <icon></icon>
</toolbar>

No meu caso específico, dentro da marcação da minha barra de ferramentas tenho o seguinte:

<div class="menubarContainer" data-bind="foreach: menubarComponents">
  <div data-bind='component: { name: controlType , params: $data}'></div>
</div>

Como você pode ver, esta é uma ligação em loop muito padrão, que resulta em uma div para cada componente, definida na matriz observável 'menubarComponents'.

Do jeito que está, funciona e funciona bem em geral.

No entanto, eu (e suspeito que muitos outros) tenho usado o novo modelo de 'caixa flexível' muito mais recentemente.

O Flexbox simplifica bastante os layouts complexos e você pode, por exemplo, adicionar o seguinte estilo a uma barra de ferramentas:

.menubarContainer{
  display: flex;
}


e que, em seguida, coloque automaticamente QUALQUER div contido dentro desse div, sob o controle de item flexível.

novamente, no meu caso específico, estou tentando fazer o seguinte

<menubar style="display: flex;">
  <icon></icon>
  <icon></icon>
  <singlespace style="flex: 0 0 40px;"></singlespace>
  <menutab></menutab>
  <menutab></menutab>
  <menutab></menutab>
  <spaceconsumer style="flex: 1;"></spaceconsumer>
  <icon></icon>
  <icon></icon>
</menubar>

Observe os estilos em alguns dos elementos desta vez?

O componente de espaço único ocupará exatamente 40 pixels de espaço utilizável, criando um espaço de 40px entre os ícones e as guias de menu, enquanto o 'consumidor de espaço' usará o espaço restante após esses 40px mais os tamanhos de todos os outros componentes foram deduzidos.

O resultado é que você acaba com uma barra de ferramentas muito bem definida, que tem 2 ícones à esquerda, 40px de espaço, 3 guias baseadas em texto e 2 ícones à direita da barra com espaço devidamente equilibrado entre eles e o elementos esquerdos.

novamente, tudo isso funciona maravilhosamente, mas, infelizmente, aqui está a picada na cauda ....

quando você define "display: flex" em um elemento pai, o layout filho flex APENAS se aplica a filhos imediatos, que no caso deste post significa o div com o data-bind contendo o componente nele. (O display flex está no div que tem o for-each)

Como eu gostaria de controlar os tamanhos e espaçamento das caixas flexíveis dos componentes que estou inserindo (a ideia é que eu possa configurar minha barra de menus reutilizável definindo uma matriz de componentes, mais provavelmente carregados via json ou passados ​​nos params), idealmente, precisamos de uma maneira de dizer, aqui está o meu elemento, por favor, adicione esta regra de estilo css em seu contêiner.

Eu contornei isso em alguns casos, onde o contêiner flex tinha um ID nele e consegui usar '#id > div.blah' no pai, mas no caso que destaquei, Eu não posso simplesmente colocar uma regra extra na div do componente porque isso se aplicaria a todos os componentes de nível inferior que pudessem ser inseridos na barra de ferramentas.

Então, em resumo, mesmo que não possamos ter uma maneira de chegar ao elemento pai (e eu entendo as razões para isso) não seria pelo menos uma ideia fornecer um mecanismo onde nossos componentes possam pelo menos aplicar algum css regras para isso.

Shawty

Esta página foi útil?
0 / 5 - 0 avaliações