bind different Knockout JS templates inside foreach loop - templates

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.

Related

How to dynamically switch views using Knockout templates

I'm working on a digital signage module for our intranet. I'm trying to add videos into the mix. I want to play videos first, then play a slide show. I will eventually have this loop. I start by calling the startSequence function, which counts down from the video duration. You can observe the console.log (F12). Then I'm changing the selectedTemplate observable from 1 to 2 , after the videos have played I call the showSlides function, but my view is not detecting the change. Here's a jsFiddle
Do I need a computed observable? -instead of this...
<div>
<!-- ko if: $root.selectedTemplate() == 1 -->
<div data-bind="template: { name: 'videoScript', foreach: $root.dynamicVideos() }"></div>
<!-- /ko -->
<!-- ko if: $root.selectedTemplate() == 2 -->
<div data-bind="template: { name: 'imageScript' }"></div>
<!-- /ko -->
</div>
Currently, you're passing string template names to the template binding.
Fortunately, the binding also supports passing observable strings, or even functions! You can find its documentation on knockout's template page.
Here's an example using a computed template name:
const index = ko.observable(0);
const loop = () => index((index() + 1) % 2);
const activeTemplate = ko.pureComputed(() =>
index() ? "t-slideshow" : "t-video"
);
ko.applyBindings({ activeTemplate });
setInterval(loop, 1000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="template: activeTemplate"></div>
<script type="text/html" id="t-slideshow">
<h2>I'm a slide show! 🖼</h2>
</script>
<script type="text/html" id="t-video">
<h2>I'm a video! 📹</h2>
</script>

How to pass an array/objects with spacebars to a meteor template?

I am trying to construct a meteor template for simplifying creating radio buttons on a form. I would like to be able to pass an array or object as an argument through spacebars to the template. How can I pass an array/object as an argument or is this even possible?
Template:
<template name="radioButton">
<div class="mdl-textfield mdl-js-textfield">{{radioLabel}}</div>
{{#each getRadioOptions}}
<label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="{{radioOptionID}}">
<input type="radio" id="{{radioOptionID}}" class="mdl-radio__button" name="{{radioID}}" value="{{optionID}}">
<span class="mdl-radio__label">{{optionLabel}}</span>
</label>
{{/each}}
</template>
Template helper:
Template.radioButton.helpers({
getRadioOptions: function () {
console.log("getRadioOptions called");
console.log(this);
console.log(this.radioOptions);
return this.radioOptions;
},
radioOptionID: function() {
return this.radioID+"-"+this.optionID;
}
});
Attempted spacebar notation:
{{> radioButton radioID="sampleID" radioLabel="Sample Radio Buttons"
radioOptions=[{optionID:"option1",optionLabel:"Option One"},
{optionID:"option2",optionLabel:"Option Two"}] }}
After running this notation and looking at the browser console, I get back this: (which shows that only null was passed for radioOptions)
getRadioOptions called
Object {radioID: "sampleID", radioLabel: "Sample Radio Buttons", radioOptions: null}
null
You almost got it right, except that you can't give the data as a javascript array but need to use a JSON string, i.e., use:
{{> radioButton radioID="sampleID" radioLabel="Sample Radio Buttons"
radioOptions='[{"optionID":"option1", "optionLabel":"Option One"}, {"optionID":"option2","optionLabel":"Option Two"}]' }}
Note that you need to use quotation marks around the field names, too, because it's JSON and not javascript!
Then, in the helper, parse the string:
getRadioOptions: function () {
console.log("getRadioOptions called");
console.log(this.radioOptions); // string
return JSON.parse(this.radioOptions); // array
},
You cannot pass an object in an #each in spacebars. It has to be an Array. This should appear in your console.
Because Meteor include underscore, what you often pass is _.toArray( myObject ).

JsViews: in-line template syntax for direct linked form element

I saw an example of linking directly to a form element using JsViews, which I found to be preferable to encapsulating the whole form in a template. Here is a jsfiddle example of what I'm trying to do, which partially works:
http://jsfiddle.net/30jpdnkt/
var app = {
selections: {
things: [
{ Name: "thingName1", Value: "thingValue1" },
{ Name: "thingName2", Value: "thingValue2" },
{ Name: "thingName3", Value: "thingValue3" }
]
},
formData: {
selectedThing: "thingValue1",
}
};
//how do I reference this template in-line, outside of another wrapping template?
$.templates({
theTmpl: "#theTmpl"
});
$("#content").link(true, app);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://www.jsviews.com/download/jsviews.js"></script>
<script id="theTmpl" type="text/x-jsrender">
<select id="thingChoice" data-link="formData.selectedThing">
<option value="-">Please select</option>
{^{for selections.things}}
<option data-link="value{:Value} {:Name} selected{:~selected === Value}"></option>
{{/for}}
</select>
</script>
<div id="content">
<!--this part works-->
<input data-link="formData.selectedThing trigger=true"/>
<!--this part does not display-->
<span {{for data tmpl="theTmpl"/}}></span>
</div>
The data-linked INPUT tag is correctly bound to the object, but I cannot find a working example of how to reference a compiled template in-line without encapsulating the entire form in another template. That it's possible to use data-link syntax outside of a template gives hope that it may be possible with correct syntax.
Is this possible?
Yes it is possible - it is what I call top-level data-linking. There will be new documentation topics on this coming very soon, but meantime you have this sample:
http://www.jsviews.com/#samples/editable/toplevel-for
And your jsfiddle - which I updated to make it work fully: http://jsfiddle.net/30jpdnkt/1/
<div id="content">
<input data-link="formData.selectedThing trigger=true"/>
<span data-link='{for tmpl="theTmpl"}'></span>
</div>

Accessing the DOM representation of a template instance that is calling a helper

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'));
}
},

Knockout template to create pagination UI / links similar to StackOverflow

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/