Handlebars.js custom helper to count nested elements - javascript

I want to make a custom helper to give {{count}} the value of an increasing number based on the number of answers. I'm having trouble getting the right equation and building the helper. I've had several attempts using the values of the parent {{#index}} and nested {{#index}} to no avail.
The purpose of this is to render a form under each question with radio buttons to select each answer. I need individual IDs to be the same on both the ID attribute on the input tag and the for attribute on the label tag. Thx.
NB the other attributes (name, value etc.) have been programmed correctly so I'm giving thm hard values in this example.
{{#questions}}
<div class="question-{{#index}}">
<form>
{{#each answers}}
<div>
<input type="radio" name="radios[0]" id="radios-{{count}}" value="1">
<label for="radios-{{count}}">Answer 1</label>
</div>
{{/each}}
<form>
</div>
{{/questions}}
Desired output in plain text class names:
div.question-0
div
input#radios-0
label[for=radios-0]
div
input#radios-1
label[for=radios-1]
div.question-1
div
input#radios-2
label[for=radios-2]
div
input#radios-3
label[for=radios-3]
div
input#radios-4
label[for=radios-4]
div.question-2
div
input#radios-5
label[for=radios-5]
div
input#radios-6
label[for=radios-6]

Use registerHelper method to add individual incremental counter
<script type="text/template" id="template">
{{#each questions}}
<div class="question-{{#index}}">
{{#each answers}}
<div class="answer-{{count}}"></div>
{{/each}}
</article>
{{/each}}
</script>
<script type="text/javascript">
var inputdata = {
questions: [
{ answers: [2,3,4,5] },
{ answers: [1, 2,3,4,5] }
]
};
var Counter = 1;
Handlebars.registerHelper('count', function() {
return Counter ++;
});
var template = Handlebars.compile($("#template").html());
$("body").html(template(inputdata));
</script>
DEMO

My solution:
{{#questions}}
{{questionIndex #index}}
<div class="question-{{#index}}">
<form>
{{#each answers}}
<div>
<input type="radio" name="radios[{{../questionIndex}}]" id="radios-{{../questionIndex}}{{#index}}" value="{{value}}">
<label for="radios-{{../questionIndex}}{{#index}}">{{answer}}</label>
</div>
{{/each}}
<form>
</div>
{{/questions}}
Handlebars.registerHelper('questionIndex', function(value) {
this.questionIndex = Number(value);
});
{{../questionIndex}}{{#index}} will result in: 00, 01, 10, 11, 20, 21 and so on...

Related

handlebars, access to parent each loop variable

I have code like here. I would like to set input name to #index from previous each loop.
Can I access #previousIndex somehow? Or can I assign inputs to some kind of group created before second each loop that will set all input names inside?
Data I receive:
questions:[
{
question:"How old are you?",
answers:[
'16',
'18',
'20',
'25',
'other'
]
},
{
question:"How many kids do you have?",
answers:[
'0',
'1',
'2',
'other'
]
}
]
hbs code:
{{#each questions}}
<li>
<h3 class='question'>{{this.question}}</h2>
<div class='flexbox'>
{{#each this.answers}}
<label class="container">
<input type='radio' name='{{#previousIndex}}' value='{{#index}}'>
<span class='checkmark'>{{this}}</span>
</label>
{{/each}}
</div>
</li>
{{/each}}
When you are using #each then you get the index in #index and if you want previousIndex why not just do parseInt(#index) - 1
Here's how you do it:
var Handlebars = require('handlebars');
Handlebars.registerHelper("previous", function(value, options)
{
return parseInt(value) - 1;
});
Now update your hbs code as:
{{#each questions}}
<li>
<h3 class='question'>{{this.question}}</h2>
<div class='flexbox'>
{{#each this.answers}}
<label class="container">
<input type='radio' name='{{previous #index}}' value='{{#index}}'>
<span class='checkmark'>{{this}}</span>
</label>
{{/each}}
</div>
</li>
{{/each}}
Hope this works for you!
I can't give you a 100% working code (environment is not currently setup) but from my previous experience with handlebars I will try to guide you to a possible solution.
{{assign "previousIndex" 0}}
{{#each questions}}
<li>
<h3 class='question'>{{this.question}}</h2>
<div class='flexbox'>
{{#each this.answers}}
<label class="container">
<input type='radio' name='{{../previousIndex}}' value='{{#index}}'>
<span class='checkmark'>{{this}}</span>
</label>
{{/each}}
</div>
</li>
{{assign "../previousIndex" "{{#index}}"}}
{{/each}}
I had to create two helper functions like here. Get is here because I wasn`t able to access variable directly from .hbs file by simply writing {{myVariableName}}.
Hbs file looks now like here.
<div class='flexbox'>
{{assign "parentIndex" #index }}
{{#each this.answers}}
<label class="container">
<input type='radio' name='{{get "parentIndex"}}' value='{{#index}}'>
<span class='checkmark'>{{this}}</span>
</label>
{{/each}}
</div>
helpers
assign: function(varName, varValue, options) {
options.data.root[varName] = varValue;
}
get: function(varName, options) {
return options.data.root[varName]
}

Vue.js change model attached to a form upon clicking a list

I have an array of objects. These objects are loaded into a list in vue.js.
Aside from this list, I have a form that displays data from one of these objects. I want to, when clicking one of the list's elements, it will bind this specific object to the form and show its data.
How can do this in Vue.js?
My list code is:
<div id="app-7">
<ul id="food-list" v-cloak>
<food-item v-for="item in foodList" v-bind:food="item" v-bind:key="item.id" inline-template>
<li class="food">
<div class="food-header">
<img :src="'img/' + food.slug +'.png'">
<div class="food-title">
<p>{{food.name}} |
<b>{{food.slug}}</b>
</p>
<p>quantity: {{food.quantity}}</p>
</div>
<div class="food-load"> // load into form upon clicking this
</div>
</div>
</li>
</food-item>
</ul>
</div>
Since I do not have the code for the form, this is my best guess without clarification.
You can add a click handler to the item you want to be clicked. It will pass the value of the food item into the method.
<div class="food-load" #click="setFoodItem(item)">
</div>
And when that method is called, it can assign the clicked item to a data property. I'm not sure where your form is, and if it is in a different component. If it is in a child component, you would have to pass it in as a prop, or emit an event to pass it to a parent component.
data() {
return {
//create a reactive field to store the current object for the form.
foodItemForm: null
};
},
methods: {
//method for setting the current item for the form.
setFoodItem(item) {
this.foodItemForm = item;
}
}
Missing quite a bit of info in your sample code, your script is very important to see to make sense of what you would like to accomplish and where things might be going wrong.
Here's a quick list of the issue I came across with your code:
v-for refers to an individual food item as 'item', inside the loop you're trying to access properties as 'food'
You don't wrap your code in a component unless you're importing the component
When binding a value to 'v-bind:src' (or shorthand ':src') only pass the url, you should be specifying this in your script not inline.
You're better off using a button and the 'v-on:click' (or shorthand '#click') to load your selected food item into your form
You should also include your Javascript
Regardless, here's how I would handle this (took the liberty in filling in some blanks):
<template>
<div id="app">
<ul id="food-list">
<!--<food-item v-for="item in foodList" v-bind:food="item" v-bind:key="item.id" inline-template>-->
<li v-for="item in foodList" class="food">
<div class="food-header">
<img :src="item.slug" v-bind:alt="item.slug" width="250px" height="auto">
<div class="food-title">
<p>{{item.name}} | <b>{{item.slug}}</b></p>
<p>quantity: {{item.quantity}}</p>
</div>
<button class="food-load" #click="loadFoodItem(item.id)">Load Food Item</button>
</div>
</li>
<!--</food-item>-->
</ul>
<form v-if="activeFoodId != null" id="foodItemForm" action="#">
<h3>Food Form</h3>
<label for="food-id">Id:</label>
<input id="food-id" type="number" v-bind:value="foodList[activeFoodId].id"><br/>
<label for="food-slug">Slug:</label>
<input id="food-slug" type="text" v-bind:value="foodList[activeFoodId].slug"><br/>
<label for="food-name">Name:</label>
<input id="food-name" type="text" v-bind:value="foodList[activeFoodId].name"><br/>
<label for="food-quantity">Quantity:</label>
<input id="food-quantity" type="number" v-bind:value="foodList[activeFoodId].quantity">
</form>
</div>
</template>
<script>
export default {
name: 'app',
data: function () {
return {
activeFoodId: null,
foodList: [
{
id: 1,
slug: 'http://3.bp.blogspot.com/-QiJCtE3yeOA/TWHfElpIbkI/AAAAAAAAADE/Xv6osICLe6E/s320/tomato.jpeg',
name: 'tomatoes',
quantity: 4
}, {
id: 2,
slug: 'https://img.purch.com/rc/300x200/aHR0cDovL3d3dy5saXZlc2NpZW5jZS5jb20vaW1hZ2VzL2kvMDAwLzA2NS8xNDkvb3JpZ2luYWwvYmFuYW5hcy5qcGc=',
name: 'bananas',
quantity: 12
}, {
id: 3,
slug: 'https://media.gettyimages.com/photos/red-apples-picture-id186823339?b=1&k=6&m=186823339&s=612x612&w=0&h=HwKqE1MrsWrofYe7FvaevMnSB89FKbMjT-G1E_1HpEw=',
name: 'apples',
quantity: 7
}
]
}
},
methods: {
loadFoodItem: function (foodItemId) {
console.log(foodItemId)
this.activeFoodId = foodItemId
}
}
}
</script>
<style>
/# Irrelevant #/
</style>
Hope it helps!

Need to add multiple divs with same content inside a single div on a click against the parent div

Here is my piece of code, a function to add a meal template:
vm.addMealTemplate = function() {
$scope.mealCount++;
$compile( $(document.createElement('div')).attr("id", 'mealDiv' + $scope.mealCount).addClass("mealDiv"+$scope.mealCount).after().html(
'<select ng-options="(option.name ) for option in mealOptions" ng-model="selectedOption'+ $scope.mealCount+'" />' +
'<input type="text" placeholder="Meal timings" id="time'+ $scope.mealCount +'"/>' +
'<a id="mealCount" ng-class="mealCount" ng-click="addItemCategory()" uib-tooltip="Add category" tooltip-trigger="mouseenter" tooltip-placement="bottom"><i class="fa fa-plus"></i></a>'
).appendTo("#meals")
// $("#meals").append(newMealDiv)
)($scope);
}
On clicking calling the addItemCategory() for the specific div, I want another div to get added as a child of that div. There can be mutiple meal templates, and for each template I can call the addItemCategory mutliple times, and I want the category to be added to the same div for which the function has been called. How do I achieve this?
Currently I am using mealCount variable from scope to have the context, but once it gets increased, I have no way to access the divs added previously, to add the new element to that div. Any way using jQuery or AngularJs?
You can use ng-repeat
For example:
angular.module('app', []).
controller('ctrl', function($scope) {
$scope.meals = [];
});
.meal {
border:1px solid;
padding:10px;
margin-bottom:10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div ng-repeat="meal in meals" class="meal">
<select ng-model="meal.count">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<input type="text" placeholder="Meal timings" ng-model="meal.timing" />
<div>
<div>Categories:</div>
<div ng-repeat="cat in meal.categories track by $index">
<input type="text" ng-model="meal.categories[$index]" />
</div>
<button ng-click="meal.categories.push('')">Add Category</button>
</div>
</div>
<button ng-click="meals.push({categories:[]})">Add meal</button>
<hr />
{{meals | json}}
</div>
Note: I changed the models etc. it's just example..
Here's an example Plunker on how your problem can be solved with ng-repeat in Angular:
http://plnkr.co/edit/POt7nFc4GqFU67SWdMp7?p=preview
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="https://code.angularjs.org/1.5.5/angular.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="restaurant" ng-controller="meal-controller">
<div ng-repeat="meal in meals track by $index">
<select ng-options="option.name for option in mealOptions" ng-model="meal.selectedMeal"></select>
<input type="text" placeholder="Meal timings" id="{{'time'+ $index }}" ng-model="meal.timing"/>
<a id="mealCount" ng-class="mealCount" ng-click="addItemCategory()" uib-tooltip="Add category" tooltip-trigger="mouseenter" tooltip-placement="bottom"><i class="fa fa-plus"></i></a>
</div>
<button ng-click="addMeal()">Add meal</button>
</body>
</html>
-
// Script.js
angular.module('restaurant', [])
.run( ['$rootScope', function( $rootScope ) {
}]
);
angular.module('restaurant').controller('meal-controller', [ '$scope', function( $scope ) {
$scope.meals = [];
$scope.mealOptions = [{ name: "option1"}, { name: "option2"}, { name: "option3" } ];
$scope.addMeal = function() {
$scope.meals.push({ selectedMeal: null, timing: "timing" });
}
$scope.addItemCategory = function() {
}
}]);
I am not entirely sure about how you want the logic to work, but I am sure that you can modify this example to fit your needs.
Ng-repeat works like a loop which prints the content it is wrapping until the end of the array. Ng-repeat has a lot of features like tracking elements in the array by index, this can used to (like in the example) give each input an unique id.
If you need to make some specific changes to a meal, you can pass it as an argument, for example if you want to delete a specific meal you can have a button inside the ng-repeat like this:
<button ng-click="deleteMeal(meal)">Delete Meal</button>
This means that you do not have to access the meal specifically by, for example, its id with jQuery.
I would say it is not recommended to mix angular and jQuery the way you are doing now. Try to stick with Angular and avoid jQuery. In some special cases where you need to do element specific modifications it can be achieved by using jQlite:
https://docs.angularjs.org/api/ng/function/angular.element

can't get data attribute with jquery

I am trying to retrieve data-cost("#packages") and append it to #form next to Ticket-price. I am not getting any errors in console. I can't seen to understand what went wrong.
JS:
<script src="jquery-1.11.2.js"></script>
<script>
$(document).ready(function()
{
var price=$("#packages").data("cost");
var amount=$("<span>"+price+"</span>");
$("#form").next().next().append(amount);
});
</script>
HTML:
<div id="packages">
<h2><u>Tourism Packages:</u></h2>
<ul>
<li data-name="Southern Travels">Travels Name: Southern Travels</li>
<li data-cost="2000">Cost per person: 2000</li>
<li>Duration: 3 days & 4 nights</li>
</ul>
</div>
<div id="form">
<label>Number of persons </label>
<input id="input"type="text"/ autofocus>
<p id="ticket-price">Ticket Price</p>
</div>
For this to work as it is, you must edit your HTML as following:
<div id="packages" data-name="Southern Travels" data-cost="2000">
<h2><u>Tourism Packages:</u></h2>
<ul>
<li>Travels Name: Southern Travels</li>
<li>Cost per person: 2000</li>
<li>Duration: 3 days & 4 nights</li>
</ul>
</div>
Either that, or access the data properties of the <li> elements instead of the div#packages (i.e #packages ul li instead of #packages)
$(document).ready(function() {
// Targeting 2nd li element inside #packages
var price=$("#packages li").eq(2).data("cost");
// create a span element with text 'price'
var amount=$("<span>"+price+"</span>");
// append as last child of the form
$("#form").append(amount);
});
You need to look for your data attribute by name - and look at the LI's.
You also need to build your html string and append it properly to the #ticket-price
WOKING EXAMPLE (FIDDLE): http://jsfiddle.net/ojLo8ojz/
JS:
$(document).ready(function(){
var price = $("#packages li[data-cost]").attr("data-cost");
var amount = "<span> "+price+"</span>";
$("#ticket-price").append(amount);
});

DOM Scripting in Google Polymer

I just worked through the Google Polymer tutorial and I am building my first own element. And I am missing some DOM-Scripting Functions I know from Prototype and jQuery that made my life very easy. But maybe my methods are just not right. This is what I have done so far:
<polymer-element name="search-field">
<template>
<div id="searchField">
<ul id="searchCategories">
<li><a id="search-categories-text" data-target="text" on-click="{{categoryClick}}">Text</a></li>
<li><a id="search-categories-videos" data-target="videos" on-click="{{categoryClick}}">Videos</a></li>
<li><a id="search-categories-audio" data-target="audio" on-click="{{categoryClick}}">Audio</a></li>
</ul>
<div id="searchContainer">
<input id="searchText" type="text" />
<input id="searchVideos" type="text" />
<input id="searchAudio" type="text" />
</div>
</div>
</template>
<script>
Polymer({
ready: function() {
},
categoryClick: function(event, detail, sender) {
console.log(sender.dataset.target);
console.log(this.$.searchField.querySelector('#searchContainer input'));
this.this.$.searchField.querySelector('#searchContainer input');
}
});
</script>
</polymer-element>
What I want to do is to set an active class to the bottom input-fields when one of the above links are clicked. On jQuery I would just observe a link and deactivate all input fields and activate the one input field I want to have. But I am not sure how to do it without jQuery. I could just use all the native javascript functions with loops etc but is there anything polymer can offer to make things easier?
Does this example do what you want?
<script src="//cdnjs.cloudflare.com/ajax/libs/polymer/0.3.3/platform.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/polymer/0.3.3/polymer.js"></script>
<polymer-element name="search-field">
<template>
<style>
.hideMe {
display: none;
}
</style>
<div id="searchField">
<ul id="searchCategories">
<template repeat="{{category in searchCatergories}}">
<li><a on-click="{{categoryClick}}">{{category}}</a></li>
</template>
</ul>
<div id="searchContainer">
<template repeat="{{category in searchCatergories}}">
<div class="{{ { hideMe: category !== selectedCategory} | tokenList }}">
<label>Search for {{category}}</label>
<input id="search{{category}}" type="text">
</div>
</template>
</div>
</div>
</template>
<script>
Polymer({
searchCatergories: [
"Text",
"Video",
"Audio"
],
selectedCategory: 'Text',
categoryClick: function(event, detail, sender) {
// grab the "category" item from scope's model
var category = sender.templateInstance.model.category;
// update the selected category
this.selectedCategory = category;
// category
console.log("category", category);
// you can also access the list of registered element id's via this.$
// try Object.keys(this.$) to see registered element id's
// this will get the currently showing input ctrl
selectedInputCtrl = this.$["search" + category];
}
});
</script>
</polymer-element>
<search-field></search-field>
I've created an array for the categories and added two repeat templates.
I've setup a .hideMe class which is set on all input elements that aren't the currently selected category.
Info on dynamic classes - https://www.polymer-project.org/docs/polymer/expressions.html#tokenlist
Info on repeat - https://www.polymer-project.org/docs/polymer/binding-types.html#iterative-templates
Hope that helps

Categories

Resources