How to pass data between templates in Meteor with ReactiveVar - javascript

Let's say I have a templateA.html with input fields and then another separate templateB.html which is supposed to display the values of the input fields from templateA.html as they are typed in. I've gotten it to work with Session.set and Session.get but the problem is the input values stay the same when the page is refreshed and it doesn't seem the best way to do this. I then tried to use ReactiveVar but since I can't find any good examples on how to use it I'm a bit lost. The following is how it works with Session so maybe this will help understand what I'm trying to do with ReactiveVar.
Template.templateA.events({
'change #batch_form': function(){
var batch_num = document.getElementById('batch_number').value;
var dist_name = document.getElementById('distributor_name').value;
var data = {
batch_number: batch_num,
dist_name: dist_name
}
Session.set('form_data', data);
}
})
Template.templateB.helpers({
input_data(){
return Session.get('form_data');
},
});
TemplateB.html
<template name='templateB'>
<p>Batch #:{{input_data.batch_number}}</p>
<p>Batch #:{{input_data.dist_name}}</p>
</template>

You should avoid Session where you can. Better scope your Template-exceeding variables with a shared scope. ES6 import/export are suitable for that.
Consider you want to share a ReactiveDict (which behaves like Session) as state between only those two Templates. You can create a new js file with the following content:
shared.js
import { ReactiveDict } from 'meteor/reactive-dict'
export const SharedAB = new ReactiveDict()
This gives you the control to share the state only between those Templates, that import the object.
templateA.js
import { SharedAB } from './shared.js'
Template.templateA.events({
'change #batch_form': function(){
var batch_num = document.getElementById('batch_number').value;
var dist_name = document.getElementById('distributor_name').value;
var data = {
batch_number: batch_num,
dist_name: dist_name
}
SharedAB.set('form_data', data);
}
})
templateB.js
import { SharedAB } from './shared.js'
Template.templateB.helpers({
input_data(){
return SharedAB.get('form_data');
},
});

if they don't have any parent-child relationship you can use common ReactiveDict variable(here Session) to pass the data between just like you're doing.
but the problem is the input values stay the same when the page is
refreshed and it doesn't seem the best way to do this
for this on onDestroyed callback of template you can clear the Session variable values
Template.templateA.onDestroyed(function(){
Session.set('form_data', false); // or {}
})
so when you come back to this page/template, there is no data to display.

Related

Global data with VueJs 2

Im relatively new with VueJS, and I've got no clue about how to make some data globally available. I would like to save data like API endpoints, user data and some other data that is retrieved from the API somewhere where each component can get to this data.
I know I can just save this with just vanilla Javascript but I suppose there is a way to do this with VueJS. I may be able to use the event bus system to get the data but I don't know how I can implement this system to my needs.
I would appreciate it if somebody can help me with this.
Make a global data object
const shared = {
api: "http://localhost/myApi",
mySharedMethod(){
//do shared stuff
}
}
If you need to expose it on your Vue, you can.
new Vue({
data:{
shared
}
})
If you don't, you can still access it inside your Vues or components if you've imported it or they are defined on the same page.
It's really as simple as that. You can pass shared as a property if you need to, or access it globally.
When you're just starting out there is no real need to get complicated. Vuex is often recommended, but is also often overkill for small projects. If, later, you find you need it, it's not that hard to add it in. It's also really for state management and it sounds like you just really want access to some global data.
If you want to get fancy, make it a plugin.
const shared = {
message: "my global message"
}
shared.install = function(){
Object.defineProperty(Vue.prototype, '$myGlobalStuff', {
get () { return shared }
})
}
Vue.use(shared);
Vue.component("my-fancy-component",{
template: "<div>My Fancy Stuff: {{$myGlobalStuff.message}}</div>"
})
new Vue({
el: "#app"
})
Now, every Vue you create and every component has access to it. Here is an example.
You can use Store which will hold your application state.
const store = new Vuex.Store({
state: {
userData: []
},
mutations: {
setUserData (state, data) {
state.userData = data
}
}
})
With this you can access the state object as store.state, and trigger a state change with the store.commit method:
store.commit('setUserData', userData)
console.log(store.state.userData)
Vue Mixin
// This is a global mixin, it is applied to every vue instance.
// Mixins must be instantiated *before* your call to new Vue(...)
Vue.mixin({
data: function() {
return {
get $asset() {
return "Can't change me!";
}
}
}
})
template
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
In Root: {{globalReadOnlyProperty}}
<child></child>
</div>
Or
Vue.prototype.$asset = 'My App'
I just use an environment.js file to store all of my endpoints as object properties.
var urls = {};
urls.getStudent = "api/getStudent/{id}";
etc...
Then I put reference to this environment.js file in the head of document on pages where I have VueJS code that needs access to those endpoints. Im sure there are many ways to do this.

Reactively call a js function/event with meteor.js

I'm new to meteor.js. Still getting used to it.
I get how templates update reactively according to the cursor updates on the server, like this:
{{#if waitingforsomething.length}} Something Happened! {{/if}}
This is good to display elements on the page, updating lists and content. Now, my question is: what if I want to call some javascript or fire some event when something gets updated reactively? What would be the right way to do it with meteor.js?
Anything inside Tracker.autorun or template instance this.autorun runs with changes in reactive data sources inside these autoruns.
Reactive data sources are ReactiveVar instances, db queries, Session variables, etc.
Template.myTemplate.onCreated(function() {
// Let's define some reactive data source
this.reactive = new ReactiveVar(0);
// And put it inside this.autorun
this.autorun(() => console.log(this.reactive.get()));
});
Template.myTemplate.events({
// Now whenever you click we assign new value
// to our reactive var and this fires
// our console.log
'click'(event, template) {
let inc = template.reactive.get() + 1;
template.reactive.set(inc);
}
});
It is a little bit outdated, but Sacha Greif's Reactivity Basics is a very quick and concise introduction to meteor's reactivity model.
Basically, you have what's called reactive computations, code that observes special data objects (sessions, subscriptions, cursors, etc.) and gets executed whenever any of these reactive sources changes.
This is exposed via the Tracker API
Computation works pretty well for me:
Template.myTemplate.onRendered(function() {
this.computation = Deps.autorun(function () {
if (something) {
$(".reactive").html("Something Happened!");
}
});
});
Template.myTemplate.destroyed = function(){
if (this.computation){
this.computation.stop()
}
};
I Hope this helps.

How to hide role based section in knockout component using asp.net MVC

What is the best way to prevent user from seeing things like admin links in a knockout component?
I don't want to make a client request if the user has right to see those links because it would expose this section on the client.
The only way I can figure it out is to use a view to represent the component template and then check if the user as right on the server-side before rendering the HTML.
But is there another way that can be cleaner than this or is it the right way to proceed?
I've faced similar challenges with AngularJS Apps.
Rather than perform an extra service request, I like to pass logged-in status and role in Model or ViewBag.
For simplicity, let's assume we're using ViewBag.
1) In the Action Method, set
ViewBag.loggedIn = User.Identity.IsAuthenticated;
ViewBag.userId = User.Identity.GetUserId();
var identity = (System.Security.Claims.ClaimsIdentity)User.Identity;
ViewBag.roles = identity.Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value);
2) In a JS File, globally define UserModel with bindings and function to check if user has correct role-name:
var UserModel = function () {
var self = this;
self.isLoggedIn = ko.observable("");
self.userId = ko.observable("");
self.roles = ko.observableArray([]);
// function to check if role passed is in array
self.hasRole = function (roleName) {
for (i = 0; 1 < self.roles.length ; i++ ) {
if (self.roles[i] == roleName)
return true
}
return false;
}
};
var UserData = new UserModel();
In .cshtml View:
3) bind Role and Logged in data
<script type="text/javascript">
UserData.isLoggedIn("#ViewBag.isLoggedIn");
UserData.userId("#ViewBag.userId");
UserData.roles(#Html.Raw(Json.Encode(ViewBag.roles));
</script>
4) Show Partial if Role is correct:
<div data-bind="visible: UserData.hasRole("Admin")>
....
</div>
Ways to hide Admin Components:
The Razor Ways:
Rather than hide components with Knockout, there are C#/Razor ways
-> Nested if clause
#((List<String>) ViewBag.roles.contains("Admin"))
{
...
}
The advantage of Razor Conditions over Knockout is the component will not render when server creates page, leaving no trace for client to see
-> Conditional sections in _Layout
#if ((List<String>) ViewBag.roles.contains("Admin"))
{
#RenderSection("Admin Section")
}
Cleaner than simple Razor Condition and automatically applied throughout application
-> Partial with Child Action
Called (Action, Controller, Parameter):
#Html.Action("AdminConsole", "Admin", new { Role = "Admin"})
public ActionResult AdminComponent(string Role)
{
If (List<String>) ViewBag.roles.contains("Admin")
return PartialView(
return View(products);
}
The cleanest of all approaches. Can be combined in a section for greater convenience.
Conclusion on how to hide Admin Components:
How you choose to hide Admin Components depends on your needs. The Razor/C# approach is more convenient and feels more secure.
On the other hand, if you are interested in giving the user the SPA experience, C# and server methods fall short. Do you allow users to Authenticate without refreshing pages? If so, an Admin may Authenticate and change from anonymous user to Admin.
If you are comfortable with refreshing the screen to show the changed content, C#/Razor is your approach. If your priority is to give the user the "responsive experience", You will want to implement a Knockout solution.
If you cannot expose the markup to the client, then the only way to do this would be on the server before the markup is generated. As you have pointed out, this would be done in the view rendering.
In your case, using a partial view to hold your KO component would be appropriate and provide the functionality that you need while allowing you to reuse the component markup.
Here is the method I have used in the past, which has worked very cleanly:
View
<script type="text/javascript">
ko.components.register("foo", {
viewModel: FooComponentViewModel,
template: { element: "component-foo" }
});
</script>
...
<foo params="IsAdmin: #(User.IsInRole("Admin") ? 'true' : 'false')"></foo>
...
#Html.Partial("Components/_Foo")
Additional context could even be passed to the partial view via the MVC view model if necessary.
Component Partial View
<template id="component-foo">
#if(#User.IsInRole("Admin"))
{
// something you DO care about the client seeing
}
...
</template>
#if(#User.IsInRole("Admin"))
{
<script type="text/javascript">
// Some JS code to hide from non admins for this component
</script>
}
Component View Model
function FooComponentViewModel(params) {
var self = this;
self.AdminDoSomething = function () {
if (!params.IsAdmin) {
return;
}
// something you DO NOT care about the client seeing...
};
}
I know of no cleaner way to do this while providing the requirements that you seek.

In marionette mvc pattern, where to put different get API calls

For example I have the following server routes set up for my user entity:
GET /users/ // gets collection of users
GET /users/:id // gets user :id
GET /users/me // gets the current user
At the beginning of my app I want to get the current user from the server and store it... Something along the lines of:
App.addInitializer(function () {
$.get('/users/me')
.done(function processCurrentUser (userJson) {
App.user = new User(userJson);
});
});
My question is where this API call should actually reside. Would it be better to have something along the lines of:
App.addInitializer(function () {
App.user = new User();
App.user.fetchMe(); // performs the api call above
});
Or should I be doing something inside of a controller?
Thanks for the help!
When doing a fetch, I always worry about how its asyn behavior is going to affect the components that depend on that data. If there are no downriver components that will need the data before it can be reasonably expected to return, then there's technically nothing wrong with your approach.
There is, however, another possible way of loading your globals. What I often do (and for a user's list, too, it so happens) is bootstrap the data to the initial load page. I generally load it on the window variable. So for your example, in your backend template,
<script>
window.globals = {};
window.globals.currentUser = #Html.Raw(Json.Encode(ViewBag.User))
</script>
Of course, you can replace #Html.Raw(Json.Encode(ViewBag.User)) (we use C#) with your favorite backend model.
Then in your app start you're guaranteed to have the models:
App.addInitializer(function () {
App.user = new User(window.globals.currentUser);
});

Updating view from service in angular?

Maybe I am designing this incorrectly ( new to angular ). Currently I have something like this:
[TVController] depends on [TVService]
[TVService] depends on [GameShowChannel] #could possibly have more channels
In code its something like so:
html
<div class='tv' ng-controller='TVController' ng-include='currentChannel()> </div>
TVController.js
var TVController = function($scope,TVService){
$scope.currentChannel = function(){ TVService.currentChannel(); }
}
TVService.js
angular.module('app.service').factory('TVService',function(GameShowChannel){
return { currentChannel: function(){ GameShowChannel.getView(); }
});
GameShowChannelService.js
angular.module('app.serivce').factory('GameShowChannel',function(){
return { getView: function(){ './partial/game_show_intro.html'} };
});
In GameShowChannelService.js I want to be able to update the scope in game_show_intro.html. I have a GameShowIntroController object in a file, but I am not able to import that into the GameShowChannelService.js. Is there a better solution to this problem? Again the problem is that I want to be able to update the view in the GameShowChannelService. I want the GameShowChannelService to be more than just a static html, but be dynamic so that I can actually 'play' something in that channel. Hope its clear enough.
Hmm..Maybe GameShowChannel should be a directive instead?
Another thought would be to broadcast event from service that some state has changed and the GameShowIntroController which controls the game_show_intr.html view, would listen and update to that state accordingly. Would this be a angular approach?

Categories