How can I call the fallback slot content from a parent component? - svelte-3

I have a Table component which has a cell slot with fallback:
<!-- .... -->
<slot name="cell" {column} {columnIndex}>
<div style="color: green">
{column} : { cells[columnIndex] }
</div>
</slot>
<!-- .... -->
When using the Table Component I'd like to override the slot Content only for one column Index:
<Table columns={['a', 'b', 'c']} cells={[1, 2, 3]}>
<div slot="cell" let:columnIndex>
{#if columnIndex == 2 }
3333
{:else }
HOW TO CALL FALLBACK OF TABLE?
{/if}
</div>
</Table>
How can i call the cell slot fallback from the Table component?
https://svelte.dev/repl/0e455d61f1db442c99a5320ce85df041?version=3.32.3

Basically in Svelte, the slot's fallback is only called when no content is provided, in your case you are providing an element in the slot so the fallback will never gonna called.
check out this Example it shows how to achieve what you want

Related

Stimulus JS error when toggling class from another element

I'm building a transaction entry form to better understand Stimulus after the Odin Project lesson (https://www.theodinproject.com/lessons/ruby-on-rails-stimulus). 1. When the user is finished entering the amount, the next button is clicked and the numpad is hidden (including the 'next' button). 2. If the user would like to edit the amount, a click on the 'amount_box' toggles the hidden state of the numpad.
I've completed the first part, but getting an error on the second part.
JS (toggle_controller.js)
export default class extends Controller {
static classes = [ "change" ]
toggle() {
this.element.classList.toggle(this.changeClass)
}
}
HTML
<div id="amount_container" >
<div id="amount_box">
<div>$999,999.99</div>
</div>
<div id="numpad" data-controller="toggle"
data-toggle-change-class='hidden'>
<div>1</div>
<div>2</div>
<div>3</div>
...
<div data-action="click->toggle#toggle">Next</div>
</div>
</div>
This hides the id="numpad". But once hidden it can't be clicked to un-hide. In that case I would click the id="amount_box". So I moved the data-controller attribute to id='amount_container', which contains both the element being clicked and being toggled.
The updated HTML below throws an error "Error: Missing attribute "data-toggle-change-class" ... element: div#amount_container". It wants to see the data-toggle-change-class on the same element which has data-controller="toggle" but putting it on div#amount_container would just hide the whole thing which is my whole problem in the first place.
<div id="amount_container" data-controller="toggle">
<div id="amount_box">
<div>$999,999.99</div>
</div>
<div id="numpad" data-toggle-change-class='hidden'>
<div>1</div>
<div>2</div>
<div>3</div>
...
<div data-action="click->toggle#toggle">Next</div>
</div>
</div>
How can I click on "Next" or "div#amount_box, and hide/unhide div#numpad?
Currently, your toggle_controller.js toggles the classlist of this.element
export default class extends Controller {
static classes = [ "change" ]
toggle() {
this.element.classList.toggle(this.changeClass)
}
}
this.element refers to the element on which the data-controller attribute is placed. By moving the data-controller from div id="numpad" to <div id="amount_container">, this.element changes scope.
The solution is to define a stimulus target attribute to specify which element to toggle.
export default class extends Controller {
static targets = [ "numpad" ]
static classes = [ "change" ]
toggle() {
this.numpadTarget.classList.toggle(this.changeClass)
}
}
<div id="amount_container" data-controller="toggle" data-toggle-change-class='hidden'>
<div id="amount_box">
<div>$999,999.99</div>
</div>
<div id="numpad" data-toggle-target="numpad">
<div>1</div>
<div>2</div>
<div>3</div>
...
<div data-action="click->toggle#toggle">Next</div>
</div>
</div>

Delay knockout binding evaluation

Knockout newbie here. I have a page to display the customer info.
1st div should be displayed when customer info is present.
2nd div should be displayed when no customers are displayed
//1st Div
<div id="custInfoList" data-role="content"
data-bind="foreach: customers, visible : customers().length > 0">
<p data-bind="text: $data.info"></p>
</div>
//2nd Div
<div id="empty" data-role="content"
data-bind="visible: customers().length === 0 ">
<p>No Customer Information</p>
</div>
My model is like this:
var myModel = {
customers : ko.observableArray();
}
..and on page load I am adding this logic:
//On Page Load, call AJAX to populate the customers
//customers = {jsonData}
My page is using jQuery Mobile. My only problem is when the page is first displayed, the second div is displayed. When the Ajax json data returns, that's where it is hidden.
Is it possible to hide the second div while the ajax is still on loading and data has not yet returned?
UPDATE 2
On a related note, I tried the KO HTML template which I just read from the net
<!-- ko if: customers().length -->
<div id="custInfoList" data-role="content"
data-bind="foreach: customers, visible : customers().length > 0">
<p data-bind="text: $data.info"></p>
</div>
<!-- /ko -->
<div id="empty" data-role="content"
data-bind="if: customers().length === 0">
<p>No Customer Information</p>
</div>
but still unsuccessful. Any thoughts what is missing?
UPDATE 3
I tried updating what #shriek demonstrated in his fiddle http://jsfiddle.net/t0wgLt79/17/
<!-- ko if: customers() -->
<div id="custInfoList" data-role="content" data-bind="foreach: customers">
<p data-bind="text: $data"></p>
</div>
<!-- /ko -->
<div id="empty" data-role="content" data-bind="if: customers().length === 0" style="display: none;">
<p>No Customer Information</p>
</div>
<button data-bind="click:popCustomers">Populate</button>
My JS:
$.support.cors = true;
var test = {
customers: ko.observableArray(),
popCustomers: function () {
for (var i = 0; i < 3; i++) {
this.customers.push(i);
}
},
popByAjax: function () {
console.log("Calling JSON...");
$.getJSON("http://api.openweathermap.org/data/2.5/weather?id=2172797", function (data) {
console.log(data);
if (data.sys) {
this.customers.push(data.sys.country);
console.log("Loaded");
}
}.bind(this));
}
};
test.popByAjax();
ko.applyBindings(Object.create(test));
On initial load, the "AU" is displayed. Now change the weather?id=2172797 into weather?id=21727971 to make it invalid. I notice that the no customer information is not displayed.
As mentioned in the comment above, for Update 3 display:none is extraneous as it's already being taken care by if on the data-bind.
Second thing is the observableArray had to be emptied after receiving bad response because hiding/displaying is based on the comparison of that observableArray's length.
Code to fiddle with:-
http://jsfiddle.net/4hmqdsup/
you see the second div as well as the first div because the knockout applyBinding to your DOM elements, has not yet been occurred, which means the visible binding has not yet been evaluated, and therefore no element will be hidden accordingly, leaving it in its default state ( which is to be shown )
to overcome this behaviour, you only need to add a style="display: none;" to those elements you want them to be hidden by default, and then the visible binding will remove the display: none if it is evaluated to true.
so your code should be like this
//1st Div
<div id="custInfoList" data-role="content"
data-bind="foreach: customers, visible : customers().length > 0">
<p data-bind="text: $data.info"></p>
</div>
//2nd Div
<div id="empty" data-role="content"
data-bind="visible: customers().length === 0" style="display: none;">
<p>No Customer Information</p>
</div>
and btw, it does not matter whether you use visible or if binding, as the problem is not with the binding itself.
I guess you did wrongly in //customers = {jsonData}.
To update a ko.observable, you need to use customers(jsonData), not customers = jsonData.
ko.observable() returns a function, the setter is customers(newValue) and the getter is customers(), you need to use function call explicitly in both setter and getter.

Dynamic checkbox control group using jquery mobile and knockout

I'm trying to dynamically create and filter a jquery mobile control group containing checkboxes using knockout binding. The basic idea is that the user selects an option which filters the list of checkboxes in the control group. I've seen similar questions on here but they all seem to be a one-time binding where once bound by ko and enhanced by jqm they remain unchanged. I have that behavior working, the issue occurs when the underlying viewModel changes and ko updates the list of checkboxes in the control group. A full demo of the behavior can be found on jsfiddle here: http://jsfiddle.net/hkrauss2/JAvLk/15/
I can see that the issue is due to jqm creating a wrapper div when enhancing the control group. Ko then puts new elements above the wrapper div when updating the DOM. Basically I'm asking if anyone has solved this issue and also if anyone thinks I'm asking for trouble by integrating these two libraries? Thanks to everyone in advance.
Here is the Html:
<div id="home" data-role="page">
<div data-role="header">
<h2>Knockout Test</h2>
</div>
<div data-role="content">
<ul id="parent-view" data-role="listview" data-inset="true" data-bind="foreach: parentCategories">
<li></li>
</ul>
<p>
To reproduce the issue select Restaurants, come back and select Nightlife or Bars
</p>
</div>
</div>
<div id="list" data-role="page">
<div data-role="header">
<h2>Knockout Test</h2>
<a data-rel="back" data-icon="carat-l" data-iconpos="notext">Back</a>
</div>
<div data-role="content">
<form>
<div id="child-view" data-role="controlgroup" data-bind="foreach: childCategories, jqmRefreshControlGroup: childCategories">
<input type="checkbox" name="checkbox-v-2a" data-bind="attr: {id: 'categoryId' + id}" />
<label data-bind="text: description, attr: {for: 'categoryId' + id}" />
</div>
</form>
</div>
</div>
And the basic javascript. Note there are two external js files not listed here. One sets $.mobile.autoInitializePage = false; on the mobileinit event. The other brings in data in the form of a JSON array which is used to initialize the Categories property in the AppViewModel.
// Custom binding to handle jqm refresh
ko.bindingHandlers.jqmRefreshControlGroup = {
update: function (element, valueAccessor) {
ko.utils.unwrapObservable(valueAccessor());
try {
$(element).controlgroup("refresh");
} catch (ex) { }
}
}
function GetView(name) {
return $(name).get(0);
}
// Define the AppViewModel
var AppViewModel = function () {
var self = this;
self.currentParentId = ko.observable(0);
self.Categories = ko.observableArray(Categories); // Categories comes from sampledata.js
self.parentCategories = ko.computed(function () {
return ko.utils.arrayFilter(self.Categories(), function (item) {
return item.parentId == 0;
});
});
self.childCategories = ko.computed(function () {
return ko.utils.arrayFilter(self.Categories(), function (item) {
return item.parentId == self.currentParentId();
});
});
self.OnClick = function (viewModel, $event) {
self.currentParentId(viewModel.id);
return true;
};
};
// Create the AppViewModel
var viewModel = new AppViewModel();
// Apply bindings and initialize jqm
$(function () {
ko.applyBindings(viewModel, GetView('#parent-view'));
ko.applyBindings(viewModel, GetView('#child-view'));
$.mobile.initializePage();
});
Update
My old solution wraps each element in a ui-controlgroup-controls div, which adds unnecessary markup. However, the enhancement part is essential.
$(element).enhanceWithin().controlgroup("refresh"); /* line 16 in fiddle */
The new solution is more dynamic to maintain clean markup with no additional wrappers:
First step: Once controlgroup is created controlgroupcreate (event), add data-bind to its' container .controlgroup("container")
Second step: Add checkbox consisted of input and label. At the same time, for each element, add data-bind
Third step: Apply bindings ko.applyBindings().
The static structure of the controlgroup should be basic, it shouldn't contain any elements statically. If a checkbox is added statically, each dynamically created checkbox will be wrapped in an additional .ui-checkbox div.
<div id="child-view" data-role="controlgroup">
<!-- nothing here -->
</div>
JS
$(document).on("controlgroupcreate", "#child-view", function (e) {
$(this)
.controlgroup("container")
.attr("data-bind", "foreach: childCategories, jqmRefreshControlGroup: childCategories")
.append($('<input type="checkbox" name="checkbox" />')
.attr("data-bind", "attr: {id: 'categoryId' + id}"))
.append($('<label />')
.attr("data-bind", "text: description, attr: {for: 'categoryId' + id}"));
ko.applyBindings(viewModel, GetView('#child-view'));
});
Demo
Old solution
As of of jQuery Mobile 1.4, items should be appended to .controlgroup("container") not directly to $("[data-role=controlgroup]").
First, you need to wrap inner elements of controlgroup in div with class ui-controlgroup-controls which acts as controlgroup container.
<div id="child-view" data-role="controlgroup" data-bind="foreach: childCategories, jqmRefreshControlGroup: childCategories">
<div class="ui-controlgroup-controls">
<input type="checkbox" name="checkbox-v-2a" data-bind="attr: {id: 'categoryId' + id}" />
<label data-bind="text: description, attr: {for: 'categoryId' + id}" />
</div>
</div>
Second step, you need to enhance elements inserted into controlgroup container, using .enhanceWithin().
$(element).enhanceWithin().controlgroup("refresh"); /* line 16 in fiddle */
Demo
Omar's answer above works very well. As he mentions in the comments however it does wrap each input/label combination in their own div. This doesn't seem to affect anything visually or functionally but there is another way as outlined below. Basically it uses the containerless control flow syntax to bind the list.
New Html
<div id="child-view" data-role="controlgroup">
<!-- ko foreach: childCategories, jqmRefreshControlGroup: childCategories, forElement: '#child-view' -->
<input type="checkbox" name="checkbox-v-2a" data-bind="attr: {id: 'categoryId' + id}"></input>
<label data-bind="text: description, attr: {for: 'categoryId' + id}"></label>
<!-- /ko -->
</div>
Using the containerless syntax means that we lose the reference to the controlgroup div in the custom binding handler. To help get that back I added the id as '#child-view' in a custom binding named forElement. The magic still all happens in the custom binding handler and Omar's enhanceWithin suggestion remains the secret ingredient. Note: I needed to change the argument list to include all arguments passed by ko.
ko.bindingHandlers.jqmRefreshControlGroup = {
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
ko.utils.unwrapObservable(valueAccessor());
try {
$(allBindings.get('forElement')).enhanceWithin().controlgroup("refresh");
} catch (ex) { }
}
}
Final note: To use a custom handler on a virtual element ko needs to be notified that it is ok. The following is the updated start up statements:
// Apply bindings and initialize jqm
$(function () {
ko.virtualElements.allowedBindings.jqmRefreshControlGroup = true; // This line added
ko.applyBindings(viewModel, GetView('#parent-view'));
ko.applyBindings(viewModel, GetView('#child-view'));
$.mobile.initializePage();
});

C# foreach within foreach, display 3 item in each item. carousel

I am using asp.net MVC 4 for my project, and I have a Model.
and I am using Bootstrap Carousel to show products for my Index Page (see the image below)
Now my question is: I want to show 3 Products in each slide item.
should I write a ViewModel for this?
<div class="carousel slide>
#for (int i = 0; i <= Model.Count()/3; i++ ) <!-- this works well, paging -->
{
<div class="item #if(i==0){<text>active</text>}">
#foreach(var well in Model)
{
<div class="span4">
<!-- some html here -->
</div>
}
</div>
}
</div>
Your inner foreach is iterating over the entire Model collection - you'll need to restrict it to just the relevant three items.
I'm guessing you want something like:
<div class="carousel slide>
#for (int i = 0; i <= Model.Count()/3; i++ ) <!-- this works well, paging -->
{
<div class="item #if(i==0){<text>active</text>}">
#foreach(var well in Model.Skip(i*3).Take(3))
{
<div class="span4">
<!-- some html here -->
</div>
}
</div>
}
</div>
One way to think about this problem is to split the original collection into groups for each 3 sequential elements. Fortunately, you can use the GroupBy LINQ method using the "element index divided by 3" as key. IMO, the advantage of this solution is that it expresses more clearly your intent and has better performance than reiterating the collection with Skip(x * 3).Take(3) in the inner loop.
<div class="carousel slide>
#foreach (var group in Model.Select((x, index) => new { element = x, index }).GroupBy(x => x.index / 3, x => x.element))
{
<div class="item #if( group.Key == 0) {<text>active</text>}">
#foreach(var well in group)
{
<div class="span4">
<!-- some html here -->
</div>
}
</div>
}
</div>
I would even change the model type to ILookup<int, TElement> and perform the grouping in the controller.

backbone view passed to jQuery Mobile

I've been trying to use backbonejs and jqm together.
I can render the main page alright. The page has a list that the user can tap on. The item selected should show a detail page with info on the list item selected. The detail page is a backbone view with a template that's rendered in the item's view object.
The detail's view .render() produces the html ok and I set the html of the div tag of the main page to the rendered item's detail markup. It looks like this:
podClicked: function (event) {
console.log("PodListItemView: got click from:" + event.target.innerHTML + " id:" + (this.model.get("id") ? this.model.get("id") : "no id assigned") + "\n\t CID:" + this.model.cid);
var detailView = new PodDetailView({ model: this.model });
detailView.render();
},
The detail view's render looks like this:
render: function () {
this.$el.html(this.template({ podId: this.model.get("podId"), isAbout_Name: this.model.get("isAbout_Name"), happenedOn: this.model.get("happenedOn") }));
var appPageHtml = $(app.el).html($(this.el));
$.mobile.changePage(""); // <-- vague stab in the dark to try to get JQM to do something. I've also tried $.mobile.changePage(appPageHtml).
console.log("PodDetailView: render");
return this;
}
I can see that the detail's view has been rendered on the page by checking Chrome's dev tools html editor but it's not displaying on the page. All I see is a blank page.
I've tried $.mobile.changePage() but, without an URL it throws an error.
How do I get JQM to apply it's class tags to the rendered html?
the HTML and templates look like this:
<!-- Main Page -->
<div id="lessa-app" class="meditator-image" data-role="page"></div>
<!-- The rest are templates processed through underscore -->
<script id="app-main-template" type="text/template">
<div data-role="header">
<h1>#ViewBag.Title</h1>
</div>
<!-- /header -->
<div id="main-content" data-role="content">
<div id="pod-list" data-theme="a">
<ul data-role="listview" >
</ul>
</div>
</div>
<div id="main-footer" data-role='footer'>
<div id="newPod" class="ez-icon-plus"></div>
</div>
</script>
<script id="poditem-template" type="text/template">
<span class="pod-listitem"><%= isAbout_Name %></span> <span class='pod-listitem ui-li-aside'><%= happenedOn %></span> <span class='pod-listitem ui-li-count'>5</span>
</script>
<script id="page-pod-detail-template" type="text/template">
<div data-role="header">
<h1>Pod Details</h1>
</div>
<div data-role="content">
<div id='podDetailForm'>
<fieldset data-role="fieldcontain">
<legend>PodDto</legend>
<label for="happenedOn">This was on:</label>
<input type="date" name="name" id="happenedOn" value="<%= happenedOn %>" />
</fieldset>
</div>
<button id="backToList" data-inline="false">Back to list</button>
</div>
<div data-role='footer'></div>
</script>
Thanks in advance for any advice... is this even doable?
I've finally found a way to do this. My original code has several impediments to the success of this process.
The first thing to do is to intercept jquerymobile's (v.1.2.0) changePage event like this:
(I've adapted the outline from jqm's docs and left in the helpful comments: see http://jquerymobile.com/demos/1.2.0/docs/pages/page-dynamic.html
)
$(document).bind("pagebeforechange", function (e, data) {
// We only want to handle changePage() calls where the caller is
// asking us to load a page by URL.
if (typeof data.toPage === "string") {
// We are being asked to load a page by URL, but we only
// want to handle URLs that request the data for a specific
// category.
var u = $.mobile.path.parseUrl(data.toPage),
re = /^#/;
// don't intercept urls to the main page allow them to be managed by JQM
if (u.hash != "#lessa-app" && u.hash.search(re) !== -1) {
// We're being asked to display the items for a specific category.
// Call our internal method that builds the content for the category
// on the fly based on our in-memory category data structure.
showItemDetail(u, data.options); // <--- handle backbone view.render calls in this function
// Make sure to tell changePage() we've handled this call so it doesn't
// have to do anything.
e.preventDefault();
}
}
});
The changePage() call is made in the item's list backbone view events declaration which passes to the podClicked method as follows:
var PodListItemView = Backbone.View.extend({
tagName: 'li', // name of (orphan) root tag in this.el
attributes: { 'class': 'pod-listitem' },
// Caches the templates for the view
listTemplate: _.template($('#poditem-template').html()),
events: {
"click .pod-listitem": "podClicked"
},
initialize: function () {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
},
render: function () {
this.$el.html(this.listTemplate({ podId: this.model.get("podId"), isAbout_Name: this.model.get("isAbout_Name"), happenedOn: this.model.get("happenedOn") }));
return this;
},
podClicked: function (event) {
$.mobile.changePage("#pod-detail-page?CID='" + this.model.cid + "'");
},
clear: function () {
this.model.clear();
}
});
In the 'showItemDetail' function the query portion of the url is parsed for the CID of the item's backbone model. Again I've adapted the code provided in the jquerymobile.com's link shown above.
Qestion: I have still figuring out whether it's better to have the code in showItemDetail() be inside the view's render() method. Having a defined function seems to detract from backbone's architecture model. On the other hand, having the render() function know about calling JQM changePage seems to violate the principle of 'separation of concerns'. Can anyone provide some insight and guidance?
// the passed url looks like #pod-detail-page?CID='c2'
function showItemDetail(urlObj, options) {
// Get the object that represents the item selected from the url
var pageSelector = urlObj.hash.replace(/\?.*$/, "");
var podCid = urlObj.hash.replace(/^.*\?CID=/, "").replace(/'/g, "");
var $page = $(pageSelector),
// Get the header for the page.
$header = $page.children(":jqmData(role=header)"),
// Get the content area element for the page.
$content = $page.children(":jqmData(role=content)");
// The markup we are going to inject into the content area of the page.
// retrieve the selected pod from the podList by Cid
var selectedPod = podList.getByCid(podCid);
// Find the h1 element in our header and inject the name of the item into it
var headerText = selectedPod.get("isAbout_Name");
$header.html("h1").html(headerText);
// Inject the item info into the content element
var view = new PodDetailView({ model: selectedPod });
var viewElHtml = view.render().$el.html();
$content.html(viewElHtml);
$page.page();
// Enhance the listview we just injected.
var fieldContain = $content.find(":jqmData(role=listview)");
fieldContain.listview();
// We don't want the data-url of the page we just modified
// to be the url that shows up in the browser's location field,
// so set the dataUrl option to the URL for the category
// we just loaded.
options.dataUrl = urlObj.href;
// Now call changePage() and tell it to switch to
// the page we just modified.
$.mobile.changePage($page, options);
}
So the above provides the event plumbing.
The other problem I had was that the page was not set up correctly. It's better to put the page framework in the main html and not put it in an underscore template to be rendered at a later time. I presume that avoids issues where the html is not present when jqm takes over.
<!-- Main Page -->
<div id="lessa-app" data-role="page">
<div data-role="header">
<h1></h1>
</div>
<!-- /header -->
<div id="main-content" data-role="content">
<div id="pod-list" data-theme="a">
<ul data-role="listview">
</ul>
</div>
</div>
<div id="main-footer" data-role='footer'>
<div id="main-newPod" class="ez-icon-plus"></div>
</div>
</div>
<!-- detail page -->
<div id="pod-detail-page" data-role="page">
<div data-role="header">
<h1></h1>
</div>
<div id="detail-content" data-role="content">
<div id="pod-detail" data-theme="a">
</div>
</div>
<div id="detail-footer" data-role='footer'>
back
</div>
</div>

Resources