So I'm trying to do a live search of some client-side info in Meteor.
I have
Template.userTable.events({
"change #nameSearchBar":function(event){
//console.log(event);
searchText = event.target.value;
filteredUsers = Meteor.users.find({"profile.name":{$regex: (".*"+searchText+".*") } });
console.log(filteredUsers.fetch());
}
});
In my js, and
Template.userTable.helpers({
usersOnline: function() {
return filteredUsers;
}
});
As well. I can see that filteredUsers is updated in the console logs, but I don't get the nice live-update of the html that lists usersOnline - instead I just get all of them, which is what the usersOnline was initialised to, by calling filteredUsers = Meteor.users.find().
How can I get the desired live-update?
Your filteredUsers variable is not reactive, so when it changes, nothing is telling the usersOnline helper to re-run. I think you can do this in one of two ways:
Use a ReactiveVar. I'm admittedly not very experienced with them, but I think you could assign the ReactiveVar to be part of the Template, and then have it watch that -- something like:
Template.userTable.created = function() {
this.data.filteredUsers = new ReactiveVar(...) // initialize this to whatever
}
Template.userTable.helpers({
usersOnline: function() {
return this.filteredUsers.get(); // pulling from the reactive var rather than a static var
}
});
Template.userTable.events({
"change #nameSearchBar":function(event){
searchText = event.target.value;
// Setting the reactive var should invalidate the "get" in the helper and trigger re-run
filteredUsers.set(Meteor.users.find({"profile.name":{$regex: (".*"+searchText+".*") } }));
}
});
Use a Session variable -- very similar, but it's accessible globally instead of set on that Template. All Session variables are reactive by default:
Template.userTable.created = function() {
Session.setDefault('filteredUsers', ...) // initialize this to whatever
}
Template.userTable.destroyed = function() {
Session.set('filteredUsers', null); // clean up after yourself when you navigate away
}
Template.userTable.helpers({
usersOnline: function() {
return Session.get('filteredUsers'); // pulling from Session var, which is reactive
}
});
Template.userTable.events({
"change #nameSearchBar":function(event){
searchText = event.target.value;
// Setting the Session var should invalidate the "get" in the helper and trigger re-run
Session.set('filteredUsers', Meteor.users.find({"profile.name":{$regex: (".*"+searchText+".*") } })); }
});
Like I said, I haven't done a lot with ReactiveVars, but I think #1 is technically the better way to go, so I'd play around with that first.
You can also define the searchtext in a Session variable and when that variable changes, display the new result.
Something like this:
Session.setDefault('searchText', null);
Template.userTable.events({
"change #nameSearchBar":function(event){;
searchText = event.target.value;
Session.set('searchText', searchText);
}
});
Template.userTable.helpers({
usersOnline: function() {
if(Session.get('searchText') == null){
return Meteor.users.find();
} else {
var searchText = Session.get('searchText');
return Meteor.users.find({"profile.name":{$regex: (".*"+searchText+".*") } });
}
}
});
Related
I am trying to update the isSelected property for a row of my data stored in the state, the property doesn't update can anyone please tell me the best way to do this?
var selectedIdsPush = [];
var selectedIdsPush = this.state.contacts.slice();
for(var i=0;i<selectedIdsPush.length;i++)
{
var idAsNumber = parseInt(id);
if (page.state.contacts[i].id === idAsNumber) {
page.state.contacts[i].isSelected = true;
break;
}
}
Reacts wants consumers to setState instead of assigning properties directly. In this example, we could build and assign a new contacts list using:
var idAsNumber = parseInt(id);
var newContacts = this.state.contacts.map(function (contact) {
if (contact.id === idAsNumber) {
contact.isSelected = true;
}
return contact;
});
this.setState({ contacts: newContacts });
The property probably does get updated, but it won't be reflected in the UI as your app isn't re-rendered.
You could call this.forceUpdate() to force a re-render, https://facebook.github.io/react/docs/component-api.html#forceupdate.
Or more likely you should use this.setState(), https://facebook.github.io/react/docs/component-api.html#setstate.
I currently struggle with when/where to use state as apparently it's not advised. Search for react avoid state for more information.
Something like :
peer.on('open', function(id){ // this is a non jquery event listener
$('#pid').text(id);
});
With something like...this is not correct:
peer.on('open', function(id){
m('#pid',[id])
});
Is this even the right approach? Should I be establishing a controller and model before I attempt to convert from jquery?
More details:
I am trying to rewrite the connect function in the PeerJS example: https://github.com/peers/peerjs/blob/master/examples/chat.html
If your event listener is something like websockets, then the event happens outside of Mithril, which means you need to manage redrawing yourself. This is what you'll need to do:
Store your data in an independent model
Use that model when rendering your Mithril view
On the open event, update your model, then call m.redraw()
Conceptual example:
var myModel = { id: 'blank' }
var MyComponent = {
view: function () {
return m('#pid', myModel.id)
}
}
m.mount(document.getElementById('app'), MyComponent)
// This happens outside mithril, so you need to redraw yourself
peer.on('open', function(id) {
myModel.id = id
m.redraw()
})
In Mithril, you should not try to touch the DOM directly. Your event handler should modify the View-Model's state, which should be accessed in your View method. If you post more code, I could give a more detailed explanation of how it pieces together.
Here is a bare-bones example that shows the data flowing through Mithril. Your situation will need to be more complicated but I'm not currently able to parse through all of that peer.js code.
http://codepen.io/anon/pen/eNBeQL?editors=001
var demo = {};
//define the view-model
demo.vm = {
init: function() {
//a running list of todos
demo.vm.description = m.prop('');
//adds a todo to the list, and clears the description field for user convenience
demo.vm.set = function(description) {
if (description) {
demo.vm.description(description);
}
};
}
};
//simple controller
demo.controller = function() {
demo.vm.init()
};
//here's the view
demo.view = function() {
return m("html", [
m("body", [
m("button", {onclick: demo.vm.set.bind(demo.vm, "This is set from the handler")}, "Set the description"),
m("div", demo.vm.description())
])
]);
};
//initialize the application
m.module(document, demo);
Notice that the button is calling a method on the View-Model (set), which is setting the value of a property (vm.description). This causes the View to re-render, and the div to show the new value (m("div", demo.vm.description())).
I have a case where I need to listen for array changes of a computed that is simply returning a filtered value of an observable.
However, I do need to have the full list of changes, as .subscribe(function(changes){},null,'arrayChange') would do on an observableArray.
What I understand is that arrayChange does not work in the case of a computed value, because it probably remakes a new array and so there's no specific change to list.
See http://jsfiddle.net/darknessm0404/A6D8u/1/ for a complete example.
// The following does not work, but I'd like it
computedTest.subscribe(function(changesList){
console.log('COMPUTED subscription : arrayChange');
}, null, 'arrayChange');
The only way I seem to achieve what I want is to create another observable array which would have push/delete depending on the changes, so I would be able to get the 'arrayChange' method work.
Full example of my idea:
this.events.listFiltered = ko.observableArray().extend({ rateLimit: 0 });
this.events.listFiltered_Worker = ko.computed(function () {
var listFiltered = me.events.listFiltered();
ko.utils.arrayForEach(me.events.list(), function (item) {
index = listFiltered.indexOf(item);
if (FILTERING_CASE_HERE) {
if (index < 0) {
listFiltered.push(item);
}
} else if (index >= 0) { // Delete
listFiltered.splice(index, 1);
}
});
return ko.utils.arrayFilter(me.events.list(), function (item) {
return !(item.end().isBefore(filterStart) || item.start().isAfter(filterEnd));
});
return __rd++;
}).extend({ rateLimit: 0 });
this.events.listFiltered.subscribe(function () {
debug('inside subscribe');
debugger;
}, null, 'arrayChange');
However I was wondering if there's a easier solution to this problem?
Knockout supports arrayChange for any observable, which you have to enable specifically.
var computedTest = ko.computed(function() {
...
}).extend({trackArrayChanges: true});
http://jsfiddle.net/mbest/A6D8u/2/
If you look at the Knockout source code, this is what's done automatically for observable arrays.
If this a common scenario in your project you could create a wrapper function that does this:
function computedArray() {
return ko.computed.apply(ko, arguments).extend({trackArrayChanges: true});
}
I have 2 services and would like to update a variable in the 1st service from the 2nd service.
In a controller, I am setting a scope variable to the getter of the 1st service.
The problem is, the view attached to the controller doesn't update when the service variable changes UNLESS I use angular.extend/copy. It seems like I should just be able to set selectedBuilding below without having to use extend/copy. Am I doing something wrong, or is this how you have to do it?
controller
app.controller('SelectedBuildingCtrl', function($scope, BuildingsService) {
$scope.building = BuildingsService.getSelectedBuilding();
});
service 1
app.factory('BuildingsService', function() {
var buildingsList = [];
var selectedBuilding = {};
// buildingsList populated up here
...
var setSelectedBuilding = function(buildingId) {
angular.extend(selectedBuilding, _.find(
buildingsList, {'building_id': buildingId})
);
};
var getSelectedBuilding = function() {
return selectedBuilding;
};
...
return {
setSelectedBuilding: setSelectedBuilding,
getSelectedBuilding: getSelectedBuilding
}
});
service 2
app.factory('AnotherService', function(BuildingsService) {
...
// something happens, gives me a building id
BuildingsService.setSelectedBuilding(building_id);
...
});
Thanks in advance!
When you execute this code:
$scope.building = BuildingsService.getSelectedBuilding();
$scope.building is copied a reference to the same object in memory as your service's selectedBuilding. When you assign another object to selectedBuilding, the $scope.building still references to the old object. That's why the view is not updated and you have to use angular.copy/extend.
You could try the following solution to avoid this problem if you need to assign new objects to your selectedBuilding:
app.factory('BuildingsService', function() {
var buildingsList = [];
var building = { //create another object to **hang** the reference
selectedBuilding : {}
}
// buildingsList populated up here
...
var setSelectedBuilding = function(buildingId) {
//just assign a new object to building.selectedBuilding
};
var getSelectedBuilding = function() {
return building; //return the building instead of selectedBuilding
};
...
return {
setSelectedBuilding: setSelectedBuilding,
getSelectedBuilding: getSelectedBuilding
}
});
With this solution, you have to update your views to replace $scope.building bindings to $scope.building.selectedBuilding.
In my opinion, I will stick to angular.copy/extend to avoid this unnecessary complexity.
I dont believe you need an extend in your service. You should be able to watch the service directly and respond to the changes:
app.controller('SelectedBuildingCtrl', function($scope, BuildingsService) {
// first function is evaluated on every $digest cycle
$scope.$watch(function(scope){
return BuildingsService.getSelectedBuilding();
// second function is a callback that provides the changes
}, function(newVal, oldVal, scope) {
scope.building = newVal;
}
});
More on $watch: https://code.angularjs.org/1.2.16/docs/api/ng/type/$rootScope.Scope
Im trying to store the stats of 'this' in my javscript object so that later on in my application I can return 'this' to a previous state. I thought I could accomplish using a closure but so far I haven't successful. My idea was to do something like this
function SavedFeature() {
var self = this;
this.savedItem;
this.storeState = function() {
this.savedItem = storeClosure();
}
function storeClosure() {
var closure = self;
return function() {
return closure;
};
};
//other things the user can change...
}
so later on in my application if I needed to return to the point when I called storeState I could just do
//return the object I put in my closure
var backToNormal = savedFeature.savedItem();
that doesn't work though because any changes to my savedFeature object after I call storeState() are being reflected in the item im retrieving from called savedItem(). I'm guessing this is happening because closure is being set to a reference of self instead of copied to a new instance.
Is there anyway to store the state of my entire object in a closure like this or do I need to store this some other way.
The issue you are running into is that in js objects are passed by reference. This means that all changes performed on your object will apply to your obj.savedItem property.
Fix: Store a deep clone into obj.savedItem
this.storeState = function() {
this.savedItem = _.cloneDeep(this); // or _.clone(this, true);
}
cloneDeep is a lodash method, most js libs supply one of their own, e.g. jQuery's $.extend, etc.
You could easily roll your own deep clone function, look up the options on this thread.
A complete example with jQuery:
function SavedFeature() {
this.savedItem;
this.clone = function() {
return $.extend(true, {}, this);
},
this.storeState = function() {
this.savedItem = this.clone();
}
}
Doing it this way allows you adapt to different environments by changing your clone method as it is facading the used library method.
There are dozens of ways how to implement it. I will do just simple one. saving property.
Take into account if you want to save entire object you need to do deep copy of the object.
this is your feature:
function SavedFeature() {
this.savedItem = {'isNew': true};
this.stateMachine = new StateMachine();
}
this is some kind of state machine:
function StateMachine () {
var state = { 'isNew' : null};
function set(newState) {
state.isNew = newState.isNew;
}
function get() {
return state.isNew;
}
return {
get : get,
set : set
};
}
which, know how to store isNew property
and a working sample:
var savedFeature = new SavedFeature();
console.log(savedFeature.savedItem); // true by default
savedFeature.stateMachine.set(savedFeature.savedItem); // saving state.
savedFeature.savedItem.isNew = false; // modifying state
console.log(savedFeature.savedItem); // return false, because of statement above
var restoredState = savedFeature.stateMachine.get(); // restoring state
console.log(restoredState); // true
savedFeature.savedItem.isNew = restoredState.isNew;
console.log(savedFeature.savedItem); // true
you can adjust that code, and reach functionality whatever you need. hope that helps