How to properly apply KO binding the following example - javascript

Basicaly, I have viewModel in KO having array of 2 values, I need to change css class prop of element (main) when <a> elemets are being clicked (when 1st li>a "Val1" clicked, <main class="stl1">... and so on). Strangely , nothing happends to <main>:
<script>
var mainViewModel = function () {
var self = this;
self.classArr = ['stl1', 'stl2'];
self.cssUsed = ko.observable(0);
self.getClass = function ( data, event ) {
var dat = event.target.value;
self.cssUsed = self.classArr[dat];
console.log( dat + ' : ' + self.cssUsed );
}
}
ko.applyBindings( new mainViewModel() );
</script>
<div class='page'>
<header>
<nav>
<ul >
<li>Val1</li>
<li>Val2</li>
</ul>
</nav>
</header>
<div id='maincontent'>
<main data-bind="css: cssUsed" >
<div class="center"></div>
</main>
</div>
</div>

You got it almost right. The problem was that your were assigning the value in the wrong way. Instead of
self.cssUsed = self.classArr[dat];
try
self.cssUsed(self.classArr[dat]);
Check it out here

Related

How to pass JQuery instance in a Handlebars template context?

I would like to use Handlebars to insert nested elements which could be jQuery elements such as:
var ul = Handlebars.compile($('#ul').html());
var li = Handlebars.compile($('#li').html());
let el = $(li({slot: 'Hello World!'})).data('foo', Object([42]))
$('#main').append(ul({slot: el}))
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.1.2/handlebars.min.js"></script>
<div id="main" class="container">
</div>
<script id="ul" type="text/x-handlebars-template">
<ul>
{{{slot}}}
</ul>
</script>
<script id="li" type="text/x-handlebars-template">
<li>
{{slot}}
</li>
</script>
Unfortunately I am struggling with two issues:
Handlebars expect a html content in the context, therefore I loose data-foo.
Using {{{slot}}} might be unsafe
What would be the alternative?
To achieve expected result, use below option using
Use attr instead of data(), as it only stores value and it will not set attribute on DOM
refer this link for more details - jQuery data attr not setting
Split appending ul and el
Code sample - https://codepen.io/nagasai/pen/PvrLba?editors=1010
var ul = Handlebars.compile($('#ul').html());
var li = Handlebars.compile($('#li').html());
let el = $(li({slot: 'Hello World!'})).attr('data-foo', Object([42]))
$('#main').append(ul)
$('#main ul').append(el)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.1.2/handlebars.min.js"></script>
<div id="main" class="container">
</div>
<script id="ul" type="text/x-handlebars-template">
<ul>
{{{slot}}}
</ul>
</script>
<script id="li" type="text/x-handlebars-template">
<li>
{{slot}}
</li>
</script>
One solution to this problem is write a wrapper that look for jQuery instances in the template context. It replaces every instances with a plain text unique identifier then insert back the jQuery instances to the rendered template:
Handlebars.jQueryCompile = (template) => {
let jQueryObjects = []
let tag = id => `jQueryObject(${id})`
let alter = (obj) => {
if (!(obj instanceof Object)) return
for (var prop in obj) {
if (obj[prop] instanceof jQuery) {
jQueryObjects.push(obj[prop])
obj[prop] = tag(jQueryObjects.length - 1)
} else {
alter(obj[prop])
}
}
}
return (context) => {
alter(context)
let obj = $(template(context))
jQueryObjects.forEach((jq, id) => {
obj.find('*:contains("' + tag(id) + '")').html('').append(jq)
})
return obj
}
}
Here your updated example:
var ul = Handlebars.jQueryCompile($('#ul').html());
var li = Handlebars.jQueryCompile($('#li').html());
let el = $(li({slot: 'Hello World!'})).data('foo', Object([42]))
$('#main').append(ul({slot: el}))
Notice that you don't need to use the {{{}}} mustaches because plain text is inserted instead of html elements.

onClick event returning Every Modal instead of a Single Modal

I'm trying to build a Jeopardy like game using React and Redux. I currently have an onClick event set to each li, but whenever I click on it, I get every Modal to pop up instead of the one that is attached to that li item. I have my code separated in different files but I believe these two files are the only ones I need to show:
const ModalView = React.createClass({
pass: function(){
console.log('pass clicked');
store.dispatch({type:"MODAL_TOGGLE"})
},
submit: function(){
console.log('submit clicked');
store.dispatch({type:"MODAL_TOGGLE"})
},
render: function(){
let question = this.props.question
let category = this.props.category
let answer = this.props.answer
let val = this.props.value
return (
<div>
<div className="modal">
<p>{category}</p>
<p>{question}</p>
<p>{answer}</p>
<p>{val}</p>
<input
type="text"
placeholder="type in your answer">
</input>
<button onClick={this.submit}>Submit</button>
<button onClick={this.pass}>Pass</button>
</div>
</div>
)
}
})
and ValuesView
const ValuesView = React.createClass({
modalPopUp: function(value){
store.dispatch({type:"MODAL_TOGGLE"})
},
render: function(){
let showClass = "show-content"
let hideClass = "hide-content"
if (this.props.modal){
showClass = "hide-content"
hideClass = "show-content"
}
return (<div>
<ul className="list">
{this.props.datum.clues.slice(0,5).map((data, i) => {
if (data.value === null){
return <div>
<div className={hideClass}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value} />
</div>
<li onClick={this.modalPopUp} key={i}>$600</li>
</div>
}
return <div>
<div className={hideClass}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value}/>
</div>
<li
category = {this.props.category}
onClick={this.modalPopUp} key={i}>${data.value}</li>
</div>
})}
</ul>
</div>
)
}
})
How would I go about only getting the corresponding Modal to display instead of every one? Thanks!!
If you just want to code real Modal I suggest you to use some already implemented component like https://github.com/reactjs/react-modal (I'm not saying is mandatory, nor even whit the example I suggest, could be other)
I think that store.dispatch({type:"MODAL_TOGGLE"}) in some way toggle modal prop between true and false. Then you just use that flag to toggle a class to show or hide content I guess (I would need to see you css).
The problem with this approach is (apart that is not the best way to do this for many reasons) that you are using the same class for every item in your clues array.
In some way you need to store which is the "modal" you want to show, and then in the render, just apply the show class to this item.
Maybe:
const ValuesView = React.createClass({
modalPopUp: function(index){
return function (event) {
store.dispatch({
type:"MODAL_TOGGLE",
payload: {
modalIndex: index // Save modalIndex prop
}
})
}
},
render: function(){
return (<div>
<ul className="list">
{this.props.datum.clues.slice(0,5).map((data, i) => {
if (data.value === null){
return <div>
<div className={(this.prosp.modal && this.props.modalIndex === i) ? "show-content" : "hide-content"}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value} />
</div>
<li onClick={this.modalPopUp(i)} key={i}>$600</li>
</div>
}
return <div>
<div className={(this.prosp.modal && this.props.modalIndex === i) ? "show-content" : "hide-content"}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value}/>
</div>
<li
category = {this.props.category}
onClick={this.modalPopUp(i)} key={i}>${data.value}</li>
</div>
})}
</ul>
</div>
)
}
})

reactjs - Function not working inside a map function

I have a component to display list of tags, and the user can select tags to follow them. The tags are displaying fine. I would like to get the selected tag and store it inside a new array tagsSelectedList. So, when the user clicks a Tag, I would like to get that tag and push it to tagsSelectedList. However I am getting an error after I placed an onClick inside the li of the map function.
return (
<li id={Tag.tagName} class="tag" key={Tag.id} onClick={this.selectTag}>{Tag.tagName}</li>
);
This is the error:
Uncaught TypeError: Cannot read property 'selectTag' of undefined
Component.js:
let tags = [
{id: "1", tagName: "Arts"},
...
...
{id: "59", tagName: "Writing"}
}];
var tagsSelectedList = [];
export default class SignUpSubscribeTags extends React.Component {
constructor(props){
super(props);
}
selectTag = (e) => {
console.log(e.target.id);
}
render() {
let tagList = tags.map(function(Tag){
var i = 0;
return (
<li id={Tag.tagName} class="tag" key={Tag.id} onClick={this.selectTag}>{Tag.tagName}</li>
);
});
return(
<div id="signup-process-wrapper-addTags">
<div id="add_tags">
<ul id="tag_list">
{tagList}
</ul>
</div>
</div>
);
}
}
However if I remove onClick={this.selectTag} from the return statement of tagList,
<li id={Tag.tagName} class="tag" key={Tag.id}>{Tag.tagName}</li>
and place a li with an onClick={this.selectTag} inside the ul,
<ul id="tag_list">
<li id="tagName" class="tag" onClick={this.selectTag}>tagName</li>
{tagList}
</ul>
it works fine! I get no error.
What am I doing wrong? Please help me. Thank you.
You need to scope this so it references the React component context
There are a couple of ways to do this:
Option 1:
Using bind()
render() {
let tagList = tags.map(function(Tag){
var i = 0;
return (
<li id={Tag.tagName} class="tag" key={Tag.id} onClick={this.selectTag}>{Tag.tagName}</li>
);
}.bind(this));
return(
<div id="signup-process-wrapper-addTags">
<div id="add_tags">
<ul id="tag_list">
{tagList}
</ul>
</div>
</div>
);
}
Option 2:
You can use the ES6 arrow function to scope it
let tagList = tags.map((Tag) => {
var i = 0;
return (
<li id={Tag.tagName} class="tag" key={Tag.id} onClick={this.selectTag}>{Tag.tagName}</li>
);
});
Option 3:
The map function also takes a second argument that specifies what this refers to
let tagList = tags.map(function(Tag) {
var i = 0;
return (
<li id={Tag.tagName} class="tag" key={Tag.id} onClick={this.selectTag}>{Tag.tagName}</li>
);
}, this);

KNOCKOUT JS observableArray duplicate

My html page is :
<div data-role="content">
<div id="menu">
<ul id="menu" data-role="listview" class="ui-listview "
data-bind="foreach: menu">
<li>
<a data-bind="text:name, attr: {href: urlmenu}"></a>
<a href="#" data-bind="{ click: $parent.remove }"
data-role="button" data-icon="delete"></a>
</li>
</ul>
</div>
</div>
<div data-role="footer" data-position="fixed">
<div data-role="navbar">
<ul id="footer">
<li>Home</li>
<li>Asignaturas</li>
<li>Mensajes</li>
</ul>
</div>
</div>
And my JS code is :
$( document ).on( "pagebeforechange" , function(e, data) {
var toPage = data.toPage[0].id;
if( toPage == "home"){
ko.cleanNode(document.getElementById('menu'));
menu();
}
});
function menuViewModel(){
var self = this;
self.menu = ko.observableArray([]);
self.menu.removeAll();
self.menu = ko.observableArray([
new EditMenuViewModel("Perfil"),
new EditMenuViewModel("Asignaturas")
]);
}
function EditMenuViewModel(name) {
this.name = ko.observable(name);
this.urlmenu = ko.observable("#"+name);
};
function menu(){
var menuviewModel = new menuViewModel();
ko.applyBindings(menuviewModel, document.getElementById('menu'));
}
When I load my page for the first time everything works fine, but when I click on link footer home, the array content is duplicated.
Example is here:
Any idea?
Thanks
You have two DOM elements with id=menu, a div and a ul.
<div id="menu"> <!-- <-- change this id for example -->
<ul id="menu" data-role="listview" class="ui-listview "
data-bind="foreach: menu">
...
</ul>
</div>
Ids should be unique, you need to change the id on one of your elements, hopefully this will also solve your problem.
Update
As you can read in this thread, ko.cleanNode will not remove items created using foreach binding.
You need to change your approach.
Here is a jsFiddle that reproduces your problem.
What you can do is stop cleaning+applying bindings, and update your observableArray instead:
$( document ).on( "pagebeforechange" , function(e, data) {
var toPage = data.toPage[0].id;
if( toPage == "home"){
menuviewModel.menu.removeAll(); //clear menu
//add whatever menu item you need
menuviewModel.menu.push(new EditMenuViewModel("New Menu1 " + (new Date()).getTime()));
menuviewModel.menu.push(new EditMenuViewModel("New Menu2 " + (new Date()).getTime()));
}
});
function menuViewModel(){
var self = this;
self.menu = ko.observableArray([]);
self.menu.removeAll();
self.menu = ko.observableArray([
new EditMenuViewModel("Perfil"),
new EditMenuViewModel("Asignaturas")
]);
}
function EditMenuViewModel(name) {
this.name = ko.observable(name);
this.urlmenu = ko.observable("#"+name);
};
//bind only once
var menuviewModel = new menuViewModel();
ko.applyBindings(menuviewModel, document.getElementById('menu'));
Here is an example
This is old thread, but I've found a (ugly) way to overcome it:
before the cleaning, I'm caching the observable array values and set it with only 1 value. After the rebind, I'm restoring the cached values. Something like this:
var self = this;
self.myArray = ko.observableArray(['val1', 'val2']);
var tempArray = [];
self.BeforeCleaning = function () {
tempArray = self.myArray()
self.myArray(['temp value']);
};
self.AfterRebinding = function () {
self.myArray(tempArray);
};
horrible, but works for me.

how to get the inside id using jquery?

I want to assign values to inside id how can i do that in Jquery
controller.cs code
public GroupModel Get()
{
IGroupTypeRepository groupTypeRepo = new GroupTypeRepository();
IGroupRepository groupRepo = new GroupRepository();
var model = new GroupModel();
model.GroupTypes = groupTypeRepo.GetAll().ToList();
Guid first = model.GroupTypes.FirstOrDefault().Id;
model.Groups = groupRepo.GetAll().Where(s => s.Type == first).ToList();
return model;
}
I tried like following
function getGroups() {
debugger;
$.getJSON(
"groupvalues",
function (data) {
if (data.GroupTypes != undefined) {
$.each(data.Groups, function (jindex, jvalue) {
debugger;
if (jvalue.Id != undefined) {
$("#GroupsTemplate").tmpl(jvalue).appendTo(".span9 .row #projects");
}
});
}
}
<div class="span9">
<div class="row">
<section id="projects">
</section>
</div>
</div>
<script id="GroupsTemplate" type="text/html">
<ul id="thumbs">
<li class="item-thumbs span3 Dhol">
<span class="font-icon-music"></span>${GroupType.TypeName}<br />
</p></div> </li>
</ul>
</script>
I guess i'm going wrong here in js function
$("#GroupsTemplate").tmpl(jvalue).appendTo(".span9 .row #projects");
I think that instead of this:
${GroupType.TypeName}
you must have only this:
${TypeName}
because the GroupType is the parameter object for the template (implicit).
Hope this helps. Cheers

Categories

Resources