实例3:ember.js一个购物网站的实例教程

段干昊然
2023-12-01

这是一个最少实现的购物网站

1、index.html

<!DOCTYPE html>
<html>
<head>

  <link href='bootstrap.css' rel='stylesheet' charset="utf-8"  />
  <link href='application.css' rel='stylesheet' charset="utf-8" />
  <script src='jquery.js'></script>
  <script src='accounting.js'></script>
  <script src='markdown.js'></script>
  <script src='handlebars.js'></script>
  <script src='ember.js'></script>
  <script src='ember-data.js'></script>
  <script src='app.js'></script>
</head>
<body>
<!-- 这里是顶级模版,使用了bootstrap的标记 主要展示了导航栏 页脚的内容 -->
  <script type='text/x-handlebars' data-template-name='application' charset="utf-8">
    <div class='navbar navbar-default'>
      <div class='container'>
        {{#link-to 'index' class='navbar-brand'}}<img src='images/logo.png' alt='logo' height='34' width='224' />{{/link-to}}
        <ul class='nav navbar-nav navbar-right'>
          {{#link-to 'index' tagName='li'}}Home主页{{/link-to}}
          {{#link-to 'products' tagName='li'}}Products产品{{/link-to}}
          {{#link-to 'contacts' tagName='li'}}Contacts联系{{/link-to}}
        </ul>
      </div>
    </div>
    <div class='container'>
      {{outlet}}
    </div>
    <footer class='container'>
      <hr />
      <p class='pull-left'>&copy; 2013 The Flint &amp; Flame</p>
      <p class='pull-right'>{{#link-to 'credits'}}Credits{{/link-to}}</p>
    </footer>
  </script>

<!--这里的index 模版的内容        -->
  <script type='text/x-handlebars' data-template-name='index'>
    <div class='jumbotron'>
      <h1>Welcome to The Flint &amp; Flame!</h1>
      <p class='tagline'>
        <img {{bind-attr src='logo'}} alt='Logo' />
        Everything you need to make it through the winter.
      </p>
      <p>
        {{#link-to 'products' class='btn btn-primary btn-lg'}}
          Browse All {{productsCount}} Items &raquo;
        {{/link-to}}
      </p>
    </div>
    <div class='row'>
      {{#each onSale}}
        {{product-details product=this classNames='col-sm-4'}}
      {{/each}}
    </div>
    <p class='pull-right text-muted'>Rendered on {{time}}</p>
  </script>

  <!-- 这里是contacts/index 模版的额你容       -->
  <script type='text/x-handlebars' data-template-name='contacts/index'>
    <div class='row'>
      <img {{bind-attr src='avatar'}} alt='Avatar' class='img-thumbnail col-sm-4'/>
      <div class='col-sm-8'>
        <h1>About The Fire Spirits</h1>
        <p>Contact {{contactName}} for more info!</p>
        <p>Current Status: {{open}}.</p>
      </div>
    </div>
  </script>
  <script type='text/x-handlebars' data-template-name='credits'>
    <h1>Thanks for the Help!</h1>
    <p>This site would not be possible without the hardworking Ember Core Team!</p>
  </script>
  <script type='text/x-handlebars' data-template-name='products'>
    <div class='row'>
      <div class='col-sm-3'>
        <div class='list-group'>
        {{#link-to 'products.deals' classNames='list-group-item'}}Deals{{/link-to}}
        {{#link-to 'products.onsale' classNames='list-group-item'}}On Sale{{/link-to}}
      </div>
        <div class='list-group'>
          {{#each}}
            {{#link-to 'product' this classNames='list-group-item'}}
              {{title}}
            {{/link-to}}
          {{/each}}
        </div>
      </div>
      <div class='col-sm-9'>
        {{outlet}}
      </div>
    </div>
  </script>
  <script type='text/x-handlebars' data-template-name='product'>
    <div class='row'>
      <div class='col-sm-7'>
        <h2>{{title}}</h2>
        <h3 class='text-success'>${{money price}}</h3>
        <p class='text-muted'>{{description}}</p>
        <p class='text-muted'>This Product has a {{rating}} star rating!</p>
        <p>Finely crafted by {{#link-to 'contact' crafter}}{{crafter.name}}{{/link-to}}.</p>
        {{render 'reviews' reviews}}
        {{#if isNotReviewed}}
          <div class='new-review'>
            <h3>Review {{title}}</h3>
            {{#if review.text}}
              <p class='text-muted'>{{review.text}}</p>
            {{/if}}
            {{textarea valueBinding=review.text}}
            {{view Ember.Select content=ratings value=review.rating}}
            <button {{action 'createReview'}} class='btn-primary'>Review</button>
          </div>
        {{/if}}
      </div>
      <div class='col-sm-5'>
        <img {{bind-attr src='image'}} class='img-thumbnail img-rounded'/>
      </div>
    </div>
    {{contact-details contact=crafter className='row'}}
  </script>
  <script type='text/x-handlebars' data-template-name='products/index'>
    <h1>Products!</h1>
    <ul class='list-unstyled'>
      {{#each}}
        {{product-details product=this classNames='row' tagName='li'}}
      {{/each}}
    </ul>
  </script>
  <script type='text/x-handlebars' data-template-name='contacts'>
    <div class='row'>
      <div class='col-sm-9'>
        {{outlet}}
      </div>
      <div class='col-sm-3'>
        <div class='list-group'>
          {{#each}}
            {{#link-to 'contact' this classNames='list-group-item'}}
              {{name}}
            {{/link-to}}
          {{/each}}
        </div>
      </div>
    </div>
  </script>
  <script type='text/x-handlebars' data-template-name='contact'>
    <div class='row'>
      <div class='col-sm-5'>
        <img {{bind-attr src='avatar' alt='name'}} class='img-thumbnail img-rounded'/>
      </div>
      <div class='col-sm-7'>
        <h2>{{name}}</h2>
        <p>{{about}}</p>
        {{render 'contact/products' products}}
      </div>
    </div>
  </script>
  <script type='text/x-handlebars' data-template-name='contact/products'>
    <h3>Products here3</h3>
    <ul>
      {{#each}}
        <li>{{#link-to 'product' this}}{{title}}{{/link-to}}</li>
      {{/each}}
    </ul>
  </script>
  <script type='text/x-handlebars' data-template-name='products/onsale'>
    <h1>Products On Sale</h1>
    <ul class='list-unstyled'>
      {{#each}}
        {{product-details product=this classNames='row' tagName='li'}}
      {{/each}}
    </ul>
  </script>
  <script type='text/x-handlebars' data-template-name='products/deals'>
    <h1>Todays Product Deals!</h1>
    <ul class='list-unstyled'>
      {{#each}}
        {{product-details product=this classNames='row' tagName='li'}}
      {{/each}}
    </ul>
  </script>
  <script type='text/x-handlebars' data-template-name='components/product-details'>
    <img {{bind-attr src='product.image'}} class='img-thumbnail col-sm-5' />
    <div class='col-sm-7'>
      <h2>{{product.title}}</h2>
      <p class='product-description'>{{product.description}}</p>
      <p>{{#link-to 'product' product class='btn btn-success'}}
        Buy for ${{money product.price}}{{/link-to}}
      </p>
      {{#if hasReviews}}
        <p class='text-muted'>Read all reviews ({{reviewsCount}}).</p>
      {{/if}}
    </div>
  </script>
  <script type='text/x-handlebars' data-template-name='components/contact-details'>
    <div class='col-sm-9'>
      <h2>About {{contact.name}}</h2>
      <p>{{contact.about}}</p>
      {{#if isProductive}}
        <p class='text-muted'>Responsible for {{productsCount}} amazing products!</p>
      {{/if}}
    </div>
    <div class='col-sm-3'>
      <img {{bind-attr src='contact.avatar' alt='contact.name'}} class='img-thumbnail img-rounded'/>
    </div>
  </script>
  <script type='text/x-handlebars' data-template-name='reviews'>
    <h3>Reviews</h3>
    <ul>
      {{#each}}
        {{#view 'App.ReviewView' tagName='li' length=text.length}}
          <div class='content'>{{markdown text}}</div>
          <span class='expand text-success'>
            Read {{#if view.isExpanded}}Less{{else}}More{{/if}}
          </span>
        {{/view}}
      {{else}}
        <li><p class='text-muted'><em>No reviews yet. Be the first to write one!</em></p></li>
      {{/each}}
    </ul>
  </script>
</body>
</html>

app.js

/* 定义创建命名空间    */

var App = Ember.Application.create({
  LOG_TRANSITIONS: true
});

/* 定义路由   */
App.Router.map(function() {
  this.route('credits', { path: '/thanks' });
  this.resource('products', function() {
    this.resource('product', { path: '/:product_id' });
    this.route('onsale');
    this.route('deals');
  });
  this.resource('contacts', function() {
    this.resource('contact', { path: '/:contact_id' });
  });
});



Ember.Handlebars.registerBoundHelper('markdown', function(text) {
 return new Handlebars.SafeString(markdown.toHTML(text));
});
Ember.Handlebars.registerBoundHelper('money', function(value) {
  return accounting.formatMoney(value/100);
});
/* index 控制器   */
App.IndexController = Ember.ArrayController.extend({
  productsCount: Ember.computed.alias('length'),
  logo: 'images/logo-small.png',
  time: function() {
    return (new Date()).toDateString();
  }.property(),
  onSale: function() {
    return this.filterBy('isOnSale').slice(0,3);
  }.property('@each.isOnSale')
});

/* contactsindex 控制器   */
App.ContactsIndexController = Ember.Controller.extend({
  contactName: 'Anostagia',
  avatar: 'images/avatar.png',
  open: function() {
    return ((new Date()).getDay() === 0) ? "Closed" : "Open";
  }.property()
});

/* Products 控制器 */
App.ProductsController = Ember.ArrayController.extend({
  sortProperties: ['title']
});

/* Products 控制器 */
App.ContactsController = Ember.ArrayController.extend({
  sortProperties: ['name'],
  contactsCount: Ember.computed.alias('length')
});

/* Products 控制器 */
App.ReviewsController = Ember.ArrayController.extend({
  sortProperties: ['reviewedAt'],
  sortAscending: false
});

/* Products 控制器 */
App.ContactProductsController = Ember.ArrayController.extend({
  sortProperties: ['title']
});

/* Products 控制器 */
App.ProductController = Ember.ObjectController.extend({
  ratings: [1,2,3,4,5],
  isNotReviewed: Ember.computed.alias('review.isNew'),
  review: function(){
    return this.store.createRecord('review',{
      product: this.get('model')
    });
  }.property('model'),
  actions: {
    createReview: function(){
      var controller = this;
      this.get('review').set('reviewedAt', new Date());
      this.get('review').save().then(function(review){
        controller.get('model.reviews')
                  .addObject(review);
      });
    }
  }
});


/*   模型 */
App.ProductsRoute = Ember.Route.extend({
  model: function() {
    return this.store.findAll('product');
  }
});

/*   模型 */
App.ContactsRoute = Ember.Route.extend({
  model: function() {
    return this.store.findAll('contact');
  }
});

/*   模型 */
App.IndexRoute = Ember.Route.extend({
  model: function(){
    return this.store.findAll('product');
  }
});

/*   模型 */
App.ProductsIndexRoute = Ember.Route.extend({
  model: function(){
    return this.store.findAll('product');
  }
});

/*   模型 */
App.ProductsOnsaleRoute = Ember.Route.extend({
  model: function(){
    return this.modelFor('products').filterBy('isOnSale');
  }
});

/*   模型 */
App.ProductsDealsRoute = Ember.Route.extend({
  model: function(){
    return this.modelFor('products').filter(function(product){
      return product.get('price') < 500;
    });
  }
});

/*   视图 */
App.ReviewView = Ember.View.extend({
  isExpanded: false,
  classNameBindings: ['isExpanded', 'readMore'],
  click: function(){
    this.toggleProperty('isExpanded');
  },
  readMore: function(){
    return this.get('length') > 140;
  }.property('length')
});
/*   视图 */
App.ProductDetailsComponent = Ember.Component.extend({
 reviewsCount: Ember.computed.alias('product.reviews.length'),
  hasReviews: function(){
    return this.get('reviewsCount') > 0;
  }.property('reviewsCount')
});
App.ContactDetailsComponent = Ember.Component.extend({
  productsCount: Ember.computed.alias('contact.products.length'),
  isProductive: function() {
    return this.get('productsCount') > 3;
  }.property('productsCount')
});
/*   视图 */
App.ProductView = Ember.View.extend({
  isOnSale: Ember.computed.alias('controller.isOnSale'),
  classNameBindings: ['isOnSale']
});

/*   适配器 */
App.ApplicationAdapter = DS.FixtureAdapter.extend();
App.Product = DS.Model.extend({
  title: DS.attr('string'),
  price: DS.attr('number'),
  description: DS.attr('string'),
  isOnSale: DS.attr('boolean'),
  image: DS.attr('string'),
  reviews: DS.hasMany('review', { async: true }),
  crafter: DS.belongsTo('contact', { async: true }),
  rating: function() {
    if(this.get('reviews.length') === 0) { return 0; }
    return this.get('reviews').reduce(function(previousValue, review) {
      return previousValue + review.get('rating');
    }, 0) / this.get('reviews.length');
  }.property('reviews.@each.rating')
});


/*   数据  */
App.Product.FIXTURES = [
 {  id: 1,
    title: 'Flint',
    price: 99,
    description: 'Flint is a hard, sedimentary cryptocrystalline form of the mineral quartz, categorized as a variety of chert.',
    isOnSale: true,
    image: 'images/products/flint.png',
    reviews: [100,101],
    crafter: 200
  },
  {
    id: 2,
    title: 'Kindling',
    price: 249,
    description: 'Easily combustible small sticks or twigs used for starting a fire.',
    isOnSale: false,
    image: 'images/products/kindling.png',
    reviews: [],
    crafter: 201
  },
  {
    id: 3,
    title: 'Matches',
    price: 499,
    description: 'One end is coated with a material that can be ignited by frictional heat generated by striking the match against a suitable surface.',
    isOnSale: true,
    reviews: [],
    image: 'images/products/matches.png',
    crafter: 201
  },
  {
    id: 4,
    title: 'Bow Drill',
    price: 999,
    description: 'The bow drill is an ancient tool. While it was usually used to make fire, it was also used for primitive woodworking and dentistry.',
    isOnSale: false,
    reviews: [],
    image: 'images/products/bow-drill.png',
    crafter: 200
  },
  {
    id: 5,
    title: 'Tinder',
    price: 499,
    description: 'Tinder is easily combustible material used to ignite fires by rudimentary methods.',
    isOnSale: true,
    reviews: [],
    image: 'images/products/tinder.png',
    crafter: 201
  },
  {
    id: 6,
    title: 'Birch Bark Shaving',
    price: 999,
    description: 'Fresh and easily combustable',
    isOnSale: true,
    reviews: [],
    image: 'images/products/birch.png',
    crafter: 201
  }
];


/*  model data */
App.Contact = DS.Model.extend({
  name: DS.attr('string'),
  about: DS.attr('string'),
  avatar: DS.attr('string'),
  products: DS.hasMany('product', { async: true })
});

/*   数据 */
App.Contact.FIXTURES = [
  {
    id: 200,
    name: 'Giamia',
    about: 'Although Giamia came from a humble spark of lightning, he quickly grew to be a great craftsman, providing all the warming instruments needed by those close to him.',
    avatar: 'images/contacts/giamia.png',
    products: [1,4]
  },
  {
    id: 201,
    name: 'Anostagia',
    about: 'Knowing there was a need for it, Anostagia drew on her experience and spearheaded the Flint & Flame storefront. In addition to coding the site, she also creates a few products available in the store.',
    avatar: 'images/contacts/anostagia.png',
    products: [2,3,5,6]
  }
];

App.Review = DS.Model.extend({
  text: DS.attr('string'),
  reviewedAt: DS.attr('date'),
  product: DS.belongsTo('product'),
  rating: DS.attr('number')
});
App.Review.FIXTURES = [
  {
    id: 100,
    reviewedAt: new Date('12/10/2013').getTime(),
    text: "Started a fire in no time!",
    rating: 4
  },
  {
    id: 101,
    reviewedAt: new Date('12/12/2013').getTime(),
    text: "Not the brightest flame, but warm!",
    rating: 5
  },
  {
    id: 102,
    reviewedAt: new Date('12/30/2013').getTime(),
    text: "This is some amazing Flint! It lasts **forever** and works even when damp! I still remember the first day when I was only a little fire sprite and got one of these in my flame stalking for treemas. My eyes lit up the moment I tried it! Here's just a few uses for it:\n\n* Create a fire using just a knife and kindling!\n* Works even after jumping in a lake (although, that's suicide for me)\n* Small enough to fit in a pocket -- if you happen to wear pants\n\n\nYears later I'm still using the _same one_. That's the biggest advantage of this -- it doesn't run out easily like matches. As long as you have something to strike it against, **you can start a fire anywhere** you have something to burn!",
    rating: 5
  }
];

转载于:https://www.cnblogs.com/cloudhan/p/5539044.html

 类似资料: