I know it's possible to add an alias to index.js, but this is not recommended for NPM modules.
So, how do I pass JavaScript variables like the options object to a Nunjucks template?
data.widget only contains an id, the type and editable. Strangely enough, it doesn't contain the label.
index.js
module.exports = {
extend: 'apostrophe-widgets',
label: 'Privacy Cookie Widget',
[...]
}
widget.html
<div class="[...]-widget">
[...]
{{ data.widget.label }} <!-- Nothing. -->
[...]
</div>
home.html
[...]
{{ apos.singleton(data.[global|page], 'widgetName', 'widget-name', {}) }}
[...]
from a template you can get to your widgets root options config like this
{{ apos.log(apos.modules['my-cool-widgets'].options.coolStuff) }}
Where coolStuff is defined in my-cool-widgets/index.js
module.exports = {
extend: 'apostrophe-widgets',
label: 'Cool Widg',
coolStuff: {
array: [1,2,3,4],
hello: 'boom boom',
hehe: true
},
addFields: [...]
};
Related
I'm very new to JS/Ember and have been able to create a static dropdown menu that functions as desired however I've duplicated the static array of objects that holds the dropdown items in my model and controller which violates DRY.
There are two routes (index and requests/new) that need to access this static array object (reasonList) that holds the dropdown items - one has a model and controller (requests) and the other (index) only has a controller. My understanding is that controllers can read from models but not vice versa so it seems like the model is the correct place for this static array object. I've tried many different ways to resolve this but none have succeeded so instead I'm just asking what the right way to do this is.
ember-cli version: 3.15.1
node version: 10.18.0
ember-source version: 3.3.2
models/request.js:
import { belongsTo } from 'ember-data/relationships';
import Model from 'ember-data/model';
import DS from 'ember-data';
export default Model.extend({
// Properties
reason_bucket: DS.attr('string'),
reason: DS.attr('string'),
approved_at: DS.attr('number'),
reason_label: computed('reason_bucket', function() {
let reasonList = [
{ value: "testDebug", label: "Test & Debug" },
{ value: "auditSupport", label: "Audit Support" },
{ value: "incidentResponse", label: "Incident Response / Case Support" }
]
return reasonList.findBy('value', get(this, 'reason_bucket')).label;
}), // snip
controllers/requests/new.js:
import lookupValidator from 'ember-changeset-validations';
import Changeset from 'ember-changeset';
import {computed, get, set} from '#ember/object';
import Controller from '#ember/controller';
import SearchAccountsMixin from '../../mixins/search-accounts';
export default Controller.extend(SearchAccountsMixin, {
init() {
this._super(...arguments);
this.clear();
this.reasonList = [
{ value: "testDebug", label: "Test & Debug" },
{ value: "auditSupport", label: "Audit Support" },
{ value: "incidentResponse", label: "Incident Response / Case Support" },
];
},
clear() {
set(this, 'reason_bucket', null);
},
selectedReason: computed('reason_bucket', function(){
return this.get('reasonList').findBy('value', this.get('reason_bucket'));
}),
actions: {
setSelectedReason(selectedVal){
this.set('reason_bucket', selectedVal.value);
get(this, 'changeset').set('reason_bucket', selectedVal.value);
}, // snip
submit(changeset) {
changeset.validate().then(() => {
if (changeset.get('isValid')) {
changeset.save().then(() => {
this.transitionToRoute('index')
})
}
})
}
},
// Computed properties
changeset: computed('model', function () {
return new Changeset(get(this, 'model').request, lookupValidator(AccessRequestValidator), AccessRequestValidator, {skipValidate: true});
}), // snip
templates/requests/new.hbs:
<div class="omitted">
{{#power-select
options=reasonList
searchField="label"
placeholder="Select a Reason for Access"
selected=selectedReason
onchange=(action 'setSelectedReason')
as |reason_bucket|
}}
{{reason_bucket.label}}
{{/power-select}}
{{group.errorsList}}
</div>
templates/index.hbs:
<div>
<div class="b">Reason </div>
<div>{{request.reason_label}}</div>
</div>
<div>
<div class="b">Reason Detail </div>
<div>{{request.reason}}</div>
</div>
If what you’re wanting to do is DRY out your list and find a good home for it, I’d suggest putting your array (and possibly search functionality in a utils file - which doesn’t exist but which you can create and import from). Then you’d be able to do something like:
// app/utils/reasonList.js
export const reasonList = [];
import { reasonList } from ‘appName/utils/reasonList’;
selectedReason: computed('reason_bucket', function(){
return reasonList.findBy('value', this.get('reason_bucket'));
}),
I've got a form with about 10 select elements built from an array in my Vue data.
The array of selectors is empty initially and then an AJAX call populates the array and Vue builds the HTML - I've kept the snippet below simplified just to demonstrate the issue I'm having with v-model
I want to create an object that has all the selected values in it, so I'm trying to use v-model="selected[ selector.name ]" as per the example below.
I want to easily be able to ask for selected.make or selected.fuel
Now this works if I initialize the selected property like this:
selected: { make: 'audi', fuel: 'petrol' }
If I leave it blank, like in the example, {}, then it doesn't get updated.
I don't want to manually hardcode all the properties of selected, I only want to be listing them once in the server side code that gets sent via AJAX
So am I missing something completely obvious, should I be doing this in a different way?
Maybe a method to find the dropdown that matches a field name and returns the value? Just that doesn't seem like a very Vue thing to do.
var app = new Vue({
el: '#example',
data: {
selectors: [
{
name: 'make',
options: ['audi','bmw']
},
{
name: 'fuel',
options: ['petrol','diesel']
}
],
selected: {}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="example">
<template v-for="selector in selectors">
<select v-model="selected[ selector.name ]">
<option v-for="option in selector.options">{{option}}</option>
</select>
</template>
<p>
{{selected.make}}
<br />
{{selected.fuel}}
</p>
</div>
it's probably becuase you're not setting new keys on an object with this.$set
try:
this.$set(this.selected, 'make', 'audi')
Not using this.$set - alias of Vue.set - will mean Vue doesn't set the new key as reactive, and in turn won't be watching for any updates to it, docs: https://v2.vuejs.org/v2/api/#vm-set
var app = new Vue({
el: '#example',
data: {
selectors: [{
name: 'make',
options: ['audi', 'bmw']
}, {
name: 'fuel',
options: ['petrol', 'diesel']
}],
selected: null,
},
created () {
// this would happen following your ajax request - but as an example this should suffice
this.selected = {}
this.selectors
.forEach((selector) => {
this.$set(this.selected, selector.name, '')
})
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="example">
<div v-if="selected">
<select v-model="selected[selector.name]" v-for="selector in selectors">
<option :value="option" v-for="option in selector.options">
{{option}}
</option>
</select>
<p>make: {{selected.make}}<p>
<p>fuel: {{selected.fuel}}</p>
<pre>{{ selected }}</pre>
</div>
</div>
I have an extremely hierarchical JSON structure as a scope variable in my AngularJS controller. I want to loop around different sections of that variable. I thought about using ng-init to specify where in the structure I am. Here is some code:
my_app.js:
(function() {
var app = angular.module("my_app");
app.controller("MyController", [ "$scope", function($scope) {
$scope.things = {
name: "a",
children: [
{
name: "a.a",
children: [
{ name: "a.a.a" },
{ name: "a.a.b" }
]
},
{
name: "a.b",
children: [
{ name: "a.b.a" },
{ name: "a.b.b" }
]
}
]
}
}]);
});
my_template.html:
<div ng-app="my_app" ng-controller="MyController">
<ul>
<li ng-init="current_thing=things.children[0]" ng-repeat="thing in current_thing.children>
{{ thing.name }}
</li>
</ul>
</div>
I would expect this to display a list:
a.a.a
a.a.b
But it displays nothing.
Of course, if I specify the loop explicitly (ng-repeat="thing in things.children[0].children") it works just fine. But that little snippet of template code will have to be run at various points in my application at various levels of "things."
(Just to make life complicated, I can get the current thing level using standard JavaScript or else via Django cleverness.)
Any ideas?
ng-init runs at a lower priority (450) than ng-repeat (1000). As a result, when placed on the same element, ng-repeat is compiled first meaning that the scope property created by ng-init won't be defined until after ng-repeat is executed.
As a result, if you want to use it in this manner, you'd need to place it on the parent element instead.
<div ng-app="my_app" ng-controller="MyController">
<ul ng-init="current_thing=things.children[0]">
<li ng-repeat="thing in current_thing.children>
{{ thing.name }}
</li>
</ul>
</div>
I am currently trying to have 3 separate templates that the user can switch between by clicking on one of 3 buttons. By using a session variable ('currentContent'), 3 buttons and 3 templates I cannot see what's going wrong with my current code.
In my javascript:
Template.priority.helpers({
expensesbtn:function(){
return Session.get('currentContent') ==='expenses'?true:false;
},
custombtn:function(){
return Session.get('currentContent') ==='cexpenses'?true:false;
},
incomebtn:function(){
return Session.get('currentContent') ==='earning'?true:false;
},
});
Template.priority.events({
"click #expensesbtn":function(event, template){
Session.set('currentContent', 'expenses')
},
"click #custombtn":function(event, template){
Session.set('currentContent', 'cexpenses')
},
"click #incomebtn":function(event, template){
Session.set('currentContent', 'earning')
}
});
and then in my html:
{{>priority}}
{{#if cexpenses}}
{{> cexpenses}}
{{/if}}
{{#if expenses}}
{{> expenses}}
{{/if}}
{{#if earning}}
{{> earning}}
{{/if}}
Any help with this would be greatly appreciated. Thanks!
#Sindis is right, your helper names must match what you are calling in your templates. But another problem is that while the conditionals aren't inside a template named priority, your helpers are attached to the priority template, so they wouldn't be recognized even if the names match. You can fix this by putting everything inside the priority template and separating the buttons out into a separate sub-template if that is your intention.
You can also make your code much more elegant and DRY by using only one session variable chosenTemplate and only one helper and avoiding repeating all those conditionals. Then use Meteor's Template.dynamic feature to display the correct template. Here is an example solution below. Make a new template called templateControl and place the buttons inside it. Then place everything inside the priority template.
<template name="priority">
{{> templateControl }}
{{> Template.dynamic template=chosenTemplate }}
</template>
<template name="templateControl">
{{#each buttons}}
<button id="{{ id }}" class="chose-template">{{ label }}</button>
{{/each}}
</template>
Template.templateControl.helpers
buttons: [
{ id: 'incomebtn', label: 'Income' },
{ id: 'expensesbtn', label: 'Expenses' },
{ id: 'custombtn', label: 'Custom' }
]
Template.templateControl.events
'click .chose-template': function(e, t) {
Session.set('chosenTemplate', this.id);
}
Template.priority.helpers
chosenTemplate: function(e, t) {
return Session.get('chosenTemplate');
}
Then be sure to give the three templates you switch between names corresponding to the template ids set in the chosenTemplate Session variable!
Hope this helps.
Your helpers have to be the same when you use them in HTML, so your js should look like:
Template.priority.helpers({
expenses: function(){
return Session.get('currentContent') === 'expenses';
},
cexpenses: function(){
return Session.get('currentContent') === 'cexpenses';
},
earning: function(){
return Session.get('currentContent') === 'earning';
}
});
I've become confused about the scoping within an {{#each}} block.
If I have the handlebars script
<script type="text/x-handlebars">
{{#each vars}}
<button {{action foo}}> {{name}} </button>
{{/each}}
</script>
and I set my application controller as
App.ApplicationController = Ember.Controller.extend({
vars: Ember.ArrayController.create({
content: [
{ name: "Cow",
foo: function(){console.log("moo");}
},
{ name: "Cat",
foo: function(){console.log("meow");}
}
]
})
});
The script can see {{name}} just fine and puts it in as the title of the button as you'd expect, but action does not get bound to the foo functions defined within the array members.
Is there are way to do this that I'm missing, or do I need to refactor to make foo be defined directly within ApplicationController?
You can set up an event on ApplicationController and pass in the individual object and call the stored foo(). Inside of the {{#each vars}}...{{/each}} block you can use this to pass the actual object to the event handler.
JS:
App.ApplicationController = Ember.Controller.extend({
vars: Ember.ArrayController.create({
content: [
{ name: "Cow",
foo: function(){console.log("moo");}
},
{ name: "Cat",
foo: function(){console.log("meow");}
}
]
}),
doFoo: function(obj) {
obj.foo();
}
});
Handlebars:
{{#each vars}}
<button {{action doFoo this}}> {{name}} </button>
{{/each}}
JSBin example
Action in handlebars template not firing
This may be due to the root element, check this posting here for more information