Can i call a JS script in knockout databinding? - data-binding

I need to invoke a JS function inside a foreach loop in knockout data-bind.
I need to do something like:
<div data-bind="foreach:items()">
.....
<script>
jQuery(function () { jQuery('#myid').rateit({ value:$data.value }); })
</script>
....
</div>
Obviously it doesn't work, i've not found a way to apply data binding inside a script tag ... is there a way to do it?

No, you can't.
If you want to execute something for every item in a collection (or on any observable) you can use a computed observable.
This would work in your scenario:
ko.computed(function () {
var items = this.items();
for (var i = 0; i < items.length; i++) {
jQuery(function () { jQuery('#myid').rateit({ value: item[i].value }); })
}
}, viewModel);

Related

Why isn't this template reactive?

Why isn't this reactive? And more importantly how can it be made reactive?
I'd like the data to be saved in Mongo and used in the template. I could use a ReactiveVar or ReactiveDict. Do I need two copies of the data?
Doesn't Suspects.findOne('bruce') return a reactive object already? I tried putting the human answer directly on Bruce, but it didn't trigger an update.
The events fire, log(this) shows bruce's answer was changed, but the template doesn't re-render. What's the good way to do this?
http://meteorpad.com/pad/KoH5Qu7Fg3osMQ79e/Classification
It's Meteor 1.2 with iron:router added:
<head>
<title>test</title>
</head>
<template name="question">
{{#unless isAnswered 'human'}} <!-- :-< I'm not reacting here -->
<div>Sir, are you classified as human?</div>
<button id="no">No, I am a meat popsicle</button>
<button id="smokeYou">Smoke you</button>
{{else}}
<div> Classified as human? <b>{{answers.human}}</b></div>
{{/unless}}
</template>
And the JavaScript:
// Why isn't this reactive?
if (Meteor.isClient) {
Template.question.helpers({
isAnswered: function (question) { // :-< I'm not reactive
var suspect = Template.instance().data;
return (typeof suspect.answers[question] !== 'undefined');
}
});
Template.question.events({
'click #no': function () {
this.answers.human = "No"; // :-< I'm not reactive
console.log(this);
},
'click #smokeYou': function() {
this.answers.human = "Ouch"; // :-< I'm not reactive
console.log(this);
}
});
}
// Collection
Suspects = new Meteor.Collection('suspects');
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Suspects.upsert('bruce', { quest: 'for some elements', answers: {}});
});
Meteor.publish('suspects', function() {
return Suspects.find({});
});
}
// Iron Router
Router.route('/', {
template: 'question',
waitOn: function() {
return Meteor.subscribe('suspects');
},
data: function() {
return Suspects.findOne('bruce');
}
});
Thanks :-)
The events are not actually updating the reactive data source (the db record). Instead of doing:
Template.question.events({
'click #no': function () {
this.answers.human = "No";
}
});
The event needs to perform a database action, either through a direct update or through a Meteor.call() to a Meteor.method. For example:
'click #no': function(){
Suspects.update('bruce', {'answers': {'human': 'no'}});
}
If you use this pattern, you will also need to set the correct allow and deny rules to permit the update from client code. http://docs.meteor.com/#/full/allow. Methods generally end up being a better pattern for bigger projects.
Also, I'm not sure off the top of my head that Template.instance().data in your helper is going to be reactive. I would use Template.currentData() instead just to be sure. http://docs.meteor.com/#/full/template_currentdata
Very close you just need to use ReactiveVar by the sound of it it pretty much explains what it's :) http://docs.meteor.com/#/full/reactivevar
and here's how to use it
if (Meteor.isClient) {
Template.question.onCreated(function () {
this.human = new ReactiveVar();
});
Template.question.helpers({
isAnswered: function (question) {
return Template.instance().human.get();
}
});
Template.question.events({
'click #no': function (e, t) {
t.human.set('No');
console.log(t.human.get());
},
'click #smokeYou': function(e, t) {
t.human.set('Ouch');
console.log(t.human.get());
}
});
}
UPDATE: if you're using a cursor I usually like to keep it on the template level not on iron router:
if (Meteor.isClient) {
Template.question.helpers({
isAnswered: function (question) {
return Suspects.findOne('bruce');
}
});
Template.question.events({
'click #no': function (e, t) {
Suspects.update({_id: ''}, {$set: {human: 'No'}});
},
'click #smokeYou': function(e, t) {
Suspects.update({_id: ''}, {$set: {human: 'Ouch'}});
}
});
}

show loading div while Knockout computed function is running

a bit new to knockout just trying to figure out how to show a loading a div while a ko computed function is running. I'm not quite sure exactly what I need maybe I need to use knockout extenders?
Anywhere here is the fiddle.
http://jsfiddle.net/zf5k9rxq/10/
html
<input data-bind="value: val" />
<p><span data-bind="text: comp"></span>
</p>
<div data-bind="if: showloading">Loading...</div>
Javascript
function model() {
var self = this;
self.val = ko.observable("hello");
self.showloading = ko.observable(true);
this.comp = ko.computed(function () {
//show loading
this.showloading(true);
// begin long running function
var i = 0;
var j = 0;
while (i < 100000) {
i++;
j = 0;
while (j < 80000) {
j++;
}
}
// end long running function
//hide loading and return
this.showloading(false);
return this.val().toUpperCase();
}, this);
}
var mymodel = new model();
$(document).ready(function () {
ko.applyBindings(mymodel);
});
I'm not sure why you need Show/Hide div in computed may be its a mock up of ajax call i believe .
You can achieve it by something like this . Check the commented lines in below code to see minor changes I've made .
view:
<input data-bind="value: val" />
<p><span data-bind="text: compute,visible:!showloading()"></span></p> /*Toggling span visibility if loader is running*/
<div data-bind="if: showloading">Loading...</div>
viewModel:
function model() {
var self = this;
self.val = ko.observable("hello");
self.showloading = ko.observable(true);
self.compute = ko.observable();
ko.computed(function () {
var val = self.val(); //Creating a subscription
//show loading
self.showloading(true);
setTimeout(function () { //Delaying execution to show Loader
self.showloading(false);
self.compute(val ? val.toUpperCase() : val) //Assigning value to observable inside computed .
}, 3000)
});
}
$(document).ready(function () {
ko.applyBindings(new model());
});
PS:You can make use of subscribe too if you want to avoid computed.
working sample goes here

Meteor: How to save files with 'submit form' instead of 'change' events

I am trying to submit an image.. and I've been at this for the past two days. It seems like it's super simple but I can't get it the way I want it to.
In the examples for collectionFS (and every other example I can find), they use an event that's called 'change'. https://github.com/CollectionFS/Meteor-CollectionFS
This event will update and save the file everytime the user wants to upload an image (or any file). They don't have to press "submit" to save it.
Is this the correct way to do things? I am trying to change it so I can blend event into my 'submit form' event, but it doesn't seem to work.
'submit form': function(event, template) {
console.log('this logs')
FS.Utility.eachFile(event, function(file) {
console.log('this doesnt log');
Images.insert(file, function(err, fileObj) {
if (err) {
// handle error
} else {
// handle success depending what you need to do
var userId = Meteor.userId();
var imagesURL = {
"profile.image": "/cfs/files/images/" + fileObj._id
};
Meteor.users.update(userId, {
$set: imagesURL
});
}
});
});
}
However, this doesn't seem to save the file. It doesn't even run the FS.Utility.eachFile part. I've tried all sorts of variations, but if I listed them all I'm afraid it would make a terribly long post. I thought perhaps someone could point me to the right direction? I've tried saving the file into a variable and then inserting them... but I can't seem to get FS.Utility to run with a submit form.
Any help would be much appreciated!
For those who struggle with this later, it's an issue with the assumptions the package makes at the present (1-5-2015). In cfs_base-packages.js:
FS.Utility.eachFile = function(e, f) {
var evt = (e.originalEvent || e);
var files = evt.target.files;
if (!files || files.length === 0) {
files = evt.dataTransfer ? evt.dataTransfer.files : [];
}
for (var i = 0; i < files.length; i++) {
f(files[i], i);
}
};
It's looking for a your event to be structured like this: event.originalEvent.target.files, however the event triggered by submit is originalEvent.target: "" So a dirty work-around would be to do something like this:
Template.templateName.events({
'submit .myForumClass': function(event, template) {
event.preventDefault();
event.originalEvent.target = {};
event.originalEvent.target.files = event.currentTarget[0].files;
//0 is the position of the input field in the parent form
FS.Utility.eachFile(event, function(file) {
Images.insert(file, function (err, fileObj) {
//stuff
});
});
},
});
Hope the below example helps.
<template name="fileupload">
<form class="form-horizontal" role="form">
<input type="file" name="...">
<button type="submit" value="Update" class="btn btn-primary">Upload File</button>
</form>
</template>
Template.fileupload.events({
'submit form': function (event, template) {
console.log("Submit form called");
event.preventDefault();
var fileObj = template.find('input:file');
FilesCollection.insert(fileObj.files[0], function (err, fileObj) {
});
}
});

Observables initialized/attached to Observable in extender not initialized at page load

I
I have created a text counter to tell the user how many characters of they have typed and how many they have remaining available. This should show when the text area has focus and disappear then the text area loses focus.
I have created a binding handler that uses an extender to extend the observable object that is being passed into it. The problem is that it works only after entering text, navigating off of the text area, and then navigating back to the text area.
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<div class="question" >
<label for="successes" data-textkey="successes">This is a question</label>
<textarea data-bind="textCounter: successes, hasFocus: successes.hasFocus, maxLength:200, event: { keyup:successes.updateRemaining }"></textarea>
<div class="lengthmessage edit" data-bind="visible:successes.hasFocus()">
<div >
<em>Length:</em> <span data-bind="text:successes.currentLength"></span>
<em>Remaining:</em> <span data-bind="text:successes.remainingLength"></span>
</div>
</div>
</div>
<script src="../Scripts/knockout-2.3.0.debug.js" type="text/javascript"></script>
<script type="text/javascript">
(function (ko) {
ko.extenders.textCounter = function (target, options) {
options = options || {};
options.maxLength = options.maxLength ? parseInt(options.maxLength) : 2000;
target.maxLength = ko.observable(options.maxLength);
target.currentLength = ko.observable(target().length);
target.remainingLength = ko.observable(target.maxLength() - target.currentLength());
target.hasFocus = ko.observable(false);
target.hasFocus.subscribe(function () {
target.currentLength(target().length);
target.remainingLength(target.maxLength() - target.currentLength());
});
target.updateRemaining = function (data, event) {
if (event.target == undefined && event.srcElement.value == "") {
target.currentLength(0);
}
else {
var e = $(event.target || event.srcElement);
target.currentLength(e.val().length);
if (target.currentLength() > target.maxLength()) {
e.val(e.val().substr(0, target.maxLength()));
target.currentLength(target.maxLength());
}
}
target.remainingLength(target.maxLength() - target.currentLength());
};
return target;
};
ko.bindingHandlers.textCounter = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var val = ko.utils.unwrapObservable(valueAccessor());
var observable = valueAccessor();
observable.extend({ textCounter: allBindingsAccessor() });
ko.applyBindingsToNode(element, {
value: valueAccessor()
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var val = ko.utils.unwrapObservable(valueAccessor());
var observable = valueAccessor();
ko.bindingHandlers.css.update(element, function () { return { hasFocus: observable.hasFocus }; });
}
};
var viewModel = function () {
this.successes = ko.observable("");
//this.successes.hasFocus = ko.observable();
}
ko.applyBindings(new viewModel());
} (ko));
</script>
</body>
</html>
If I uncomment:
//this.successes.hasFocus = ko.observable();
The page will behave the way that I want it to, from the very beginning, but it defeats the whole purpose of using the extender since my view model now has one of the objects from the extender in it.
I have got to believe that there is something relatively simple that I am missing here.
Thanks for your help..
The issue is that hasFocus has not been defined when the binding string here is parsed:
<textarea data-bind="textCounter: successes, hasFocus: successes.hasFocus, maxLength:200, event: { keyup:successes.updateRemaining }"></textarea>
So, when the binding string is parsed successes.hasFocus is undefined.
One option would be to apply the hasFocus binding inside of your textCounter binding after your hasFocus property is available.
Also, in Knockout 3.0 (released today), the parsing of the binding string happens when the value is accessed in the binding itself. So, your code actually works property in KO 3.0 already.

Meteor, How can I display a url query in the client?

I am new to meteor, but it seems like this should be simple. I want to create a page that pulls a get variable down and displays it on the client
ex: www.example.com?yourname=bob
and the page would display
bob
I feel like this should be easy, but so far have not been able to do it. I created an call when the client loads that asks for the info, but it doesn't work on the first load for some reason. On subsequent loads it does.
<head>
<title>Page Chat</title>
</head>
<body>
<div id="wrapper">
{{> name}}
</div>
</body>
<template name="name">
{{name}}
<br />
<input type="button" value="Click" />
</template>
js code
if (Meteor.isClient) {
Meteor.startup(function(){
Meteor.call("getData", function(error, result){
if(error){
Session.set("name", "bob");
}
else{
Session.set("name", result.name);
}
});
});
Template.name.name = function(){
return Session.get("name");
};
Template.name.events({
'click input' : function () {
// template data, if any, is available in 'this'
if (typeof console !== 'undefined')
console.log("You pressed the button");
}
});
}
if (Meteor.isServer) {
var connect = Npm.require('connect');
var app = __meteor_bootstrap__.app;
var post, get;
app
// parse the POST data
.use(connect.bodyParser())
// parse the GET data
.use(connect.query())
// intercept data and send continue
.use(function(req, res, next) {
post = req.body;
get = req.query;
return next();
});
Meteor.startup(function () {
});
Meteor.methods({
getData: function() {
return get;
},
postData: function(){
return post;
}
});
}
If possible I would like to share the data on the initial page load, it seems like a waste to create an separate page load to get information thats already there when the page is first loading.
It might be easier to use something like meteor-router. Then you could do
server side js:
Meteor.Router.add('/something', function() {
return this.params.yourname;
});
So if you visited example.com/something?yourname=Bob you would get back Bob.
Be careful when displaying something directly to the client from a querystring/input parameter as if you don't check it before it could be used for XSS.
Original url is "http://example.com:3000/test?xyz", you can use any one below
Router.current().request.url
"http://example.com:3000/test?xyz"
Router.current().url
"http://example.com:3000/test?xyz"
Router.current().originalUrl
"http://example.com:3000/test?xyz"
Router.current().route._path
"/test"
Router.current().route.getName()
"test"
https://github.com/iron-meteor/iron-router/issues/289

Resources