What parameters does shopify's 'OptionSelectors' actually need? - javascript

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.

Related

Tracking Conversion Value on GTM without Data Layer on Ecommerce

how are you doing? Hope you're fine.
My name is Thiago. I'm a brazilian enterpreneur that runs a Marketing Agency.
It's been a very long while since I'm in need of help with a very specific matter.
I'll try to explain it here the clearest and easiest as possible.
I'd like to ask sorry for any bad english in advanced. =D
So, the thing is, conversion is a big game changing, whatever it's on Facebook or Google.
Setting up conversions it self isn't that hard, when you have an ecommerce platform that comes with a data layer already setup.
Most of my clients doesn't have an ecommerce with a Data Layer.
I was following the Measure School youtube tutorial to install Active Campaign Tracking, and by doing this I add a template in my Google Tag Manager that almost soved the problem, but the code is note quite working.
And that's where I need help. =D (sorry for the long story kkk)
The Custom Variable "Purchase Function" code is this:
function() {
var price = str.split("Adicionar R$ ", "{{Click Text}}",[1])
return price
}
It's returning unedefined.
I'll add some prints showing the whole process with the most details as possible.
0 - Adding Product to Cart
1 - Product Price in Button Click Text
2 - Purchase Price Not Working
3 - GTM Price Function
I really need help making this Custom Variable "Purchase Price" works.
Thanks a lot.
You are using the split function wrong (for starters, you apply it to a variable str which is undefined. Also, wrong number of parameters and wrong parameters).
Look at the documentation for split here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split
Your function should look something like this:
function() {
// splits Click Text by whitespace an returns an array
var parts = {{Click Text}}.split(" ");
// pop removes the last part from the array and returns it
// numerical index might not work if there is more than one whitespace in the click text
price = parts.pop();
return price;
}

Check Shopify variant stock from variant ID in JavaScript with JSON

I've created a JQuery script that has multiple selector forms to narrow down a complicated multi-sku product in Shopify.
By the time the customer has selected everything, the shopify variant IDs are returned as variables via an array (not getting data directly from Shopify).
e.g.
var idone = "15065378226219"
var idtwo = "13367973249067"
Now I want to use these values to get the variant inventory quantity. I've tried this code
function getValues(callback) {
$.getJSON("/admin/variants/15065378226219.json",function(result){
callback(result); // this should be the return value
});
}
getValues(function(values) {
alert(values);
});
But the alert doesn't do anything, so I don't think anything's happening. My goal is to get the variant inventories for both IDs so I can validate in JS whether or not the product is available (both IDs have inventory greater than 0).
Any guidance would be appreciated!
Thanks
When you access your complex product in Liquid, there is a filter that shows the product off as JSON. Amazingly, this filter is 'json'. So try this Liquid:
var fizzborked = {{ product | json }};
You can inspect that in a comment, or however you wish. Look for your variants. Note the inventory quantity being there. Use that to build your logic. You can no longer make callbacks to Shopify for inventory amounts. 10 years ago people were complaining about having their inventory levels scraped, so Shopify cut that off.
As far as more sophisticated approaches go, you could always use a private App to support a Proxy call, allowing you to make secure Ajax calls to cull whatever data you need to support your use-case. Win Win!

Parse.com relations count

I want to query object from Parse DB through javascript, that has only 1 of some specific relation object. How can this criteria be achieved?
So I tried something like this, the equalTo() acts as a "contains" and it's not what I'm looking for, my code so far, which doesn't work:
var query = new Parse.Query("Item");
query.equalTo("relatedItems", someItem);
query.lessThan("relatedItems", 2);
It seems Parse do not provide a easy way to do this.
Without any other fields, if you know all the items then you could do the following:
var innerQuery = new Parse.Query('Item');
innerQuery.containedIn('relatedItems', [all items except someItem]);
var query = new Parse.Query('Item');
query.equalTo('relatedItems', someItem);
query.doesNotMatchKeyInQuery('objectId', 'objectId', innerQuery);
...
Otherwise, you might need to get all records and do filtering.
Update
Because of the data type relation, there are no ways to include the relation content into the results, you need to do another query to get the relation content.
The workaround might add a itemCount column and keep it updated whenever the item relation is modified and do:
query.equalTo('relatedItems', someItem);
query.equalTo('itemCount', 1);
There are a couple of ways you could do this.
I'm working on a project now where I have cells composed of users.
I currently have an afterSave trigger that does this:
const count = await cell.relation("members").query().count();
cell.put("memberCount",count);
This works pretty well.
There are other ways that I've considered in theory, but I've not used
them yet.
The right way would be to hack the ability to use select with dot
notation to grab a virtual field called relatedItems.length in the
query, but that would probably only work for me because I use PostGres
... mongo seems to be extremely limited in its ability to do this sort
of thing, which is why I would never make a database out of blobs of
json in the first place.
You could do a similar thing with an afterFind trigger. I'm experimenting with that now. I'm not sure if it will confuse
parse to get an attribute back which does not exist in its schema, but
I'll find out, by the end of today. I have found that if I jam an artificial attribute into the objects in the trigger, they are returned
along with the other data. What I'm not sure about is whether Parse will decide that the object is dirty, or, worse, decide that I'm creating a new attribute and store it to the database ... which could be filtered out with a beforeSave trigger, but not until after the data had all been sent to the cloud.
There is also a place where i had to do several queries from several
tables, and would have ended up with a lot of redundant data. So I wrote a cloud function which did the queries, and then returned a couple of lists of objects, and a few lists of objectId strings which
served as indexes. This worked pretty well for me. And tracking the
last load time and sending it back when I needed up update my data allowed me to limit myself to objects which had changed since my last query.

Angular input model conditionally reads from one property, writes to another

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'];
}

Whats the best way to resolve name conflicts in javascript?

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>

Categories