I'm using a helper method to render a template, like so:
module.exports = (Handlebars) => {
Handlebars.registerHelper('foo', () => {
var template = require('../path/to/template.hbs')
return template;
})
}
In the rendered template, I'd like to call another helper, bar.
// template.hbs
<div>
{{foo}}
</div>
// bar.js
module.exports = (Handlebars) => {
Handlebars.registerHelper('bar', ()=> {
// do something here
}
}
But the rendered template seems to have no visibility of the helper, i.e. bar isn't called. What's a good way to get around this?
Related
I am trying to make a simple test to check that the button is emitting an event called "click" every time it is detected, the problem is that when I use wrapper.emitted('click') to validate that it is receiving it, it always arrives as an empty object... I don't know what I may be doing wrong.
Current versions:
Vue: 3.2.31
Vitest: 0.7.12
Vite: 2.8.6
Vue/test-utils: 2.0.0-rc.17
<template>
<button
class="eci-button"
:disabled="props.disabled"
#click="handleClick"
>
{{ props.label }}
</button>
</template>
<script setup lang="ts">
/* Interfaces and types */
interface Props {
label: string
disabled?: boolean
}
/* Props */
const props = withDefaults(defineProps<Props>(), {
disabled: false
})
/* Events */
const emit =
defineEmits<{ (e: 'click'): void }>()
/* Methods */
const handleClick = () => {
emit('click')
}
</script>
<style lang="scss" src="./Button.scss"></style>
Test
test('should render and emit event at click', async () => {
const label = 'Siguiente'
const wrapper = mount(Button, {
props: {
label
}
})
wrapper.trigger('click')
expect(wrapper.emitted()).toHaveProperty('click')
})
Result
OK, I found the problem... I am using happy-dom as test environment together with Vitest... the problem is that when you pass a property as "optional" for some reason it interprets it as if it was "true"... to explain myself...
I have a default prop "disabled" set to "false" which means that my button should print as:
in the DOM.. but, for some reason it interprets it as being true and that causes the "click" events not being executed.. i will keep looking for some fix for this, at the moment i have migrated for jsdom and solved.
I am trying to pass a function as property inside a component in Vue3. Here is the code:
// ExampleComponent.vue
<template>
Here goes component content...
</template>
<script>
export default {
name: ''
}
</script>
<script setup>
import { onMounted } from "vue"
const props = defineProps({
onLoad: {
type: Function,
default() {
return {}
}
}
})
onMounted(() => {
if (props.onLoad) { // This doesn't work and passes every time
props.onLoad()
}
})
</script>
Here is the parent component calling child ExampleComponent.vue. It may or may not pass the onLoad function.
<ExampleComponent \>
What I want is that call this function only if the property on-load is passed? Is there a way in Vue3 to check if the property is passed explicitly?
you can use watcheffect :
javascript
watchEffect(() => {
if(props.onLoad) props.onLoad()
});
once '''onLoad''' value changed, it will tigger.
I want to test a stencil component and configure a global variable in my test like this:
describe('my-component', () => {
const myVarMock = 1;
let page;
let shadowRoot: ShadowRoot;
beforeEach(async () => {
page = await newSpecPage({
components: [MyComponent],
html: `<my-component></my-component>`,
supportsShadowDom: true,
autoApplyChanges: true
});
shadowRoot = page.root.shadowRoot;
});
it('should test', () => {
page.rootInstance.myVar= myVarMock;
page.rootInstance.componentWillLoad();
page.rootInstance.render();
console.log(shadowRoot.innerHTML.toString());
const buttonElement = shadowRoot.querySelector('.my-button'); //is null because shadow root is empty
});
});
My Component only renders something, when myVar is set. In the console.log of my test, shadowRoot is always empty, although I explicitly call render() in the test and when I go through the render function in debug-mode it has a value for myVar and renders everything. But why is shadowRoot then empty and my buttonElement is undefined?
Component:
#Component({
tag: 'my-component',
shadow: true,
})
export class MyComponent{
public myVar;
componentWillLoad() {
...
}
render() {
return (
<Host>
{this.myVar? (
<div class="my-button"></div>
): null}
</Host>
)
}
}
Calling those life-cycle hooks like componentWillLoad and render manually does not do what I think you're expecting it to do. The Stencil runtime calls render and uses the return value (JSX) to eventually render your component. Manually calling render does not render or re-render your component. In fact, it doesn't do anything except returning some JSX to you but you're not doing anything with the return value.
I think the main issue in your case is that myVar is not declared as a property with the #Prop() decorator. So even though you have marked your class member as public and are able to change it from the outside, Stencil will not wire up anything for you regarding that prop. See https://stenciljs.com/docs/properties.
Instead, you'll have to define it as:
#Prop() myVar?: number;
That way it will cause Stencil to re-render your component every time you update the prop's value.
Your test case should just look like
it('should test', () => {
page.root.myVar = myVarMock;
console.log(shadowRoot.innerHTML.toString());
const buttonElement = shadowRoot.querySelector('.my-button');
expect(buttonElement).not.toBeNull();
});
I am trying to render and append a template (ticket_edit) to the body. I need to set a context to the newly appended template, and the events of ticket_edit should be bound to that template.
The code:
Template.ticket.events = {
'click a.edit' : function (event) {
//when the edit button has been clicked, load template 'ticket_edit'
//with the current context. Please see situation 1 and 2.
}
}
Template.ticket_edit.events = {
'click a.save' : function (event) {
//this won't do anything when i have supplied a context!
}
}
So the problem is:
-I can set the context, but then the events are not bound to the newly added template.
-If I don't set the context the events are bound properly.
But i need both the events and the context.
Situation 1:
'click a.edit' : function (event) {
//applying a context to the template will result in events not being bound.
$('body').append(Meteor.render(Template.ticket_edit(this)));
}
Sitation 2:
'click a.edit' : function (event) {
//this way, the events will execute properly and i can save my ticket.
//However, no context is supplied!
$('body').append(Meteor.render(Template.ticket_edit));
}
Does anybody have a good method for doing this? I'm fairly new to Meteor, so maybe you have a better method of dynamically loading templates.
Don't use jQuery for this, just do it directly with the template and a #with block. Something like this:
<body>
{{> tickets}}
</body>
<template name="tickets">
{{#each tickets}}
{{> ticket}}
{{/each}}
{{#with currentTicket}}
{{> editTicket}}
{{/with}}
</template>
Template.tickets.tickets = function() {
return Tickets.find();
};
Template.tickets.currentTicket = function () {
return Tickets.findOne({ _id: Session.get( "currentTicket" ) });
};
Template.ticket.events({
'click a.edit' : function () {
Session.set( "currentTicket", this._id );
}
});
Because we're using a #with block, the editTicket template won't get rendered until you click the edit button (setting the "currentTicket" in the Session).
It's also possible to just do this (no #with block):
{{> editTicket currentTicket}}
But that causes editTicket to always be rendered, just without any context until the Session var gets set.
Note that because we're using Session, the user won't be interrupted by reloads/hot code pushes.
I have this fiddle, and can not make this work. I believe that the reason resides in that two li elements with a custom directive edit-in-place share scope.
The solution would be to say to the directive to create a copy of the scope that binds on the parent - can transclude help?
angular.module('bla', [])
.directive('editInPlace', ['$parse','$compile', function($parse, $compile) {
return {
restrict: 'A',
scope: true,
link: function (scope, element, attribs) {
var inputStart = '<input style="border: 2 solid black" name="inPlaceInput" style="display:none" value="';
var inputEnd = '">';
scope.editModeAccessor = $parse(attribs.editInPlace);
scope.modelAccessor = $parse(attribs.ngBind);
scope.$watch(attribs.editInPlace, function(newValue, oldValue){
if (newValue){
console.debug("click");
console.debug("value: " + scope.modelAccessor(scope));
var inputHtml = inputStart + scope.modelAccessor(scope) + inputEnd;
element.after(inputHtml);
jQuery(element).hide();
scope.inputElement = jQuery("input[name=inPlaceInput]");
scope.inputElement.show();
scope.inputElement.focus();
scope.inputElement.bind("blur", function() {
blur();
});
} else {
blur();
}
});
function blur(){
console.debug("blur secondary");
if (scope.inputElement){
console.debug("blur secondary inputElement found");
var value = scope.inputElement.val();
console.debug("input value: "+ value);
scope.inputElement.remove();
jQuery(element).show();
scope.editModeAccessor.assign(scope, false);
scope.modelAccessor.assign(scope, value);
}
}
}
}
}]);
function ContactsCtrl($scope, $timeout){
$scope.contacts = [{number:'+25480989333', name:'sharon'},{number:'+42079872232', name:''}];
$scope.editMode = false;
var editedId;
$scope.edit = function(id){
$scope.editMode = true;
jQuery("#"+id).hide();
editedId = id;
//TODO show delete button
}
$scope.$watch('editMode', function(newValue, oldValue){
if (!newValue && editedId){
jQuery("#"+editedId).show();
}
});
}
<div ng-app="bla">
<div ng-controller="ContactsCtrl">
<h4>Contacts</h4>
<ul>
<li ng-repeat="contact in contacts">
<span edit-in-place="editMode" ng-bind="contact.number"></span>
<span edit-in-place="editMode" ng-bind="contact.name"></span>
<span id="{{$index}}" ng-click="edit($index)"><i class="icon-edit">CLICKtoEDIT</i></span>
</li>
</ul>
</div></div>
I think cloning the scope is not the best solution.
When creating a directive in angular, you should encapsulate all the functionality within the directive. You should also avoid mixing jQuery in when you don't have to. Most of the time (as in this case) you're just introducing unnecessary complexity. Lastly, classes are the best way of controlling display, rather than the style attribute on an element.
I took the liberty of rewriting your directive in a more "angular" way - with no jQuery. As you can see from the updated jsFiddle, it is simpler and cleaner. Also, it works!
This directive can be easily modified to add lots of additional awesome functionality.
app.directive( 'editInPlace', function() {
return {
restrict: 'E',
scope: { value: '=' },
template: '<span ng-click="edit()" ng-bind="value"></span><input ng-model="value"></input>',
link: function ( $scope, element, attrs ) {
// Let's get a reference to the input element, as we'll want to reference it.
var inputElement = angular.element( element.children()[1] );
// This directive should have a set class so we can style it.
element.addClass( 'edit-in-place' );
// Initially, we're not editing.
$scope.editing = false;
// ng-click handler to activate edit-in-place
$scope.edit = function () {
$scope.editing = true;
// We control display through a class on the directive itself. See the CSS.
element.addClass( 'active' );
// And we must focus the element.
// `angular.element()` provides a chainable array, like jQuery so to access a native DOM function,
// we have to reference the first element in the array.
inputElement[0].focus();
};
// When we leave the input, we're done editing.
inputElement.prop( 'onblur', function() {
$scope.editing = false;
element.removeClass( 'active' );
});
}
};
});