CanJS on click outside of component effecting component - canjs

I would like to add an event listener in in <some-component> that reacts to the button.
<some-component></some-component>
<button class="click">click here</button>
I am sure this is really simple. I am very new to CanJS and working on it.
<can-component tag="some-component">
<style type="less">
<!-- stuff -->
</style>
<template>
<!-- stuff -->
</template>
<script type="view-model">
import $ from 'jquery';
import Map from 'can/map/';
import 'can/map/define/';
export default Map.extend({
define: {
message: {
value: 'This is the side-panels component'
}
}
});
</script>
</can-component>
I tried adding a $('body').on('click', '.click', function() {}); to the component and it didn't seem to work. Been reading a lot of documentation, but I am still missing some fundamental understanding.
UPDATE
I tried this:
<some-component-main>
<some-component></some-component>
<button class="click">click here</button>
</some-component-main>
with the event listener in some-component-main
events: {
".click click": function(){
console.log("here I am");
}
},
But that also didn't work.

<some-component-main>
<some-component></some-component>
<button class="click">click here</button>
</some-component-main>
with the event listener in some-component-main
events: {
".click click": function(){
console.log("here I am");
}
},
This did work once I realized that components ending with a number causes other issues that was preventing it.

You can make things inside your component available to the parent scope using the {^property-name} or {^#method-name} syntax. Read about it here: https://canjs.com/docs/can.view.bindings.toParent.html
Here's a fiddle: http://jsbin.com/badukipogu/1/edit?html,js,output
In the following example, <my-compontent> implements a doSomething method and we the button to call that method when clicked. We expose the method as "doFooBar".
<my-component {^#do-something}="doFooBar" />
<button ($click)="doFooBar">Button</button>
and the code:
can.Component.extend({
tag: "my-component",
template: can.view('my-component-template'),
viewModel: can.Map.extend({
doSomething: function () {
alert('We did something');
}
})
});
But why does the example use ^#do-something="..." instead of ^#doSomething="..."??
DOM node attributes are case insensitive, so there's no way to tell the difference between doSomething="", DoSomEthiNg="", or DOSOMETHING="" - all three are equivalent. CanJS is following the way browsers work by converting attributes with dashes to camelCase and vice versa.
Consider native data attributes - if you do something like <div data-my-foo="my bar">, then the value is accessible via JavaScript by doing [div].dataset.myFoo (notice the camelCasing). The same applies to css properties where css uses "background-color" but javascript uses backgroundColor. CanJS is following this convention.

Related

Mixing Alpine.js with 'static' serverside markup, while getting the benefits of binding, etc

I'm new to Alpine and struggling to wrap my head around how to make a scenario like this work:
Let's say I have a serverside built page, that contains some buttons, that represent newsletters, the user can sign up to.
The user might have signed up to some, and we need to indicate that as well, by adding a css-class, .i.e is-signed-up.
The initial serverside markup could be something like this:
<button id='newsletter-1' class='newsletter-signup'>Newsletter 1</button>
<div>some content here...</div>
<button id='newsletter-2' class='newsletter-signup'>Newsletter 2</button>
<div>more content here...</div>
<button id='newsletter-3' class='newsletter-signup'>Newsletter 3</button>
<div>and here...</div>
<button id='newsletter-4' class='newsletter-signup'>Newsletter 4</button>
(When all has loaded, the <button>'s should later allow the user to subscribe or unsubscribe to a newsletter directly, by clicking on one of the buttons, which should toggle the is-signed-up css-class accordingly.)
Anyway, then I fetch some json from an endpoint, that could look like this:
{"newsletters":[
{"newsletter":"newsletter-1"},
{"newsletter":"newsletter-2"},
{"newsletter":"newsletter-4"}
]}
I guess it could look something like this also:
{"newsletters":["newsletter-1", "newsletter-2", "newsletter-4"]}
Or some other structure, but the situation would be, that the user have signed up to newsletter 1, 2 and 4, but not newsletter 3, and we don't know that, until we get the JSON from the endpoint.
(But maybe the first variation is easier to map to a model, I guess...)
Anyway, I would like to do three things:
Make Alpine get the relation between the model and the dom elements with the specific newsletter id (i.e. 'newsletter-2') - even if that exact id doesn't exist in the model.
If the user has signed up to a newsletter, add the is-signed-up css-class to the corresponding <button> to show its status to the user.
Bind to each newsletter-button, so all of them – not just the ones, the user has signed up to – listens for a 'click' and update the model accordingly.
I have a notion, that I might need to 'prepare' each newsletter-button beforehand with some Alpine-attributes, like 'x-model='newsletter-2', but I'm still unsure how to bind them together when Alpine has initialising, and I have the data from the endpoint,
How do I go about something like this?
Many thanks in advance! 😊
So our basic task here is to add/remove a specific item to/from a list on a button click. Here I defined two component: the newsletter component using Alpine.data() creates the data (subs array), provides the toggling method (toggle_subscription(which)) and the checking method (is_subscribed(which)) that we can use to set the correct CSS class to a button. It also handles the data fetching in the init() method that executes automatically after the component is initialized. I have also created a save method that we can use to send the subscription list back to the backend.
The second component, subButton with Alpine.bind() is just to make the HTML code more compact and readable. (We can put each attribute from this directly to the buttons.) So on click event it calls the toggle_subscription with the current newsletter's key as the argument to add/remove it. Additionally it binds the bg-red CSS class to the button if the current newsletter is in the list. For that we use the is_subscribed method defined in our main component.
.bg-red {
background-color: Tomato;
}
<script src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js" defer></script>
<div x-data="newsletter">
<button x-bind="subButton('newsletter-1')">Newsletter 1</button>
<button x-bind="subButton('newsletter-2')">Newsletter 2</button>
<button x-bind="subButton('newsletter-3')">Newsletter 3</button>
<button x-bind="subButton('newsletter-4')">Newsletter 4</button>
<div>
<button #click="save">Save</button>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('newsletter', () => ({
subs: [],
init() {
// Fetch list of subscribed newsletters from backend
this.subs = ['newsletter-1', 'newsletter-2', 'newsletter-4']
},
toggle_subscription(which) {
if (this.subs.includes(which)) {
this.subs = this.subs.filter(item => item !== which)
}
else {
this.subs.push(which)
}
},
is_subscribed(which) {
return this.subs.includes(which)
},
save() {
// Send this.sub to the backend to save active state.
}
}))
Alpine.bind('subButton', (key) => ({
'#click'() {
this.toggle_subscription(key)
},
':class'() {
return this.is_subscribed(key) && 'bg-red'
}
}))
})
</script>

Custom Element Web Component Shadow DOM Vendor Scripts/Elements

When working with Custom Elements that leverage Shadow DOM, what is the official approach to injecting 3rd party scripts and elements such as Invisible reCAPTCHA which require scripts such:
<script src="https://www.google.com/recaptcha/api.js" async defer></script>`
for HTML elements such as a <button> to be leaded and reCAPTCHA to be rendered?
shadowRoot doesn't seem to have anything like head, is the script supposed to be added to the created template's innerHTML? Or is a <script> to be appended to the shadowRoot via appendChild in connectedCallback()? What is the official approach to working with 3rd party libraries within Shadow DOM? Loading the script on the page containing the rendered Custom Element doesn't seem to trigger a render due to Shadow DOM.
const template = document.createElement('template');
template.innerHTML = `
<form>
<button class="g-recaptcha"
data-sitekey="your_site_key"
data-callback='onSubmit'>Submit</button>
</form>
`;
class CustomElement extends HTMLElement {
constructor() {
super(); // always call super() first in the ctor.
this.attachShadow({mode: 'open'});
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
...
}
disconnectedCallback() {
...
}
attributeChangedCallback(attrName, oldVal, newVal) {
...
}
}
Thank you for any guidance you can provide.
There's no offical approach because the solution depends on the 3rd party library implementation.
However, in the case of reCaptcha, the workaround is to expose the <button> in the normal DOM, and inject it in the Shadow DOM via the <slot> element.
class extends HTMLElement {
constructor() {
super()
this.attachShadow( {mode: 'open'} ).innerHTML= `
<form>
<slot></slot>
</form>`
}
connectedCallback() {
this.innerHTML = `
<button
class="g-recaptcha"
data-sitekey="your_site_key"
data-callback="onSubmit">Submit</button>`
}
})

Vuejs deferred events on ajax content

I have some vuejs events.
<div #mouseover="activate" #mouseout="deactivate" class="item featured">
They work fine but when the content is loaded in via a simple jquery load() it does not trigger. How can I defer these events in vuejs?
edit:
The load is triggered by clicking the nav
<li v-on:click="filterTalents" data-department="hardware">
filterTalents: function(event) {
var dept= $(event.target).closest('li').data('department');
$( ".content" ).load( "includes/"+dept+".html", function()
});
},
activate: function(event) {
$(event.target).closest('.item').addClass('active');
},
deactivate: function(event) {
$(event.target).closest('.item').removeClass('active');
},
Given that you want to add class dynamically to an element, you can use dynamic class binding provided by vue.
A simple example would be to pass an object to v-bind:class to dynamically toggle classes:
<div v-bind:class="{ active: isActive }"></div>
The above syntax means the presence of the active class will be determined by the truthiness of the data property isActive.

Kendo-Grid with Angular

I am somewhat new to Angular and trying to learn how to use Kendo Grid without jQuery using Angular. I get the jQuery code that is used for the widget configuration is written in javascript but i am not getting the HTML directives.
<kendo-grid options="mainGridOptions">
What does "options" attribute mean ? I am assuming its an attribute that the kendo-grid (as defined by the directive) widget has ? But when i go the documentation, I don't see it in the configuration of fields drop-down ?
You should use k-options like this...
<kendo-grid k-options="mainGridOptions"></kendo-grid>
... and then on your controller scope you can expose your options object as so.
...
$scope.mainGridOptions = {
dataSource: {
data: myData
},
height: 550
};
...
This is how you reference the options object.
In jQuery based Kendo UI it is passed into the constructor like this...
$('myGrid').kendoGrid({
dataSource: {
data: myData
},
height: 550
});
As a side note, most if not all configuration options are available directly on the directive with the k- prefix.
For Example...
<kendo-grid
k-data-source="myData"
k-height="550"
></kendo-grid>
.. and then you would just expose your data on the controller...
...
$scope.myData
...
Another note is that if you use the directive as an attribute like this...
<div kendo-grid="myGrid"
k-data-source="myData"
k-height="550"
></div>
... you are assigning it a reference allowing you to access the widget's Kendo object in the controller's scope.
...
$scope.myGrid.resize();
...
The attribute k-options can be used to store the whole widget configuration in the controller. This attribute can also be used in other Kendo components like scheduler, date picker etc.
Here an example for Kendo datepicker implemented with the k-options attribute:
<div ng-app="app" ng-controller="MyCtrl">
<input kendo-date-picker k-options="monthPickerConfig">
</div>
<script>
angular.module("app", ["kendo.directives"]).controller("MyCtrl", function($scope) {
$scope.monthPickerConfig = {
start : "year",
depth : "year",
format : "MMMM yyyy"
};
});
</script>

Grails remoteLink ajax issue populating JQuery accordion

Following the simple example from Jquery: Accordion Example
Using a remoteLink function from grails (AJAX) the information is pulled back from the controller and sent back to the GSP, which works fine. I do however want this data to be placed within a Accordion container... click here for screenshot of current functionality.
(Event Create Page rendering form template) _form - GSP:
<g:remoteLink controller="event" action="showContacts" method="GET" update="divContactList">Show Contacts!</g:remoteLink>
<div id="divContactList">
<g:render template="contactListAjax" model="[contactList: contactList]" />
</div>
<script type="text/javascript">
$(document).ready(function()
{
alert("Checking if I'm ready :)");
$( "#accordion" ).accordion({
header: 'h3',
collapsible: true
});
});
</script>
_contactListAjax - GSP Template
<div id="accordion">
<g:each in="${contactList}" status = "i" var="contact">
<h3>${contact.contactForename}</h3>
<div><p>${contact.email}</p></div>
</g:each>
</div>
Not quite sure what I'm doing wrong here, as I'm encasing the data with the div with an id accordion, yet doesn't load. Please refer to screenshot link above to see what is currently happening.
UPDATE
Event (Standard Generated CRUD, only showing where the relivant imports are) create - GSP
<link rel="stylesheet" href="${resource(dir: 'css', file: 'jquery.ui.accordion.css')}"/>
<g:javascript src="jquery-1.7.1.min.js"></g:javascript>
<g:javascript src="jquery-ui.min.js"></g:javascript>
Try replacing:
$(function() {
$( "#accordion" ).accordion({
collapsible: true
});
})
with
$( "#accordion" ).accordion({
collapsible: true
});
Edit:
You can't use an <r:script /> tag after the request is finished. The server has already laid out all of the resources. Change it to a standard javascript tag. Also the inclusion of your javascript and css files should be elsewhere on the page.
I solved this... (well Hack & Slash for now... not the best, but only viable solution for now)
var whatever is the stored HTML generated by the AJAX and placed into the divContactList on the update function. The Accordion is deleted and then rebuilt (not the best approach I realise)
var whatever = $('#divContactList').html();
$('#accordion').append(whatever)
.accordion('destroy').accordion({
collapsible: true,
active: false
});

Resources