dust js dynamic #eq condition - javascript

This seems simple, but I just can't figure how to dynamically change the #eq condition. The example shows a default render, i.e., curly, but what I need to do is accept user input, e.g., click a button, and change it to larry or moe.
See my jsfiddle http://jsfiddle.net/bodyrock/a7nmurnr/4/
From what I've read it appears I have to use one of the following techniques:
- base.push onto context
- makebase
- partials
- inline parameter
- variable lookup
<script type="javascript">
$(document).ready(function () {
var data = {
"title": "Famous People",
"names" : [{ "name": "Larry", "props":[{"name":"height","value":"5.8"},{"name":"weight","value":"160"}] },{ "name": "Curly", "props":[{"name":"height","value":"5.9"},{"name":"weight","value":"200"}]},{ "name": "Moe", "props":[{"name":"height","value":"5.8"},{"name":"weight","value":"160"}]}]
}
var source = $("#entry-template").html();
var compiled = dust.compile(source, "intro");
dust.loadSource(compiled);
dust.render("intro", data, function(err, out) {
$("#output").html(out);
});
});
</script>
<script id="entry-template">
{title}
<ul>
{#names}
{#eq key=name value="Curly"}
<li>
{name}
<ul><li>Weight: {#props}{#eq key=name value="weight"}{value}{/eq} {/props}</li></ul>
</li>
{/eq}
{/names}
</ul>
</script>
<input type="button" value="moe" onclick="alert('change to moe');"><input type="button" value="larry" onclick="alert('change to larry');"><input type="button" value="curly" onclick="alert('change to curly');">
<div id="output"></div>

You just need to rerender the template with a new context that contains information about what person you want to show.
JSFiddle
$('input').on('click', function() {
var ctx = dust.makeBase({ currentName: $(this).val() }).push(data);
dust.render("intro", ctx, function(err, out) {
$("#output").html(out);
});
}).first().click();
and
{#names}
{#eq key=name value=currentName}
<li>
{name}
<ul><li>Weight: {#props}{#eq key=name value="weight"}{value}{/eq}{/props}</li></ul>
</li>
{/eq}
{/names}

Related

How to attach a button to a JSON object field and send the field to another webpage

I loop over a JSON object to populate dynamically a <ul> list.
Each <li> item has an <input> element with a click event attached to it.
Here follows my code
index.html
<div class="row">
<div class="col s12 l6">
<ul id ="moduleList" class> </ul>
</div>
</div>
js
var data = {
"modules": [
{
"name": "mod1",
"description": "mod1 description",
},
{
"name": "mod2",
"description": "mod2 description",
},
{
"name": "mod3",
"description": "mod3 description",
}
]
}
document.addEventListener('DOMContentLoaded', function() {
$.each(data.modules, function(i, field){
let module_name = field.name;
let module_description = field.description;
//build the html <li> element
// add name and description info
let li_html = "<div> Name: " + module_name + "</div>";
li_html += "<div> Description: " + module_description + "</div>";
//build the <input type="button"> element and add it to the li_html string
let select_button = "<div> <input class= \"set_inputs\" type=\"button\" value = \"select\"/> </div>";
li_html +=select_button;
// append the <li> item code to the <ul> element
$("<li />").html(li_html).appendTo("#moduleList");
//attach the click event to current field
$(".set_inputs").click(function(){
// save field info in the web browser
localStorage.setItem('moduleObj', JSON.stringify(field));
console.log("module id= " + i + " module_name= " + module_name );
window.location.href = 'select_inputs.html';
});
});
});
select_inputs.html
[..]
<div class="row">
<div class="col s12 l6">
<ul id ="moduleList" class> </ul>
</div>
</div>
[..]
<script>
document.addEventListener('DOMContentLoaded', function() {
// get the active field
var retrivedModuleObj = JSON.parse(localStorage.getItem('moduleObj') || '{}');
console.log("MODULE " + retrivedModuleObj.name);
// remove the field from the web browser
localStorage.removeItem("moduleObj");
});
</script>
When I load index.html, I get the expected name + description text and a select button for each JSON field.
But when I click any button, in the console I get:
// index.html
module id= 1 module_name= mod2
module id= 2 module_name= mod3
//select_inputs.html
MODULE undefined
I have two issues:
in index.html, why clicking on one mod1 button, mod2 and mod3 fire the click event?
in select_inputs.html, the active field element is not received
I'm pretty new to js and can't understand what is going on.
Any help would be really appreciated.
Thank you.
EDIT 1: as suggested, I change the code that retrieves the object field in select_inputs.html. This solves issue number 2, but something is still wrong in index.html since I always get
MODULE mod3
despite the button I click.
You serialised the value you put in to localStorage to JSON. Therefore you need to deserialise it again when you try and read the value:
var retrivedModuleObj = JSON.parse(localStorage.getItem('moduleObj') || '{}');
console.log("MODULE " + retrivedModuleObj.name);
In addition, due to the loop completing before the click handler can be executed the field variable will only ever hold the value of the final iteration. A better approach would be to use a data attribute to hold the field related to each button element, and a delegated event handler to manage the click event on them. Try this:
var data = {
"modules": [{
"name": "mod1",
"description": "mod1 description",
}, {
"name": "mod2",
"description": "mod2 description",
}, {
"name": "mod3",
"description": "mod3 description",
}]
}
jQuery($ => {
$.each(data.modules, function(i, field) {
let $li = $(`<li><div>Name: ${field.name}</div><div>Description: ${field.description}</div></li>`).appendTo('#moduleList');
$('<input class="set_inputs" type="button" value="select" />').data('field', field).wrap('<div />').parent().appendTo($li);
});
$('#moduleList').on('click', '.set_inputs', e => {
let $button = $(e.currentTarget);
console.log($button.data('field'));
// Uncomment these two lines in your production version. They are only commented
// here as they cause issues within SO snippets
//localStorage.setItem('moduleObj', JSON.stringify($button.data('field')));
//window.location.href = 'select_inputs.html';
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="row">
<div class="col s12 l6">
<ul id="moduleList"></ul>
</div>
</div>

How to render data with Handlebars and jQuery

I have this html where I need to render the data
default.hbs
<div class="chart-container" data-action="chartContainer">
<ul>
<li class="department">
<h3>Enterprise</h3>
<ul class="sections">
// HERE I NEED TO RENDER THE DATA IN AN <li> TAG
</ul>
</li>
</ul>
</div>
and here is the code
APP.chartContainer = (function () {
var Handlebars = window.Handlebars;
var bindEventsToUI = function () {
$.getJSON('maindata.json')
.done(function(data) {
localStorage.setItem('jsonData', JSON.stringify(data));
}).fail(function(err) {
console.log(err);
});
var chartContainerTemplate = $(".chart-container").html();
var theTemplate = Handlebars.compile(chartContainerTemplate);
var getData = localStorage.getItem('jsonData');
var iterateObj = $.each(JSON.parse(getData), function(key, val) {
return val;
});
var theCompiledHtml = theTemplate(iterateObj[0].enterprise);
$(".sections").append(theCompiledHtml);
};
var init = function (element) {
bindEventsToUI();
};
/**
* interfaces to public functions
*/
return {
init: init
};
}());
the function iterateObj returns this
[
{
"enterprise":[
{
"id":"10",
"name":"Hellen Quesada",
"role":"Principal Software Engineer"
},
{
"id":"11",
"name":"Jonathan Chavez",
"role":"Principal Creative Engineer"
}
]
},
{
"consumer":[
{
"id":"18",
"name":"Helga Martinez",
"role":"Production Manager"
},
{
"id":"19",
"name":"Leroy Bernard",
"role":"Sr. Software Engineer"
}
]
}
]
but all I need to render for now is the enterprise part of the data, that is why in my function I am doing iterateObj.[0].enterprise but I am not getting anything in the DOM yet, how do I iterate properly in the over the object in order to get the rendering of the data I need?
What am I missing ?
The template needs to be a script not html. The script can contain the needed html though.
<script id="foo" type="text/x-handlebars-template">
<div class="chart-container" data-action="chartContainer">
<ul>
<li class="department">
<h3>Enterprise</h3>
<ul class="sections">
//Not really sure what you want here
//But access the data like this
<li>{{enterprise.id}}</li>
</ul>
</li>
</ul>
</div>
</script>
Then you compile the template (ie the script):
//var chartContainerTemplate = $(".chart-container").html();
//rename the script to a better id
var chartContainerTemplate = $("#foo").html();
Lastly I would highly suggest reading the docs. There are ways of looping and accessing data. The above template is very basic.

linking data from different json objects in jsrender

I am trying to link the data from foos and selectedFoos. I wish to list the selectedFoos and show the name from the foos object. The fooid in the selectedFoos would be linked to the foos id.
EDIT: I dont want to alter the structure of foos or selectedFoos.
fiddle is here
Html, Template
<div id="content"></div>
<script id="content_gen" type="x-jsrender">
<ul> {^{for sf}}
<li > {{: fooid}} - {{: code}} {{foo.name}} </li>
{{/for}}
</ul>
</script>
JS
var foos = [{
"id": 1,
"name": "a"
}, {
"id": 2,
"name": "b"
}, {
"id": 3,
"name": "c"
}];
var selectedFoos = [{
"fooid": 1,
"code": "z"
}, {
"fooid": 3,
"code": "w"
}];
var app = {
sf: selectedFoos,
f: foos
};
var templ = $.templates("#content_gen");
templ.link("#content", app);
You could add a view converter to lookup the name by id.
Like this - http://jsfiddle.net/Fz4Kd/11/
<div id="content"></div>
<script id="content_gen" type="x-jsrender">
<ul> {^{for sf}}
<li>{{id2name:fooid ~root.f }} - {{: code}} </li>
{{/for}}
</ul>
</script>
js
var app = {
sf: selectedFoos,
f: foos
};
$.views.converters("id2name", function (id, foos) {
var r = $.grep(foos, function (o) {
return o.id == id;
})
return (r.length > 0) ? r[0].name : '';
});
var templ = $.templates("#content_gen");
templ.link("#content", app);
Scott's answer is nice. But since you are using JsViews - you may want to data-link so you bind to the name and code values. Interesting case here, where you want to bind while in effect traversing a lookup...
So there are several possible approaches. Here is a jsfiddle: http://jsfiddle.net/BorisMoore/7Jwrd/2/ that takes a modified version of Scott's fiddle, with a slightly simplified converter approach, but in addition shows using nested {{for}} loops, as well as two different examples of using helper functions.
You can modify the name or the code, and see how the update works. You'll see that code updates in all cases, but to get the name to update is more tricky given the lookup.
You'll see that in the following two approaches, even the data-binding to the name works too.
Nested for loops
Template:
{^{for sf }}
{^{for ~root.f ~fooid=fooid ~sf=#data}}
{{if id === ~fooid}}
<li>{^{:name}} - {^{:~sf.code}} </li>
{{/if}}
{{/for}}
{{/for}}
Helper returning the lookup object
Helper:
function getFoo(fooid) {
var r = $.grep(foos, function (o) {
return o.id == fooid;
})
return r[0] || {name: ""};
}
Template:
{^{for sf}}
<li>{^{:~getFoo(fooid).name}} - {^{:code}} </li>
{{/for}}
See the many topics and samples here
http://www.jsviews.com
such as the following:
http://www.jsviews.com/#converters
http://www.jsviews.com/#helpers
http://www.jsviews.com/#fortag
http://www.jsviews.com/#iftag
http://www.jsviews.com/#samples/data-link/for-and-if
You should iterate over selectedFoos and lookup the name with fooid by iterating over foos. Then combine that data before rendering.
function getNameById(id) {
for (var i = 0; i < foos.length; i++)
if (foos[i].id == id)
return foos[i].name;
return '';
}
This function will return the name when given the id.
Usage:
alert(getNameById(2)); // alerts "b"

Knockout bindings not working as expected for manipulating observable array

We have a view using Razor and Knockout.js that displays a form. Part of the form asks the user to enter a list of values, and we're using a ko.observablearray to keep track of them. This list is represented as a bunch of text boxes, one per value, with a "Delete" button next to each box and a single "Add" button underneath all of them. It works similarly to the demo project at http://learn.knockoutjs.com/#/?tutorial=collections.
Our form is acting unexpectedly in two ways:
When a delete button is clicked, it removes all values from the ko.observablearray, not just the one corresponding to what was clicked.
When the "Submit" button for the overall form is clicked, it adds a new element to the ko.observablearray instead of submitting the form to our server.
Why are we seeing this behavior? (I know that these are two separate issues, but I'm not sure if they're caused by the same underlying problem or not, which is why I'm posting them in one question.)
Here is our Razor view:
#model OurProject.Models.Input.InputModel
#{
ViewBag.Title = "Input";
}
<h2>Inputs</h2>
<div id="inputForm">
<!-- snip - lots of input elements to fill in that are bound to KO -->
<div>
#Html.LabelFor(model => model.POSTransactionCodes)
</div>
<div>
<span class="help-block">Separate values by commas.</span>
</div>
<div>
<ul data-bind="foreach: POSTransactionCodes">
<li><input data-bind="value: $data" /> Delete</li>
</ul>
<button data-bind="click: addPOSTransactionCode">Add another POS Transaction Code</button>
#Html.ValidationMessageFor(model => model.POSTransactionCodes, null, new { #class = "help-inline" })
</div>
<!-- snip - more input elements -->
<button data-bind="click: save">Submit</button>
</div>
<script type="text/javascript" src='~/Scripts/jquery-1.8.2.min.js'></script>
<script type="text/javascript" src='~/Scripts/knockout-2.1.0.js'></script>
<script type="text/javascript" src='~/Scripts/OP/OP.js'></script>
<script type="text/javascript" src='~/Scripts/OP/Input/OP.Input.Input.Form.js'></script>
<script type="text/javascript" src='~/Scripts/OP/Input/OP.Input.Input.Data.js'></script>
<script type="text/javascript">
var elementToBindTo = $("#inputForm")[0];
OP.Input.Input.Form.init(elementToBindTo);
</script>
Here is our main piece of Knockout code, OP.Input.Input.Form.js:
extend(OP, 'OP.Input.Input.Form');
OP.Input.Input.Form = function (jQuery) {
//The ViewModel for the page
var ViewModel = function () {
var self = this;
//Fields
/* snip - lots of ko.observables() */
self.POSTransactionCodes = ko.observableArray([]); //is a list of transaction codes
/* snip - lots of ko.observables() */
//Set up with initial data
self.initialize = function () {
var c = function (data, status, response) {
if (status === "success") {
/* snip - lots of ko.observables() */
ko.utils.arrayPushAll(self.POSTransactionCodes, data.POSTransactionCodes);
self.POSTransactionCodes.valueHasMutated();
/* snip - lots of ko.observables() */
} else {
}
};
OP.Input.Input.Data.GetInput(c);
}
//When saving, submit data to server
self.save = function (model) {
var c = function (data, status, response) {
if (status === "success") {
//After succesfully submitting input data, go to /Input/Submitted
//in order to let MVC determine where to send the user next
window.location.href = "~/Input/Submitted";
} else {
}
};
OP.Input.Input.Data.SaveInput(model, c);
}
//Modifying POSTransactionCodes array
self.removePOSTransactionCode = function (POScode) {
self.POSTransactionCodes.remove(POScode)
}
self.addPOSTransactionCode = function () {
self.POSTransactionCodes.push("");
}
};
//Connect KO form to HTML
return {
init: function (elToBind) {
var model = new ViewModel();
ko.applyBindings(model, elToBind);
model.initialize();
}
};
} ($);
Here is OP.Input.Input.Data.js:
extend(OP, 'OP.Input.Input.Data');
OP.Input.Input.Data = {
GetInput: function (callback) {
$.get("/API/Input/InputAPI/GetInputModel", callback);
},
SaveInput: function (input, callback) {
$.ajax({
url: "/API/Input/InputAPI/SaveInput",
type: "post",
data: input,
complete: callback
});
}
};
You need to be pushing a new ViewModel into your observable array. Which will contain observable properties.
So to do this I created a new view model called TransactionCodeView
var TransactionCodeView = function() {
var self = this;
self.code = ko.observable("");
};
Then when the user clicks "Add another POS Transaction Code":
self.addPOSTransactionCode = function () {
self.POSTransactionCodes.push(new TransactionCodeView());
}
The only other thing changed was in the HTML binding:
<li><input data-bind="value: code" /> Delete</li>
Because code is the observable property in the new viewmodel we bind the input value to that.
Take a look at this jsfiddle. I haven't tested the submit functionality for obvious reasons ;-)
This is why the submit functionality wasn't working on my form:
In the view, I had this Razor:
<div>
<ul data-bind="foreach: POSTransactionCodes">
<li><input data-bind="value: $data" /> Delete</li>
</ul>
<button data-bind="click: addPOSTransactionCode">Add another POS Transaction Code</button>
#Html.ValidationMessageFor(model => model.POSTransactionCodes, null, new { #class = "help-inline" })
</div>
Using the button element for my "Add" button was causing it to respond to the user pressing enter instead of the submit button at the end of the form. When I changed the button into an input element instead, it started working as expected.
<input type="button" value="Add another POS Transaction Code"
data-bind="click: addPOSTransactionCode" />

How to modify object on a click

I'm trying to modify an object on a click. Here's what I have.
<form>
<ul class="tabs" data-tabs="tabs" data-bind="template: 'lineTemplate'"></ul>
<div class="pill-content" data-bind="template: 'lineDivTemplate'" ></div>
</form>
<script id="lineTemplate" type="text/html">
{{each(i, line) lines()}}
<li><a data-bind="click: function() { viewModel.setActive(line) }, attr : { href : '#line' + id() }"><span style="font-size: 15px;" data-bind="text : model"/></a></li>
{{/each}}
</script>
var viewModel = {
lines: ko.observableArray([]),
setActive : function(line) {
**//I need to modify this object**
line.activeTab = true;
}
};
$.getJSON("/json/all/lines", { customer_id : customer_id } , function(data) {
ko.mapping.fromJS(data, null, viewModel.lines);
});
ko.applyBindings(viewModel);
Basically when the user clicks the tab I need it to update the model(and eventually the database) that it is the currently active tab. The first way I had was the delete the object modify it and then push it back to the array, but pushing adds it to the end of the array, which I don't want. Thanks for any help.
Typically, you would mantain something like a "selectedTab" or "activeTab" observable.
var viewModel = {
lines: ko.observableArray([]),
activeTab: ko.observable(),
};
viewModel.setActive = function(line) {
this.activeTab(line);
}.bind(viewModel);
Then, you can do any binding that you want against activeTab. In KO 1.3, you could do:
<div data-bind="with: activeTab">
...add some bindings here
</div>
Prior to that you could do:
<script id="activeTmpl">
...add your bindings here
</script>

Categories