Svelte: Reference to self for use in modules - svelte-3

I have a simple popup component in Popup.svelte:
<script>
let hidden = true;
</script>
<button on:click={() => hidden = !hidden}> Pop </button>
<div class:hidden> Extra Content </div>
<style>
.hidden { display: none; }
</style>
and I have multiple of these shown in my app:
<script>
import Popup from './Popup.svelte';
</script>
<div> <Popup /> </div>
<div> <Popup /> </div>
<div> <Popup /> </div>
I would like to hide other popups at the component level when clicked, meaning only one popup can be visible at any time. I thought svelte module contexts would suit, but I am not able to add a reference to self using which I can call say setHidden function for others.
<script>
import {onMount} from 'svelte';
let hidden = true;
export const setHidden = value => hidden = value;
const toggleHidden = () => {
if (hidden === true) { // transition to false
hideOthers();
}
hidden = !hidden
}
onMount(() => elements.add(self);
</script>
<script context="module">
const elements = new Set();
const hideOthers = () => elements.forEach(e => e.setHidden(true))
</script>
Playground here

The context approach is a good one, but instead of adding self to the elements, you can add the component's setHidden function:
<script context="module">
const elements = new Set();
const hideOthers = () => elements.forEach(hide => hide())
</script>
<script>
onMount(() => elements.add(setHidden))
</script>

For simply hiding things, #Stephane's answer works just fine. But if there are multiple functions, you could add them inside an object and add that to the module context.
You can also check if you are with the current instance or not while looping through all the elements by using a Map instead of a set. I'm using object keys to get unique references to each instance.
<script context="module">
const elements = new Map();
const hideOthers = () => Array.from(elements.entries()).forEach(([k, funcs]) => {
funcs.setHidden(true)
// if (k === key)
// true for current instance
// do something in the current instance
});
</script>
<script>
let key = {};
let hidden = true; ​
​ const funcs = {
setHidden: value => hidden = value,
setSomething: value => something = value,
setElse: value => else = value
}
​onMount(() => elements.set(key, funcs))
</script>

Related

svelte keep updating store var without clicking to update

My app automatically update $content value without me clicking on buttons. I know it is a simple question, but I can't find out why, I'm learning svelte.
App.svelte
<script>
import { content } from './store.js';
import Item from './Item.svelte';
$content = [{ id:0,obj: "Fell free to browse any category on top." }];
function addContent(value) {
$content = [{ id:0,obj: value}]
}
</script>
<li><button on:click={addContent("Home Page")}>Home</button></li>
<li><button on:click={addContent("Products Page")}>Products</button></li>
<div class="Content">
<p>Fell free to browse any category on top.</p>
{#each $content as item}
<p><svelte:component this={Item} objAttributes={item} /></p>
{/each}
</div>
store.js
import { writable } from 'svelte/store';
export let content = writable({});
Item.svelte
<script>
import { fade } from 'svelte/transition';
export let objAttributes = {};
</script>
<p transition:fade>
{objAttributes.obj}
{#if objAttributes.otherattrib}<em>{objAttributes.otherattrib}</em>{/if}
</p>
This is because your on:click events are defined wrongly.
The on:click takes as argument a function like this
<button on:click={functionGoesHere}>
or, if you want it inlined
<button on:click={() => { }>
What happens in your case is that you directly call a function and the result of this function will then be called when the button is clicked. You can see that in this example:
<script>
function createFn() {
return () => console.log('logging this')
}
</script>
<button on:click={createFn}>Click here</button>
in this example the function () => console.log('logging this') will be attached the button.
So to come back to your code, this is easily fixed by making it a function instead of a function call:
<li><button on:click={() => addContent("Home Page")}>Home</button></li>

React onChange method not working in react_on_rails

I am trying to create a sample app based on react_on_rails gem. In my react code react inbuild function like onChange or onSubmit are not working.
My HelloWorldWidget Component looks like this.
...
constructor(props, context) {
super(props, context);
_.bindAll(this, 'handleChange');
}
handleChange(e) {
const name = e.target.value;
console.log(name);
//this.props.updateName(name);
}
render() {
const { name } = this.props;
return (
<div className="container">
<h3>
Hello, {name}!
</h3>
<input className="form-control input-sm col-sm-4" type="text" onChange={this.handleChange}/>
</div>
);
}
Also if I disable server side pre-render of my component in my views/hello_world/index.html.erb file then the component is not rendering on UI.
<%= react_component("HelloWorldApp", props: #hello_world_props , prerender: false) %>
Github Repo: react-on-rails-sample-app
I'm not sure where _.bindAll method came from but the orthodox way of binding handlers is with this syntax:
this.handleChange = this.handleChange.bind(this);
If you use arrow function you don't need to bind it to the class;
handleChange = (e) => {
const name = e.target.value;
console.log(name);
//this.props.updateName(name);
}
Try to use arrow functions like this:
onChange={(e) => this.handleChange(e)}

Adding class to error-span using unobtrusive validation

I'm trying to get the example shown here to work within my project.
My code currently looks like:
<!-- jQuery.validate -->
#Scripts.Render("~/Bundles/jQueryVal")
<script type="text/javascript">
TypeScript.Framework.Helper.GlobalHelper.initClientValidation();
</script>
#Scripts.Render("~/Bundles/jQueryValUnobtrusive")
The two bundles are for jquery.validation.js and jquery.validation.unobtrusive.js as I've already know, that I have to include validation, make my changes, and then include unobtrusive.
Here the TypeScript:
const defaultOptions = {
errorClass: "help-block",
highlight(element) {
const idAttr = `#${$(element).attr("id")}Feedback`;
$(element).closest(".form-group").removeClass("has-success").addClass("has-error");
$(idAttr).removeClass("glyphicon-ok").addClass("glyphicon-remove");
},
unhighlight(element, errorClass, validClass) {
const idAttr = `#${$(element).attr("id")}Feedback`;
$(element).closest(".form-group").removeClass("has-error").addClass("has-success");
$(idAttr).removeClass("glyphicon-remove").addClass("glyphicon-ok");
},
errorElement: "span",
errorPlacement(error, element) {
if (element.length) {
error.insertAfter(element);
} else {
error.insertAfter(element);
}
}
};
$.validator.setDefaults(defaultOptions);
My login-view has two simple inputs for username and password:
#Html.LabelFor(m => m.Request.Username, new {#class = "sr-only"})
#Html.TextBoxFor(m => m.Request.Username, new {#class = "form-control"})
#Html.ValidationMessageFor(m => m.Request.Username)
However, the help-block class never gets assigned to the span.
On error it renders to:
<span class="field-validation-error" data-valmsg-for="Request.Username" data-valmsg-replace="true">
<span id="Request_Username-error" class="">The Username field is required.</span>
</span>
Any ideas?

Adding events to button doesn't work with backbone

I read over 20 different articles and forum topics about that, tried different solutions but I didn't cope with it.
The following code doesn't work. I need someone's help...
LoginView.js
var LoginView = Backbone.View.extend({
//el: $('#page-login'),
initialize: function() {
_.bindAll(this, 'gotoLogin', 'render');
//this.render();
},
events: {
'click #button-login': 'gotoLogin'
},
gotoLogin : function(e){
e.preventDefault();
$('#signup-or-login').hide();
$('#login').show();
return true;
}
});
login.html
<div data-role="page" id="page-login">
<!-- SignUp or Login section-->
<div id="signup-or-login" data-theme="a">
<a data-role="button" data-theme="b" id="button-signup"> Sign Up </a>
<a data-role="button" data-theme="x" id="button-login"> Login </a>
</div>
<!-- Login section-->
<div id="login" data-theme="a">
<button data-theme="b"> Login </button>
<button data-theme="x"> Cancel </button>
</div>
</div>
The page is created in method of Backbone.Router extended class.
loadPage('login.html', new LoginView());
From what I understand, $.mobile.loadPage() grabs the desired html and attaches it to the DOM.
Currently, you're trying to set el after the View has been instantiated.
However, notice that Backbone.View attaches el and $el when it's instantiated:
var View = Backbone.View = function(options) {
...
this._ensureElement();
this.initialize.apply(this, arguments);
this.delegateEvents();
};
Also notice that View.setElement() sets $el by passing a selector or a jQuery objected to View.el:
setElement: function(element, delegate) {
if (this.$el) this.undelegateEvents();
this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
this.el = this.$el[0];
if (delegate !== false) this.delegateEvents();
return this;
}
Bottom line:
You need to set el (in your case with the provided jQuery object) while instantiating it:
// Where `view` is a reference to the constructor, not an instantiated object
var loadPage = function(url, view) {
$.mobile.loadPage(url, true).done(function (absUrul, options, page) {
var v,
pageId = page.attr('id');
v = new view({
el: page
});
...
}
}
You now call loadPage() like so:
loadPage('login.html', LoginView);
This gives Backbone.View the $el which to delegate your events.

Expressions are not evaluated in $watch of custom directive of angularjs

I have a below custom directive in angularjs which uses model thats gets updated from server,
I have added a watch listener to watch the changes of that model,
var linkFn;
linkFn = function(scope, element, attrs) {
scope.$watch('$parent.photogallery', function(newValue, oldValue) {
if(angular.isUndefined(newValue)) {
return;
}
var $container = element;
alert($container.element);
$container.imagesLoaded(function() {
$container.masonry({
itemSelector : '.box'
});
});
});
};
return {
templateUrl:'templates/Photos_Masonry.htm',
replace: false,
transclude:true,
scope: {
photogallery: '=photoGallery',
},
restrict: 'A',
link: linkFn
However, when i debug in my watch directive, i still see that expressions in templates are still unresolved.i.e. photo.label, ng-src all are still unresolved. AFIK, $digest would be called only after $eval. Is this intended behavior?
My jQuery calls are not working due to this? Is there any other event where i get the result element with evaluated expressions?
Here is my template, which has ng-repeat in it,
<div id="container" class="clearfix">
<div class="box col2" ng-repeat="photo in photogallery">
<a ng-href="#/viewphotos?id={{photo.uniqueid}}&&galleryid={{galleryid}}"
title="{{photo.label}}"><img
ng-src="{{photo.thumbnail_url}}" alt="Stanley" class="fade_spot" /></a>
<h3>
<span style="border-bottom: 1px solid black;font-weight:normal;font-size:14px;">{{galleryname}}</span>
</h3>
<h3>
<span style="color:#20ACB8;font-weight:normal;font-size:17px;">{{photo.seasonname}}</span>
</h3>
</div>
</div>
photogallery is initialized in parent controller,
function MyCtrlCampaign($scope, srvgallery, mygallery) {
$scope.updatedata = function() {
$scope.photogallery = srvgallery.getphotos($routeParams);
};
$scope.getphotos = function() {
srvgallery.photos().success(function(data) {
$scope.updatedata();
}).error(function(data) {
});
};
Directive is used in below way,
<div masonry photo-gallery="photogallery" >
</div>
Kindly let me know your views on this.
Looks like this has been resolved in your Github issue (posted for the convenience of others).

Resources