:class not adding class - alpine.js

So I have this button, and x-data for a parent div had this: days: {SUN: false}. It finds the variable so no problem. Here is the button:
<button type="button" class="date-btn" #click="days[$el.textContent] = !days[$el.textContent]; alert(days[$el.textContent])" :class="{'bg-blue-800' ? days[$el.textContent]: ''}">SUN</button>
The alert shows that the value switches from true and false on click, but the color of the button never changes.
Here is my .date-btn styles, but even stripping it dry except of m and px gives the same result:
#layer components {
.date-btn {
#apply m-2 px-7 py-3 text-white font-medium text-sm leading-snug
uppercase rounded shadow-md hover:shadow-lg
focus:shadow-lg focus:outline-none focus:ring-0
active:shadow-lg transition duration-150 ease-in-out drop-shadow-lg;
}
}
I also did :class="'bg-blue-800' ? days[$el.textContent]: ''" but no banana.
What is wrong with my :class call?

The input of the ternary operator should be a condition:
:class="days[$el.textContent] ? 'bg-blue-800' : ''"
You can also use a shorthand version:
:class="days[$el.textContent] && 'bg-blue-800'"

I'd have a read in the docs. x-bind, what is what you're using when using the shorthand :class, uses booleans to define what class to bind. As per example you've given, you've mixed up the ternary statement and object syntax. This won't ever result in anything. However, say you hadn't wrapped it in an object, then it would still fail, since 'bg-blue-800' is always truthy, which will always bind '' as class.
If you want to use a ternary statement, instead you'd have to write it as such: :class="days[$el.textContent] ? 'bg-blue-800' : ''".
Because AlpineJS aims at making life easy, you can also use shorthand syntax to replace the ternary operator. In this case, you could rewrite above example to :class="days[$el.textContent] && 'bg-blue-800'"
The last method you already mention in your own answer. The object class: boolean syntax, which (in my opinion) is the most logical way to write the x-bind is the last possible method to bind elements with. By passing a class as key, and a boolean as value, AlpineJS knows which class(es) to bind.

Ok for some reason this worked :class="{'bg-blue-800' : days[$el.textContent]}"
Shoutout to https://daily.dev/blog/alpine-js-the-ultimate-guide

Related

Binding classes issue with AlpineJS - ternary operator and curly brackets

EDIT: Due to the general syntax of AlpineJS, writing a ternary operator inside curly brackets is a mistake you may easily run into.
This is just a "grammar" issue but I really want to figure it out... Let's jump into it.
Everything is ok if I write:
:class="{ 'some_class': activeSlide == slide }"
On the contrary it doesn't work (i.e. 'some_class' is not added as a class) if I write:
:class="{ activeSlide == slide ? 'some_class' : '' }"
What's wrong with it?
(I don't think it's relevant, but you can have a look at the entire code here, inside 'template' tags: link)
The ternary operator works only with the non-object class syntax:
:class="activeSlide == slide ? 'some_class' : ''"
You can use the shorthand conditionals as well:
:class="activeSlide == slide && 'some_class'"

Alpine.js - sync computed field back to model

I have a form with some dynamic inputs, and I need to define a readonly computed field, which will have a value count from other fields. This I am doing using x-bind:value='myFormulaFunction()' and it shows the value on the field correctly, and it even updates live if the variables change.
The problem is how to sync this count value back to the model. Even when I define x-model on the field, it doesn't update with the value. And apparently no functions like #change and #input are triggered on the value update either, neither does x-effect.
I made a quick fiddle here: https://jsfiddle.net/janzikmund/cm3zqeyj/12/
I know I should probably make a get function to achieve this, but I am doing this within x-for of dynamically generated array and having the logic on the field itself would just be much more convenient. Anything I am missing?
For that purpose you can use the $watch magic method that actually supports watching for multiple variables. So when one of the variables changes, it updates the value of count.sum.
Note: $watch checks for each child attribute, so don't watch the whole count variable because it will cause an infinite loop.
<script defer src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js"></script>
<div x-data="{ count: { f1: 0, f2: 0, sum: 0} }"
x-init="$watch('count.f1,count.f2', () => {count.sum = !isNaN(count.f1) && !isNaN(count.f2) ? parseInt(count.f1) + parseInt(count.f2) : 0})">
<input type="number" x-model="count.f1">
<input type="number" x-model="count.f2">
<input type="text" readonly x-model="count.sum">
<h2 x-text="count.sum"></h2>
</div>

Thymeleaf Template not rendering param value inside th if condition

I am using thymeleaf template for my spring boot application. Here below the main page,
<div th:replace="content :: content"></div>
and inside content fragment,
<div th:fragment="content">
<h4 th:if="${param.val== 'abc'}">SOME-TEXT</h4> // not working
<h4 th:if="${param.val== 'abc'}" th:text="${param.val}"></h4> // not working
<h4 th:text="${param.val}"></h4> // working and value is abc
<h4 th:unless="${param.val== 'abc'}" th:text="${param.val}"></h4> // working - value in html text is abc
<h4 th:unless="${param.val== 'abc'}">SOME-TEXT</h4> // Working, value is SOME-TEXT
</div>
URL: domain/?val=abc
I want to display: SOME-TEXT in html if param.val == 'abc'.
Value 'abc' is coming inside th:text. But inside th:if failing.
Seems some hidden extra strings added to param.val?
Any suggestion?
The Thymeleaf function ${param.val} will return a request parameter called val. But this could be a multivalued object (e.g. an array) - for example consider this (which is a valid construction):
?val=abc&val=def
So to work with a single-valued string, you can do this:
<h4 th:if="${#strings.toString(param.val)} == 'abc'" th:text="'SOME-TEXT-2'">SOME-TEXT-1</h4>
This prints SOME-TEXT-2 in the web page.
Or you can use this:
<h4 th:if="${#strings.toString(param.val)} == 'abc'">SOME-TEXT-1</h4>
Which prints SOME-TEXT-1.
Just out of interest, if you used that first example val=abc&val=def, then you can see what happens with this:
<h4 th:text="${param.val}"></h4>
It prints an array:
[abc, def]
You may see something like this when processing a series of related checkboxes (just as one example).
Update:
For a null-check, using Thymeleaf, you can do this:
<h4 th:if="${param.val} != null and
${#strings.toString(param.val)} == 'abc'">SOME-TEXT-2</h4>
In this specific case, it isn't really needed, as you are not doing anything with the null value which might cause a problem.
It's more relevant if you are chaining values in objects foo.bar.baz - and you need to check if foo or bar are null to avoid a null pointer exception.
Bear in mind that Spring's expression language has the safe navigation operator, which can be very helpful in such cases: foo.?bar.?baz, allowing you to write more concise null handling than with Thymeleaf alone. But again, not relevant to your specific example from the question.

PHP possibilities in Slim Template language for Ruby

In PHP I can do this:
<div class="foo <?php if($a) echo "bar"; ?>">
<?php if ($b) echo "</div>"; ?>
It is incredibly convenient. I can break a string in any place, between any quotes, between any HTML symbols, just wherever I want.
And I need to implement the same in Ruby-HTML. I'm trying to port a PHP project to Ruby. I use the Slim template language. I tried this but it doesn't work, Slim throws errors:
<div class="foo
- if (x == 1)
= bar
"></div>
For now with Slim I know only one way:
- if (a == true)
<div class="foo"></div>
- else
<div class="foo bar"></div>
Firstly, duplication. Secondly, my HTML-PHP part of code is quite complicated. It is with two loops (for loop and foreach loop inside it) and I use more than one such an embeds to add div's attributes according to conditions. And just cannot imagine how to implement it with Slim. It throws an error even for this, I cannot break long html string:
- if(i != 5)
<div class="foo bar"
id="item_#{i}"
style="background-color:red;"
data-im="baz">
</div>
- else
Does Slim allow to break strings with conditional ifs between quotes or element attributes? How to do it?
If you're using Rails, you're free to facilitate ActionView::Helpers this way:
= content_tag :li, class: ( a == true ? "foo bar" : "foo") do
inside div
Elsewise you're free to create some helper method to cover this logic for you
Nevertheless it's considered ill practice to include much logic in a view. Consider using some Presenter pattern
edit.
Looking into some slim docs found you're able to achieve your goal this way
div.foo class="#{'bar' if a == true}"
| Text inside div

xpath for accessing JS generated class instance

I need to use xpath as a locator for Selenium Webdriver to click on a button. It is in a dialog that is dynamically generated. Firebug/firepath gives div references with numbers that will change. I've read a lot of great tips here and am close but can't seem to get the exact specification. I need xpath to access Close and Cancel:
<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix">
<button class="ui-button ui-widget ui-state-default ui-corner-all
ui-button-text-only ui-state-hover" type="button" role="button" aria-disabled="false">
<span class="ui-button-text">Close</span>
</button>
<button class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only"
type="button" role="button" aria-disabled="false">
<span class="ui-button-text">Create</span>
No success with either:
xpath="//*[#class='ui-button-text' and #value='Close'")
xpath="//span[contains(#class='ui-button-text' and #value='Close')]")
Use:
//span[#class = 'ui-button-text' and . = 'Close']
These selects all span elements in the XML document, with string value "Close" and the string value of whose class attribute is "ui-button-text" .
Use #Dimitre's expression:
//span[#class='ui-button-text' and .='Close']
I'm answering to explain where you went wrong with your original expressions.
First expression:
//*[#class='ui-button-text' and #value='Close'")]
This selects all elements (*) anywhere in the document (//) that have an attribute (#) named class whose value is ui-button-text and an attribute named value whose value is Close. The attribute axis specifier is the # symbol. This is short for attribute::. The following expressions are equivalent:
//*[#class='ui-button-text' and #value='Close'")]
//*[attribute::class='ui-button-text' and attribute::value='Close'")]
The above expressions can be fully expanded to:
/descendant-or-self::node()/child::*[attribute::class='ui-button-text' and
attribute::value='Close'")]
In short: try to understand XPath's syntactic abbreviations when constructing expressions.
Second expression:
//span[contains(#class='ui-button-text' and #value='Close')]
The XPath contains function has the following signature:
boolean contains(string, string)
...and is described in the spec like this:
The contains function returns true if the first argument string
contains the second argument string, and otherwise returns false.
You seem to be trying to treat it as a more general, magic function that checks whether the element "contains" some attributes, but it is fundamentally a string function.
I recommend a quick (or not so quick) read of the XPath 1.0 Recommendation:
http://www.w3.org/TR/xpath/
Until then you'll just be guessing.

Resources