Issue #2573Opened February 17, 2020by rastaprime200 reactions

[QUESTION] Get all related CSS Rules from mulit-level CSS Selectors

Question

Hi @artf

Hope you are doing well. I've been cracking on this matter for the past few days now, I've been searching whether I got the same scenario with the others, but unfortunately, I can't find any solutions that can solve my problem, so I decided to raise an issue ticket. I hope I will make sense.

So I have a 'Save Block/Component' feature that I need to grab the HTML and CSS of the selected component. I can easily grab the HTML of the selected component with its child elements. by doing const blockHTML = block.toHTML(); that's working fine. However, my problem lies on the CSS rules of the elements from the parent element going to the child elements.

  1. So my first try is to iterate to each child component of the selected component so that I can use the getStyle() function to extract the CSS of the child components, unfortunately, it's returning an empty object. Please refer to screenshot below for the code block that does this.

Screenshot from 2020-02-17 15-58-34

Here are the logs from the browser for item 1.

Screenshot from 2020-02-17 16-01-43

It seems that it works with the parent component which is the #banner, it can grab the CSS rules, however when I go deeper with the child components it's returning an empty object. Please refer below for screenshot of the HTML markup for the component with it's CSS rules.

#banner Screenshot from 2020-02-17 16-08-03

#banner header Screenshot from 2020-02-17 16-08-43

  1. Then my second try was to go with editor.CodeManager.getCode(childBlock, 'css', {cssc: editor.CssComposer}); That resulted with the following logs.

Screenshot from 2020-02-17 16-12-37

It gives all the CSS rules in the CSS code editor, wasn't able to pull out the related class rules for the specific component.

  1. Tried the CssComposer API with the following code block, please refer below, but I ended up getting all results as null

Screenshot from 2020-02-17 16-17-11

I don't know how can I solve this problem by now, so I'm seeking some help that you may guide me to a proper solution. Looking forward from hearing from you. Thank you.

Regards, Tyrone

Answers (2)

artfMarch 7, 20200 reactions

This is the command I use in Grapedrop to get all the CSS from a component

import each from 'lodash/each';

export default (config = {}) => ({
  run(editor, snd, opts = {}) {
    const component = opts.target || editor.getWrapper();
    const cssc = editor.CssComposer;
    const rules = cssc.getAll();
    let result = '';

    const { atRules, notAtRules } = this.splitRules(this.matchedRules(component, rules));
    notAtRules.forEach(rule =>  result += rule.toCSS());
    this.sortMediaObject(atRules).forEach(item => {
      let rulesStr = '';
      const atRule = item.key;
      const mRules = item.value;

      mRules.forEach(rule => {
        const ruleStr = rule.getDeclaration();

        if (rule.get('singleAtRule')) {
          result += `${atRule}{${ruleStr}}`;
        } else {
          rulesStr += ruleStr;
        }
      });

      if (rulesStr) result += `${atRule}{${rulesStr}}`;
    });

    return result;
  },

  /**
   * Get matched rules of a component
   * @param {Component} component
   * @param {Array<CSSRule>} rules
   * @returns {Array<CSSRule>}
   */
  matchedRules(component, rules) {
    const el = component.getEl();
    let result = [];

    rules.forEach(rule => {
      try {
        if (rule.selectorsToString().split(',').some(
          selector => el.matches(this.cleanSelector(selector))
        )) {
          result.push(rule);
        }
      } catch (err) {}
    });

    component.components().forEach(component => {
      result = result.concat(this.matchedRules(component, rules))
    });

    // Remove duplicates
    result = result.filter((rule, i) => result.indexOf(rule) == i);

    return result;
  },

  /**
   * Return passed selector without states
   * @param {String} selector
   * @returns {String}
   */
  cleanSelector(selector) {
    return selector.split(' ').map(item => item.split(':')[0]).join(' ');
  },

  /**
   * Split an array of rules in atRules and not
   * @param {Array<CSSRule>} rules
   * @returns {Object}
   */
  splitRules(rules) {
    const atRules = {};
    const notAtRules = [];

    rules.forEach(rule => {
      const atRule = rule.getAtRule();

      if (atRule) {
        const mRules = atRules[atRule];

        if (mRules) {
          mRules.push(rule);
        } else {
          atRules[atRule] = [rule];
        }
      } else {
        notAtRules.push(rule);
      }
    });

    return {
      atRules,
      notAtRules,
    };
  },

  /**
   * Get the numeric length of the media query string
   * @param  {String} mediaQuery Media query string
   * @return {Number}
   */
  getQueryLength(mediaQuery) {
    const length = /(-?\d*\.?\d+)\w{0,}/.exec(mediaQuery);
    if (!length) return Number.MAX_VALUE;

    return parseFloat(length[1]);
  },

  /**
   * Return a sorted array from media query object
   * @param  {Object} items
   * @return {Array}
   */
  sortMediaObject(items = {}) {
    const itemsArr = [];
    each(items, (value, key) => itemsArr.push({ key, value }));
    return itemsArr.sort(
      (a, b) => this.getQueryLength(b.key) - this.getQueryLength(a.key)
    );
  }
})
rastaprime20March 7, 20200 reactions

Hi @artf

Thank you so much for your response! I will try this out, and let you know if I run into any problems. I really appreciate this.

Related Questions and Answers

Continue research with similar issue discussions.

Paid Plugins That Match This Issue

Curated by issue keywords and label relevance to help you ship faster.

View all plugins

Loading paid plugin recommendations...

Browse Plugin Categories

Jump directly to plugin category pages on the marketplace.