Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

can I use my custom control bar to draw? #874

Open
dyh333 opened this issue Mar 6, 2019 · 15 comments
Open

can I use my custom control bar to draw? #874

dyh333 opened this issue Mar 6, 2019 · 15 comments
Labels
feature ui Buttons & user interactions

Comments

@dyh333
Copy link

dyh333 commented Mar 6, 2019

hi, can I use my custom control bar to draw, not use the MapboxDraw's controls? thanks

@fxi
Copy link

fxi commented Mar 7, 2019

You can simply use mapbox-gl-js method to add your controls : https://docs.mapbox.com/mapbox-gl-js/api/#icontrol

That, with hiding the default bar controls would do the trick.

You can also make a monkey patch (demo) to add more buttons to the bar :

var draw = new MapboxDraw();
var drawBar = new extendDrawBar({
  draw: draw,
  buttons: [
    {
      on: 'click',
      action: save,
      classes: ['fa', 'fa-floppy-o']
    }
  ]
});

map.addControl(drawBar, 'top-left');

/**
* Helpers
*/
function save() {
  console.log('saved');
}

/**
 * Patch
 */

class extendDrawBar {
  constructor(opt) {
    let ctrl = this;
    ctrl.draw = opt.draw;
    ctrl.buttons = opt.buttons || [];
    ctrl.onAddOrig = opt.draw.onAdd;
    ctrl.onRemoveOrig = opt.draw.onRemove;
  }
  onAdd(map) {
    let ctrl = this;
    ctrl.map = map;
    ctrl.elContainer = ctrl.onAddOrig(map);
    ctrl.buttons.forEach((b) => {
      ctrl.addButton(b);
    });
    return ctrl.elContainer;
  }
  onRemove(map) {
    let ctrl = this;
    ctrl.buttons.forEach((b) => {
      ctrl.removeButton(b);
    });
    ctrl.onRemoveOrig(map);
  }
  addButton(opt) {
    let ctrl = this;
    var elButton = document.createElement('button');
    elButton.className = 'mapbox-gl-draw_ctrl-draw-btn';
    if (opt.classes instanceof Array) {
      opt.classes.forEach((c) => {
        elButton.classList.add(c);
      });
    }
    elButton.addEventListener(opt.on, opt.action);
    ctrl.elContainer.appendChild(elButton);
    opt.elButton = elButton;
  }
  removeButton(opt) {
    opt.elButton.removeEventListener(opt.on, opt.action);
    opt.elButton.remove();
  }
}

I just started to use it. There is maybe a better way.

@tomasdev
Copy link

tomasdev commented Apr 16, 2019

@fxi thoughts on having buttons be setupable via onSetup() of a mode? currently buttonElements is a private object from ui.js that would be very useful to be able to extend it without having to create this whole extend class you provided.
I could make a PR for it

@fxi
Copy link

fxi commented Apr 16, 2019

Yep, a PR with a proper way of doing that could be more useful than the thing I wrote.
No idea if that fits the need for other people. Try to reach one of the maintainers. :)
And by the way, I had no idea what mode was before 3 minutes ago : I'm definitely useless here.

@rodmaz
Copy link

rodmaz commented Dec 31, 2019

@tomasdev Does this mean we cannot right now use the default mapbox-gl-draw control box and simply add additional buttons to it? I could not find any example on how to do it.

@Aliber009
Copy link

Aliber009 commented Apr 7, 2021

hello , how I can add an icone with the patch monkey

@fxi
Copy link

fxi commented Apr 7, 2021

@Aliber009 You should probably use modes instead – but as you ask :

 [...]
  //  add a save button using the "monkey patch"
  const draw = new MapboxDraw();
  const drawBar = new extendDrawBar({
    draw: draw,
    buttons: [{
      on: 'click',
      action: ()=>{ alert('🐒')},
      // Here ⤵️⤵️⤵️⤵️  you can change classes. E.g. using font awesome classes or your own. 
      classes: ['fa', 'fa-floppy-o']
    }]
  });
  map.addControl(drawBar);

Or you can modify the patch to add text content :
https://jsfiddle.net/fxi/vr10g3zc/
image

@jo-chemla
Copy link

jo-chemla commented May 20, 2022

@Aliber009 You should probably use modes instead – but as you ask :

Hi there! From my understanding, the modes property only allows you to select togglable modes. Controls to be displayed on the toolbar can only be selected from existing controls (draw polygon, line, trash etc). Custom Controls cannot be added to the toolbar. Or should I define the icon associated to the custom control somewhere in the definition of that control?

const modes = MapboxDraw.modes
modes['draw_rectangle'] = DrawRectangle;
// ...
<MapboxDraw>
  modes={modes}
  displayControlsDefault={false}
  controls={{
    polygon: true,
    trash: true, 
    // rectangle: true // custom control display
  }}
</MapboxDraw>

Adding rectangle to the controls property results in this error:
Type '{ polygon: true; trash: true; rectangle: true; }' is not assignable to type 'MapboxDrawControls'. Object literal may only specify known properties, and 'rectangle' does not exist in type 'MapboxDrawControls'.ts(2322)

Edit: seems this is because the DrawRectangle custom control does not implement draw.activateUiButton('image') on setup as shown in other default polygon/line_string components here: activateUiButton

@Aliber009
Copy link

Thanks it worked

@mtjosue
Copy link

mtjosue commented Oct 28, 2022

@Aliber009 You should probably use modes instead – but as you ask :

 [...]
  //  add a save button using the "monkey patch"
  const draw = new MapboxDraw();
  const drawBar = new extendDrawBar({
    draw: draw,
    buttons: [{
      on: 'click',
      action: ()=>{ alert('🐒')},
      // Here ⤵️⤵️⤵️⤵️  you can change classes. E.g. using font awesome classes or your own. 
      classes: ['fa', 'fa-floppy-o']
    }]
  });
  map.addControl(drawBar);

Or you can modify the patch to add text content : https://jsfiddle.net/fxi/vr10g3zc/ image

Hello, I'm trying to simply give all the functionality of the draw_polygon button to a new custom button. It just seems like it can be hard to understand its functionality, replacing this icon with the word/text = "Draw" might be more intuitive for the regular user. Any guidance on how I can accomplish this functionality?, thank you ahead of time

@mtjosue
Copy link

mtjosue commented Oct 28, 2022

image
something like this but the functionality in

image

@Usmankt1999
Copy link

HI All,

Can i create a CUSTOM Button and call the delete function to delete the drawn polygon,
What call can trigger the delete function for the polygon in mapbox ?

@meticoeus
Copy link

For those looking to build their own custom control UI, it looks like this is already doable.

Example: hide the buttons and render your own:

let map; // get/construct your Mapbox instance

const draw = new MapboxDraw({
  // don't render the default controls
  displayControlsDefault: false,
});

// position probably doesn't matter.
map.addControl(draw, 'top-left')

// track current mode so you can set active status on custom control buttons
let drawMode = draw.getMode();
map.on("draw.modechange", (e) => (drawMode = e.mode));

// example polygon button click handler.
// add this to your custom rendered controller's polygon button
function handleSelectPolygonMode () {
  draw.changeMode(draw.modes.DRAW_POLYGON)
  // draw.changeMode() doesn't appear to properly trigger map.on('draw.changemode')
  // so we need to manually update our cached mode here
  drawMode = draw.modes.DRAW_POLYGON;

  // or you could make it a toggle button
  if (drawMode === draw.modes.DRAW_POLYGON) {
    drawMode = draw.modes.SIMPLE_SELECT;
  } else {
    drawMode = draw.modes.DRAW_POLYGON;
  }
  draw.changeMode(drawMode);
}

@trumbitta
Copy link

For those looking to build their own custom control UI, it looks like this is already doable.

Example: hide the buttons and render your own:

let map; // get/construct your Mapbox instance

const draw = new MapboxDraw({
  // don't render the default controls
  displayControlsDefault: false,
});

// position probably doesn't matter.
map.addControl(draw, 'top-left')

// track current mode so you can set active status on custom control buttons
let drawMode = draw.getMode();
map.on("draw.modechange", (e) => (drawMode = e.mode));

// example polygon button click handler.
// add this to your custom rendered controller's polygon button
function handleSelectPolygonMode () {
  draw.changeMode(draw.modes.DRAW_POLYGON)
  // draw.changeMode() doesn't appear to properly trigger map.on('draw.changemode')
  // so we need to manually update our cached mode here
  drawMode = draw.modes.DRAW_POLYGON;

  // or you could make it a toggle button
  if (drawMode === draw.modes.DRAW_POLYGON) {
    drawMode = draw.modes.SIMPLE_SELECT;
  } else {
    drawMode = draw.modes.DRAW_POLYGON;
  }
  draw.changeMode(drawMode);
}

This shows only the click handler, but how do you actually render a custom button with that click handler attached to it?

@joel-daros
Copy link

I have a working example here:
https://stackblitz.com/edit/vitejs-vite-bvutvb?file=src%2FExtendedMapboxDraw.ts

I’m adding a custom button do draw rectangles, but it can do anything you want.

🚨 Open the map preview in a NEW WINDOW to see the custom draw bar on the map. When opened side by side with the code, Stackblitz can’t display the draw bar.

@vsidamonidze
Copy link

vsidamonidze commented Jun 18, 2024

Here's a React-based solution that avoids a lot of rigmarole. I ran partially with @joel-daros's solution by overriding onAdd, but instead of injecting the buttons manually I captured a reference to the container element, and then used ReactDOM.createPortal to inject them (expressed as React elements). The goal here is to re-use the control group styling and the built-in control positioning.

import { default as _MapboxDraw } from "@mapbox/mapbox-gl-draw";

class MapboxDraw extends _MapboxDraw {
  _container: HTMLElement | undefined;

  constructor(props: DrawControlProps) {
    super(props);

    const onAdd = this.onAdd;

    this.onAdd = (map: mapboxgl.Map) => {
      this._container = onAdd(map);
      return this._container;
    };
  }
}

type DrawControlProps = {
  position?: ControlPosition;
  drawOptions?: MapboxDraw.MapboxDrawOptions;
};

function DrawControl(props: DrawControlProps) {
  const [container, setContainer] = useState<HTMLElement | undefined>(undefined);

  const control = useControl(
    () =>
      new MapboxDraw({
        ...props.drawOptions,
        displayControlsDefault: false,
      }),
    { position: props.position },
  );

  useEffect(() => {
    if (!container && control._container) {
      setContainer(control._container);
    }
  }, [control, container]);

  return container
    ? createPortal(
        <>
          <button
            title="Polygon tool (p)"
            className="mapbox-gl-draw_ctrl-draw-btn mapbox-gl-draw_polygon"
            onClick={() => control.changeMode("draw_polygon")}
          />
        </>,
        container,
      )
    : null;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature ui Buttons & user interactions
Projects
None yet
Development

No branches or pull requests