I have a functioning Knockout template for some pagination UI that works with a Knockout-based shared data grid. This template renders an HREF for each "page" of data in the grid.
The template works but it's klunky because if I fetch a lot of data, then I end up with dozens and dozens of navigation page links below the grid. Here's the current template:
<div class="idTemplate_ko_simpleGrid_pageLinks">
<p>
<span>Go to page:</span>
<!-- ko foreach: ko.utils.range(0, maxPageIndex) -->
<a href="javascript:void(0);"
class="grid-pagination"
data-bind="text: $data + 1, click: function() { $root.currentPageIndex($data) }, css: { selected: $data == $root.currentPageIndex() }"></a>
<!-- /ko -->
</p>
</div>
The 'currentPageIndex' value is just a simple ko observable in the model:
this.currentPageIndex = ko.observable(0);
And 'maxPageIndex' is a computed observable in the model:
this.maxPageIndex = ko.computed(function () {
return Math.ceil(ko.utils.unwrapObservable(this.filteredItems()).length / this.pageSize()) - 1;
}, this);
How can I modify the template and model to enable paging UI similar to StackOverflow?
For example:
prev 1 ... 3 4 5 6 7 ... 69 next
This is exactly the pager style I have been using for a while now.
I just finished extracting the pager functionality I used on several projects, into an extension to knockout and template by example.
See https://github.com/remcoros/ko.pager for the source and http://remcoros.github.com/ko.pager/example.html for a working example.
All computations and some convenient properties are provided by the 'Pager' class, which you can create and bind to. But an example working template is included.
See the source example.html for usage.
First thing I would do is look if there are any custom bindings or libraries out there that do this. If there are, create a custom binding that uses that library.
Back up plan - make your own custom binding.
I'd make something like:
<div data-bind="pagination: { maxIndex: maxPageIndex(), numToShow: 7 }">
...
</div>
Then in my custom binding, do something like this:
ko.bindingHandlers.pagination = {
update: function(element, valueAccessor) {
if (valueAccessor().maxPageIndex > valueAccessor().numToShow) {
// use jquery to loop and append new $("<a>") tags to $(element), using "1", then ... and a segment in the middle, followed by ... and the last index.
}
else {
// loop over the regular amount.
}
}
};
Im so nice so I made one for you in exactly two minutes :P (So it probably has bugs)
Its based on the first pager i found which was jQuery pagination
http://jsfiddle.net/tymTz/2/
Related
I have a standard component defined something like:
export default Ember.Component.extend({
hideIfEmptyChange: function() {
var thingOfInterest = ...;
var otherThingOfInterest = ...;
...
// Perform some logic
...
// Perform some logic using thingOfInterest
// Perform exactly the same logic using otherThingOfInterest
}.observes('hideIfEmpty.#each')
});
I want to move the logic for the last two pieces into their own function to prevent writing out the same code twice just for two different variables.
I could write an action on my component and use this.send('myAction',...) - but would it be best practice to have this as an action if it isn't going to be used (or even usable) from the component's template? If you shouldn't do it this way then how should you?
My other thought was mixin's - but the code here will be completely specific to the component, so again this doesn't feel 100% right.
Edit
The component is used here to observe an array of widgets, which are displayed in a sidebar. To start with the sidebar is hidden away as there are no widgets. When a widget is added the sidebar then slides out using a css3 transition between a bootstrap class I added (col-md-0) and something like col-md-2, at the same time the main column shrinks from something like col-md-10 to col-md-8. Because of the nature of it coming out from zero width the widgets inside squish around during the animation and so need to be hidden during it. So the generic piece of code will be:
function(element, classes) {
var lowWidth = 50;
// Transition looks awful if columns start from small width, so hide inner content until css3 transition complete if starting from low width
if ($(element).width() < lowWidth) {
$('div', element).hide();
$(element).toggleClass(classes);
$(element).on('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function(e) {
$('div', element).fadeIn(250);
$(this).off(e);
});
}
else {
$(element).toggleClass(classes);
}
}
My component here is in block form and actually has the sidebar within it in the template.
{{#hideable-cols hideIfEmpty=rightSidebarWidgets leftId="det-main" leftMinClass="col-md-8" leftMaxClass="col-md-10" rightId="det-sidebar-right" rightMinClass="col-md-0" rightMaxClass="col-md-2"}}
<div id="det-main" class="col-md-10 resizes">
{{outlet}}
</div>
<div id="det-sidebar-right" class="det-sidebar resizes col-md-0">
{{widget-area widgets=rightSidebarWidgets saveObjectFromWidget="saveObjectFromWidget" removeWidget="removeRightWidget"}}
</div>
{{/hideable-cols}}
The only other way to do what I wanted would have been if I could have set 'inner' components to only be able to react to changes in the array of widgets AFTER the parent block component is happy/done. I've used something similar in knockout js but couldn't see this kind of feature in Ember, so my workaround was to hide the div where the widgets are added, do the transition and show the div again afterward.
I am trying to access a DOM element corresponding to the particular template instance that is calling a helper function. As I read the Meteor documentation, Template.instance() should return the template instance object that called the helper, and something like Template.instance().$() would allow me to grab DOM elements within that instance.
However, the following code (and similar variations) is not working for me:
* HTML *
<template name="input_container">
<div class="small-12 medium-12 large-6 columns empty {{isActive}}"></div>
</template>
* JS *
Template.input_container.helpers({
isActive: function() {
if (Template.instance().$('.empty') && [some Session variable logic] {
return 'active';
}
}
});
When I do something like:
if (some Session logic) {
console.log(Template.instance())
}
I get the helper properly logging multiple versions of:
Blaze.TemplateInstance {view: Blaze.View, data: 7,
firstNode: div.small-12.medium-12.large-6.columns.empty.active-container,
lastNode: div.small-12.medium-12.large-6.columns.empty.active-container, $: function…}
(With data: values going from 1-12 appropriately, but otherwise each seems to be the same)
How do I get from this to being able to used the template methods such as template.$ or template.find?
EDIT:
While not a perfect solution, I did manage to work around some of these issues by using Template.currentData() and setting an identifier on each instance of the input.
Template.create_form.helpers(
# Create 12 input containers
inputContainer: () ->
[0..11]
And then:
Template.input_container.helpers(
isActive: () ->
# Get which template instance we are working with, will return the number 0-11 that was used to create it
current = Template.currentData()
# Now I can do $(".input-container").eq(current) to grab the correct DOM element
)
But it seems a little dirty to need to use so much jQuery.
I'm not sure if I've interpreted the question correctly, but if you're trying to access another DOM element on the page - I was able to use a jquery selector.
For example, given html of
<input type="textfield" id="initials" value=" ">
and a simple meteor template of
<template name="demo">
<input type="button" div id="s0">
</template>
I can successfully access the initials field when the s0 button is clicked as follows
Template.demo.events({
'click .cell': function(event, template) {
if ($('#initials').val().trim().length > 0) {
console.log($('#initials').val().trim() + ' - you clicked button '+$(event.target).attr('id'));
}
},
How can I can concatenate strings( or how to add classes ) on templates on EmberJs?
ex.
<script type="text/x-handlebars">
// This div I want to add a class go, Is this the right way to do it?
<div class="fly {{isGo}}">Fly now</div>
// Or it's something like this?
<div class="fly "{{isGo}} >Fly now</div>
</script>
bind-attr used to be a good way of working around a limitation within Ember's rendering. Now with HTMLbars Ember has recommend that we move away from bind-attr as we have more powerful methods.
Ember 1.13 deprecated bind-attr in favor of the new syntax.
http://emberjs.com/deprecations/v1.x/#toc_bind-attr
Working example of the two proposed methods can be seen in action on ember twiddle ,here:
https://ember-twiddle.com/38f69f01d2fd994af3b0965f10882005?openFiles=templates.application.hbs%2C
Method 1
If you want to do the combination inside your handlebars template you could do something like:
<div class={{concat "fly " isGo}}>Fly now</div>
Method 2
otherwise use a computed property like:
flyingClass: Ember.computed('isGo', function() {
// return a string with 'fly' and the value of
// isGo. Will be updated if isGo changes.
// Array values are created with space delimited by
// ['className', 'anotherClassName', 'lastClastName'].join(' ');
// => "className anotherClassName lastClassName"
let going = this.get('isGo') ? 'going' : ''
return ['fly', going].join(' ');
})
and then in your handlebars template:
<div class={{flyingClass}}>Fly now</div>
The main difference between the two methods depends on how you want your separation of concerns. Right now it might be easier to just do Method 1, but as conditions get more complicated you could hide more of the work in the computed property.
There is a complete discussion of this in the Ember guide: http://emberjs.com/guides/templates/binding-element-class-names/
But you'd do it like this:
<div {{bind-attr class="isGo"}}>Fly now</div>
And in your controller:
App.MyController = Ember.ObjectController.extend({
flightIsAGo: true,
isGo: function() {
return "fly"+this.get('flightIsAGo') ? ' isGo' : '';
}.property('flightIsAGo')
}
I'm trying to create a html template for displaying posts via html/template Go package.
I also want to make pagination on my page, to display 5 posts per page.
So I take the post count from my post repository, dividing it by posts per page value and rounding it (ceil). That's the total number of pages with posts currently available.
I pass the total number of pages to my html template.
Now, in my html template I need to display page buttons from 1 to the total number.
In the text/html package there is an awesome documentation about how to work with pipelines, but I didn't find any example of creating simple loop.
I got the solution, but I am not sure it is the good one.
I can pass to template not just the total number of pages, but an array of available pages, so in my template I can do something like:
{{range .pages}}
<div class="page">{{.}}</div>
{{end}}
But maybe there is a better way to do this than passing an array of pages?
I also know about possibility of passing custom functions to template. Could it be a solution?
The rule is that the template must contain the minimal logic possible (and that's the reason why the native functions and controls are so limited into the template package).
You should prepare your data into the controller by putting it into a dedicated struct (to be passed to the template). Then you can display this struct (composed of variables and arrays) into the template by using the range function as you intended to do.
try this, i have do my best...
package main
import "html/template"
import "os"
type data struct {
Url string
Title string
}
type show struct {
Pages []data
}
const html = `<html>
{{range .Pages}}
<div class="page">{{.Title}}
</div>
{{end}}
</html>`
func show_template() {
webpage, _ := template.New("template").Parse(html)
mydata := []data{{
Url: "page-1.html",
Title: "go to page 1",
}, {
Url: "page-2.html",
Title: "go to page 2",
}, {
Url: "page-3.html",
Title: "go to page 3",
}, {
Url: "page-3.html",
Title: "go to page 3",
}}
web_data := show{mydata}
webpage.Execute(os.Stdout, web_data)
}
func main() {
show_template()
}
and this is the result..
<html>
<div class="page">go to page 1</div>
<div class="page">go to page 2</div>
<div class="page">go to page 3</div>
<div class="page">go to page 3</div>
</html>
I am trying to use KO templates to parse a JSON file (see here) into some pretty looking grid layouts.. (think similar to masonry/isotope layouts).. each template section will have different sized rectangles and squares inside it but the overall template conforms to a grid of 5 boxes wide by 3 boxes high (for example)
Given this what i have been trying to do is detect the template, then iterate through each item, if its a certain index in the iteration load either the single, double, or triple subtemplate..
my problem is that i cant seem to get it to check which key in the ViewTestProposal array im currently on..
below is my WIP code..
<div data-bind="template: {if: Template == 'basic2', visible: Template == 'basic2', foreach: ViewTestProposal}">
<div data-bind="template: {if: ViewTestProposal[0], name: 'single'}"></div>
</div>
<script type="text/html" id="single">
<div class="box single">
<p data-bind="text: Artist, attr:{title: Artist}"></p>
</div>
</script>
I have tried changing the if: ViewTestProposal[0] section to with: ViewTestProposal[0],if: ViewTestProposal() === 0 and if: ViewTestProposal == 0 to no avail.
Thanks in advance for any help you can provide
The template name parameter can be a function that returns the name based on the current item in the array (See note 4). With this you could access a property on each item that has the template name:
<div data-bind="template: {
foreach: ViewTestProposal,
name: function(item) {return item.boxsize;}
}"></div>
If you need to use the index of the item in the array, this is available starting with Knockout version 2.1 through the $index context property. Starting with version 2.2 (not yet released [1/Oct/2012], but release candidate version available), the name function can also access the context object. Then you could do something like this:
<div data-bind="template: {
foreach: ViewTestProposal,
name: function(item, context) {
switch(context.$index()) {
case 0:
return 'single';
case 1:
return 'double';
// etc.
}
}
}"></div>
Obviously, the function itself could be defined in your view model.