You may call it reinventing the wheel, however, I'd like a senior architect level to answer this, especially who knows the underpinnings of angular js especially or can do himself in plain vanilla JavaScript. Although Angular is more sophisticated but I want to get it in the simplest, efficient and usable form without any memory leaks.
There's a major div with a larger scope in a page, I'd call it a component of a SPA page (of course containing several other components too), loads different templates at runtime into the parent div, and plugs in some code that acts around that loaded template. For the sake I'd take two templates and two JS classes.
IMO and in general a component is like a state machine, that goes through different visual states as user interacts with it, each state has some visible tags, buttons and associated event handlers, javascript code, I could load all html templates and associated JS at page load but it's more efficient to load html and plug JS on need basis.
HTML SPA
<ul>
<li><a href='#/products'>Products</a></li>
<li><a href='#/customers'>Customers</a></li>
</ul>
<div id='main'></div>
templates/products.html
<a href='#/products/buy/1'>Product 1</a>
<a href='#/products/buy/2'>Product 2</a>
templates/customers.html
<a href='#/customers/contact/1'>Customer 1</a>
<a href='#/customers/contact/2'>Customer 2</a>
js/products.js
function Product() {
window.addEventListener("hashchange", function(){
// check for #buy/1, #buy/2
});
}
Product.prototype.load = function() {
// load templates/products.html into #main
}
js/customers.js
function Customer() {
window.addEventListener("hashchange", function(){
// check for #contact/1, #contact/2
});
}
Customer.prototype.load = function() {
// populate customer.html into #main
}
some JS code that monitors the hash change event on the browser, and will load corresponding templates/products.html and templates/users.html
main.js (checking hash change and instantiating classes)
var product = new Product()
var customer = new Customer()
window.addEventListener("hashchange", function(){
// if #/products
product.load();
// if #/customers
customer.load()
}, false);
Question:
I'm trying to get it "right", follow the industry way, simulate ngRoute or uiRouter (not completely, but to an extent) and looking forward for suggestions and improvements and most importantly for corrections.
Also do I've a memory leak problem here?
Related
I would like to know which is the proper way to navigate between pages using ajax calls.
An example, we got this 3 html pages.
users.html (with users.js which initializes it and has its own functions)
cars.html (with cars.js which initializes it and has its own functions)
bills.html (with bills.js which initializes it and has its own functions)
What would be the proper way to go from users.html to cars.html ? I got this problem because I dont know how to "load" the cars.js after doing the ajax call in users.html.
¿If I load it with $.getScript(), how can I remove the users.js after adding the cars.js?
Thanks.
You can try to build a SPA (Single Page Application). You will have one index html file that uses the other html files as templates. For example you have a div main container whose content is replaced with users.html/cars.html/bills.html upon clicking a link.
Routing helps you get that done without refreshing the page. It also supports history.
Look up dependency injection so that you learn how you can download only the js files you depend on.
If you don't use routing and you only change the page content you lose history which is a really neat thing to have.
SPA with Routing and Templating
Routing with Sammy.js
Examples:
<body>
Cars
Bills
<div id="wrapper"></div>
<script src="jquery.js"></script>
<script src="sammy.js"></script>
<script>
(function() {
app.router = Sammy(function () {
var selector = '#wrapper';
this.get('#/cars', function() {
$.get('cars.html', function (view) {
$(selector).html(view);
});
this.get('#/bills', function() {
$.get('bills.html', function (view) {
$(selector).html(view);
});
});
});
app.router.run('#/cars'); //Link to load on app opening
}());
</script>
</body>
You can call page with $.get() like
$.get( "cars.html", function( data ) {
$(document).html(data);
alert( "Load was performed." );
});
In cars.js use all functions with $(document).ready()
but all functions must be:
$(document).on("yourevent","selector",function(){
});
while you load page cars.js will load if you import it in cars.html page
Read more about jquery.get() and jquery.on()
When a view's template is refreshed due to changes of the model, the didInsertElement hook is not triggered as Ember tries to reuse views as much as possible. This causes some problems for me in that I need to select the first element of a list when the template is rerendered. This happens, for example, when new data is loaded from the server or the application transitions to a different route.
The odd thing is that the view's init method does get called when the application transitions to a different route. The issue is that the template hasn't been rendered at that point, that is, the view hasn't been populated with the new contents of the controller.
In short, how can I be notified after the template of a view has finished rerendering so that I can manipulate the view's contents?
For your information, observing the controller's model is not an option either as the view hasn't been updated by the time the callback is fired.
<script type="text/x-handlebars" id="list">
<div>
<ul>
{{#each model.items}}
<li class="item">
<p>{{title}}</p>
</li>
{{/each}}
</ul>
</div>
</script>
Update The view needs to be notified when the each block is rerendered. I wanted to use the solution in this question, but it seems that only the each block is rerendered and not the entire template (which is what I want). I could add a trigger event in the each block, but that is very expensive as the custom event would be triggered in each loop of the each block.
Maybe this could work:
App.ListView = Ember.View.extend({
templateName: 'list',
contentChanged: function() {
Ember.run.scheduleOnce('afterRender', this, 'selectFirstElement')
}.observes('controller.content.[]'),
selectFirstElement: function() {
// for simplicity
this.$('li:first').addClass('selected');
}
});
Using observes('controller.content.[]') make the contentChanged function be called, if some change is performed in the controller content. To don't get that function called a lot of times in the same runloop we use the Ember.run.scheduleOnce('afterRender', ...), so selectFirstElement is called just once, and because it's scheduled in the afterRender queue, you can manipule the dom.
Live demo http://jsfiddle.net/marciojunior/4b2V3/
App.MagicView = Ember.View.extend({
doMagic: function() {
// magic goes here
}.on('didInsertElement');
});
Update: I see now. Here's my brain dump: http://emberjs.jsbin.com/URidoBi/3/edit. Not the cleanest way, but should get you around the problem.
I am working on a single-page web site targeted for mobile users (eventually going to be ported to Phonegap). I have broken down my screens into 'cards', which are basically just <div>s that I am showing/initializing/hiding as needed.
Currently I am having trouble deciding on the proper structure to use in order to implement linking these panels together into a coherent app. My current implementation goes something like this (currently using Knockout as I am familiar with it):
//Javascript
var LoginCard = function() {
this.goToRegister = function() {
// IF registerCard is not initialized
// THEN ko.applyBindings(new RegisterCard(), document.getElementById('registerCard'));
// ELSE $('#registerCard').show();
};
this.doLogin = function() { /* Goes to home card after login */ };
}
var RegisterCard = function() {
this.goToLogin = function() { /* Goes back to login card */ };
this.doRegister = function() { /* Goes to login card after reg */ };
}
ko.applyBindings(new LoginCard(), document.getElementById('loginCard'));
//HTML
<div id="loginCard">
<button data-bind="click: goToRegister" id="btnReg">Register Account</button>
<button data-bind="click: doLogin" id="btnLogin">Login</button>
</div>
<div id="registerCard">
<button data-bind="click: goToLogin" id="btnBackToLogin">Back To Login</button>
<button data-bind="click: doRegister" id="btnDoReg">Submit Registration</button>
</div>
As you can see, the linking occurs within the view model itself, so the different view models (e.g. loginCard, registerCard, homeCard) become tightly coupled with each other.
A more "low-level" alternative would just be to use jQuery to bind the button events so that each card does not have to know details about the other cards:
//But this means I have to specify a ton of unique IDs for all the elements in the page.
$('#btnReg').click(function() { /* Initialize and go to registerCard. */ });
I also thought of using hash-routing/pushState so while the click events are still inside each view model, all it has to know is the URL to go to? Something like:
var LoginCard = function() {
this.goToRegister = function() {
window.location.hash = 'register';
//or history.pushState('state', '', 'register';
};
}
This is my first attempt at creating a single-page application, so I am really confused about design choice. Which one would be better, or can anyone suggest the standard way to go regarding this?
I recommend you to create another object for the routing which depends on routing library such as SammyJS or CrossroadsJS.
Please refer my hobby project, MyStory.Spa, it is also single page application style web (not for the mobile app), which is using SammyJS for browser level routing.
In the MyStory.Spa architecture, webapp/app/infra/router.js takes a role for the routing and detailed information about routing, view, viewmodels are in the /webapp/app/infra/routing.table.js.
In this way you can decouple View, ViewModel, Model, Data Service, Routing and so on.
I have a single paged website, in which i've got a div named sitecontent with the width of 4400, which holds 4 "pages". The pages start at 0px of sitecontent, 1100 px of sitecontent, 2200px of sitecontent, and 3300px.
I use Jquery to set de div position to the right px, so i get the right text displayed. After pressing a link i get for example:
<div id="site-content" style="left: -1100px;">
At one of the pages i have to refresh the page, and after this refresh i want the page to display the same "page" on 1100px, but it starts at 0px, the home page.
Is there any way how i can make sure that the sitecontent starts at -1100px of home?
Thanks in advance,
Cheers
You need to append some identifier onto the hash of the URL that you can parse on the page load.
For example:
http://www.somewebpage.com/somepage#page1
Then in the load of the page, you can inspect this hash value and immediately change the UI to show the new page:
var hash = window.location.hash;
if(hash == "#page1")
$('#site-content').css('left', '-1100px');
You can use a cookie to store the value, then, every time the page loads, you need to check the cookie and deal with the value collected:
The link for Jquery Cookie with download and usage manual!
HTML (example)
<a href="#" title="Go Page 1" id="page_01" class="setCookie">
Click to view page 01
</a>
JQUERY (jquery.cookie)
// SET THE COOKIE
$('.setCookie').bind("click", function() {
var pageNumber = $(this).attr("id");
$.cookie('the_cookie_name', pageNumber, { expires: 7, path: '/' });
});
// READ THE COOKIE
$(function() {
var oldNumber = $.cookie('the_cookie_name');
if (oldNumber !== NULL) {
$("#page_"+oldNumber).trigger("click");
}
});
Note:
The link that you currently use to change pages, should have the class "setCookie" to trigger the cookie creation, and also the id that is being used to identify the page number.
One advantage of this is that you can control for how long is the cookie preserved and thus allowing the visitant to resume the website experience.
An approach very similar to what Tejs is proposing would be to use a hash-based routing framework that listens to hash changes. That will result in much cleaner code since you don't need to define the scrolling in two different places.
Every link in your page is currently being observed by a jQuery event listener (onclick -> moves the content container to the show the desired content). The HTML looks probably somewhat like this: Contact details.
With this approach, you don't need to watch those links. Instead, simply make them change the hash: Contact details.
Now observe the hash and react to changes. I'm using the Simrou framework (https://github.com/buero-fuer-ideen/Simrou) but you can go with any other framework that provides similar functionality.
jQuery(document).ready(function() {
// Define a function that moves the content, e.g. moveContent(3300);
function moveContent(pixelPosition) {
$('#site-content').css('left', '-' + pixelPosition + 'px');
}
// Setup the router
var router = new Simrou({
'page1': function() { moveContent(0); },
'page2': function() { moveContent(1100); },
'page3': function() { moveContent(2200); },
'page4': function() { moveContent(3300); }
});
router.start();
});
That's all the javascript you need!
(and here is a quick and dirty fiddle, demonstrating the whole thing: http://jsfiddle.net/R7F6r/)
I am trying to build a very specific module that integrates Twitter bootstrap (for the tabs), jQuery (for ease of development) and knockout.js for client-side view models. The idea is that I have one HTML page, which has 3 div's defined in it. Each div is a tab, with only one div visible at a particular time. When a div is displayed it should call a load method on a knockout.js viewmodel that is scoped to the tab and reload the data from the server.
I have written the following code (which works), however it is very specific to a particular tab in my application.
// only configure this once the DOM is ready for processing.
// this code snippet is very long winded and quite a hack, however it allows the content of a bootstrap.js
// tab to be reloaded when the tab is made visible. It does this by calling the LoadCategory() method on the
// knockout.js view model.
//
// it is also worth noting that the view model is bound using knockout to only descendents of the div that contains
// the tab contents. This is to ensure that we can have several knockout view models in one page without needing to
// worry about them interfering with each other.
$(document).ready(function () {
// initialise the model...
var todaysQuestionsModel = new ViewModel(categoryId);
// if this tab is visible to begin with, load the view model.
if ($('#todays-questions').hasClass('active')) {
todaysQuestionsModel.LoadCategory();
}
// only apply these bindings to the elements that descend from the div that contains this tab.
ko.applyBindings(todaysQuestionsModel, document.getElementById("#todays-questions"));
$('a[data-toggle="tab"]').on('shown', function (e) {
if (e.target.hash == '#todays-questions') {
todaysQuestionsModel.LoadCategory();
}
});
});
I would like to be able to wrap this up in a javascript module that I can reuse for different parts of my application, however I am at a loss as to how to generify this code. Ideally I would like to be able to just make a simple function call to configure all of this behaviour automatically.
I assume for the part where the todaysQuestionsModel.LoadCategory() call is made that this should be a callback function. I also assume there should be some way that I don't have to specify the id selector, but any attempt I have made so far doesn't seem to work.
Could someone help me out with this, I am quite out of my depth here :-).
Cheers,
Aidan
I would use the event handling by knockout.
This is a modified sample code: http://jsfiddle.net/xVxKD/52/
<div class="tabbable">
<ul class="nav nav-tabs" data-bind="foreach: tabs">
<li data-bind="css: { active: $root.selected() === $data }">
</li>
</ul>
<div class="tab-content" data-bind="foreach: tabs">
<div class="tab-pane" data-bind="css: { active: $root.selected() === $data }">
<p data-bind="text: content"></p>
</div>
</div>
</div>
<script type='text/javascript'>//<![CDATA[
$(window).load(function(){
function Tab(id, title, content) {
this.id = ko.observable(id);
this.title = ko.observable(title);
this.content = ko.observable(content);
}
var viewModel = {
tabs: ko.observableArray([
new Tab(1, "one", "one content"),
new Tab(2, "two", "two content"),
new Tab(3, "three", "three content")
]),
selected: ko.observable(),
doSomething: function($data, $event){
//alert(' = '+$event.target.hash)
$data.content($data.content() + '\n Do i need to fetch new content for you?')
viewModel.selected($data);
}
};
//select second tab by default
viewModel.selected(viewModel.tabs()[1]);
ko.applyBindings(viewModel);
});//]]>
</script>