Pass an array as a parameter for view rendering in twig - javascript

I'm pretty new to Twig.js, and notice that it lacks some documentation. In fact, I could only find extremely basic usage information on their GitHub wiki.
My views rely on a bunch of variables. All views extend from layout.twig which includes navbar.twig. The last one takes a lot of parameters, like color, names and others I haven't implemented yet.
The problem with this is that I would need to pass lots of variables every time a view gets rendered. So I thought a solution would be to pass an array each time, instead of multiple fieds. My question comes there, as in how I'd interact with this array
My current and inefficient solution can be represented when displaying an error:
res.render('error', {title: appConfig.name, color: appConfig.color (...)});
It would be better if I could pass an array and then interact with it inside of my twig view.
I'm open to other solutions. Being able to access appConfig from inside the view would be one of them, although I don't think that is possible. If it this though, please tell me how! I'll thank you forever.
Thanks in advance.

Pass appConfig:
var appConfig = { name: 'Jeremy', color: 'green' };
res.render('home', appConfig );
Render it:
<div class="{{ color }}">Hello {{ name }}</div>
Twig can work just fine with nested objects too:
var navConfig = { color: 'salmon' }
res.render('home', { nav: navConfig, name: 'Arnold' })
<nav class="{{ nav.color }}">{{ name }}</nav>

You can simply parse appConfig to JSON Object using JSON.parse.
Note - JSON.parse can tie up the current thread because it is a synchronous method. So if you are planning to parse big JSON objects use a streaming json parser.

Related

I have some questions about Sapper/Svelte

I just started using Sapper (https://sapper.svelte.technology) for the first time. I really like it so far. One of the things I need it to do is show a list of the components available in my application and show information about them. Ideally have a way to change the way the component looks based on dynamic bindings on the page.
I have a few questions about using the framework.
First, I'll provide a snippet of my code, and then a screenshot:
[slug].html
-----------
<:Head>
<title>{{info.title}}</title>
</:Head>
<Layout page="{{slug}}">
<h1>{{info.title}}</h1>
<div class="content">
<TopBar :organization_name />
<br>
<h3>Attributes</h3>
{{#each Object.keys(info.attributes) as attribute}}
<p>{{info.attributes[attribute].description}} <input type="text" on:keyup="updateComponent(this.value)" value="Org Name" /></p>
{{/each}}
</div>
</Layout>
<script>
import Layout from '../_components/components/Layout.html';
import TopBar from '../../_components/header/TopBar.html';
let COMPONENTS = require('../_config/components.json');
export default {
components: {
Layout, TopBar
},
methods: {
updateComponent(value) {
this.set({organization_name: value});
}
},
data() {
return {
organization_name: 'Org Name'
}
},
preload({ params, query }) {
params['info'] = COMPONENTS.components[params.slug];
return params;
}
};
</script>
Now my questions:
I notice I can't #each through my object. I have to loop through its keys. Would be nice if I could do something like this:
{{#each info.attributes as attribute }}
{{attribute.description}}
{{/each}}
Before Sapper, I would use Angular-translate module that could do translations on strings based on a given JSON file. Does anyone know if a Sapper/Svelte equivalent exists, or is that something I might need to come up with on my own?
I'm not used to doing imports. I'm more use to dependency injection in Angular which looks a bit cleaner (no paths). Is there some way I can create a COMPONENTS constant that could be used throughout my files, or will I need to import a JSON file in every occurence that I need access to its data?
As a follow-up to #3, I wonder if there is a way to better include files instead of having to rely on using ../.. to navigate through my folder structure? If I were to change the path of one of my files, my Terminal will complain and give errors which is nice, but still, I wonder if there is a better way to import my files.
I know there has got to be a better way to implement what I implemented in my example. Basically, you see an input box beside an attribute, and if I make changes there, I am calling an updateComponent function which then does a this.set() in the current scope to override the binding. This works, but I was wondering if there was some way to avoid the function. I figured it's possible that you can bind the value of the input and have it automatically update my <TopBar> component binding... maybe?
The preload method gives me access to params. What I want to know if there is some way for me to get access to params.slug without the preload function.
What would be really cool is to have some expert rewrite what I've done in the best possible way, possibly addressing some of my questions.
Svelte will only iterate over array-like objects, because it's not possible to guarantee consistent behaviour with objects — it throws up various edge cases that are best solved at an app level. You can do this sort of thing, just using standard JavaScript idioms:
{{#each Object.values(info.attributes) as attr}}
<p>{{attr.description}} ...</p>
{{/each}}
<!-- or, if you need the key as well -->
{{#each Object.entries(info.attributes) as [key, value]}}
<p>{{attr.description}} ...</p>
{{/each}}
Not aware of a direct angular-translate equivalent, but a straightforward i18n solution is to fetch some JSON in preload:
preload({ params, query }) {
return fetch(`/i18n/${locale}.json`)
.then(r => r.json())
.then(dict => {
return { dict };
});
}
Then, you can reference things like {{dict["hello"]}} in your template. A more sophisticated solution would only load the strings necessary for the current page, and would cache everything etc, but the basic idea is the same.
I guess you could do this:
// app/client.js (assuming Sapper >= 0.7)
import COMPONENTS from './config/components.json';
window.COMPONENTS = COMPONENTS;
// app/server.js
import COMPONENTS from './config/components.json';
global.COMPONENTS = COMPONENTS;
Importing isn't that bad though! It's good for a module's dependencies to be explicit.
You can use the resolve.modules field in your webpack configs: https://webpack.js.org/configuration/resolve/#resolve-modules
This would be a good place to use two-way binding:
{{#each Object.values(info.attributes) as attr}}
<p>{{attr.description}} <input bind:value=organization_name /></p>
{{/each}}
Yep, the params object is always available in your pages (not nested components, unless you pass the prop down, but all your top-level components like routes/whatever/[slug].html) — so you can reference it in templates as {{params.slug}}, or inside lifecycle hooks and methods as this.get('params').slug, whether or not a given component uses preload.

How to have all the static strings in one place

I am creating a vue webapp, I have few pages with Dynamic content and also few pages which has mostly static content. I want to move all these static strings to one place.
One option can be to use vue-i18n or vue-multilanguage, these gives support to have content files like this, but I really have no use case of support of multiple languages, so it also seems a bit over kill to me.
Another option can be to have a vuex store for all the strings, vuex I am already using for state management.
What can be good approach to do this.
I am not aware of a standard way of doing this, also this would be applicable to all the web frameworks. That said it is an interesting and valid problem.
If I had to do something about it:
I would want these strings to be available everywhere.
I would prefer not having to import these strings in all the components and each time I needed to use them.
I would want the storage space to be descriptive so that I don't have to go back and forth to check what I want to import. [The toughest part in my opinion]
To achieve 1, we can use:
Vuex
A services/some.js file which exports an object.
Plugins
I would go with plugins because:
I can get the strings by merely using this in a component, Vue.use(plugin) prevents the same plugin getting used twice, and at the same time achieve all the points (3rd will still be a tough nut to crack). Only disadvantage that I know of it might clutter the vue-instance.
So plugin can be designed like:
// stringsHelperPlugin.js
const STRING_CONST = {
[component_1_Name]: {
key1: val1,
key2: val2,
....
},
[component_2_Name]: {
key1: val1,
key2: val2,
....
},
...
}
StringConst.install = function (Vue, options) {
Vue.prototype.$getStringFor = (componentName, key) => {
return STRING_CONST['componentName'][key]
}
}
export default StringConst
in main.js this can be used like:
import StringConst from 'path/to/plugin'
Vue.use(StringConst)
and you could use this in a component template like so:
<div>
{{ $getStringFor(<component_1_name>, 'key1') }}
</div>
You can use something like this.$getStringFor(<componentName>, key) in a method. Pretty much everything that vuejs to has to offer.
Why I call the 3rd point hardest is: Maintainance if you ever change component names, you might also have to change it in the object returned by the plugin. This problem again, can be handled in many ways.
You can make an npm module with JSON files containing your strings
If you don't use vuex in your project, put your content in some javascript files which will be basically objects with all your static content and import them where you need just like Belmin menionted I am using Vue js and python flask as my backend. I want to have some local variable set. How can it be done?
A similar approach can be used for urls, configurations, errors etc.
If you use vuex, centralize everything there and make getters which you can use in each of your components.

Blaze Meteor dynamically instanciate template and datacontext

I'm dynamically instanciating template on event / or array change (with observe-like functionality).
To achieve that, I use
//whatever event you want, eg:
$(".foo").on("click", function(){
Blaze.renderWithData(Template.widgetCard, d, $(".cards").get(0));
}
That is working, but obviously, instances aren't bound to any parent's template.
Because I just rendered this template on the div.cards I'm unable to use the Template.parentData(1) to get the parent datacontext, even so this div.cards is include on a template.
The quick fix would be to set the wanted reference (which in my case is an object) variable parent's datacontext on global scope, or even use Session, or directly pass this context through the renderWithData's data.
Do you know any other way,even better the proper one (I mean Meteor fancy one), to achieve that?
Is it a good Blaze.renderWithData use case?
Tell me if i'm unclear or more code is needed.
EDIT:
Complementary context info:
I've a chart (d3) where it's possible to select some parts of it.
It has an array property to stock this selected data part.
Chart = function Chart(clickCb, hoverCb, leaveCb, addSelectionCb, removeSelectionCb){
var chart = this;
chart.selectedParts = [];
//... code
}
From outside of this Chart class (so on the Meteor client side), the chart.selectedParts is modified (add/delete).
The dream would be to "bind" this array chart.selectedParts like:
Template.templateContainingAllThoseCards.helpers({
selectedDataChart: function(){
return Template.instance.chart.selectedParts;
},
//...
});
and on the template being able to do something like that:
<div class="row">
<div class="large-12 columns">
<div class="cards">
{{#each selectedDataChart}}
{{> cardWidget}}
{{/each}}
</div>
</div>
</div>
Like that, if the chart.selectedParts was reactive, Blaze could automatically create or remove cardWidget template instance due to the binding.
I've tried to use manuel:reactivearray package on it (and it's kind of anoying cause I'm doing complex manipulation on this array with Underscore, which obviously don't work with none-native Array type such reactiveArray).
Not working, but I dunno if it should have worked.
What do you think?
At this time, I'm doing things a bit dirty I suppose; I juste instanciate/destroying Blaze View on element added/removed chart.selectedParts as: Blaze.renderWithData(Template.widgetCard, {data: d, chart: this}, $(".cards").get(0));
So here how I manage to do that.
Actually I don't think using Blaze.renderWithData() is a good solution.
Best way I've found is to pass your data on "Reactive mode", to be able to use all Template functionalities, and keep using Spacebars to instanciate templates. (Like parent DataContext link).
Easiest way to have reactive datasource is to always match your data with your Mongo, so I don't have to declare a custom Reactive Data source (which could be tricky with complex from a complex js data structure).
If someone have the same problem, I'm pretty sure it's because you don't follow the "good" way to do (which was my case).
One con with always updating your DB as reactive Data source should be a case where you're doing a lot of UI state change, and after all, saving the change. On this case, it's pretty useless to always pass by the DB, but it's from far the quickest solution.
Ask me if you have any similar issue understanding philosophy/way to do, I'm starting to understand what i'm doing!

Iron Router, Collection Helpers, and Session Variables. How to call an item attribute in a javascript function

I am working on a Meteor application and am trying to pass an attribute of an item in a collection to a javascript function. In this instance I working with instafeed.js, a javascript plugin for the instagram API, but I would like to understand the process on a more fundamental level.
I’ve been able to pull records from my Teams collection into the /teams/:id route, and display attributes of a team using the {{name}} and {{igHandle}} template helpers.
I have also been able to get instafeed.js to work, using this package etjana:instafeed and the demo provided online. The tag I am pulling is assigned statically via:Session.setDefault(‘tagName’,’football’);
Eventually I would like to pull the user profiles, but that requires an access token from oauth2. I think that can be achieved with a {{$.Session.get access_token}} handlebar helper, but I need to figure out to feed a variable into the instafeed.js function first.
Could someone please explain how to pass the {{igHandle}} attribute through to the tagName in the javascript function. My code is as follows:
Team Template:
<template name="teamView">
<div class=“ui container”>
<h3>{{name}}</h3>
<p>
<i class="fa fa-instagram fa-lg"></i>
<a target="_blank" href="http://instagram.com/{{insta_hndl}}">&nbsp{{insta_hndl}}</a>
</p>
<br>
<h3>Instagrams</h3>
{{> igTeamFeed}}
</div>
</template>
Everything works, except the {{>igTeamFeed}} render. I am able to get content to show, but it is currently static. (assigned via (Session.setDefault('tagValue','football').
Instafeed Template:
<template name="igTeamFeed">
<div class="ui container”>
<h3>#{{insta_hndl}} Instagram</h3>
<div id="instafeed"></div>
</div>
</template>
Content is displaying, but again only through the static (Session.setDefault('tagValue','football') code.
Router:
Router.route('/teams/:_id', {
name: 'teamView',
template: 'teamView',
data: function(){
var currentTeam = this.params._id;
return Teams.findOne({ _id: currentTeam });
},
action: function() {
if (this.ready()) {
this.render('teamView');
} else {
this.render('loading');
}
}
});
Works with template helpers, so I am thinking I am ok here. Also following the instructions of a user per one of my prior posts.
Instafeed Javascipt: (needs some work)
Template.igTeamFeed.helpers ({
igData: function() {
return Session.get('insta_hndl');
},
});
Template.igTeamFeed.onRendered(function () {
//clear any previously stored data making the call
Session.get('insta_hndl'); //extra add-in
Session.set('insta_hndl', null);
Session.set('insta_hndl', this.data.insta_hndl); //extra add-in
var igHandle = this.data.insta_hndl;
});
Tracker.autorun(function(){
feed = new Instafeed({
get: 'tagged',
tagName: Session.get('insta_hndl'),
clientId: ‘xxxxxxxxxxxxxxxxxxx’,
resolution: 'thumbnail',
sortBy: 'most-liked',
limit: '15'
});
feed.run();
});
I have a helper to get the current insta_hndl for the session. The session item is passed through the team/:_id tag of the url, and defined in the router
The onRendered is wiping out the old handle, and inserting the new one. I added two additional Session.get and Session.set functions since I was getting an error that insta_hndl was undefined (differing from the response on my previous post). Could be wrong there.
The tracker.autorun function was code I had working with an instafeed example. Should it be somewhere else? Maybe in a helper or onRendered function? Also do I need tracker.autorun to use instafeed? To my understanding it dynamically updates the feed when a tagName changes, but aren't there other ways to do this?
How to Solve
These are some ways I'm thinking I could to solve this. Please advise on how to do this / what you think is best practice:
Collection Helpers: Was thinking I could call something like Teams.findOne().insta_hndl but didn't have much luck. Is that the right code block to use? Do I have to define a variable in the template helper, or can I call it directly in the feed js function?
Handlebars Helpers: Thinking I could do something with a Session helper, but not sure if it would work if one user had two different instances of the instafeed open (two tabs open with different teams selected).
Reactive Methods: Allows one to call methods synchronously inside Tracker.autorun. Thought i read something that said Meteor already has this functionality baked in, but please advise.
Iron Router Query: Part of me still isn't convinced the insta_hndl isn't getting passed through. I've explored adding the instagram handle as a query param to the URL, but do not think this is a best practice.
If someone could help me get this working that woudld be great, but explanations would even better! Really spending a lot of time on this, and many of the online resources are using depreciated syntax.
Also two more related questions questions:
Should I use a controller? Should i write out a controller separately? Or just include it in Router.route functions? Seeing some people rely heavily on controllers, but a lot of documentation does everything through Router.route.
Should I break out the instafeed function into a method? If so how should I do this? I spent a great amount of time tryig to set up the whole instafeed function as a server side method, but couldn't seem to get it to work. I only foresee myself using this function in one or two other templates, so I figured it was fine to leave as is. Please advice.
Sorry that this was a bit confusing. The correct javascript is:
Template.igTeamFeed.helpers ({
teams: function() {
return Teams.find();
}
});
Template.igTeamFeed.onCreated( function() {
var igHandle = Teams.findOne(Router.current().params._id).insta_hndl;
feed = new Instafeed({
get: 'tagged',
tagName: Session.get('insta_hndl'),
clientId: ‘xxxxxxxxxxxxxxxxxxx’,
resolution: 'thumbnail',
sortBy: 'most-liked',
limit: '15'
});
feed.run(); //run the new Instafeed({})
});
There is no need for any of the things I suggested. The attribute can be grabbed with Collection.findOne(Router.current().params._id).attrib, where Collection is your collection and attrib is the non-_id value you want to grab.
Also did not need tracker.autorun, that was throwing me off as well.

Passing object available in the template into the {{render}} helper doesn't seem to work

I have an object defined globally as App.configObj which contains a property data. Inside a view's template I can use {{App.configObj.data}} to display the value and it works fine.
Inside that same template, I use {{render "viewC" model config=App.configObj}} to render a similar view, but the config property on that view remains null on didInsertElement. Other arguments set to primitive values are correctly set at that point.
Since App.configObj is definitely available in that context, shouldn't I be able to pass it into that view?
Here is the jsbin that illustrates the situation: http://emberjs.jsbin.com/misiyaki/12/edit
If you comment out the render call for ViewC, you can see that {{App.configObj.data}} renders just fine in the template.
My goal is to use an object encapsulating several properties to configure the view, so I need to be able to pass that object in. I spent a lot of time searching for similar content online but didn't find anyone trying this.
What am I missing?
Thanks!
I understand your struggle here with not being able to pass in a property in your render code... but in this case it doesn't seem that that is truly necessary.
Here is a fiddle with some changes to show you another way, that is essentially the same thing if i understood your intentions correctly. http://emberjs.jsbin.com/misiyaki/15/edit
The new code for your view:
App.ViewCView = Em.View.extend({
name: 'testName',
config: function () {
return App.configObj;
}.property(),
data: function () {
return this.get('config.data')
}.property('config'),
templateName: 'view-c'
});
Hope this helps!

Categories

Resources