My code is below: The question is:
How do I make it so that my jade file will render a description of the event based on the value of the select menu? Basically if the select option has a value of 1 then p should render p #{event[1].description}.
considering Jade precompiles I don't have access to the DOM which eliminates document.getElementbyId("test").selectedIndex; Below is my attempt at solving it on server side. I think I am missing something though because to me no matter the approach I will need access to the DOM to see the value change in the select menu to spit out the "event description".
Any help would be appreciated.
Jade File
extends layout
block content
form(role='form', action="/doSomthing", method="post")
select#test.form-control(name = "choices")
each events, i in event
option(value=i) #{events.event}
button.btn.btn-default(type='submit') Submit
a(href='/')
button.btn.btn-primary(type="button") Cancel
p#{events[0].description} //I want to pass an array instead of explicitly calling [0]
Route
router.get('/eventreg', function(req, res) {
var ei = event.eventid;
var eiarray = Event.findOne({'eventid': cc });
Event.find({}, function(err, event){
res.render('eventreg', {user : req.user, event: event});
console.log(event);
console.log(eiarray);
});
});
My thought was that I could create the looping array on server side so it would compile, but once the page has rendered and I change the selected option value it wouldn't recompile again on the fly.
Would React solve this problem? It seems like a bit overkill.
Thank you again for your time everyone.
You can add a piece of JavaScript to your page that then does the desired text changes in the browser. Add something like this to the bottom of your Jade page:
script.
$('#theOptionField').on('change', function(e) {
// get value that the user selected
// modify the HTML of the element that should display the value
});
Related
I only do scientific programming so I am not too familiar with javascript. I am trying to add a widget to one single tumblr post. The website I got the widget from makes it specifically for tumblr and it is:
<div class="shopthepost-widget" data-widget-id="4665345"><script type="text/javascript">!function(w,i,d,g,e,t){d.getElementById(i)||(element=d.createElement(t),element.id=i,element.src="https://widgets.rewardstyle.com"+e,d.body.appendChild(element)),w.hasOwnProperty(g)===!0&&"complete"===d.readyState&&w[g].init()}(window,"shopthepost-script",document,"__stp","/js/shopthepost.js","script")</script><div class="rs-adblock"><img src="https://assets.rewardstyle.com/production/424ab6aff12fe31b5b93d8f5ce7cc70d2953e565/images/search/350.gif" onerror='this.parentNode.innerHTML="Disable your ad blocking software to view this content."' style="width: 15px; height: 15px"><noscript>JavaScript is currently disabled in this browser. Reactivate it to view this content.</noscript></div></div>
If this is added to the actual theme on tumblr, it works great. However, I am trying to put this in just one post on tumblr. When I'm trying to make the post I will go to the html and add it in there but it will not actually show up on the site. Any tips on how to solve this? Thank you in advance.
You have two choices available to you.
First: If you can host the javascript file somewhere remotely you can link it in the bottom of the post via the editor. The beauty of this is that you can update the js without having to update your main template.
For post edits you can select settings and choose HTML markup
then add a link to the script in the bottom of the page.
Or for a page edit it is the same concept you can switch to HTML markup and add it there.
Second: you can create a function in your template and then only fire it on the relevant post or page.
The way I have done this in the past is to get the post/page name from the file path, split the file path into an array and add them to the classnames for the html element. Example here:
const dom = document.querySelector('html');
const path = document.location.pathname.split('/');
const primaryDir = path[1]; // get the primary folder
const secondaryDir = path[2]; // get secondary folder
const tertiaryDir = path[3]; // get the tertiary folder
const setIndexPage = () => {
if (!primaryDir) { // if there is no primaryDir we should assume we are on the home/index page
dom.classList.add('index');
} else if (tertiaryDir) {
dom.classList.add(primaryDir + ' ' + secondaryDir + ' ' + tertiaryDir);
} else if (secondaryDir) {
dom.classList.add(primaryDir + ' ' + secondaryDir);
} else {
dom.classList.add(primaryDir);
}
}
Now we can target our function to run only if it finds the correct selector
if (document.querySelector('.yourClassName').length) {
// run your function here
}
For .yourClassName you need to pass in the page or post name. By default Tumblr will create a unique post id (integer) and add that to the filepath. But you can also use text/verbose file names in addition.
The beauty of this method, is that the classnames will be added to every page, but you can chose to target only certain posts/pages with your js function.
Alternatively you could use this method and add the html selector only to your post/page content. So wrap your content in a div and give that a unique id or classname, but the method is the same.
Here is an example of a page where I am doing this (although I am concatenating the class names slightly differently).
I hope this makes sense.
Using Haml, ruby 2.7.2, rails 6.1.2.1, stimulus ^2.0.0
So, I am using a nested form that shows a select type. Based on the value of the select type, I will either show or hide a div. I'm converting my coffeescript to stimulus, and I'm not sure how to force trigger a change event, or instead just run the code that checks the value of the select to decide if I should hide or show the div. In the past, Inside my html.haml view I would call this:
:javascript
$('select.answer_type').change()
Based on another post in here: https://discuss.hotwire.dev/t/triggering-turbo-frame-with-js/1622, I tried to change it to say:
:javascript
q_typeTarget.dispatchEvent(new Event('change'));
And that didn't work. Maybe there is another way to do this, or I'm using the wrong vocabulary to look up the example I want to do? I'd rather just manually execute the Controller#action and give it the select object to work off of right away, but I'm not sure how to do this in html/script tag. Thanks for any help!
Here is my relevant code that should help:
_question_fields.html.haml
.nested-fields.question
.form_group.grid-x
.fields.cell.shrink
= f.select :answer_type,
options_for_select(["String","Option","Checkbox"],
f.object.answer_type),{},
class: 'answer_type',
data: {nested_form_target: 'q_type',
action: 'nested-form#update_q_type'}
.cell.answers_group{ style: 'visibility: hidden; display: none'}
= render partial: 'answers', locals: { f: f, q_level: q_level }
:javascript
q_typeTarget.dispatchEvent(new Event('change'));
nested_form_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
static targets = ["q_type", "answers"]
update_q_type(event) {
event.preventDefault()
console.log('You have selected ' + this.q_type )
let d_question = event.target.closest(".question")
let d_select = d_question.querySelector("select")
let d_answer = d_question.querySelector(".answers_group")
console.log('Select: ' + d_select.value )
switch(d_select.value) {
case 'Option':
case 'Checkbox':
console.log(' Show Answers ')
d_answer.style.visibility = "visible"
d_answer.style.display = null
return
default:
console.log(' Hide Answers ')
d_answer.style.visibility = 'hidden'
d_answer.style.display = 'none'
return
}
}
Here is an example .gif of the nested forms, shown below:
I add a Question
I select an option from the select tag, which triggers the select → change event: nested-form#update_q_type
If I select either Option or Checkbox, then I show the div, otherwise I hide the div
Now, here is why I'm trying to trigger the select → change event. If I start to edit this record again, it loads the data just fine. However, it does not know when to show the answers div or not. That's why I need to trigger the select → change event so that it can properly set up the answers div to either hide or show. Otherwise, it's defaulted to always 'hide'.
I have another .gif below. As you can see, it loads the select with Option selected. if I try to select Option again, it doesn't do anything...because it hasn't really changed. If I change it to Checkbox, it of course updates. So basically, I need to do this onLoad, you can say.
Maybe there is a different way to do this? Thank you for your help!
Update: I didn't realize my html tags were being interpreted as html. Oops. Changed all tags to italicized words. Ex: '/</select/>/' is now just select. Also I expanded on the question for more clarification.
StimulusJS has an initialize function that essentially runs when the page loads. You would use that for this purpose. The only other issue here is that you're passing in an event to update_q_type and you won't have an event on initialize. So what I find works is to extract most of the code to a separate function that my initialize and click function can both call. This helps you keep your code DRY.
export default class extends Controller {
static targets = ["q_type", "answers"]
initialize() {
this.switch(this.q_typeTarget)
}
update_q_type(event) {
event.preventDefault
this.switch(this.q_typeTarget)
}
switch(q_type) {
...
}
}
One thing to note, it's not clear if you have one stimulus controller running here or one for every select box combo. You could do the 2nd and not have to use the closest method. It's meant to be very modular.
How is it possible to show changes of existing items in a dom-repeat template in polymer?
i tried really all i could think of and i could find in the polymer documentation or in the web. but nothing works. below you find a html-page that uses a small list and tries to change one entry in the list to another value when you click the change button. But the only thing that would change the item in the list next to the change-button is the line that is commented out. all the other lines try it, but fail.
i understand that re-rendering a template is a time-consuming task and that it only should take place when it is necessary and that polymer tries to avoid it as much as possible. but why is it not possible for me (from the view of the code the god of their world ^^) to force a render on purpose?
the method of creating a complete new object, deleting the old item from the list and inserting the new object (thats what the commented line does) is a workaround, but it is a really huge effort, when the items are more complex and have properties or arrays that are not even displayed.
What am i missing? What did i not try? I would be very glad if anybody could tell me what i could do to achieve such a (from my point of view) simple and very common task.
EDIT (solved):
the solution of Tomasz Pluskiewicz was partly successful. but i updated the code to show my current problem. the name of the item is bound using the method format(...). when a button is clicked the item will be removed from the list. this works good. but if you remove the last item of the list, then the new last item in the list should get the name "Last". this also works, when the name is bound directly to the property name. but if i want to do some formatting of the name (surround it with # for example) then the display of this item is not updated.
EDIT2 (partially solved):
The next example that doesn't work, occurs when a value inside the method that is called for displaying a value changes. This can be seen in the example if a change-button is clicked multiple times. It increases an internal counter and the corresponding text will display this value. But this is only true for the first change of the counter. Subsequent clicks won't change the display again. The display shows the value of the counter after the first click. But if another change button is clicked, then the text in front of this button shows the increased counter value. But also only once. It also doesn't display changes on subsequent clicks. The notifyPath-method seems to check if the value changed, but doesn't consider that inside the method that is used for displaying the value, something could have been changed to show the data in another way.
i included a partial solution in this example. If the method that gets called has a parameter that changes when something in the method is changed, then the update will be executed. This can be seen in the second variable that is bound with the parameter displayEnforcer - format(item.name,displayEnforcer). This variable is set to a random value everytime the counter is changed. And this triggers the display update.
But this is a really strange solution and should not be necessary. it would be great if someone has a better solution to this problem.
<link rel="import" href="components/bower_components/polymer/polymer.html">
<link rel="import" href="components/bower_components/paper-button/paper-button.html">
<dom-module id="polymer-test">
<template>
<table>
<template id="tpl" is="dom-repeat" items="{{list}}">
<tr>
<td>{{item.id}} - {{format(item.name)}}- {{format(item.name,displayEnforcer)}}</td>
<td><paper-button raised on-tap="tapDelete">delete</paper-button></td>
<td><paper-button raised on-tap="tapChange">change</paper-button></td>
</tr>
</template>
</table>
</template>
</dom-module>
<script>
Polymer(
{
is: "polymer-test",
properties:
{
count: {type: Number, value:0}
,list: {type: Array, value: [{id:0,name:"First"}
,{id:1,name:"Second"}
,{id:2,name:"Third"}
,{id:3,name:"Fourth"}
,{id:4,name:"Fifth"}
,{id:5,name:"Last"}
]}
,displayEnforcer: {type:Number,value:Math.random()}
},
format: function(name,dummy)
{
return "#" + name + " - " + this.count + "#";
},
tapChange: function(e)
{
this.count++;
this.displayEnforcer = Math.random();
this.notifyPath("list." + (e.model.index) + ".name","changed");
},
tapDelete: function(e)
{
if(this.list.length == 1)
return;
this.splice("list",e.model.index,1);
if(this.list.length > 0)
this.list[this.list.length-1].name = "Last";
this.notifyPath("list." + (this.list.length-1) + ".name",this.list[this.list.length-1].name);
}
});
</script>
You can use notifyPath to refresh binding of single list element's property:
tapChange: function(e) {
this.notifyPath('list.' + e.model.index + '.name', 'changed');
}
See: https://github.com/Polymer/polymer/issues/2068#issuecomment-120767748
This way does not re-render the entire repeater but simply updates the necessary data-bound nodes.
EDIT
Your format function is not getting updated because it doesn't use the notified path which is an item's name. When you remove an element, the index of that element within the rendered repeater doesn't change, even though the actual item changes. In other words, when you remove fifth element, the index 4 is still 4.
As a workaround you can add item.name to format call so that it is refreshed when you notify that path:
<td>{{item.id}} - {{format(index, item.name)}}</td>
You don't even have to use that value in the example. It's enough that the binding is reevaluated.
I have a DropDownList where onChange sets the content of the TextArea which is my CKEditor control.
When the editor is not in use I run this bit of code for onChange:
$(".setBody").change(function() {
//
var className = "." + $(this).attr("sExternalId");
var text = $(this).val();
//
$(className).val(text);
});
I'm not very experienced with Javascript/JQuery and I just can't figure out how to perform the same using CKEditor's setData() function. This is what I've tried:
$(".setCKBody").change(function() {
//
var className = "." + $(this).attr("sExternalId");
var text = $(this).val();
//
var editor = $(className).ckeditorGet();
editor.setData(text, function() {
alert("The content was set");
});
});
$(".setCKBody").change(function() {
//
var className = "." + $(this).attr("sExternalId");
var text = $(this).val();
//
CKEDITOR.instances[$(className)].setData(text, function() {
alert("The content was set");
});
});
Am I close? I think one of the main limitations is that I have multiple editor controls with the same id and name, only the class can tell them apart which is why I'm using that with the JQuery. I've tried working through some examples online, but I'm not sure how to apply them to this scenario - that's probably my inexperience coming through there...
Thanks.
EDIT
This is how my textarea and dropdownlist appears in view source:
<textarea class="editArea M3" cols="20" id="Body" name="Body" rows="5">
*some text*
</textarea>
<select class="setCKBody" id="Templates" name="Templates" sExternalId="M3">
<option value="some value">Option 1</option>
<option value="some value">Option 2</option>
</select>
The onChange event above is triggered from the dropDownList changing and is linked to the textArea via the "sExternalId" attribute. I realised I used "id" as the attribute name in the example above which was in error, so I changed that.
I use this to set it as a CKEditor control:
<script>CKEDITOR.replaceAll('editArea');</script>
I have between 2 to 6 textarea controls on the same page, created with razor like this:
#Html.TextAreaFor(m => m.Body, new { #class = "span12 editArea " + Model.ExternalId, rows = 5 })
They are contained within a partial view that is used like this:
#foreach (MailTemplateModel oTemplate in Model.Templates)
{
#Html.Partial("_MailPartial", oTemplate)
}
This is why each text area has "Body" set as the id and name. I think this is the heart of the problem, with there being multiple elements with the same id and name CKEditor is not able to select the correct one. I've tried to do CKEDITOR.instances["className"] but that was undefined, whereas doing CKEDITOR.instances.Body did work, but would only ever return the same value.
I'm going to restructure the way the page is created to avoid this, hopefully my issues will be solved at the same time.
Here's a few pointers.
Use class="foo" if you have many things that you refer to as a group, like like here it looks like you would have many setCKBody elements you listen to for change events.
Use id="foo" if you have one single specific thing.
Using the same id and class for one element usually is not the right thing to do.
CKEDITOR.instances[xxx] <-- xxx should be a string, not a jquery object - so CKEDITOR.instances[className] might work better (I can't say not having seen your HTML).
It would help if we saw your HTML; textarea definitions and setCKBody definitions. Do you have many ckeditors and many setCKBody elements?
My original approach to this scenario was all wrong, I had a model that contained multiple mail templates and so I rendered each one via a partial view within the same page so that the user could click to edit any one of them and the details would appear in a modal popup - within the same window. What I wanted to avoid was forcing the user to navigate to another window to edit a mail template, but this lead to multiple elements having the same id and name attributes which prevented me from accessing them correctly.
I've now added a list box where the user can select a template to edit, the selected template is rendered underneath and so avoids the multiple name and id issue. Now I know there is only ever 1 CKEditor control so I can access it in my js like this:
var editor = CKEDITOR.instances.SelectedTemplate_Body;
SelectedTemplate_Body is the name and id of the element I made into a CKEditor control. The onChange function I wrote for the dropdownlist is now written like this:
$(document).ready(function() {
//
$(".setBody").change(function() {
//
var templateId = $(this).val();
//
$.ajax({
type: "GET",
url: msHost + "MailTemplates/UpdateBody",
data: { "templateId": templateId },
cache: false,
dataType: "text",
success: function (data) {
CKEDITOR.instances.SelectedTemplate_Body.setData(data);
}
})
});
});
The tempalteId attribute is the value associated to the dropdownlist selection, this lets me know which template to use for setting the content of my editor control.
MailTemplates/UpdateBody points to a public method in my MailTemplates controller which runs a search on available mail templates and matches against the template Id passed in, the method then returns the body of the template as a string.
public string UpdateBody(string tempalteId)
{
TemplateQuery oQuery;
//
oQuery = new TemplateQuery();
oQuery.Execute();
foreach (MailTemplate oTemplate in oQuery.Results)
if (oTemplate.Id.Equals(templateId))
return oTemplate.Body;
//
return string.Empty;
}
This line replaces the contents of the CKEditor control with the response from the controller method.
CKEDITOR.instances.SelectedTemplate_Body.setData(data);
Thanks #Nenotlep for trying to help out, you gave me a few things to think about there.
I have a page, in which there are a couple of Option elements whose Value point to external pages. My aim is to use Ajax to extract the chosen Option's Value & use that to load content from external pages. E.g. When a user chooses Volleyball, I will get the 'volleyball.html' value, use Ajax to retrieve that page and load its #catalog content into the current page. Here's my code:
$("select").change(function(){
var selectedURL=$("option:selected",this).val();
if(selectedURL!=="Select One"){
$("#center").html("<p class='processing'></p>");
$.get(selectedURL,function(data){
var extractedContent=$("#catalog",data);
console.log(extractedContent); //Firebug says extractedContent is '[]'
$("#center").html(extractedContent); //Nothing is inserted inside div#content
},"html");
}
I'm not good at jQuery, and have kind of mixed and matched code from a few posts here to derive at the above. Now I'm not sure what went wrong, but nothing is loaded - the #center div block that is supposed to hold the extracted content is empty.
Can someone please help me spot just what's wrong with my code above? Many thanks in advance.
The load() method is perfect for this:
$('select').change(function () {
var selectedURL = $('option:selected', this).val();
if (selectedURL !== 'Select One') {
$('#center').html('<p class="processing"></p>').load(selectedURL + ' #catalog');
}
});
It can take only a URL, or it can take a URL followed by a selector which will only load the contents of the matched element (#catalog) into the element it is called on (#center).
You can't use $('#catalog', data) just like that. To extract content, you can create a jQuery object, and assign the HTML returned then extract from there:
var extractedContent = $('<div />').html(data).find('#catalog').html();
console.log(extractedContent);
$("#center").html(extractedContent);
$("select").change(function(){
var selectedURL=$("option:selected",this).val();
if(selectedURL!=="Select One"){
$("#center").html("<p class='processing'></p>");
$.get(selectedURL,function(data){
$("#center").html($(data).find("#catalog"));
},"html");
}
});