Shopify Cart API bundled section rendering in Dawn - shopify

I am attempting to build a mini cart using Shopify's base Dawn template.
In assets/cart.js the getSectionsToRender function supplies a section id from a data-id attribute. This is then used by a fetch to the Cart Api which has bundled section rendering.
getSectionsToRender() {
return [
{
id: 'main-cart-items',
section: document.getElementById('main-cart-items').dataset.id || 'main-cart-items',
selector: '.js-contents',
},
{
id: 'cart-icon-bubble',
section: 'cart-icon-bubble',
selector: '.shopify-section'
},
{
id: 'cart-live-region-text',
section: 'cart-live-region-text',
selector: '.shopify-section'
},
{
id: 'main-cart-footer',
section: document.getElementById('main-cart-footer').dataset.id || 'main-cart-footer',
selector: '#main-cart-footer',
}
];
}
updateQuantity(line, quantity, name) {
this.enableLoading(line);
const body = JSON.stringify({
line,
quantity,
sections: this.getSectionsToRender().map((section) => section.section),
sections_url: window.location.pathname
});
fetch(`${routes.cart_change_url}`, {...fetchConfig(), ...{ body }})
.then((response) => {
return response.text();
})
.then((state) => {
const parsedState = JSON.parse(state);
console.log(parsedState.sections);
});
}
When the dynamic section id is used eg template--15179940757682__cart-footer the cart api returns the full main-cart-footer element. However the dynamic section id is not available on other pages and in this context I am using main-cart-footer as the section id. Without the dynamic id the cart api returns main-cart-footer with some of the content missing. I have used the same approach with main-cart-items and there are no issues.
I have tried using sections_url parameter set to /cart to specify the page context but this is not working either.
Anyone have any idea how to get the main-cart-footer without a dynamic section id?

Did you try to create a static section just like "cart-live-region-text"? If you create a section like this and add your code in the section(in your case cart footer where cart subtotal gets updated) then you don't need a dynamic section id, you can write the section name(the section which you create) directly in the section id.
You can checkout the following link👇
https://shopify.dev/api/section-rendering#find-section-ids

Related

Add Route Parameter to all Razor Pages in an Area

I'm trying to add a route parameter to all pages in a Razor Pages Area so every URL within an Area has an OrgId e.g. /dashboard/{orgId}/{page}/{route}. I can add them using the AddAreaPageRoute as shown below, but I can't help feeling there's a way to apply this to all pages without having to define an entry for every page in the Area. Is there a way to create a route for all pages in an Area?
.AddRazorPages(options =>
{
options.Conventions.AddAreaPageRoute("Dashboard", "/Index", "Dashboard/{orgId}");
options.Conventions.AddAreaPageRoute("Dashboard", "/AddItem", "Dashboard/{orgId}/AddItem");
options.Conventions.AddAreaPageRoute("Dashboard", "/Items", "Dashboard/{orgId}/Items");
options.Conventions.AddAreaPageRoute("Dashboard", "/Items/Index", "Dashboard/{orgId}/Items/{id}");
})
You can change your code like below:
services.AddRazorPages(options =>
{
options.Conventions.AddAreaFolderRouteModelConvention("Dashboard", "/", model =>
{
foreach (var selector in model.Selectors)
{
var c = selector.AttributeRouteModel.Template.ToString();
selector.AttributeRouteModel = new AttributeRouteModel
{
Order = -1,
Template =c.Replace("Dashboard", "Dashboard/{orgId}")
};
}
});
});
You can see the details in the doc.

Vuex state structure and fetching when using same type of data with different values on different routes

I'm creating a portfolio with vue, vuex and vue-router that will show images.
On the homepage i will show images with 'show_home: true'.
Then there is "tag" pages (portfolio/:tagSlug) that will show images based on a slug, eg. 'weddings' with infinite scroll (auto populate pagination).
An image object will look something like:
{
id: 1,
title: 'Lorem..',
path: '..',
show_home: true
tags: [ {id: 1, slug: 'weddings'} ]
},
{
id: 2,
title: 'Lorem2..',
path: '..',
show_home: false
tags: [ {id: 1, slug: 'weddings'}, {id: 2, slug: 'water'} ]
}
Endpoints examples:
Homepage: GET: ../images/homepage?p=1
Tag page: GET: ../images/:slug?p=1
I can't figure out how I should structure this in vuex and handle the fetching..
Should i just create i single 'images: []' state and populate it with ALL the images after fetching them from the api in each route, then filter them with getters? How can i get the pagination in there in that case? Or do you have a better solution?
Thanks in advance
My preferred approach is to "flatten" the relationships and pull them as needed. This also allows you to only pull what you need from the server or related modules.
tags vuex module:
all: {
1: { <-- indexed by tag id
name: "weddings"
images: [1,2,3,4] <-- image ids
}
}
active: false <-- When there is an active tag, this becomes the id of the tag.
The vuex images module would follow this same pattern:
all: {
1: { <-- indexed by image id
title: 'Lorem..',
path: '..',
show_home: true
tags: [1,2,3,4] <-- tag ids
}
}
active: false <-- When there is an active image, this becomes the id of the image.
Then use a getter to hydrate the images or tags from the respective vuex module.
There is a great write up on this approach on this blog: https://medium.com/js-dojo/structuring-vuex-modules-for-relationships-speed-and-durability-de25f7403643
With this approach you will have fewer and smaller api calls, pagination is manageable and you don't need to worry about stale data in your relationships.
EDITED -- API info:
Two approaches come to mind.
1) always load the images with the tag.
Tag index request would not load any images, just the basic info for each tag.
When the user clicks on a tag, this inits an API call for the tag details:
Tag show request (tags/1 or tags/weddings) would return the tag with loaded relationships:
public function show($id)
{
$tag = Tag::where('id', $id)->with('images')->firstOrFail();
return new TagResource($tag); <-- will have all related images loaded.
}
2) set up a nested REST endpoint if needed
You can use the the resource controllers to shortcut the boilerplate like this:
api.php
Route::apiResource('tags.images', 'tags\TagImageController');
This route will watch your api calls and determine if it is index/store/show/delete. From your front end you can make a call like https://backendsite.com/tags/1/images (If wedding tag has an id of 1)
Then in the TagImageController you would have something like this:
public function index(Request $request, $id)
{
$tag = MemTag::find($id);
$images = $tag->images()->get();
$images->load(Image::allowedIncludes); <- or you can manually list relationships you want to load
return ImageResource::collection($images);
}

How to get ID of Tab of exist url

i want to get ID of Tab of exist url
For exemple i have 3 tabs in chrome
tab 1 is youtube
tab 2 is google
tab 3 is twitter
and i want get id of tab already existed url google com
Here is an example:
// get all the tabs, you can also limit it to the current window if you wish
// chrome.tabs.query({currentWindow: true}, ...)
chrome.tabs.query({}, tabs => {
// loop through the tabs
for (const tab of tabs) {
if (tab.url === 'theOneYouWant) {
// do whatever needed with tab.id
// break/stop the loop
break;
}
}
});
You can change the code to to check for the domain (not the whole URL) or any other relevant criteria.
Use chrome.tabs API in your extension page such as the browserAction popup or the background script.
manifest.json:
"permissions": ["tabs"]
Simplest case - no variations in domain name:
chrome.tabs.query({url: 'https://www.youtube.com/*'}, tabs => {
// use 'tabs' inside the callback
});
Simple case - variations in sub-domain but the TLD (top level domain) name doesn't have variations:
chrome.tabs.query({url: 'https://*.twitter.com/*'}, tabs => {
// use 'tabs' inside the callback
});
Tough case - TLD varies:
const RE_ALL_GOOGLE = /^https:\/\/(www\.)?google\.([a-z]{2,3}|com?\.[a-z]{2})\//;
// the tabs API doesn't accept wildcards in TLD so we need to enumerate all tabs
// and we restrict the list to https-only as an optimization for the case of many open tabs
chrome.tabs.query({url: 'https://*/*'}, tabs => {
const googleTabs = tabs.filter(({url}) => RE_ALL_GOOGLE.test(url));
// use 'googleTabs' here inside the callback
});
You can write a more restrictive regexp by using the full list of all Google domains and a RegExp generator like this one.
Content scripts can't use chrome.tabs directly so you'll need to do it via a background script.
content script:
chrome.runtime.sendMessage({
action: 'getTabs',
url: 'https://*/*',
// messaging can't transfer regexps so we convert it to a string
pattern: /^https:\/\/(www\.)?google\.([a-z]{2,3}|com?\.[a-z]{2})\//.source,
}, tabs => {
// use 'tabs' inside the callback
});
manifest.json:
"permissions": ["tabs"],
"background": {
"scripts": ["background.js"],
"persistent": false
}
background.js:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.action === 'getTabs') {
chrome.tabs.query({url: msg.url}, tabs => {
if (msg.pattern) {
const re = new RegExp(msg.pattern);
tabs = tabs.filter(({url}) => re.test(url));
}
sendResponse(tabs);
});
// keep the reponse channel open since the chrome.tabs API is asynchronous
return true;
}
});

What is the proper way of adding a custom field to Keystone (to be included in an admin UI form)?

I can see the nice explanation for fields, and what they are made of, here: https://github.com/keystonejs/keystone/tree/v4.0.0-beta.5/fields
How do you go about adding a custom field?
Is adding a custom field (versioned in my own project which depends on keystone, or perhaps done generic enough that could be pushed to npm) a matter of importing it during the keystone setup script and somehow mutating the keystone instance or whatever in order that it also loads my field along with the built-in ones?
EDIT:
The use case is in the context of the admin UI (e.g. you have a User keystone model, and you want the User form to have a new custom field whose UI is an arbitrary react component you implement)
The framework does support storage fields like local file, s3, azure, cloudinary images and embedly fields. That might satisfy your file field needs.
Custom Fields
It appears that the keystone wiki has a short tutorial on the keystonejs wiki and at time of writing, custom types aren't supported in the admin UI.
The example code in the wiki includes a validation method for a credit card number, so this might be the type of functionality that you're looking for.
Here's a short example of what a custom type would look like. It's a field that only accepts Jeff or Alexander as a valid value. You would put it in its own myNameType.js file.
var keystone = require('keystone');
var util = require('util');
/*
Custom FieldType Constructor
#extends Field
#api public
*/
function myName(list, path, options) {
// add your options to this
// call super_
this._nativeType = Text;
myName.super_.call(this, list, path, options);
}
/* inherit Field */
util.inherits(myName, keystone.Field);
/* override or add methods */
myName.prototype.validateInput = function(data) {
console.log('validate my name');
var isValid = false;
if (data && (data.toLower() === 'jeff' || data.toLower() === 'alexander')) {
isValid = true;
}
return isValid;
};
Then register your type in the keystonejs startup file:
// Require keystone
var keystone = require('keystone');
// add a new custom Field Type
Object.defineProperty(
keystone.Field.Types,
'MyName',
{
get: function() {
// or whatever your path is
return require('./myName.js');
}
}
);
From there you can use it in a model (remember to set it to hidden because of the lack of admin UI support):
var keystone = require('keystone');
var Types = keystone.Field.Types;
var Person = new keystone.List('Post', {
map: { name: 'title' },
autokey: { path: 'slug', from: 'title', unique: true },
sortable: 'unshift',
perPage: 5,
track: true,
autocreate: true
});
Person.add({
name: { type: Types.MyName, label: 'My Name', hidden: true },
heightInInches: { type: Types.Number, label: 'Height (inches)' },
});
Person.register();

Pass data-attribute value of clicked element to ajax settings

For an implementation of Magnific Popup, I need to pass a post id to the ajax settings. The post id is stored in a data attribute of the element to which Magnific Popup is bound. I would like this to work:
html element:
<a data-id="412">Clicke me</a>
Javascript:
$('.element a').magnificPopup({
type: 'ajax',
ajax: {
settings: {
url: php_array.admin_ajax,
type: 'POST',
data: ({
action:'theme_post_example',
id: postId
})
}
}
});
Where postId is read from the data attribute.
Thanks in advance.
$('.element a').magnificPopup({
callbacks: {
elementParse: function(item){
postData = {
action :'theme_post_example',
id : $(item.el[0]).attr('data-id')
}
var mp = $.magnificPopup.instance;
mp.st.ajax.settings.data = postData;
}
},
type: 'ajax',
ajax: {
settings: {
url: php_array.admin_ajax,
type: 'POST'
}
}
});
Here is how to do it:
html:
<a class="modal" data-id="412" data-action="theme_post_example">Click me</a>
jquery:
$('a.modal').magnificPopup({
type: 'ajax',
ajax: {
settings: {
url : php_array.admin_ajax,
dataType : 'json'
}
},
callbacks: {
elementParse: function() {
this.st.ajax.settings.data = {
action : this.st.el.attr('data-action'),
id : this.st.el.attr('data-id')
}
}
},
parseAjax: function( response )
{
response.data = response.data.html;
}
});
php
function theme_post_example()
{
$id = isset( $_GET['id'] ) ? $_GET['id'] : false;
$html = '<div class="white-popup mfp-with-anim">';
/**
* generate your $html code here ...
*/
$html .= '</div>';
echo json_encode( array( "html" => $html ) );
die();
}
As this answer was the original question regarding inserting data into Magnific's ajax call, I'll post this here.
After many hours of trying to figure this out, you should know that if you're using a gallery with the ability to move between gallery items without closing the popup, using elementParse to set your AJAX data will fail when you visit an item after already viewing it (while the popup is still open).
This is because elementParse is wrapped up in a check that it makes detect if an item has already been 'parsed'. Here's a small explanation as to what happens:
Open gallery at item index 2.
Item has not been parsed yet, so it sets the parsed flag to true and runs the elementParse callback (in that order). Your callback sets the ajax options to fetch this item's data, all is well.
Move (right) to item index 3.
Same as above. The item has not been parsed, so it runs the callback. Your callback sets the data. It works.
Move (left) back to item index 2.
This time the item has been parsed. It skips re-parsing the item's element for assumed potential performance reasons.Your callback is not executed. Magnific's ajax data settings will remain the same as if it were item index 3.
The AJAX call is executed with the old settings, it returns with item index 3's data instead, which is rendered to the user. Magnific will believe it is on index 2, but it is rendering index 3's data.
To resolve this, you need to hook onto a callback which is always executed pre-ajax call, like beforeChange.
The main difference is that the current item isn't passed through into the callback. Fortunately, at this point, magnific has updated their pointers to the correct index. You need to fetch the current item's element by using:
var data = {}; // Your key-value data object for jQuery's $.ajax call.
// For non-closures, you can reference mfp's instance using
// $.magnificPopup.instance instead of 'this'.
// e.g.
// var mfp = $.magnificPopup.instance;
// var itemElement = mfp.items[mfp.index].el;
var itemElement = this.items[this.index].el;
// Set the ajax data settings directly.
if(typeof this.st.ajax.settings !== 'object') {
this.st.ajax.settings = {};
}
this.st.ajax.settings.data = data;
This answer can also be used as a suitable alternative to the currently highest voted, as it will work either way.
You may use open public method to open popup dynamically http://dimsemenov.com/plugins/magnific-popup/documentation.html#public_methods
postId = $(this).attr('data-id')
$(this) retrieve the current element (the link you clicked on), and attr the value of the specified attribute.