Skip to content
Browse files

Close #19 - custom tab panel is easy

  • Loading branch information...
1 parent 4711da0 commit a721ca4d00fdc61ef7079387d278c6a0ca7f8034 @finnsson committed Sep 2, 2012
Showing with 747 additions and 29 deletions.
  1. +39 −1 README.md
  2. +3 −0 demo/demo.css
  3. +11 −8 demo/demo.js
  4. +56 −12 demo/index.html
  5. +625 −2 demo/pager.amd.min.js
  6. +13 −6 pager.js
View
40 README.md
@@ -398,9 +398,23 @@ where
The source code is documented using JsDoc.
+### Tab panel custom widget
+
+ <ul class="nav nav-tabs" data-bind="foreach: $page.children">
+ <li data-bind="css: {active: isVisible}"><a data-bind="text: $data.getValue().title, page-href: getId()"></a></li>
+ </ul>
+
+ <div data-bind="page: {id: 'Slagsmålsklubben', title: 'Slagsmålsklubben', sourceOnShow: 'https://embed.spotify.com/?uri=spotify:album:66KBDVJnA6c0DjHeSZYaHb', frame: 'iframe'}" class="hero-unit">
+ <iframe width="300" height="380" frameborder="0" allowtransparency="true"></iframe>
+ </div>
+
+ <div data-bind="page: {id: 'Binärpilot', title: 'Binärpilot', sourceOnShow: 'https://embed.spotify.com/?uri=spotify:album:67LKycg4jAoC06kZgjvbNd', frame: 'iframe'}" class="hero-unit">
+ <iframe width="300" height="380" frameborder="0" allowtransparency="true"></iframe>
+ </div>
+
+
## In the pipeline
-### Tab panel custom widget
### Wildcards should deep-load content if configured so
@@ -446,6 +460,30 @@ if
return '<h1>Zoidberg</h1>;
});
+### Should be possible to send URI (fragment identifier) parameters to a page
+
+A page should be able to access the information in the current route - changing a view-model.
+
+Sending parts of the fragment identifier to variables in the view-model is possible using
+`params`.
+
+ <div data-bind="page: {id: 'search', params: {'name', dateFrom: 'fromdate', product: '{1}'}}">
+ </div>
+
+where `name` indicates that the variable `name` will be bound to the parameter `name`,
+`dateFrom: 'fromdate'` indicates that the variable `dateFrom` will be bound to the parameter `fromdate`,
+and `product: '{1}'` indicates that the variable `product` will be bound to the last part of the route.
+
+An example route for the example above could look like
+
+ example.com/#search/tv?name=samsung&fromdate=20121010
+
+or if HTML5 history is used
+
+ example.com/search/tv?name=samsung&fromdate=20121010
+
+
+
### Document architecture and guiding principles
The architecture - and guiding principles - should be documentet.
View
3 demo/demo.css
@@ -0,0 +1,3 @@
+body > .container > div {
+ display: none;
+}
View
19 demo/demo.js
@@ -19,13 +19,13 @@ require(['jquery', 'knockout', 'underscore', 'pager', 'bootstrap'], function ($,
}
});
},
- textLoader: function(page, element) {
+ textLoader:function (page, element) {
var loader = {};
- var txt = $('<div></div>', {text: 'Loading ' + page.getValue().title});
- loader.load = function() {
+ var txt = $('<div></div>', {text:'Loading ' + page.getValue().title});
+ loader.load = function () {
$(element).append(txt);
};
- loader.unload = function() {
+ loader.unload = function () {
txt.remove();
};
return loader;
@@ -40,11 +40,14 @@ require(['jquery', 'knockout', 'underscore', 'pager', 'bootstrap'], function ($,
};
};
- pager.extendWithPage(viewModel);
- ko.applyBindings(viewModel);
- pager.start(viewModel);
+ $(function () {
- $('.dropdown-toggle').dropdown();
+ pager.extendWithPage(viewModel);
+ ko.applyBindings(viewModel);
+ pager.start(viewModel);
+
+ $('.dropdown-toggle').dropdown();
+ });
});
View
68 demo/index.html
@@ -2,8 +2,10 @@
<html>
<head>
<title>Pager.js Demo</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link href="bootstrap/css/bootstrap.css" rel="stylesheet">
+ <link href="demo.css" rel="stylesheet">
<script data-main="demo.js" type="text/javascript" src="require.js"></script>
@@ -32,7 +34,7 @@
<div class="dropdown">
<a class="btn dropdown-toggle"><i class="icon-tasks"></i> Menu</a>
- <ul data-bind="foreach: $page.children" class="dropdown-menu" role="menu"
+ <ul data-bind="foreach: $__page__.children" class="dropdown-menu" role="menu"
aria-labelledby="dropdownMenu">
<li>
<a data-bind="text: (valueAccessor().title || valueAccessor().id), attr: {href: '#' + valueAccessor().id}"></a>
@@ -592,14 +594,12 @@ <h3 data-bind="text: name"></h3>
show: void
showElement: void
hideElement : void
-isVisible : void
</pre>
<p>
We want our custom binding to always show the first child element and only hide the second child element.
- Thus we need to override <code>showElement</code>, <code>hideElement</code> and <code>isVisible</code>.
- <code>showElement</code> should show the second child, <code>hideElement</code> should hide the second child
- and <code>isVisible</code> should return true if the second child is visible.
+ Thus we need to override <code>showElement</code> and <code>hideElement</code>.
+ <code>showElement</code> should show the second child and <code>hideElement</code> should hide the second child.
</p>
@@ -624,11 +624,6 @@ <h3 data-bind="text: name"></h3>
pager.PageAccordionItem.prototype.showElement = function () {
$(this.getAccordionBody()).slideDown();
};
-
-// return true if the second child is visible
-pager.PageAccordionItem.prototype.isVisible = function () {
- return $(this.getAccordionBody()).is(':visible');
-};
</pre>
<p>
@@ -792,7 +787,8 @@ <h3 data-bind="text: name"></h3>
</header>
<a href="#custom_hide_show/fry">Show Fry</a>
- <div data-bind="page: {id: 'fry', showElement: showFry, hideElement: hideFry}">
+
+ <div data-bind="page:{id: 'fry', showElement: showFry, hideElement: hideFry}">
<h2>Fry</h2>
<a class="btn" data-bind="page-href: '../'">Hide Fry</a>
</div>
@@ -842,7 +838,7 @@ <h3 data-bind="text: name"></h3>
<a href="#loader/Sabrepulse">Load Sabrepulse</a>
<div data-bind="page: {id: 'Sabrepulse', frame: 'iframe', title: 'Sabrepulse', loader: textLoader, sourceOnShow: 'https://embed.spotify.com/?uri=spotify:album:43tqiFDcU8JcMVSYj6NTi3'}">
- <iframe width="300" height="380" frameborder="0" allowtransparency="true"></iframe>
+ <iframe width="300" height="380" frameborder="0" allowtransparency="true"></iframe>
</div>
<pre class="prettyprint linenums">
@@ -869,6 +865,54 @@ <h3 data-bind="text: name"></h3>
}
</pre>
+ <a href="#tabpanel">Read about developing a tab panel</a>
+
+
+</div>
+
+<div data-bind="page: {id: 'tabpanel', title: 'Tab Panel'}">
+
+ <header class="jumbotron subhead">
+ <h1>Tab Panel</h1>
+
+ <p class="lead">
+ There is no special tab panel widget shipping with pager.js.
+
+ It is however very easy to construct one using the <code>children</code>
+ observableArray and <code>isVisible</code> observable property.
+ </p>
+ </header>
+
+ <div>
+ <ul class="nav nav-tabs" data-bind="foreach: $page.children">
+ <li data-bind="css: {active: isVisible}"><a data-bind="text: $data.getValue().title, page-href: getId()"></a></li>
+ </ul>
+
+ <div data-bind="page: {id: 'Slagsmålsklubben', title: 'Slagsmålsklubben', sourceOnShow: 'https://embed.spotify.com/?uri=spotify:album:66KBDVJnA6c0DjHeSZYaHb', frame: 'iframe'}" class="hero-unit">
+ <iframe width="300" height="380" frameborder="0" allowtransparency="true"></iframe>
+ </div>
+
+ <div data-bind="page: {id: 'Binärpilot', title: 'Binärpilot', sourceOnShow: 'https://embed.spotify.com/?uri=spotify:album:67LKycg4jAoC06kZgjvbNd', frame: 'iframe'}" class="hero-unit">
+ <iframe width="300" height="380" frameborder="0" allowtransparency="true"></iframe>
+ </div>
+ </div>
+
+ <pre class="linenums prettyprint">
+&lt;div&gt;
+ &lt;ul class="nav nav-tabs" data-bind="foreach: $page.children"&gt;
+ &lt;li data-bind="css: {active: isVisible}"&gt;&lt;a data-bind="text: $data.getValue().title, page-href: getId()"&gt;&lt;/a&gt;&lt;/li&gt;
+ &lt;/ul&gt;
+
+ &lt;div data-bind="page: {id: 'Slagsmålsklubben', title: 'Slagsmålsklubben', sourceOnShow: 'https://embed.spotify.com/?uri=spotify:album:66KBDVJnA6c0DjHeSZYaHb', frame: 'iframe'}" class="hero-unit"&gt;
+ &lt;iframe width="300" height="380" frameborder="0" allowtransparency="true"&gt;&lt;/iframe&gt;
+ &lt;/div&gt;
+
+ &lt;div data-bind="page: {id: 'Binärpilot', title: 'Binärpilot', sourceOnShow: 'https://embed.spotify.com/?uri=spotify:album:67LKycg4jAoC06kZgjvbNd', frame: 'iframe'}" class="hero-unit"&gt;
+ &lt;iframe width="300" height="380" frameborder="0" allowtransparency="true"&gt;&lt;/iframe&gt;
+ &lt;/div&gt;
+&lt;/div&gt;
+ </pre>
+
</div>
View
627 demo/pager.amd.min.js
@@ -1,4 +1,627 @@
-/*! pager.js - v0.3.0 - 2012-09-01
+/*! pager.js - v0.3.0 - 2012-09-02
* http://oscar.finnsson.nu/pagerjs/
* Copyright (c) 2012 Oscar Finnsson; Licensed MIT */
-define(["jquery","underscore","knockout"],function(a,b,c){var d={},e={};return e.value=c.utils.unwrapObservable,e.arrayValue=function(a){return b.map(a,function(a){return e.value(a)})},d.ChildManager=function(a,d){this.currentChildO=c.observable(null);var e=this;this.page=d,this.hideChild=function(){e.currentChild&&e.currentChild.getId()!=="start"&&(e.currentChild.hidePage(function(){}),e.currentChild=null,e.currentChildO(null))},this.showChild=function(c){var d=e.currentChild;e.currentChild=null;var f=!1,g=c[0],h=null;b.each(a(),function(a){if(!f){var b=a.getId();if(b===g||(g===""||g==null)&&b==="start")f=!0,e.currentChild=a;b==="?"&&(h=a)}});var i=!1,j=e;while(!e.currentChild&&j.page.parentPage&&!j.page.getValue().modal){var k=j.page.parentPage.children;b.each(k(),function(a){if(!f){var b=a.getId(),c=a.getValue().modal;if(c){if(b===g||(g===""||g==null)&&b==="start")f=!0,e.currentChild=a,i=!0;b==="?"&&!h&&(h=a,i=!0)}}}),e.currentChild||(j=j.page.parentPage.childManager)}!e.currentChild&&h&&(e.currentChild=h,e.currentChild.currentId=g),e.currentChild&&(e.currentChildO(e.currentChild),i?e.currentChild.currentParentPage(e.page):e.currentChild.currentParentPage(null)),d&&d===e.currentChild?e.currentChild.showPage(c.slice(1)):d?d.hidePage(function(){e.currentChild&&e.currentChild.showPage(c.slice(1))}):e.currentChild&&e.currentChild.showPage(c.slice(1))}},d.start=function(b){var c=function(){d.routeFromHashToPage(window.location.hash)};a(window).bind("hashchange",c),c()},d.extendWithPage=function(a){a.$page=new d.Page,d.childManager=new d.ChildManager(a.$page.children,a.$page),a.$page.childManager=d.childManager},d.routeFromHashToPage=function(a){a[0]==="#"&&(a=a.slice(1));var b=a.split("/");d.childManager.showChild(b)},d.Page=function(a,b,e,f,g){this.element=a,this.valueAccessor=b,this.allBindingsAccessor=e,this.viewModel=f,this.bindingContext=g,this.children=c.observableArray([]),this.childManager=new d.ChildManager(this.children,this),this.parentPage=null,this.currentId=null,this.currentParentPage=c.observable(null)},d.Page.prototype.showPage=function(a){this.show(),this.childManager.showChild(a)},d.Page.prototype.hidePage=function(a){this.hideElementWrapper(a),this.childManager.hideChild()},d.Page.prototype.init=function(){var a=this.getValue();this.parentPage=this.getParentPage(),this.parentPage.children.push(this),this.hideElement(),a.source&&this.loadSource(a.source);var b=null;return a["with"]?b=e.value(a["with"]):a.withOnShow?b={}:b=this.viewModel,this.childBindingContext=this.bindingContext.createChildContext(b),c.utils.extend(this.childBindingContext,{$page:this}),a.withOnShow||c.applyBindingsToDescendants(this.childBindingContext,this.element),{controlsDescendantBindings:!0}},d.Page.prototype.getValue=function(){return e.value(this.valueAccessor())},d.Page.prototype.getParentPage=function(){return this.bindingContext.$page||this.bindingContext.$data.$page},d.Page.prototype.getId=function(){return e.value(this.getValue().id)},d.Page.prototype.sourceUrl=function(a){var b=this;return this.getId()==="?"?c.computed(function(){return e.value(a).replace("{1}",b.currentId)}):c.computed(function(){return e.value(a)})},d.Page.prototype.loadSource=function(b){var f=this.getValue(),g=this,h=this.element,i=null,j=f.loader||d.loader;if(f.frame==="iframe"){var k=a("iframe",a(h));k.length===0&&(k=a("<iframe></iframe>"),a(h).append(k)),j&&(i=e.value(j)(g,k),i.load()),f.sourceLoaded&&k.one("load",function(){i&&i.unload(),f.sourceLoaded()}),c.applyBindingsToNode(k[0],{attr:{src:this.sourceUrl(b)}})}else j&&(i=e.value(j)(g,g.element),i.load()),c.computed(function(){var d=e.value(this.sourceUrl(b));a(h).load(d,function(){i&&i.unload(),c.applyBindingsToDescendants(g.childBindingContext,g.element),f.sourceLoaded&&f.sourceLoaded.apply(g,arguments)})},this)},d.Page.prototype.show=function(a){var d=this.element,e=this.getValue();this.showElementWrapper(a),this.getValue().title&&(window.document.title=this.getValue().title),e.withOnShow&&(this.withOnShowLoaded||(this.withOnShowLoaded=!0,e.withOnShow(b.bind(function(a){var b=this.bindingContext.createChildContext(a);c.utils.extend(b,{$page:this}),c.applyBindingsToDescendants(b,this.element)},this)))),e.sourceOnShow&&(!e.sourceCache||!d.__pagerLoaded__||typeof e.sourceCache=="number"&&d.__pagerLoaded__+e.sourceCache*1e3<Date.now())&&(d.__pagerLoaded__=Date.now(),this.loadSource(e.sourceOnShow))},d.Page.prototype.showElementWrapper=function(a){this.getValue().beforeShow&&this.getValue().beforeShow(this),this.showElement(a),this.getValue().afterShow&&this.getValue().afterShow(this)},d.Page.prototype.showElement=function(b){this.getValue().showElement?this.getValue().showElement(this,b):d.showElement?d.showElement(this,b):a(this.element).show(b)},d.Page.prototype.hideElementWrapper=function(a){this.getValue().beforeHide&&this.getValue().beforeHide(this),this.hideElement(a),this.getValue().afterHide&&this.getValue().afterHide(this)},d.Page.prototype.hideElement=function(b){this.getValue().hideElement?this.getValue().hideElement(this,b):d.hideElement?d.hideElement(this,b):(a(this.element).hide(),b&&b())},d.Page.prototype.isVisible=function(){return a(this.element).is(":visible")},d.Page.prototype.getFullRoute=function(){return c.computed(function(){if(this.currentParentPage&&this.currentParentPage()){var a=this.currentParentPage().getFullRoute()();return a.push(this.getId()),a}if(this.parentPage){var a=this.parentPage.getFullRoute()();return a.push(this.getId()),a}return[]},this)},c.bindingHandlers.page={init:function(a,b,c,e,f){var g=new d.Page(a,b,c,e,f);return g.init()},update:function(a,b,c,d,e){}},d.useHTML5history=!1,d.rootURI="/",c.bindingHandlers["page-href"]={init:function(b,f,g,h,i){var j=i.$page||i.$data.$page,k=j,l=c.computed(function(){var c=e.value(f()),d=0;while(c.substring(0,3)==="../")d++,c=c.slice(3);var g=k.getFullRoute()(),h=g.slice(0,g.length-d).join("/"),i=(h===""?"":h+"/")+c,j={href:"#"+i};return a(b).attr(j),i});d.useHTML5history&&window.history&&history.pushState&&a(b).click(function(a){a.preventDefault(),history.pushState(null,null,d.rootURI+l()),d.childManager.showChild(l().split("/"))})},update:function(){}},d.PageAccordionItem=function(a,b,c,e,f){d.Page.apply(this,arguments)},d.PageAccordionItem.prototype=new d.Page,d.PageAccordionItem.prototype.getAccordionBody=function(){return a(this.element).children()[1]},d.PageAccordionItem.prototype.hideElement=function(b){this.pageAccordionItemHidden?(a(this.getAccordionBody()).slideUp(),b&&b()):(this.pageAccordionItemHidden=!0,a(this.getAccordionBody()).hide())},d.PageAccordionItem.prototype.showElement=function(b){a(this.getAccordionBody()).slideDown(),b&&b()},c.bindingHandlers["page-accordion-item"]={init:function(a,b,c,e,f){var g=new d.PageAccordionItem(a,b,c,e,f);g.init()},update:function(){}},d});
+
+define(['jquery','underscore','knockout'], function($,_,ko) {
+
+var pager = {};
+
+// common KnockoutJS helpers
+var _ko = {};
+
+_ko.value = ko.utils.unwrapObservable;
+
+_ko.arrayValue = function (arr) {
+ return _.map(arr, function (e) {
+ return _ko.value(e);
+ });
+};
+
+/**
+ * @class pager.ChildManager
+ */
+
+/**
+ *
+ * @param {pager.Page[]} children
+ * @param {pager.Page} page
+ * @constructor
+ */
+pager.ChildManager = function (children, page) {
+
+ this.currentChildO = ko.observable(null);
+ var me = this;
+ this.page = page;
+
+ this.hideChild = function () {
+ if (me.currentChild) {
+ if (me.currentChild.getId() !== 'start') {
+ me.currentChild.hidePage(function () {
+ });
+ me.currentChild = null;
+ me.currentChildO(null);
+ }
+ }
+ };
+
+ /**
+ *
+ * @param {String[]} route
+ */
+ this.showChild = function (route) {
+ var oldCurrentChild = me.currentChild;
+ me.currentChild = null;
+ var match = false;
+ var currentRoute = route[0];
+ var wildcard = null;
+ _.each(children(), function (child) {
+ if (!match) {
+ var id = child.getId();
+ if (id === currentRoute ||
+ ((currentRoute === '' || currentRoute == null) && id === 'start')) {
+ match = true;
+ me.currentChild = child;
+ }
+ if (id === '?') {
+ wildcard = child;
+ }
+ }
+ });
+ // find modals in parent - but only if 1) no match is found, 2) this page got a parent and 3) this page is not a modal!
+ var isModal = false;
+
+ var currentChildManager = me;
+
+ while(!me.currentChild && currentChildManager.page.parentPage && !currentChildManager.page.getValue().modal) {
+ var parentChildren = currentChildManager.page.parentPage.children;
+ _.each(parentChildren(), function (child) {
+ if (!match) {
+ var id = child.getId();
+ var modal = child.getValue().modal;
+ if (modal) {
+ if (id === currentRoute ||
+ ((currentRoute === '' || currentRoute == null) && id === 'start')) {
+ match = true;
+ me.currentChild = child;
+ isModal = true;
+ }
+ if (id === '?' && !wildcard) {
+ wildcard = child;
+ isModal = true;
+ }
+ }
+ }
+ });
+ if(!me.currentChild) {
+ currentChildManager = currentChildManager.page.parentPage.childManager;
+ }
+ }
+
+ if (!me.currentChild && wildcard) {
+ me.currentChild = wildcard;
+ me.currentChild.currentId = currentRoute;
+ }
+ if (me.currentChild) {
+ me.currentChildO(me.currentChild);
+
+ if(isModal) {
+ me.currentChild.currentParentPage(me.page);
+ } else {
+ me.currentChild.currentParentPage(null);
+ }
+
+ }
+ if (oldCurrentChild && oldCurrentChild === me.currentChild) {
+ me.currentChild.showPage(route.slice(1));
+ } else if (oldCurrentChild) {
+ oldCurrentChild.hidePage(function () {
+ if (me.currentChild) {
+ me.currentChild.showPage(route.slice(1));
+ }
+ });
+ } else if (me.currentChild) {
+ me.currentChild.showPage(route.slice(1));
+ }
+ };
+};
+
+/**
+ */
+pager.start = function () {
+
+ var onHashChange = function () {
+ pager.routeFromHashToPage(window.location.hash);
+ };
+ $(window).bind('hashchange', onHashChange);
+ onHashChange();
+};
+
+/**
+ *
+ * @param viewModel
+ */
+pager.extendWithPage = function (viewModel) {
+ viewModel.$__page__ = new pager.Page();
+
+ pager.childManager = new pager.ChildManager(viewModel.$__page__.children, viewModel.$__page__);
+ viewModel.$__page__.childManager = pager.childManager;
+};
+
+
+/**
+ *
+ * @param {String} hash
+ */
+pager.routeFromHashToPage = function (hash) {
+ // skip #
+ if (hash[0] === '#') {
+ hash = hash.slice(1);
+ }
+ // split on '/'
+ var hashRoute = decodeURIComponent(hash).split('/');
+
+ pager.childManager.showChild(hashRoute);
+
+};
+
+/**
+ * @class pager.Page
+ */
+
+/**
+ * @param {Node} element
+ * @param {Observable} valueAccessor
+ * @param allBindingsAccessor
+ * @param {Observable} viewModel
+ * @param bindingContext
+ * @constructor
+ */
+pager.Page = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
+ /**
+ *
+ * @type {Node}
+ */
+ this.element = element;
+ /**
+ *
+ * @type {Observable}
+ */
+ this.valueAccessor = valueAccessor;
+ /**
+ *
+ * @type {*}
+ */
+ this.allBindingsAccessor = allBindingsAccessor;
+ /**
+ *
+ * @type {Observable}
+ */
+ this.viewModel = viewModel;
+ /**
+ *
+ * @type {*}
+ */
+ this.bindingContext = bindingContext;
+
+ /**
+ *
+ * @type {ObservableArray}
+ */
+ this.children = ko.observableArray([]);
+
+ /**
+ *
+ * @type {pager.ChildManager}
+ */
+ this.childManager = new pager.ChildManager(this.children, this);
+ /**
+ *
+ * @type {pager.Page}
+ */
+ this.parentPage = null;
+ /**
+ *
+ * @type {String}
+ */
+ this.currentId = null;
+
+ /**
+ *
+ * @type {Observable/pager.Page}
+ */
+ this.currentParentPage = ko.observable(null);
+
+
+ /**
+ *
+ * @type {Observable}
+ */
+ this.isVisible = ko.observable(false);
+};
+
+/**
+ * @method showPage
+ * @param route
+ */
+pager.Page.prototype.showPage = function (route) {
+ this.isVisible(true);
+ this.show();
+ this.childManager.showChild(route);
+};
+
+/**
+ * @method hidePage
+ * @param {Function} callback
+ */
+pager.Page.prototype.hidePage = function (callback) {
+ this.isVisible(false);
+ this.hideElementWrapper(callback);
+ this.childManager.hideChild();
+};
+
+/**
+ * @method init
+ * @return {Object}
+ */
+pager.Page.prototype.init = function () {
+
+ var value = this.getValue();
+ this.parentPage = this.getParentPage();
+ this.parentPage.children.push(this);
+
+
+ this.hideElement();
+
+ // Fetch source
+ if (value.source) {
+ this.loadSource(value.source);
+ }
+
+ var ctx = null;
+ if (value['with']) {
+ ctx = _ko.value(value['with']);
+ } else if (value['withOnShow']) {
+ ctx = {};
+ } else {
+ ctx = this.viewModel;
+ }
+ this.childBindingContext = this.bindingContext.createChildContext(ctx);
+ ko.utils.extend(this.childBindingContext, {$page:this});
+ if (!value['withOnShow']) {
+ ko.applyBindingsToDescendants(this.childBindingContext, this.element);
+ }
+ return { controlsDescendantBindings:true};
+};
+
+/**
+ * @method getValue
+ * @returns {Object} value
+ */
+pager.Page.prototype.getValue = function () {
+ return _ko.value(this.valueAccessor());
+};
+
+/**
+ * @method pager.Page#getParentPage
+ * @return {pager.Page}
+ */
+pager.Page.prototype.getParentPage = function () {
+ return this.bindingContext.$page || this.bindingContext.$data.$__page__;
+};
+
+/**
+ * @method pager.Page#getId
+ * @return String
+ */
+pager.Page.prototype.getId = function () {
+ return _ko.value(this.getValue().id);
+};
+
+
+/**
+ *
+ * @param {Observable/String} source
+ * @return {Observable}
+ */
+pager.Page.prototype.sourceUrl = function (source) {
+ var me = this;
+ if (this.getId() === '?') {
+ return ko.computed(function () {
+ // TODO: maybe make currentId an ko.observable?
+ return _ko.value(source).replace('{1}', me.currentId);
+ });
+ } else {
+ return ko.computed(function () {
+ return _ko.value(source);
+ });
+ }
+};
+
+/**
+ * @method loadSource
+ * @param source
+ */
+pager.Page.prototype.loadSource = function (source) {
+ var value = this.getValue();
+ var me = this;
+ var element = this.element;
+ var loader = null;
+ var loaderMethod = value.loader || pager.loader;
+ if (value.frame === 'iframe') {
+ var iframe = $('iframe', $(element));
+ if (iframe.length === 0) {
+ iframe = $('<iframe></iframe>');
+ $(element).append(iframe);
+ }
+ if(loaderMethod) {
+ loader = _ko.value(loaderMethod)(me, iframe);
+ loader.load();
+ }
+ if (value.sourceLoaded) {
+ iframe.one('load', function() {
+ if(loader) {
+ loader.unload();
+ }
+ value.sourceLoaded();
+ });
+ }
+ // TODO: remove src binding and add this binding
+ ko.applyBindingsToNode(iframe[0], {
+ attr:{
+ src:this.sourceUrl(source)
+ }
+ });
+ } else {
+ if(loaderMethod) {
+ loader = _ko.value(loaderMethod)(me, me.element);
+ loader.load();
+ }
+ // TODO: remove all children and add sourceUrl(source)
+ ko.computed(function () {
+ var s = _ko.value(this.sourceUrl(source));
+ $(element).load(s, function () {
+ if(loader) {
+ loader.unload();
+ }
+ ko.applyBindingsToDescendants(me.childBindingContext, me.element);
+ if (value.sourceLoaded) {
+ value.sourceLoaded.apply(me, arguments);
+ }
+ });
+ }, this);
+ }
+};
+
+/**
+ * @method pager.Page#show
+ * @param {Function} callback
+ */
+pager.Page.prototype.show = function (callback) {
+ var element = this.element;
+ var value = this.getValue();
+ this.showElementWrapper(callback);
+ if (this.getValue().title) {
+ window.document.title = this.getValue().title;
+ }
+ if (value.withOnShow) {
+ if (!this.withOnShowLoaded) {
+ this.withOnShowLoaded = true;
+ value.withOnShow(_.bind(function (vm) {
+ var childBindingContext = this.bindingContext.createChildContext(vm);
+
+ ko.utils.extend(childBindingContext, {$page:this});
+ ko.applyBindingsToDescendants(childBindingContext, this.element);
+ }, this));
+ }
+ }
+
+ // Fetch source
+ if (value.sourceOnShow) {
+ if (!value.sourceCache ||
+ !element.__pagerLoaded__ ||
+ (typeof(value.sourceCache) === 'number' && element.__pagerLoaded__ + value.sourceCache * 1000 < Date.now())) {
+ element.__pagerLoaded__ = Date.now();
+ this.loadSource(value.sourceOnShow);
+ }
+ }
+};
+
+/**
+ * @method pager.Page#showElementWrapper
+ * @param {Function} callback
+ */
+pager.Page.prototype.showElementWrapper = function (callback) {
+ if (this.getValue().beforeShow) {
+ this.getValue().beforeShow(this);
+ }
+ this.showElement(callback);
+ if (this.getValue().afterShow) {
+ this.getValue().afterShow(this);
+ }
+};
+
+/**
+ * @method showElement
+ * @param {Function} callback
+ */
+pager.Page.prototype.showElement = function (callback) {
+ if (this.getValue().showElement) {
+ this.getValue().showElement(this, callback);
+ } else if (pager.showElement) {
+ pager.showElement(this, callback);
+ } else {
+ $(this.element).show(callback);
+ }
+};
+
+/**
+ *
+ * @param {Function} callback
+ */
+pager.Page.prototype.hideElementWrapper = function (callback) {
+ if (this.getValue().beforeHide) {
+ this.getValue().beforeHide(this);
+ }
+ this.hideElement(callback);
+ if (this.getValue().afterHide) {
+ this.getValue().afterHide(this);
+ }
+};
+
+/**
+ *
+ * @param {Function} callback
+ */
+pager.Page.prototype.hideElement = function (callback) {
+ if (this.getValue().hideElement) {
+ this.getValue().hideElement(this, callback);
+ } else if (pager.hideElement) {
+ pager.hideElement(this, callback);
+ } else {
+ $(this.element).hide();
+ if (callback) {
+ callback();
+ }
+ }
+};
+
+
+/**
+ *
+ * @return {Observable}
+ */
+pager.Page.prototype.getFullRoute = function () {
+ return ko.computed(function () {
+ var res = null;
+ if (this.currentParentPage && this.currentParentPage()) {
+ res = this.currentParentPage().getFullRoute()();
+ res.push(this.getId());
+ return res;
+ } else if (this.parentPage) {
+ res = this.parentPage.getFullRoute()();
+ res.push(this.getId());
+ return res;
+ } else { // is root page
+ return [];
+ }
+ }, this);
+};
+
+ko.bindingHandlers.page = {
+ init:function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
+ var page = new pager.Page(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
+ return page.init();
+ },
+ update:function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
+ }
+};
+
+// page-href
+
+/**
+ *
+ * @type {Boolean}
+ */
+pager.useHTML5history = false;
+/**
+ *
+ * @type {String}
+ */
+pager.rootURI = '/';
+
+// TODO: extract this into a separate class pager.PageHref
+ko.bindingHandlers['page-href'] = {
+ init:function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
+ var $page = bindingContext.$page || bindingContext.$data.$page;
+ var page = $page;
+
+ // The href reacts to changes in the value
+ var path = ko.computed(function () {
+ var value = _ko.value(valueAccessor());
+ var parentsToTrim = 0;
+ while (value.substring(0, 3) === '../') {
+ parentsToTrim++;
+ value = value.slice(3);
+ }
+
+ var fullRoute = page.getFullRoute()();
+ var parentPath = fullRoute.slice(0, fullRoute.length - parentsToTrim).join('/');
+ var fullPath = (parentPath === '' ? '' : parentPath + '/') + value;
+ var attr = {
+ 'href':'#' + fullPath
+ };
+ $(element).attr(attr);
+ return fullPath;
+ });
+
+ if (pager.useHTML5history && window.history && history.pushState) {
+ $(element).click(function (e) {
+ e.preventDefault();
+ history.pushState(null, null, pager.rootURI + path());
+ pager.childManager.showChild(path().split('/'));
+
+ });
+ }
+
+
+ },
+ update:function () {
+ }
+};
+
+/**
+ * @class pager.PageAccordionItem
+ * @inherits pager.Page
+ */
+
+/**
+ *
+ * @param {Node} element
+ * @param {Observable} valueAccessor
+ * @param allBindingsAccessor
+ * @param {Observable} viewModel
+ * @param bindingContext
+ * @constructor
+ */
+pager.PageAccordionItem = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
+ pager.Page.apply(this, arguments);
+};
+
+pager.PageAccordionItem.prototype = new pager.Page();
+
+/**
+ *
+ * @return {Node}
+ */
+pager.PageAccordionItem.prototype.getAccordionBody = function () {
+ return $(this.element).children()[1];
+};
+
+pager.PageAccordionItem.prototype.hideElement = function (callback) {
+ if (!this.pageAccordionItemHidden) {
+ this.pageAccordionItemHidden = true;
+ $(this.getAccordionBody()).hide();
+ } else {
+ $(this.getAccordionBody()).slideUp();
+ if (callback) {
+ callback();
+ }
+ }
+};
+
+pager.PageAccordionItem.prototype.showElement = function (callback) {
+ $(this.getAccordionBody()).slideDown();
+ if (callback) {
+ callback();
+ }
+};
+
+ko.bindingHandlers['page-accordion-item'] = {
+ init:function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
+ var pageAccordionItem = new pager.PageAccordionItem(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
+ pageAccordionItem.init();
+ },
+ update:function () {
+ }
+};
+return pager;});
View
19 pager.js
@@ -135,10 +135,10 @@ pager.start = function () {
* @param viewModel
*/
pager.extendWithPage = function (viewModel) {
- viewModel.$page = new pager.Page();
+ viewModel.$__page__ = new pager.Page();
- pager.childManager = new pager.ChildManager(viewModel.$page.children, viewModel.$page);
- viewModel.$page.childManager = pager.childManager;
+ pager.childManager = new pager.ChildManager(viewModel.$__page__.children, viewModel.$__page__);
+ viewModel.$__page__.childManager = pager.childManager;
};
@@ -152,7 +152,7 @@ pager.routeFromHashToPage = function (hash) {
hash = hash.slice(1);
}
// split on '/'
- var hashRoute = hash.split('/');
+ var hashRoute = decodeURIComponent(hash).split('/');
pager.childManager.showChild(hashRoute);
@@ -225,13 +225,20 @@ pager.Page = function (element, valueAccessor, allBindingsAccessor, viewModel, b
*/
this.currentParentPage = ko.observable(null);
+
+ /**
+ *
+ * @type {Observable}
+ */
+ this.isVisible = ko.observable(false);
};
/**
* @method showPage
* @param route
*/
pager.Page.prototype.showPage = function (route) {
+ this.isVisible(true);
this.show();
this.childManager.showChild(route);
};
@@ -241,6 +248,7 @@ pager.Page.prototype.showPage = function (route) {
* @param {Function} callback
*/
pager.Page.prototype.hidePage = function (callback) {
+ this.isVisible(false);
this.hideElementWrapper(callback);
this.childManager.hideChild();
};
@@ -272,7 +280,6 @@ pager.Page.prototype.init = function () {
ctx = this.viewModel;
}
this.childBindingContext = this.bindingContext.createChildContext(ctx);
-
ko.utils.extend(this.childBindingContext, {$page:this});
if (!value['withOnShow']) {
ko.applyBindingsToDescendants(this.childBindingContext, this.element);
@@ -293,7 +300,7 @@ pager.Page.prototype.getValue = function () {
* @return {pager.Page}
*/
pager.Page.prototype.getParentPage = function () {
- return this.bindingContext.$page || this.bindingContext.$data.$page;
+ return this.bindingContext.$page || this.bindingContext.$data.$__page__;
};
/**

0 comments on commit a721ca4

Please sign in to comment.
Something went wrong with that request. Please try again.