I am working on a little project with knockout where i have to simulate the answer to asome questions.
I dynamically build 3 buttons and when i click one i compare the text on it with the correct answer and if it's correct it should become green.., if it's not correct it should become red and the right one should become green..
I was trying to use the binding to css property of knockout, but it seems that the property is not correctly refreshed, even if the computed associated with the css changes
The html:
<ul data-bind="foreach: chosenQuestionAnswers">
<li>
<button data-bind="text: answerText, css: $root.answerStatus(), click: $root.selectButton"></button>
</li>
</ul>
The ko part:
selectedAnswer: ko.observable(),
isAnswerCorrect: ko.observable(),
selectButton: function (value) { my.AppViewModel.isAnswerCorrect(my.AppViewModel.checkIfCorrectAnswer(value.answerText));
},
checkIfCorrectAnswer: function (value) {
return (value == my.AppViewModel.chosenQuestionCorrectAnswer());
},
my.AppViewModel.answerStatus = ko.computed(function () {
var exit = this.isAnswerCorrect() == true ? "highlightRight" : "highlightWrong";
console.log(this.isAnswerCorrect());
console.log(exit);
return exit;
}, my.AppViewModel);
The console.log shows the correct value of the var exit... but the css of the button does not change...
Any idea why?..
Thanks.... I.
you're referencing an old version of KO (2.1), the latest as of today is version is 2.2.1. Your fiddle works when referencing the new version # http://knockoutjs.com/downloads/knockout-2.2.1.js. Here's a fork: http://jsfiddle.net/drdamour/xe2M5/
The dynamic css binding (how you're using it) was only added in 2.2 see http://blog.stevensanderson.com/2012/10/29/knockout-2-2-0-released/
stack won't let me submit without some code so...here some worthless code:
var x = {a:1}
css classes are part of the view, try to keep them out of the ViewModel, it should focus on business logic.
I would do something like
http://jsfiddle.net/hLXbq/
HTML
<button data-bind="css: { valid: valid, invalid: invalid }, click: toggle">Toggle</button>
JS
ViewModel = function () {
this.valid = ko.observable(false);
this.invalid = ko.computed(function () {
return !this.valid();
}, this);
};
Related
I'm working in meteor trying to use a reactive var to switch the content in the main panel between two tabs. I've been able to test the content successfully on it's own so I'm fairly confident the issue lies in the reactive var code. Specifically I think the issue is with the tab: function() but after many searches and reading documentation I haven't found a solution.
The relevant js:
Template.content.onCreated( function() {
this.currentTab = new ReactiveVar('form');
});
Template.content.helpers({
tab: function() {
return Template.instance().currentTab.get();
}
});
Template.content.events({
'click .nav li': function (event, template) {
var currentTab = $( event.target ).closest( "li" );
currentTab.addClass( "active" );
$( ".nav li" ).not( currentTab ).removeClass( "active" );
Template.currentTab.set();
}
});
The relevant html:
<template name ="content">
<ul class ="nav">
<li data-template="form">Form</li>
<li data-template="results">Results</li>
</ul>
{{ > Template.dynamic template=tab}}
</template>
{{ > Template.dynamic template=tab}}
This is calling the tab helper to get a string that is the name of the template you want to show here. It should work the first time because you start out by setting the value of currentTab to "form".
To change the template that gets shown, you need to change the value of currentTab to a string matching the name of the new template. You're not doing that.
Template.currentTab.set();
This is where you should be doing that. Instead you're calling set() on the currentTab property of Template, which I don't think exists. Template with a capital T is a Meteor object, not the template instance that I think you're trying to refer to. And to set a new value for currentTab, you actually need to provide a value. Like so:
Template.content.events( {
'click .nav li': function(event, instance) {
//logic to decide which template you want to show
//and put the name of that template in templateName
instance.currentTab.set(templateName)
}
});
Using v-on:click I'd like to set a variable with the id of the div in Vue.JS - how do I reference this?
<div id="foo" v-on:click="select">...</div>
<script>
new Vue({
el: '#app',
data: {
},
methods: {
select: function(){
divID = this.id // ??
alert(divID)
}
}
})
</script>
You can extend your event handler with the event object $event. That should fit your needs:
<div id="foo" v-on:click="select($event)">...</div>
The event is passed on in javascript:
export default {
methods: {
select: function(event) {
targetId = event.currentTarget.id;
console.log(targetId); // returns 'foo'
}
}
}
As mentioned in the comments, `$event` is not strictly necessary, when using it as the only parameter. It's a nice reminder that this property is passed on, when writing it explicitly.
However, nobody will stop you from writing the short notation:
<div id="foo" #click="select">...</div>
Beware that the method will not receive the `$event` object when you add another parameter. You need to explicitly add it at the position you will handle it in the listener. Any parameter order will work:
<div id="foo" #click="select(bar, $event)">...</div>
To find more options of the v-on directive, you can look through the corresponding entry in the vue documentation:
Vue API Documentation - v-on
Inspired by #nirazul's answer, to retrieve data attributes:
HTML:
<ul>
<li
v-for="style in styles"
:key="style.id"
#click="doFilter"
data-filter-section="data_1"
:data-filter-size="style.size"
>
{{style.name}}
</li>
</ul>
JS:
export default {
methods: {
doFilter(e) {
let curTarget = e.currentTarget;
let curTargetData = curTarget.dataset;
if (curTargetData) {
console.log("Section: " + curTargetData.filterSection);
console.log("Size: " + curTargetData.filterSize);
}
}
}
}
Just to highlight another option than the selected answer for the same question, I have a delete button on a record and want to perform an action with the record's unique id (a number). I could do the selected answer as before:
<button :id="record.id" #click="del">×</button>
This leaves the unfortunate reality that my del function needs to pull the id attribute out of the javascript event, which is more about the API (the DOM) than my domain (my app). Also using a number as an element id isn't ideal and could cause a conflict if I do it more than once in a view. So here's something that's just as clear and avoids any future confusion:
<button #click="()=>del(record.id)">×</button>
methods: {
del(id) {
fetch(`/api/item/${id}`, {method:"DELETE"})
}
}
You see, now my del function takes the record id instead of an event, simplifying things.
Note that if you do this wrong, you will invoke your delete function immediately, which is not what you want. Don't do this:~~
<button #click="del(record.id)">×</button>
If you end up doing that, Vue will call the del function every time this html fragment is rendered. Using the anonymous function ()=>del(record.id) will return a function that's ready to be executed when the click event happens.
Actually #nirazul proved this is fine. Not sure what my issue was.
Banging my head on how to use polymer two way binding.
I have a home made Polymer element that defines a boolean property through
Polymer({
is: "test-element",
ready: function() {},
properties: {
propEnabled: {
type: Boolean,
notify: true,
value: false,
observer: "propEnabledChanged"
}
},
// Called when aoEnabled is changed
propEnabledChanged: function() { console.log("propEnabled value switched to " + this.propEnabled); },
});
Now I'm using this in an HTML page
<body>
<template id="t" is="dom-bind">
<test-element id="testElement"></test-element>
<paper-toggle-button checked="{{Model.propEnabled}}">prop. enabled</paper-toggle-button>
<button id="switchInternalState">switch state</button>
</template>
</body>
<script>
var t = document.querySelector('#t');
document.addEventListener('WebComponentsReady', function() {
console.log('WebComponentsReady');
// We have to bind the template with the model
var t = document.querySelector('#t');
t.Model = document.getElementById("testElement");
// chaging the property directly does not reflect in the GUI... :-(
var button = document.getElementById("switchInternalState");
button.addEventListener("click", function() {
t.Model.set("propEnabled", !t.Model.propEnabled);
});
});
</script>
But when clicking on the switch state button...
I get the log propEnabled value switched to true
But the toogle button on the page does not change...
If I add a simple
<label>{{Model.propEnabled}}</label>
The label does not change either...
To me it looks a bit like one way binding where it should be 2 way as
toggling the button fire the log and properly change the component propEnabled value. So it really looks like one way binding to me.
So... How can we actually benefit from two way binding with Polymer templates ????
You need to assign the propEnabled property from dom-bind to the test-element through html.
<test-element id="testElement" prop-enabled={{propEnabled}}></test-element>
<paper-toggle-button checked="{{propEnabled}}">prop. enabled</paper-toggle-button>
Also you don't need the variable t.Model in your script. You can remove it. The event listener should be like below
button.addEventListener("click", function() {
t.propEnabled = !t.propEnabled;
});
The following plunker has the working code: http://embed.plnkr.co/13QJ7QFETIBg4bEiCMS7/preview
I have the following template:
<template name="modalTest">
{{session "modalTestNumber"}} <button id="modalTestIncrement">Increment</button>
</template>
That session helper simply is a go-between with the Session object. I have that modalTestNumber initialized to 0.
I want this template to be rendered, with all of it's reactivity, into a bootbox modal dialog. I have the following event handler declared for this template:
Template.modalTest.events({
'click #modalTestIncrement': function(e, t) {
console.log('click');
Session.set('modalTestNumber', Session.get('modalTestNumber') + 1);
}
});
Here are all of the things I have tried, and what they result in:
bootbox.dialog({
message: Template.modalTest()
});
This renders the template, which appears more or less like 0 Increment (in a button). However, when I change the Session variable from the console, it doesn't change, and the event handler isn't called when I click the button (the console.log doesn't even happen).
message: Meteor.render(Template.modalTest())
message: Meteor.render(function() { return Template.modalTest(); })
These both do exactly the same thing as the Template call by itself.
message: new Handlebars.SafeString(Template.modalTest())
This just renders the modal body as empty. The modal still pops up though.
message: Meteor.render(new Handlebars.SafeString(Template.modalTest()))
Exactly the same as the Template and pure Meteor.render calls; the template is there, but it has no reactivity or event response.
Is it maybe that I'm using this less packaging of bootstrap rather than a standard package?
How can I get this to render in appropriately reactive Meteor style?
Hacking into Bootbox?
I just tried hacked into the bootbox.js file itself to see if I could take over. I changed things so that at the bootbox.dialog({}) layer I would simply pass the name of the Template I wanted rendered:
// in bootbox.js::exports.dialog
console.log(options.message); // I'm passing the template name now, so this yields 'modalTest'
body.find(".bootbox-body").html(Meteor.render(Template[options.message]));
body.find(".bootbox-body").html(Meteor.render(function() { return Template[options.message](); }));
These two different versions (don't worry they're two different attempts, not at the same time) these both render the template non-reactively, just like they did before.
Will hacking into bootbox make any difference?
Thanks in advance!
I am giving an answer working with the current 0.9.3.1 version of Meteor.
If you want to render a template and keep reactivity, you have to :
Render template in a parent node
Have the parent already in the DOM
So this very short function is the answer to do that :
renderTmp = function (template, data) {
var node = document.createElement("div");
document.body.appendChild(node);
UI.renderWithData(template, data, node);
return node;
};
In your case, you would do :
bootbox.dialog({
message: renderTmp(Template.modalTest)
});
Answer for Meteor 1.0+:
Use Blaze.render or Blaze.renderWithData to render the template into the bootbox dialog after the bootbox dialog has been created.
function openMyDialog(fs){ // this can be tied to an event handler in another template
<! do some stuff here, like setting the data context !>
bootbox.dialog({
title: 'This will populate with content from the "myDialog" template',
message: "<div id='dialogNode'></div>",
buttons: {
do: {
label: "ok",
className: "btn btn-primary",
callback: function() {
<! take some actions !>
}
}
}
});
Blaze.render(Template.myDialog,$("#dialogNode")[0]);
};
This assumes you have a template defined:
<template name="myDialog">
Content for my dialog box
</template>
Template.myDialog is created for every template you're using.
$("#dialogNode")[0] selects the DOM node you setup in
message: "<div id='dialogNode'></div>"
Alternatively you can leave message blank and use $(".bootbox-body") to select the parent node.
As you can imagine, this also allows you to change the message section of a bootbox dialog dynamically.
Using the latest version of Meteor, here is a simple way to render a doc into a bootbox
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,MyCollection.findOne({_id}),box.find(".modal-body")[0]);
If you want the dialog to be reactive use
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,function() {return MyCollection.findOne({_id})},box.find(".modal-body")[0]);
In order to render Meteor templates programmatically while retaining their reactivity you'll want to use Meteor.render(). They address this issue in their docs under templates.
So for your handlers, etc. to work you'd use:
bootbox.dialog({
message: Meteor.render(function() { return Template.modalTest(); })
});
This was a major gotcha for me too!
I see that you were really close with the Meteor.render()'s. Let me know if it still doesn't work.
This works for Meteor 1.1.0.2
Assuming we have a template called changePassword that has two fields named oldPassword and newPassword, here's some code to pop up a dialog box using the template and then get the results.
bootbox.dialog({
title: 'Change Password',
message: '<span/>', // Message can't be empty, but we're going to replace the contents
buttons: {
success: {
label: 'Change',
className: 'btn-primary',
callback: function(event) {
var oldPassword = this.find('input[name=oldPassword]').val();
var newPassword = this.find('input[name=newPassword]').val();
console.log("Change password from " + oldPassword + " to " + newPassword);
return false; // Close the dialog
}
},
'Cancel': {
className: 'btn-default'
}
}
});
// .bootbox-body is the parent of the span, so we can replace the contents
// with our template
// Using UI.renderWithData means we can pass data in to the template too.
UI.insert(UI.renderWithData(Template.changePassword, {
name: "Harry"
}), $('.bootbox-body')[0]);
My situation
I have a BIG list of people which all have their own profile pic and information. All items have some CSS3 styling/animations.
Im using:
<li ng-repeat="person in people">
Where people is an array with over 150 objects from an external JSON file that all have to show on the same page.
The page loads/renders slowly, and its not because of the $http GET function that gets the JSON data with people (which only takes about 100ms), but the rendering of the page and seemingly the rendering of all the css3 styling applied to each item (if i turn my CSS off it gets much faster).
My main problem
I can live with the page rendering slowly but what i really want is to detect whenever my heavy page has loaded its content in order to start and stop a loading indicator. Ive tried making a directive like this:
directive("peopleList", function () {
return function (scope, element, attrs) {
scope.$watch("people", function (value) {
var val = value || null;
console.log("Loading started");
if (val){
console.log("Loading finished");
}
});
};
});
And put this directive on the wrapper div that is loading my list of people. But the loading appears to stop whenever my object with JSON data has been filled, which only takes around 100ms, but the actual visual rendering of the page takes more like 4-5s.
Ive also tried solutions like http://jsfiddle.net/zdam/dBR2r/ but i get the same result there.
What i need
When i navigate through my pages (which all have the same kind of lists) i need something that tells me whenever the page has fully loaded and is visible for the user so that i know when to hide my loading indicator.
Try adding this directive -- the magic is in the link function:
directives
.directive('onFinishRender', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
scope.$evalAsync(attr.onFinishRender);
}
}
}
});
Add a loader as the first item:
<li ng-show="!isDoneLoading">
<div class="loading">Loading...</div>
</li>
Then in your li, add this:
<li ng-repeat="..." on-finish-render="isDoneLoading = true;"> ... </li>