In angularjs scope.$watch() can be used to execute a function each time a variable value changes.
scope.$watch('myvar', function(newValue, oldValue) {
changeCallback();
});
(The angularjs sample is only for showing what i want to do. I want to use only rivets.js, not anglarjs at all.)
I could use an event listener on the element that can change the value, but then i have to have event listeners everywhere where the variable value might get changed from.
<input type='text' rv-on-change='changeCallback' rv-value='myvar'>
Or if the value gets changed from javascript code i would have to execute the change function from there too.
myvar = 'changed value';
changeCallbacl()
QUESTION: Is there a way to execute a function each time a variable value changes in rivers.js without adding any code to the other end where the value gets changed from?
Found the solution here http://jsfiddle.net/nsisodiya/2mkjx44j/ . You need to use sightglass wich is a dependency for rivets and included in rivets.bundled.min.js .
// configure sightlass with same adapters as rivets
sightglass.adapters = rivets.adapters;
sightglass.root = '.';
// listen for changes on list.val
// list is an object and val is property to listen for changes on
sightglass(list, 'val', function() {
log('value changed...');
});
Here is a working sample on codepen
http://codepen.io/mstadius/pen/azroda
And here is code of same sample
html
<div id='app'>{list.val}<br>
<input rv-value='list.val'><br>
<button rv-on-click='list.reset'>Reset</button>
</div>
<div id='log'></div>
js
var log = function(msg){
$('#log').prepend('<div>'+msg+'</div>');
};
var list = {
val: 1
, reset: function(){
log('Clicked reset...');
list.val = 1;
}
};
var a = rivets.bind($('#app'), {list: list});
sightglass.adapters = rivets.adapters;
sightglass.root = '.';
sightglass(list, 'val', function() {
log('value changed...');
});
Related
My template looks like this:
<input type="text" name="inputValue" id="inputVal">
{{#each group}}
<section>
<h2>{{title}}</h2>
{{#each element}}
<p>{{description}} {{numberValue}} {{unit}}</p>
{{/each}}
</section>
{{/each}}
So far this is working. But now I want to do a little calculating by multiplicate the value of the inputfield with the numberValue (group.element.numberValue) and use this with {{calculatedValue}} in the template:
<p>{{description}} {{calculatedValue}} {{unit}}</p>
Therefore I need a helper:
Template.usedTemplate.helpers({
'calculatedValue': function() {
return document.getElementById("#inputVal").value * this.numberValue;
}
});
I hope this is the correct coding for creating the helper as the values are used in an each-loop.
But my problem is, that the user types the inputValue after the site has been loaded. So what do I have to do, to recalculate the rows after the user typed some values in the input field?
(Also it would be great if there would be a kind of output info text at the beginning as there is no value given by default. After typing some value the result is been shown.)
I think you can simply use Session or ReactiveVar to solve your problem
Session.setDefault("input-value", "")
Template.usedTemplate.events({
'keypress #inputVal': function (event) {
Session.set("input-value", Template.instance().$("#inputVal").val());
}
});
Template.usedTemplate.helpers({
'calculatedValue': function() {
return Session.get("input-value") * this.numberValue;
}
});
or use ReactiveVar
Template.usedTemplate.onCreated(function(){
this.inputValue = new ReactiveVar("")
})
Template.usedTemplate.events({
'keypress #inputVal': function (event) {
Template.instance().set(Template.instance().$("#inputVal").val());
}
});
Template.usedTemplate.helpers({
'calculatedValue': function() {
return Template.instance().get() * this.numberValue;
}
});
I suggest using ReactiveVar, because Session is global val.
And you should add ReactiveVar to your app before using.
meteor add reactive-var
I haven't tested this code, but try this:
var userInput = new Tracker.Dependency;
Template.usedTemplate.events({
'keypress #inputVal': function (event) {
userInput.changed();
}
});
Template.usedTemplate.helpers({
'calculatedValue': function() {
userInput.depend();
return Template.instance().$("#inputVal").val() * this.numberValue;
}
});
Side note: document.getElementById() already expects an ID as an argument, so you don't need the # prefix.
Normally, the calculatedValue helper will not update automatically because it contains no reactive method calls, such as someCollection.find() or Session.get('xyz'). But using Tracker.Dependency, you can make anything reactive.
So I know scoping in JavaScript can be a little wonky and I've tried a few different things including attaching the variable declaration to the window (i.e. window.var) and declaring the variable inside and outside different parts of the function but to no avail. Here's what I've got:
$(".some_field").on('focus', function () {
// Store the previous option value before the change
prev = String($(":selected", $(this)).data('option_id'));
}).change( function() {
alert(prev); // alerts '4'
current = String($(":selected", $(this)).data('option_id'));
alert(current) // alerts '5'
alert(prev); // alerts '5' ..... it should alert '4'
});
Essentially within the change function I need to do stuff with both the previous and current option id's
Instead of using a global, I recommend decorating the input with an old value data attribute.
Try this: http://jsfiddle.net/zpe7azsq/16/
For example:
$(".some_field").on('focus', function () {
$(this).data('oldValue', $(this).data('option_id'));
}).change(function () {
alert("Old value on focus was: " + $(this).data('oldValue'));
alert("Current option_id value: "+$(this).data('option_id'));
});
You need to use one variable/property for storing the previous value per element. A global variable won't help. Actually you don't even need those focus events.
$(".some_field").each(function () {
$(this).data('id_value', $(":selected", this).data('option_id'));
}).change(function(e) {
var previous = $(this).data('id_value'),
current = $(":selected", this).data('option_id');
alert(…); // or whatever
$(this).data('id_value', current);
});
With #Scott 's answer as a guide here is what ended up working for me. I used the parent element to store the data instead... #Bergi's answer also works though!
$(".some_field").on('focus', function () {
// Store the previous option value before the change
$(this).parent().data('prev_option_id', $(":selected", $(this)).data('option_id'));
}).change( function() {
current = String($(":selected", $(this)).data('option_id'));
alert("Old value on focus was: " + $(this).parent().data('prev_option_id'));
alert("Current option_id value: "+current);
});
I have set up the following select for changing semesters on page. When the select detects a change, the changeSemesters function is fired, which runs an AJAX that replaces the current data on the page with data specific to the selected semester.
View
<select data-bind="options: semestersArr, optionsText: 'name', optionsValue: 'id', value: selectedSemester, event: { change: changeSemesters }"></select>
ViewModel
this.selectedSemester = ko.observable();
//runs on page load
var getSemesters = function() {
var self = this, current;
return $.get('api/semesters', function(data) {
self.semestersArr(ko.utils.arrayMap(data.semesters, function(semester) {
if (semester.current) current = semester.id;
return new Model.Semester(semester);
}));
self.selectedSemester(current);
});
};
var changeSemesters = function() {
// run ajax to get new data
};
The problem is that the change event in the select fires the changeSemester function when the page loads and sets the default value. Is there a way to avoid that without the use of a button?
Generally what you want to do in these scenarios is to use a manual subscription, so that you can react to the observable changing rather than the change event. Observables will only notify when their value actually changes.
So, you would do:
this.selectedSemester.subscribe(changeSemesters, this);
If selectedSemester is still changing as a result of getting bound, then you can initialize it to the default value.
this.selectedSemester = ko.observable(someDefaultValue);
I have a checkbox that I would like to trigger a simple 'select all' functionality. The problem is that I can't figure out how to connect the checkbox's action to an action in my controller so that I can actually update the records.
App.LanguagesController = Ember.ArrayController.extend({
actions: {
toggleAllVisibility: function() {
var newVisibility = !this.get('allAreVisible');
var needingVisibilityChange = this.filterBy('visible', !newVisibility);
needingVisibilityChange.setEach('visible', newVisibility);
}
},
allAreVisible: function(param) {
return this.filterBy('visible', false).get('length') === 0;
}.property('#each.visible'),
});
In my template, I have the following input helper
{{input type='checkbox' checked=allAreVisible}}
This properly updates the checkbox when I change the elements manually (i.e. if all of them are selected, then checkbox updates), but no actions fire when I toggle the checkbox.
It looks like in older versions of Ember.js I could simply add an action parameter to the input helper but that doesn't work anymore. I'm assuming I need to setup something that observes when the computed property attempts to change, but I couldn't find anything in the docs or other help.
I also tried extending checkbox to send the click event:
App.AllLanguagesCheckbox = Ember.Checkbox.extend(Ember.ViewTargetActionSupport, {
click: function() {
this.triggerAction({
action: 'toggleAllVisibility'
});
}
});
And then loaded that in my template with
{{view App.AllLanguagesCheckbox checkedBinding=allAreVisible}}
That allows the checkbox to trigger the action, but does not update based on the computed property in the controller.
I feel like I'm missing something obvious here.
EDIT
Based on kingpin2k's answer below, here's the working controller code:
App.LanguagesController = Ember.ArrayController.extend({
toggleAllVisibility: function() {
var newVisibility = !this.get('controller').get('allAreVisible');
var needingVisibilityChange = this.get('controller').filterBy('visible', !newVisibility);
needingVisibilityChange.setEach('visible', newVisibility);
},
allAreVisible: function(param) {
return this.filterBy('visible', false).get('length') === 0;
}.property('#each.visible'),
});
It's not called with the normal scope so you have to explicitly go through the controller to get the model array, but it works as expected now.
Here's the accompanying input helper:
{{input type='checkbox' checked=allAreVisible change=toggleAllVisibility}}
The problem is your checkbox is connected to a computed property, the computation should derive the value (aka you shouldn't be setting it), which is what would be happening when someone tries to check.
_allAreVisible:false,
allAreVisible: function(param) {
if(this.filterBy('visible', false).get('length') === 0){
// set to true;
// else set to false
}.observes('#each.visible'),
http://emberjs.jsbin.com/abODIKoj/1/edit
I want to see an alert message when the value of a div changes. This value is being modified by modify_div. When I click the button this function modifies the div, but the alert "value changed" is not displayed. Am I missing something?
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" " http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<script src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js"></script>
<script>
YUI().use('node', function (Y) {
var demo = Y.one('#test');
demo.on('click', function (e) {
//alert('You clicked me');
});
});
YUI().use('node','event', function (Y) {
var demo = Y.one('#variable-name');
demo.on('change', function (e) {
alert('Value changed');
});
});
</script>
<script type="text/javascript">
function modify_div()
{
//var thevar = "This is a test";
var thevar = 7;
document.getElementById('variable-name').innerHTML = thevar;
}
</script>
</head>
<body>
<!-- Click me button -->
<input type="button" id="test" value="Click me" enabled="true" onclick="modify_div();"> </input>
</br>
<div id="variable-name" style="display:inline;">01010101</div>
</body>
</html>
based on http://www.quirksmode.org/dom/events/change.html,
change event only fires if its form field
e.g. input textarea and select
so change event will not fire when contents of div is changed.
It will work if you replace div with input and update its value.
other option is to manually fire event where ever you are changing the value your variable
http://tech.groups.yahoo.com/group/ydn-javascript/message/13216
following SO question has answers but it requires jQuery
Detect element content changes with jQuery
The correct answer was given by #N30: there is no change event for divs. He provides good alternatives but no YUI specific information, so I'd like to extend his answer with an example of a YUI Plugin.
Like he explained, the basic idea is to keep a value in JavaScript memory and fire an event when you change that value. You can do this by extending Y.EventTarget which provides you with custom events:
YUI().use('node', 'plugin', function (Y) {
function NodeValuePlugin(config) {
// Boilerplate
NodeValuePlugin.superclass.constructor.apply(this);
// config.host is the Y.Node instance
this._node = config.host;
// we keep the value in a private property
this._value = this._node.get('text');
// we publish a 'change' event and set a default
// function to run when the event is fired
// This function will change the private property
// and update the DOM
// This means you can call e.preventDefault() and
// stop the default behavior (the change of value)
this.publish('change', {
emitFacade: true,
defaultFn: this._defValueChangeFn
});
}
Y.extend(NodeValuePlugin, Y.EventTarget, {
set: function (newVal) {
// we want to do stuff only when the value changes
if (newVal != this._value) {
// instead of changing the DOM here,
// we fire an event and let the event
// do the work
// We pass it the new and old values
this.fire('change', {
newVal: newVal,
prevVal: this._value
});
}
// make this method chainable
return this;
},
get: function () {
return this._value;
},
_defValueChangeFn: function (e) {
// sync everything
this._value = e.newVal;
this._node.set('text', e.newVal);
},
// this is necessary boilerplate for plugins
destroy: function () {}
}, {
// we can access the plugin from node[namespace]
// in this case, node.value
NS: 'value'
});
var node = Y.one('#test').plug(NodeValuePlugin);
node.value.on('change', function (e) {
console.log('Here\'s the old value: ' + e.prevVal);
console.log('Here\'s the new value: ' + e.newVal);
});
// Freebie:
// since we're using node.set('text', foo)
// this prevents XSS vulnerabilities
node.value.set('qwer');
});
You can learn more about plugins from the Plugin User Guide in the YUI website.