[SOLVED]Ember-cli: Route models break jquery plugins


#1

Hey everyone…me again :frowning: . I recently got my ember-cli up and running with css,scss,js, and jquery. Everything works just fine until I add return this.store.find('element'); into my routes model. I am using ember-cli mocks to serve up the json. Here is my elements mock and model:

Mock:

module.exports = function(app) {
  var express = require('express');
  var elementsRouter = express.Router();

  elementsRouter.get('/', function(req, res) {
    res.send({
      'elements': [{
            id: 1,
            thumbnail: 'http://lorempixel.com/100/100/abstract/',
            title: 'Moorors Epic Stripe',
            author: 'Mooror',
            rating: 2,
            date: '1999-05-18',
            description: 'Its just to epic to talk about',
            type:'stripe',
                },
        {
            id: 2,
            thumbnail: 'http://lorempixel.com/100/100/abstract/',
            title: 'For The Forg',
            author: 'Ben Forg',
            rating: 4,
            date: '1912-08-12',
            description: 'its just for doing neat stuff',
            type:'stripe',
                },
        {
            id: 3,
            thumbnail: 'http://lorempixel.com/100/100/abstract/',
            title: 'Jim for the win',
            author: 'Jim Blake',
            rating: 5,
            date: '1999-05-15',
            description: 'This one is practical',
            type:'stripe',
                },
        {
            id: 4,
            thumbnail: 'http://lorempixel.com/100/100/abstract/',
            title: 'Js son is cool',
            author: 'Json',
            rating: 1,
            date: '1859-07-28',
            description: 'json plugin for making cool noises with your mouth',
            type:'stripe',
                },
      {
            id: 5,
            thumbnail: 'http://lorempixel.com/100/100/abstract/',
            title: 'Js son is cool',
            author: 'Json',
            
            date: '1859-07-28',
            description: 'json plugin for making cool noises with your mouth',
            type:'stripe',
                }]
    });
  });

  elementsRouter.post('/', function(req, res) {
    res.status(201).end();
  });

  elementsRouter.get('/:id', function(req, res) {
    res.send({
      'elements': {
        id: req.params.id
      }
    });
  });

  elementsRouter.put('/:id', function(req, res) {
    res.send({
      'elements': {
        id: req.params.id
      }
    });
  });

  elementsRouter.delete('/:id', function(req, res) {
    res.status(204).end();
  });

  app.use('/api/elements', elementsRouter);
};

Model:

import DS from "ember-data";
 
export default DS.Model.extend({
    title: DS.attr('string'),
    author: DS.attr('string'),
    thumbnail: DS.attr('string'),
    rating: DS.attr('number', {defaultValue: 0}),   
    date: DS.attr('string', {defaultValue: function() { return new Date();}}), 
    description: DS.attr('string'),
    type: DS.attr('string')
    

});

And as for the actual js I am using app.imports for my plugins and then using inline script code for initializing them (using ember-cli-inline-content). Here’s my inline code:

$( document ).ready(function() {
     $(document).foundation();
    $(".placeholder-container").click(function(){
        var modalName = $(this).data('component-type');
        window.location.href='/modals/' + modalName;
    }); 
    $(".edit-element").html('<i class="icon-plus-circled"></i>');
    $(".edit-element").click(function(){
        var modalName = $(this).data('component-type');
        window.location.href='/modals/' + modalName;
    }); 
    
    $('#rate').raty({ 
        starOff : 'star-off.png',
        starOn  : 'star-on.png',
        readOnly: true,
        score: function() {
            return $(this).attr('data-score'); 
        },
        path: 'assets/images/'
    });
    
});

#2

I would suggest wrapping the jquery in a component, then it can be used in your template with a single tag. (I know this doesn’t deal with the route#model issue. If it doesnt work after this, can you post the whole route#model function?)

// index.hbs
{{jquery-raty value=rating}}
{{jquery-raty value=3 readOnly=true}}
// components/jquery-raty.js
import Ember from 'ember';
export default Ember.Component.extend({

  // called with element created
  _init: function() {
    var self = this;
    var options = {
      // set options you want to use in template
      score: this.get('value'),
      readOnly: this.get('readOnly'),

      // this option will be set for all instances
      cancel: true,

      // same as value set in Brocfile.js
      path: 'assets/images'
    };

    // set click handler function
    options['click'] = function(score) {
      self.set('value', score);
    };

    // apply raty to the component element
    this.$().raty(options);

  }.on('didInsertElement'),

  // watches for changes to 'value' attr and updates raty
  _changed: function() {
   this.$().raty('score', this.get('value'));
  }.observes('value')

});

This a little old, (uses ‘vendor’ instead of ‘bower_components’) but I found it very useful wrapping my own components http://eviltrout.com/2014/06/03/jquery-component.html


#3

Don’t forgot to cleanup http://emberjs.com/api/classes/Ember.Component.html#method_willDestroy and wrap your call back function to an external lib with a Ember.run.

didInsertElement: function() {
  var _this = this;
  var pikaday = new Pikaday({
    // this.$() grabs the jquery dom element of this component
    // the zeroith element grabs the native dom element
    field: this.$()[0],
    onSelect: function() {
      // You need to wrap third party event logic in Ember.run
      // if you don't do this unit tests will require you wrap
      // calls that trigger this event in an Ember.run
      // http://guides.emberjs.com/v1.11.0/understanding-ember/run-loop/#toc_how-is-runloop-behaviour-different-when-testing
      Ember.run(function() {
        // sending an action so that parent components know data has changed
        _this.sendAction('selected');
      });
    }
  });
  this.set('_pikaday', pikaday);
},

// because the dom element corresponding to this component
// will be destroyed and recreated whenever there is a template
// rerender it's essential to cleanup your third party lib
willDestroyElement: function() {
  this.get('_pikaday').destroy();
}

#4

Once again dpreston I am in dept to you for your kindness. I cannot express how helpful you have been in this thread and others. Your code worked wonders for me after I added a ) you missed after the this.$().raty('score', this.get(value);. This component works just fine with or without my routes model being included. This is very interesting to me as another component I have been using called ember-rl-dropdown also worked with my model data when all my jquery plugins would not. I think I will go with this approach for all my jquery plugins needs as it works very well. Thanks again Preston.

P.S. I didn’t point out the missing ) to be critical of your work. I only wanted people to know exactly what I did to get this working.

P.P.S Will I still need to use the cleanup code that varblob suggested with this approach?


#5

Yes, you should use the run loop and cleanup code @varblob provided. And thanks for the missing ), I’ll fix that in my code example


#6

Sorry to bother you gentlemen again I just setup all of the code that you have both so helpfully provided when it dawned on me what varblob was meaning in regards to the willDestroyElement being essential for cleanup. That being said I must say that I unfortunately have very little knowledge pertaining to the code he posted above. I assume that this code was taken from a plugin configuration for a plugin called pikaday and not an example specifically for my plugin. Could someone perhaps point me in the right direction for my plugins equivalent?Or perhaps point me to some clear documentation on it?Thanks in advance and I am sorry for my lack of knowledge in this area. Ember Is my first MVC/client side framework.

Mooror


#7

It’s not an mvc thing as much as it’s a general programming/event thing. Basically if you use events you have remove your event listener, and if whatever library you’re using has a destroy function call that too. Sometimes destroy automatically cleans up the event listeners but it’s library specific.


#8

So what would I use .destroy(); on in the above code that dpreston gave? I dont quite understand. Does the code you gave go in the brocfile or the component .js file? And If It goes in the brocfile then I cant understand why I would need to declare my component functionality twice (once in the component .js and once in the Brocfile). Please clear this up for me

Mooror


#9

You use destroy in the component.js

// components/jquery.raty.js
  _init: function() {...}.on('didInsertElement'), // called when component renders

  _destroy: function() {
    this.$().raty('destroy'); // call raty's .destroy() function
  }.on('willDestroyElement'), // called when component removed, route changes etc.

The Brocfile handles the lib.js and static assets, it doesnt know anything about how the component/lib works.


#10

Thank you sir for clearing this up for me. I regret to say that I am still coming across the problem of the rating not updating when I change routes.For example I am using dynamic urls like so:

this.route('stripes');
this.route('stripe', { path: 'stripes/:element_id' });

And I have successfully got the system working so that when I go to stripes/1,stripes/2,stripes/3,etc. that they show the corresponding data for that stripe. However when I change routes from something like stripes/1 to stripe/2 all the data changes correctly except for the rating which uses the component. Perhaps the .raty('destroy') is not working? Here’s my full component code:

import Ember from 'ember';
export default Ember.Component.extend({
    classNames: ['rating'],
    // called with element created
    _init: function () {
        var self = this;
        var options = {
            // set options you want to use in template
            score: this.get('score'),
            readOnly: this.get('readOnly'),
            
            // this option will be set for all instances
            cancel: false,
            starOn: 'thumbs-on.png',
            starOff: 'thumbs-off.png',
            // same as value set in Brocfile.js
            path: 'assets/images'
        };

        // set click handler function
        options['click'] = function (score) {
            self.set('score', score);
        };

        // apply raty to the component element
        this.$().raty(options);

    }.on('didInsertElement'),

    _destroy: function() {
        this.$().raty('destroy'); // call raty's .destroy() function
    }.on('willDestroyElement'), // called when component removed, route changes etc.
    
    // watches for changes to 'value' attr and updates raty
    _changed: function () {
        this.$().raty('score', this.get('score'));
    }.observes('score')
    
    

});

Once again I thank you both for your amazing help and I apologize for using up so much of your time. Mooror


#11

Are you using readOnly=true? That was the only way I was able to replicate the problem.

Your code above worked until I used the readOnly option as my previous statement that ‘willDestroyElement’ is called on all route changes is incorrect (oops). Changing dynamic segments ('/:element_id') but remaining in the same route declaration doesnt destroy the element.

Do you need readOnly? Maybe a static non-reactive list of rating stars would work better.


#12

You can observe the parent model for changes and rerender the whole component. Its not a great solution (I’m not sure it will continue to work with routeable components, and it is fairly tightly bound to the parent) but it may be what you’re looking for.

// component.js

  _modelChanged: function() {
    this.rerender();
  }.observes('parentModel'),
  parentModelBinding: Ember.Binding.oneWay('targetObject.model'),

Another option is to turn off readOnly before setting the score in _changed


[SOLVED]Ember-cli: How to initialize jquery plugins?
#13

Thank you dpreston for all your help. I decided to take your advise and it turned out to be better then what I had originally had in mind. Thank you both!

Mooror


#14

Heres a few more things to handle when wrapping components

Cleaning up after components in Ember.js