mercredi 3 octobre 2018

EmberJS: Customize Ember CLI build

I want to transform a file and put it in another directory as a part of the dev and prod build processes. How can I achieve this?




How to determine the environment of your ember app

How can I access the environment of my ember application? My goal is to have a computed property called isStaging to check if the environment is staging




Remove showing Ember Handlebars Indentation Error in terminal

<h2> Login Here </h2>
<form >
    
    
    <button type = "submit"> Login </button>
</form>

Even this simple Ember program showing indentation error, which is not making any problem to run the code, but still it's irritating. Please find a solution to remove showing the indentation error




mardi 2 octobre 2018

Ember Data 403 Get Response Not Entering Error Action

So I am trying to secure an API endpoint so only the user who owns an object can fetch details about that object.

The API is returning a 403 response, as well as a JSONAPI compliant json payload of:

{
  "errors": [
    { "status": "403", "title": "Forbidden", "detail": "You are not authorized to access this resource" }

  ]
}

Great! So at this point I am trying to hook into the route's lifecycle to transition the user to the home page should they try and look at a resource that belongs to someone else.

https://www.emberjs.com/api/ember-data/release/classes/DS.AdapterError implies this is as simple as adding an error action to the route and doing whatever to handle it.

//routes/my-resource.js
export default Route.extend({
  model(params) {
    this.store.findRecord('my-resource', params.id)
  }
});

//routes/application.js
export default Route.extend(ApplicationRouteMixin, {
  actions: {
    error(error, transition) {
      debugger
    }
  }
});

I never hit this debugger, because the error action is never called. I have tried it at the application route level and the specific route level. Instead the route loads as normal, but there is a generic Ember Error (pasted below) in the console, and obviously the resource is not in the store.

I'm somewhat at a loss of what to try. I hooked into handleResponse at the adapter level and tried manually emitting the DS.ForbiddenError, but the route still does not call the error hook.

Uncaught ErrorClass {isAdapterError: true, stack: "Error: Ember Data Request GET /api/my-resource… (http://localhost:4200/assets/vendor.js:3609:31)", description: undefined, fileName: undefined, lineNumber: undefined, …}code: undefineddescription: undefinederrors: [{…}]fileName: undefinedisAdapterError: truelineNumber: undefinedmessage: "Ember Data Request GET /api/my-resource/2 returned a 403↵Payload (Empty Content-Type)↵[object Object]"name: "Error"number: undefinedstack: "Error: Ember Data Request GET /api/my-resource/2 returned a 403↵Payload (Empty Content-Type)↵[object Object]↵    at ErrorClass.EmberError (http://localhost:4200/assets/vendor.js:13638:31)↵    at ErrorClass.AdapterError (http://localhost:4200/assets/vendor.js:90664:17)↵    at new ErrorClass (http://localhost:4200/assets/vendor.js:90682:24)↵    at Class.handleResponse (http://localhost:4200/assets/vendor.js:103063:18)↵    at Class.handleResponse (http://localhost:4200/assets/vendor.js:110305:19)↵    at Class.superWrapper [as handleResponse] (http://localhost:4200/assets/vendor.js:53436:28)↵    at ajaxError (http://localhost:4200/assets/vendor.js:103345:25)↵    at ajaxErrorHandler (http://localhost:4200/assets/vendor.js:103372:12)↵    at Class.hash.error (http://localhost:4200/assets/vendor.js:103140:23)↵    at fire (http://localhost:4200/assets/vendor.js:3609:31)"__proto__: EmberError
onerrorDefault @ rsvp.js:24
trigger @ rsvp.js:66
(anonymous) @ rsvp.js:886
invoke @ backburner.js:247
flush @ backburner.js:167
flush @ backburner.js:326
_end @ backburner.js:748
end @ backburner.js:513
_run @ backburner.js:793
_join @ backburner.js:769
join @ backburner.js:567
join @ index.js:164
hash.error @ rest.js:880
fire @ jquery.js:3268
fireWith @ jquery.js:3398
done @ jquery.js:9307
(anonymous) @ jquery.js:9548
load (async)
send @ jquery.js:9567
ajax @ jquery.js:9206
_ajaxRequest @ rest.js:893
_ajax @ rest.js:913
(anonymous) @ rest.js:883
initializePromise @ rsvp.js:397
Promise @ rsvp.js:877
ajax @ rest.js:873
findRecord @ rest.js:436
Ember.RSVP.Promise.resolve.then @ -private.js:9195
tryCatcher @ rsvp.js:200
invokeCallback @ rsvp.js:372
(anonymous) @ rsvp.js:436
(anonymous) @ rsvp.js:14
invoke @ backburner.js:247
flush @ backburner.js:167
flush @ backburner.js:326
_end @ backburner.js:748
end @ backburner.js:513
_run @ backburner.js:793
_join @ backburner.js:769
join @ backburner.js:567
join @ index.js:164
(anonymous) @ index.js:265
mightThrow @ jquery.js:3534
process @ jquery.js:3602
setTimeout (async)
(anonymous) @ jquery.js:3640
fire @ jquery.js:3268
fireWith @ jquery.js:3398
fire @ jquery.js:3406
fire @ jquery.js:3268
fireWith @ jquery.js:3398
ready @ jquery.js:3878
completed @ jquery.js:3888




Ember one focus active list - recursive problem

I am building a multi-select checkdown group item list. The goal is to have only ONE active group at a time. So the user can select parents - but if they select a child item, that child group becomes the focus and the selected parents become unchecked.

<div class="checkboxhandler">
  <input 
    type="checkbox" 
    checked=
    onclick=
  >
  <label> -- checked: </label>

  
    

       <CheckboxGroup @item= @onClick= />

    
  
</div>

I've got as far with the checkboxes with recursive helper checks.

This is the helper tree - where the logic to deselect takes place. This application also needs to hold the array for selectedItems - but needs to clear those array's as well as the checkboxes.

const toggle = value => !value;
const disable = () => false;

// the roots / siblings are contained by arrays
export function check(tree, id, transform = toggle) {
  if (tree === undefined) return undefined;

  if (Array.isArray(tree)) {
    return tree.map(t => check(t, id, transform));
  } 

  if (tree.id === id || id === 'all') {
    return checkNode(tree, id, transform);
  }

  if (tree.children) {
    return checkChildren(tree, id, transform);
  }

  return tree;
}

function selectOnlySubtree(tree, id, transform) {
  return tree.map(subTree => {
    const newTree = check(subTree, id, transform);

    if (!newTree.children || (transform !== disable && didChange(newTree, subTree))) {
      return newTree;
    } 

    return disableTree(subTree);
  });
}

function isTargetAtThisLevel(tree, id) {
  return tree.map(t => t.id).includes(id);
}

function checkNode(tree, id, transform) {
  return { 
    ...tree, 
    checked: transform(tree.checked),
    children: disableTree(tree.children)
  };
}

function disableTree(tree) {
  return check(tree, 'all', disable);
}

function checkChildren(tree, id, transform) {
  const newChildren = check(tree.children, id, transform);
  const changed = didChange(tree.children, newChildren);
  const checked = changed ? false : (
    id === 'all' ? transform(tree.checked) : tree.checked
  );

    return { 
        ...tree, 
        checked: checked,
    children: check(tree.children, id, transform) 
  };
}

export function didChange(treeA, treeB) {
  const rootsChanged = treeA.checked !== treeB.checked;

  if (rootsChanged) return true;

  if (Array.isArray(treeA) && Array.isArray(treeB)) {
    return didChangeList(treeA, treeB);
  }

  if (treeA.children && treeB.children) {
        return didChangeList(treeA.children, treeB.children);
  }

  return false;
}

function didChangeList(a, b) {
  const compares = a.map((childA, index) => {
    return didChange(childA, b[index]);
  });

  const nothingChanged = compares.every(v => v === false);

  return !nothingChanged;
}

//latest ember fiddle https://canary.ember-twiddle.com/468a737efbbf447966dd83ac734f62ad?openFiles=tests.unit.utils.tree-helper-test.js%2C

so these are valid conditions -- just parents selected enter image description here

-- just children selected enter image description here

but the current bugs are occuring

  1. currently - I can select chilli - then burger - and no decheck of chilli occurs - so that's a bug
  2. currently - I can select coffee maker - then pickle - and no decheck of coffee maker occurs - so that's a bug
  3. currently - I can select filter - then chilli - and no decheck of filter occurs - so that's a bug

//illustration of error 1 - so only burger should remain selected in this instance enter image description here

//illustration of error 2 - so only pickle should remain selected in this instance enter image description here

//illustration of problem 3 - so only chilli should remain selected in this instance enter image description here




How to test if element has focus in ember component test?

I am writing component test that I am testing if the element has focused. I tried using the document.activeElement in order to accomplish this however, I am getting mix results sometimes the assert fails sometimes the asserts succeeds. Is there another strategy to test focus?




lundi 1 octobre 2018

How to add updating labels in D3 Vertical Bar Chart in an Ember Application

I have a vertical bar chart in my Ember application and I am struggling to attach text labels to the top of the bars.

The chart is broken up into the following functions:

Drawing the static elements of the chart:

didInsertElement() {
    let svg = select(this.$('svg')[0]);
    this.set('svg', svg);
    let height = 325
    let width = 800


    let padding = {
      top: 10,
      bottom: 30,
      left: 40,
      right: 0
    };
    this.set('barsContainer', svg.append('g')
      .attr('class', 'bars')
      .attr('transform', `translate(${padding.left}, ${padding.top})`)
    );
    let barsHeight = height - padding.top - padding.bottom;

    this.set('barsHeight', barsHeight);
    let barsWidth = width - padding.left - padding.right;

    // Y scale & axes
    let yScale = scaleLinear().range([barsHeight, 0]);
    this.set('yScale', yScale);
    this.set('yAxis', axisLeft(yScale));
    this.set('yAxisContainer', svg.append('g')
      .attr('class', 'axis axis--y axisWhite')
      .attr('transform', `translate(${padding.left}, ${padding.top})`)
    );

    // X scale & axes
    let xScale = scaleBand().range([0, barsWidth]).paddingInner(0.15);
    this.set('xScale', xScale);
    this.set('xAxis', axisBottom(xScale));
    this.set('xAxisContainer', svg.append('g')
      .attr('class', 'axis axis--x axisWhite')
      .attr('transform', `translate(${padding.left}, ${padding.top + barsHeight})`)
    );


    // Color scale
    this.set('colorScale', scaleLinear().range(COLORS[this.get('color')]));

    this.renderChart();
    this.set('didRenderChart', true);
  },

This re-draws the chart when the model changes:

 didUpdateAttrs() {
    this.renderChart();
  },

This handles the drawing of the chart:

  renderChart() {
    let data = this.get('data');
    let counts = data.map(data => data.count);

    // Update the scales
    this.get('yScale').domain([0, Math.max(...counts)]);
    this.get('colorScale').domain([0, Math.max(...counts)]);
    this.get('xScale').domain(data.map(data => data.label));

    // Update the axes
    this.get('xAxis').scale(this.get('xScale'));
    this.get('xAxisContainer').call(this.get('xAxis')).selectAll('text').attr("y", 0)
    .attr("x", 9)
    .attr("dy", ".35em")
    .attr("transform", "rotate(40)")
    .style("text-anchor", "start");
    this.get('yAxis').scale(this.get('yScale'));
    this.get('yAxisContainer').call(this.get('yAxis'));


    let barsUpdate = this.get('barsContainer').selectAll('rect').data(data, data => data.label);
    // Enter
    let barsEnter = barsUpdate.enter()
    .append('rect')
    .attr('opacity', 0);
    let barsExit = barsUpdate.exit();
    let div = select('body')
    .append("div")
    .attr("class", "vert-tooltip");

    // Update
    let rafId;
    barsEnter
    .merge(barsUpdate)
    .transition()
    .attr('width', `${this.get('xScale').bandwidth()}px`)
    .attr('height', data => `${this.get('barsHeight') - this.get('yScale')(data.count)}px`)
    .attr('x', data => `${this.get('xScale')(data.label)}px`)
    .attr('y', data => `${this.get('yScale')(data.count)}px`)
    .attr('fill', data => this.get('colorScale')(data.count))
    .attr('opacity', data => {
      let selected = this.get('selectedLabel');
      return (selected && data.label !== selected) ? '0.5' : '1.0';
    })
    .on('start', (data, index) => {
      if (index === 0) {
        (function updateTether() {
          Tether.position()
          rafId = requestAnimationFrame(updateTether);
        })();
      }
    })
    .on('end interrupt', (data, index) => {
      if (index === 0) {
        cancelAnimationFrame(rafId);
      }
    });


    // Exit
    barsExit
      .transition()
      .attr('opacity', 0)
      .remove();

}

I have stripped some tooltip and click events to maintain clarity.

To add the labels I have tried to add the following in the renderChart() function:

barsEnter.selectAll("text")
        .data(data)
        .enter()
        .append("text")
        .text(function (d) { return d.count; })
        .attr("x", function (d) { return xScale(d.label) + xScale.bandwidth() / 2; })
        .attr("y", function (d) { return yScale(d.count) + 12; })
        .style("fill", "white");

with the above code I receive an error to say that xScale and yScale are not found because they are not within this functions scope. If I use:

.attr("x", function (d) { return this.get('xScale')(d.label) + this.get('xScale').bandwidth() / 2; })
.attr("y", function (d) { return this.get('yScale')(d.count) + 12; })

I generate 'this.get' is not a function errors and the context of 'this' becomes the an object with the value of (d).

If I add the X and Y scales as variables to this function like:

let xScale = this.get('xScale')
let yScale = this.get('ySCale')

...

        .attr("x", function (d) { return xScale(d.label) + xScale.bandwidth() / 2; })
        .attr("y", function (d) { return yScale(d.count) + 12; })

Then the x and y attrs are returned as undefined. Please let me know if I have missed anything out.