Eu gostaria de construir um aplicativo móvel, desenvolvido a partir de nada mais que html / css e JavaScript. Embora eu tenha um conhecimento decente de como construir um aplicativo da web com JavaScript, pensei que poderia dar uma olhada em uma estrutura como jquery-mobile.
No início, pensei que o jquery-mobile não era nada mais do que uma estrutura de widget voltada para navegadores móveis. Muito semelhante ao jquery-ui, mas para o mundo móvel. Mas percebi que o jquery-mobile é mais do que isso. Ele vem com um monte de arquitetura e permite que você crie aplicativos com uma sintaxe html declarativa. Portanto, para o aplicativo mais fácil de pensar, você não precisaria escrever uma única linha de JavaScript sozinho (o que é legal, porque todos nós gostamos de trabalhar menos, não é?)
Para dar suporte à abordagem de criação de aplicativos usando uma sintaxe html declarativa, acho que é uma boa ideia combinar jquery-mobile com knockoutjs. Knockoutjs é uma estrutura MVVM do lado do cliente que visa trazer superpoderes MVVM conhecidos do WPF / Silverlight para o mundo do JavaScript.
Para mim, o MVVM é um novo mundo. Embora eu já tenha lido muito sobre isso, na verdade nunca usei antes.
Portanto, esta postagem é sobre como arquitetar um aplicativo usando jquery-mobile e knockoutjs juntos. Minha ideia era escrever a abordagem que eu vim com depois de olhar para ela por várias horas e ter algum jquery-mobile / knockout yoda para comentá-la, me mostrando por que é uma merda e por que eu não deveria fazer programação no primeiro Lugar, colocar ;-)
O html
jquery-mobile faz um bom trabalho fornecendo um modelo de estrutura básica de páginas. Embora eu esteja bem ciente de que poderia carregar minhas páginas via ajax posteriormente, decidi apenas mantê-las em um arquivo index.html. Neste cenário básico, estamos falando de duas páginas para que não seja muito difícil ficar por dentro das coisas.
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<link rel="stylesheet" href="libs/jquery-mobile/jquery.mobile-1.0a4.1.css" />
<link rel="stylesheet" href="app/base/css/base.css" />
<script src="libs/jquery/jquery-1.5.0.min.js"></script>
<script src="libs/knockout/knockout-1.2.0.js"></script>
<script src="libs/knockout/knockout-bindings-jqm.js" type="text/javascript"></script>
<script src="libs/rx/rx.js" type="text/javascript"></script>
<script src="app/App.js"></script>
<script src="app/App.ViewModels.HomeScreenViewModel.js"></script>
<script src="app/App.MockedStatisticsService.js"></script>
<script src="libs/jquery-mobile/jquery.mobile-1.0a4.1.js"></script>
</head>
<body>
<!-- Start of first page -->
<div data-role="page" id="home">
<div data-role="header">
<h1>Demo App</h1>
</div><!-- /header -->
<div data-role="content">
<div class="ui-grid-a">
<div class="ui-block-a">
<div class="ui-bar" style="height:120px">
<h1>Tours today (please wait 10 seconds to see the effect)</h1>
<p><span data-bind="text: toursTotal"></span> total</p>
<p><span data-bind="text: toursRunning"></span> running</p>
<p><span data-bind="text: toursCompleted"></span> completed</p>
</div>
</div>
</div>
<fieldset class="ui-grid-a">
<div class="ui-block-a"><button data-bind="click: showTourList, jqmButtonEnabled: toursAvailable" data-theme="a">Tour List</button></div>
</fieldset>
</div><!-- /content -->
<div data-role="footer" data-position="fixed">
<h4>by Christoph Burgdorf</h4>
</div><!-- /header -->
</div><!-- /page -->
<!-- tourlist page -->
<div data-role="page" id="tourlist">
<div data-role="header">
<h1>Bar</h1>
</div><!-- /header -->
<div data-role="content">
<p><a href="#home">Back to home</a></p>
</div><!-- /content -->
<div data-role="footer" data-position="fixed">
<h4>by Christoph Burgdorf</h4>
</div><!-- /header -->
</div><!-- /page -->
</body>
</html>
O JavaScript
Então, vamos para a parte divertida - o JavaScript!
Quando comecei a pensar em camadas do aplicativo, tinha várias coisas em mente (por exemplo, testabilidade, acoplamento fraco). Vou mostrar como decidi dividir meus arquivos e comentar coisas como por que escolhi uma coisa em vez de outra enquanto vou ...
App.js
var App = window.App = {};
App.ViewModels = {};
$(document).bind('mobileinit', function(){
// while app is running use App.Service.mockStatistic({ToursCompleted: 45}); to fake backend data from the console
var service = App.Service = new App.MockedStatisticService();
$('#home').live('pagecreate', function(event, ui){
var viewModel = new App.ViewModels.HomeScreenViewModel(service);
ko.applyBindings(viewModel, this);
viewModel.startServicePolling();
});
});
App.js é o ponto de entrada do meu aplicativo. Ele cria o objeto App e fornece um namespace para os modelos de visualização (em breve). Ele escuta o evento mobileinit que o jquery-mobile fornece.
Como você pode ver, estou criando uma instância de algum tipo de serviço ajax (que veremos mais tarde) e salvando-a na variável "serviço".
Eu também conecto o evento pagecreate para a página inicial na qual crio uma instância do viewModel que obtém a instância do serviço passada. Este ponto é essencial para mim. Se alguém pensa, isso deveria ser feito de forma diferente, por favor, compartilhe sua opinião!
A questão é que o modelo de visualização precisa operar em um serviço (GetTour /, SaveTour etc.). Mas não quero que o ViewModel saiba mais sobre isso. Por exemplo, em nosso caso, estou apenas transmitindo um serviço Ajax simulado porque o back-end ainda não foi desenvolvido.
Outra coisa que devo mencionar é que o ViewModel tem conhecimento zero sobre a visão real. É por isso que estou chamando ko.applyBindings (viewModel, this) de dentro do manipulador pagecreate . Eu queria manter o modelo de visualização separado da visualização real para tornar mais fácil testá-lo.
App.ViewModels.HomeScreenViewModel.js
(function(App){
App.ViewModels.HomeScreenViewModel = function(service){
var self = {}, disposableServicePoller = Rx.Disposable.Empty;
self.toursTotal = ko.observable(0);
self.toursRunning = ko.observable(0);
self.toursCompleted = ko.observable(0);
self.toursAvailable = ko.dependentObservable(function(){ return this.toursTotal() > 0; }, self);
self.showTourList = function(){ $.mobile.changePage('#tourlist', 'pop', false, true); };
self.startServicePolling = function(){
disposableServicePoller = Rx.Observable
.Interval(10000)
.Select(service.getStatistics)
.Switch()
.Subscribe(function(statistics){
self.toursTotal(statistics.ToursTotal);
self.toursRunning(statistics.ToursRunning);
self.toursCompleted(statistics.ToursCompleted);
});
};
self.stopServicePolling = disposableServicePoller.Dispose;
return self;
};
})(App)
Embora você encontre a maioria dos exemplos de modelos de visualização de knockoutjs usando uma sintaxe literal de objeto, estou usando a sintaxe de função tradicional com objetos auxiliares 'self'. Basicamente, é uma questão de gosto. Mas quando você deseja que uma propriedade observável faça referência a outra, você não pode escrever o literal do objeto de uma vez, o que o torna menos simétrico. Esse é um dos motivos pelos quais estou escolhendo uma sintaxe diferente.
O próximo motivo é o serviço que posso passar como parâmetro, conforme mencionei antes.
Há mais uma coisa com esse modelo de visualização que não tenho certeza se escolhi o caminho certo. Quero pesquisar o serviço ajax periodicamente para buscar os resultados do servidor. Portanto, optei por implementar os métodos startServicePolling / stopServicePolling para fazer isso. A ideia é iniciar a votação no pagehow e interrompê-la quando o usuário navegar para outra página.
Você pode ignorar a sintaxe que é usada para pesquisar o serviço. É mágica RxJS. Apenas certifique-se de que estou pesquisando e atualizando as propriedades observáveis com o resultado retornado, como você pode ver na parte Assinar (função (estatísticas) {..}) .
App.MockedStatisticsService.js
Ok, só falta mostrar a você. É a implementação real do serviço. Não vou entrar em muitos detalhes aqui. É apenas uma simulação que retorna alguns números quando getStatistics é chamado. Há outro método mockStatistics que uso para definir novos valores por meio do console js do navegador enquanto o aplicativo está em execução.
(function(App){
App.MockedStatisticService = function(){
var self = {},
defaultStatistic = {
ToursTotal: 505,
ToursRunning: 110,
ToursCompleted: 115
},
currentStatistic = $.extend({}, defaultStatistic);;
self.mockStatistic = function(statistics){
currentStatistic = $.extend({}, defaultStatistic, statistics);
};
self.getStatistics = function(){
var asyncSubject = new Rx.AsyncSubject();
asyncSubject.OnNext(currentStatistic);
asyncSubject.OnCompleted();
return asyncSubject.AsObservable();
};
return self;
};
})(App)
Ok, escrevi muito mais do que planejei inicialmente. Meu dedo dói, meus cachorros me pedem para levá-los para passear e me sinto exausto. Tenho certeza de que há muitas coisas faltando aqui e que coloquei um monte de erros de digitação e de gramática. Grite comigo se algo não estiver claro e eu irei atualizar a postagem mais tarde.
A postagem pode não parecer uma pergunta, mas na verdade é! Gostaria que você compartilhasse seus pensamentos sobre minha abordagem e se você acha que é boa ou ruim ou se estou perdendo coisas.
ATUALIZAR
Devido à grande popularidade que esta postagem ganhou e porque várias pessoas me pediram para fazer isso, coloquei o código deste exemplo no github:
https://github.com/cburgdorf/stackoverflow-knockout-example
Obtenha enquanto está quente!
fonte
Respostas:
Estou trabalhando na mesma coisa (knockout + jquery mobile). Estou tentando escrever um post sobre o que aprendi, mas aqui estão algumas dicas. Lembre-se de que também estou tentando aprender o knockout / jquery mobile.
Exibir modelo e página
Use apenas um (1) objeto de modelo de visualização por página do jQuery Mobile. Caso contrário, você pode ter problemas com eventos de clique que são disparados várias vezes.
View-Model e clique em
Use ko.observable-fields apenas para eventos de clique de modelos de visualização.
ko.applyBinding uma vez
Se possível: chame ko.applyBinding apenas uma vez para cada página e use ko.observable em vez de chamar ko.applyBinding várias vezes.
pagehide e ko.cleanNode
Lembre-se de limpar alguns modelos de visualização no pagehide. ko.cleanNode parece atrapalhar a renderização do jQuery Mobiles - fazendo com que ele re-renderize o html. Se você usar ko.cleanNode em uma página, será necessário remover data-role's e inserir o jQuery Mobile html renderizado no código-fonte.
ocultar a página e clicar
Se você estiver vinculando a eventos de clique - lembre-se de limpar .ui-btn-active. A maneira mais fácil de fazer isso é usando este snippet de código:
fonte