I recently wrote some javascript code that filled a drop down list based on some XML, pretty simple stuff. The problem was I had to write similar code to do almost the same thing on a different page.
Because the code was almost identical I named most of the functions the same, thinking that they would never be included in the same page. However, naming conflicts arose because both javascript files were eventually included in the same HTML page.
When I had to go back and change the names I simply added first_ or second_ to the method's names. This was a pain and it doesn't seem very elegant to me. I was wondering if there is a better way to resolve name conflicts in javascript?
Try the JavaScript module pattern (or namespaces) used in various libraries.
Try to be DRY (don't repeat yourself) so you can avoid name collisions. If the code is almost the same you better avoid code duplication by creating a function which can handle both cases. The function can take two parameters: which dropdown to populate and with what data. This helps maintainability as well.
update: I assume that you take the XML from an AJAX request. In this case you can create on-the-fly anonymous functions with the appropriate parameters for callback inside a loop.
I would look at how I could merge the two pieces of code (functions?) into a single function. If you need to populate a list box, then pass the list box id into the function, so you are not hard-coded to operate on one single control only...
I did this on my rocket business's web site where I sold rocket motors with different delay values, but in essence, they were the same product, just a different delay value.
Perhaps this might try and explain what I'm trying to say... I use this if an image file happens to be missing, it will display a "no image" image in place of the real image.
function goBlank(image)
{
if(image) {
var imgobj = document[image];
imgobj.src="/images/blank.png";
}
}
In this case, you call it with:
<img src="/images/aerotech.png" name="header" onError="goBlank('header');">
If you need more example with things like list boxes used, let me know. Perhaps even post some sample code of yours.
Another option (if possible) is to carefully tie the code to the element itself.
e.g.
<input type="text" name="foo" id="foo" value="World" onchange="this.stuff('Hello ' + this.value);"/>
<script>
document.getElementById('foo').stuff = function(msg){
//do whatever you want here...
alert('You passed me: ' + msg);
};
</script>
Related
In Qualtrics, I am trying to set two embedded data fields based on the answer to a yes or no question by using an if/else statement in JavaScript. I tried to come up with the correct code, but I am new to JS and what I've come up with (shown below) isn't working; the fields don't get populated.
I have created the embedded data fields in the beginning of the survey flow, so I don't think that's the issue.
Qualtrics.SurveyEngine.addOnPageLoad(function() {
if("${q://QID14/ChoiceGroup/SelectedChoices}"!="Yes")
{Qualtrics.SurveyEngine.setEmbeddedData("Active_Duty_Yes", "X");
Qualtrics.SurveyEngine.setEmbeddedData("Active_Duty_No", "")}
else if("${q://QID14/ChoiceGroup/SelectedChoices}"!="No")
{Qualtrics.SurveyEngine.setEmbeddedData("Active_Duty_No", "X");
Qualtrics.SurveyEngine.setEmbeddedData("Active_Duty_Yes", "")};
});
Your onload function name is wrong. It should be addOnload instead of addOnPageLoad.
A couple of other suggestions:
Is is generally better to use use recodes instead of strings in
logic (e.g., "${q://QID14/SelectedChoicesRecode}"!="1"). That way
if you change the choice text the logic doesn't have to change.
It is generally better to give embedded data flags values of 1 and 0.
I'm running circles here and I'm out of idea's/google searches. There are so many different examples but all seem to do something different or don't work. According to shopify, this is the only documentation I can find around using their API: https://shopify.dev/tutorials/customize-theme-use-products-with-multiple-options
A ghost object I see, no matter, more and more searches I still can't figure out what this parameter is supposed to be.
I've attempted passing a json object of products as I've seen it done in various other theme examples:
var product = document.querySelector("[data-product-json]").innerHTML,
product = JSON.parse(product || '{}');
console.log(product);
jQuery(function($) {
new Shopify.OptionSelectors('productSelect-' + product.id, {
product: product,
onVariantSelected: selectCallback
});
});
The console log gives the correct object and json, nice.
OptionSelectors errors out:
Uncaught TypeError: Cannot read property 'parentNode' of null
at Shopify.OptionSelectors.replaceSelector (option_selection-fe6b72c2bbdd3369ac0bfefe8648e3c889efca213baefd4cfb0dd9363563831f.js:1)
at new Shopify.OptionSelectors (option_selection-fe6b72c2bbdd3369ac0bfefe8648e3c889efca213baefd4cfb0dd9363563831f.js:1)
I've given it just the product.id and various other things.
I'm going to out on a whim here and say the Shopify documentation is detailed, yes, but it is not developer-friendly in my opinion. They give you so much information but never what you really need.
Shopify products have a pretty simple, but weird in a way organization. First off, a product has an array of up to three things known as options. Can be empty. But as soon as you assign an option to a variant, this gets filled in. So you have your three options. Eg: Name, Size, Color, Material and on and on.
A variant has the actual value of the options. So if you provided option 1 as Size, a variant would have option1 equal to a size value, like "large". Repeat and layer in the other options, till a variant perhaps has 3. Now, reverse that process to simply get an ID so you can update a price, or some other logic!
So in this way, up to 100 variants can have 3 distinguishing options, all different. Going way back to Shopify early days, they produced some code that ended up lasting about ten years, and your snippet of OptionSelectors is an offshoot of that mess.
The challenge is to do what that old code did, but for your theme purposes. Many libraries and themes have done just that. But be aware they also used code that is not exactly easy to fork and use for your own purposes either.
So if you find hacking this old Shopify code to be a mind-numbing experience, you might do better to just rebuild Humpty Dumpty yourself so you completely understand it. You do not need to use their code. It is also super confusing because when Theme authors spawn yet another version of this code, they often thought they'd be clever and rename a few things or target a few different things, and thus establish themselves as "unique, more skilled" players, but in fact, this just adds to your misery, as they did not accomplish much.
So yes, all the best to you in your endeavors. Taking apart that code and learning it is a rite of passage most theme authors undergo. Yes, they swear. Yes it reveals some WTF moments. But in the end, you'll control your variants, and achieve glory.
I strongly recommend David Lazar's answer and that you take some time to build your own function(s) that can do the job of breaking down a product's options and associated values into customer-friendly options and then translating those selections into a single valid variant ID. It's not too hard and sometimes kinda fun.
However, if you just want to get the OptionSelectors code working:
var product = document.querySelector("[data-product-json]").innerHTML,
product = JSON.parse(product || '{}');
console.log(product);
jQuery(function($) {
new Shopify.OptionSelectors('productSelect-' + product.id, {
product: product,
onVariantSelected: selectCallback
});
});
The first parameter that goes into the function is the ID of the (usually hidden) element inside your form that will store the value of the ID of the selected variant.
The error you are getting from the OptionSelectors code:
Uncaught TypeError: Cannot read property 'parentNode' of null
is most often thrown when the code doesn't find that element.
So to fix your problem, in your product form you should just need to find (or create) an input field with name="id" and make sure that element has an ID that matches what you're using.
For example:
<input type="hidden" id="productSelect-{{ product.id }}" name="id" value="{{ product.selected_or_first_available_variant.id }}" />
(Alternatively, find your input with name="id" and take the ID attribute from that field and use it in your call to OptionSelectors)
The problem you will encounter is that different themes change what they pass. You are better off creating your own event handler to get the variant id and lookup the variant details that your code requires.
See Shopify trigger function on variant selection but don't override existing functionality for some tips on how to set that up.
Hopefully I explain this to where it makes sense, the most I could find by searching terms like I used in the title gave plenty of autocomplete examples, but nothing quite what I'm looking for. I have a list of buttons (they're coded as inputs right now) and I want to add a search field that will narrow down the buttons as the user types in a search field.
Say for example, I have 30 buttons with popular websites. If a person wanted to pull Google, they'd start typing it out which would start by including everything with the letter "G" in it, then "O", etc. Everything else would "disappear" from the page.
I can sort of think of a way to do this manually, but I think my code wouldn't be DRY. Possibly set an "on" and "off" ID, and use CSS to display:none or something to that effect.
I think the best way to do this would be via AJAX, but there may be some javascript voodoo more applicable.
To easy. At first, its unneccessary to filter the answers serverside, if all the data is already at the users. Also, you shouldnt write html and filter it with js, you should write it in js and generate an html output. Lets start with the structure:
var links=[
{
name:"google",
url:"http://google.com"
},
{nextone}
];
Now generate the links in html:
window.onload=function(){
var container=document.body;//change this to your needs
for(i=0;i<links.length;i++){
var link=links[i];
link.html=document.createElement("a");
link.html.innerHTML=link.name;
link.html.src=link.url;
container.appendChild(link.html);
}
};
If sth is inputed, hide the the unmatched ones:
function filter(string){
//loop trough links
for(i=0;i<links.length;i++){
var link=links[i];
//if string doesnt match name
if(!link.name.split(string)[1]){
link.html.style.display="none";
}else{
link.html.style.display="block";
}
}
}
Use like this:
filter("goo");
You could bind that to an input:
yourinput.addEventListener("onchange",function(){filter(this.value)},false);
I have a Person class where edits made to the person must be verified by an admin user.
Each attribute has an "approved" and "tmp" version. Sometimes the "tmp" version is not set:
person = {first:'Bob', firstTmp:'Robert', last:'Dobbs', lastTmp:undefined}
When displaying the person, I want to display the "tmp" value if it is set, otherwise display the "approved" value. When writing, I want to write to the "tmp" value (unless logged in as an admin).
Ideally, this would not require a lot of custom markup, nor writing cover methods for each property (there are around 100 of them). Something like this would be nice:
<input ng-model="person.first"
tmp-model="person.firstTmp"
bypass-tmp="session.user.isAdmin" />
When displaying the value, display the tmp value if it is defined. Otherwise display the approved value.
When writing the value, write to the tmp value, unless logged in as an admin. Admins write directly to the approved value.
What's a good clean way to implement this in Angular?
Extend NgModelController somehow?
Use a filter/directive on the input?
Cover methods?
Just do the writing server-side?
I will try to go through your options one by one:
Extend NgModelController somehow?
I don't think this is a good idea. It won't be nice if something goes wrong and you don't know if you can even rely on something as basic as ng-model
Just do the writing server-side?
This would seem like the easier way (if you already know or find it easy to manage it in the back end), although the interaction would need a new request to the server.
Use a filter/directive on the input?
I believe this would be the best way to do it, as it is easy to understand what is going on by just taking a look at the markup. It's angular, you already know that some property like tmp-model is extending the markup.
Cover methods?
This would also be easy to implement, and you would be implementing some sort of "business logic" as a validator in your cover method.
Given that I've extended a bit in my answer, I can give you an inline example of the last one.
<input ng-model="person.firstTmp"
ng-init="person.firstTmp = person.firstTmp || person.first"
ng-change="updateProperty(person, 'first')" />
And on the controller, you could do something like:
$scope.updateProperty = function(person, propertyName) {
// The temporary property has already been changed, update the original one.
if($scope.session.user.isAdmin)
person[propertyName] = person[propertyName + 'Tmp'];
}
I will first explain what I'm trying to do then I will explain why just in case you get bored of reading the whole scenario.
Basically I have some HTML markup stored in a variable I now need to a wait to access the different elements within the variable. For example:
var markUp = "<h3>h3 tag</h3><p>paragraph tag</p>";
What I need to know is if there is a way for me to query the variable to retrieve say the h3 tag, in a similar way you would use the query function ? I have seen some other practices where people append the var to a hidden div then query the div. I would prefer to avoid this but if that is the only way I will proceed.
I have come across this problem whilst developing a drag and drop application, on drop i use a custom creator function to change the items structure once it is dropped.
If further explanation is needed please say, thanks advance Jonathan
You can use dojo._toDom to create a DOM fragment from your string.
var markup = "<h3>h3 tag</h3><p>paragraph tag</p><p>another paragraph</p>";
var domFragment = dojo._toDom(markup);
dojo.query("p", domFragment).forEach(function(element,i) {
console.debug(element.innerHTML);
});
The underscore prefix in _toDom means that it's a "private" member method of dojo. Normally, it's bad practice to use these as if they were public (like I do here). However, in the case of _toDom I believe it's generally considered acceptable, and according to this trac entry, it sounds like it'll be made public in the next version.