2017-06-05 63 views
0

我是Vue.js的新手,但試圖去掌握它。到目前爲止,它已經進展順利,而且我還有很長的路要走,但我堅持擴展父母模板。Vue.js + Require.js擴展父項

我正在試圖使擴展默認小部件佈局(在Boostrap中)的儀表板小部件。請注意,下面的代碼使用Vue,Require,Underscore & Axios。

父文件 - _Global.vue

<template> 
    <div class="panel panel-default"> 
     <div class="panel-heading"> 
      <b>{{ widgetTitle }}</b> 
      <div class="pull-right"> 
       <a href="#" v-on:click="toggleMinimized" 
        v-bind:title="(isMinimized ? 'Show widget' : 'Hide widget')"> 
        <i class="fa fa-fw" v-bind:class="isMinimized ? 'fa-plus' : 'fa-minus'"></i> 
       </a> 
      </div> 
     </div> 
     <div class="panel-body" v-if="!isMinimized"> 


      <div class="text-center text-muted" v-if="!isLoaded"> 
       <i class="fa fa-spin fa-circle-o-notch"></i><br /> 
      </div> 

      <parent v-if="isLoaded"> 
       <!-- parent content should appear here when loaded --> 
      </parent> 
     </div> 
    </div> 
</template> 
<script> 
    export default { 

     // setup our widget props 
     props: { 
      'minimized': { 
       'default': false, 
       'required': false, 
       'type': Boolean 
      } 
     }, 

     // define our data 
     data: function() { 
      return { 
       widgetTitle: 'Set widget title in data', 
       isLoaded: false, 
       isMinimized: this.$props.minimized 
      } 
     }, 

     // when vue is mounted, open our widget 
     mounted: function() { 
      if(!this.isMinimized) { 
       this.opened(); 
      } 
     }, 

     // define our methods 
     methods: { 

      // store our widget state to database 
      storeWidgetState: function() { 

       // set our data to send 
       let data = { 
        'action' : 'toggleWidget', 
        'widget' : this.$options._componentTag, 
        'state' : !this.isMinimized 
       }; 

       // post our data to our endpoint 
       axios.post(axios.endpoint, data); 
      }, 

      // toggle our minimized data 
      toggleMinimized: function (e) { 

       // prevent default 
       e.preventDefault(); 

       // toggle our minimized state 
       this.isMinimized = !this.isMinimized; 

       // trigger opened if we aren't minimized 
       if(!this.isMinimized) this.opened(); 

       // save our widget state to database 
       this.storeWidgetState(); 
      }, 

      // triggered when opened from being minimized 
      opened: function() { 
       console.log('opened() method is where all widget logic should be placed'); 
      } 
     } 

    } 
</script> 

子文件 - Example.vue 應該使用_Global.vue混入延伸,然後內.panel-body

<template> 
    <div> 
     I want this content to appear inside the .panel-body div 

     {{ content }} 

     <img v-bind:src="image.src" v-bind:alt="image.alt" 
      v-if="image.src" class="img-responsive" style="margin: 0 auto" /> 
    </div> 
</template> 
<script> 

    // import our widgets globals 
    import Global from './_Global.vue' 

    export default { 

     components: { 
      'parent': { 
       // what can I possibly put here?? 
      } 
     }, 

     // use our global mixin for all widgets 
     mixins: [Global], 

     // setup our methods for this widget 
     methods: { 

      opened: _.debounce(function() { 

       // make sure this can only be opened once 
       if(this.hasBeenOpened) return; 
       this.hasBeenOpened = true; 

       // temporarily allow axios to make external requests 
       let axiosHeaders = axios.defaults.headers.common; 
       let vm = this; 
       axios.defaults.headers.common = {}; 

       axios.get('https://yesno.wtf/api') 
        .then(function (res) { 

         // set our content 
         vm.content = null; 

         // set our image content 
         vm.image.src = res.data.image; 
         vm.image.alt = res.data.answer; 

        }) 
        .catch(function (err) { 

         // set our error text 
         vm.content = String(err); 

        }) 
        .then(function() { 

         // this will always hit.. 
         vm.isLoaded = true; 

        }); 

       // restore our axios headers for security 
       axios.defaults.headers.common = axiosHeaders; 
      }, 300) 
     }, 

     // additional data 
     data: function() { 
      return { 

       // set our widgets title 
       widgetTitle: 'Test title', 

       // logic for the specific widget 
       hasBeenOpened: false, 
       content: 'Loaded and ready to go...', 
       image: { 
        src: false, 
        alt: null 
       } 
      }; 
     }, 

    } 
</script> 

目前我的顯示內容父模板只是完全覆蓋我的子視圖。我可以使用它的唯一方法是通過顯式定義組件中的模板參數 - > parent:{},但我不想這麼做......?

+1

嘿克里斯,你已經嘗試過插槽? https://vuejs.org/v2/guide/components.html#Content-Distribution-with-Slots –

+0

@GerardoRosciano看起來如此接近我想要的,但我不太清楚重構我現有的代碼...可以我仍然使用該mixin例如?? 我可以有很多不同名稱的模板。問題是任何時候我創建一個.vue文件標籤它只是完全覆蓋主模板... – Chris

+0

@chis,我不確定使用混合。我會做的是顛倒組合,我會使用一個小部件組件,稱爲使用和內部widet(_global)我會調用一個動態組件:(as在props.type中):https://vuejs.org/v2/guide/components.html#Dynamic-Components希望它有幫助! –

回答

0

好的,謝謝Gerardo Rosciano爲我指出了正確的方向。我習慣了插槽來想出最終的解決方案。然後,我們訪問父級方法和數據屬性,只是爲了讓所有事情都按照它的原則工作。

Example.vue - 我們的例子中部件

<template> 
    <div> 
     <widget-wrapper> 
      <span slot="header">Example widget</span> 
      <div slot="content"> 
       <img v-bind:src="image.src" v-bind:alt="image.alt" 
        v-if="image.src" class="img-responsive" style="margin: 0 auto" /> 

       {{ content }} 
      </div> 
     </widget-wrapper> 
    </div> 
</template> 
<script> 

    // import our widgets globals 
    import WidgetWrapper from './_Widget.vue' 

    export default { 

     // setup our components 
     components: { 
      'widget-wrapper': WidgetWrapper 
     }, 

     // set our elements props 
     props: { 
      'minimized': { 
       'type': Boolean, 
       'default': false, 
       'required': false 
      } 
     }, 

     // setup our methods for this widget 
     methods: { 

      loadContent: _.debounce(function() { 

       // make sure this can only be opened once 
       if(this.hasBeenOpened) return; 
       this.hasBeenOpened = true; 

       // temporarily allow axios to make external requests 
       let axiosHeaders = axios.defaults.headers.common; 
       let vm = this; 
       axios.defaults.headers.common = {}; 

       axios.get('https://yesno.wtf/api') 
        .then(function (res) { 

         // set our content 
         vm.content = null; 

         // set our image content 
         vm.image.src = res.data.image; 
         vm.image.alt = res.data.answer; 

        }) 
        .catch(function (err) { 

         // set our error text 
         vm.content = String(err); 

        }) 
        .then(function() { 

         // this will always hit.. 
         vm.isLoaded = true; 

        }); 

       // restore our axios headers for security 
       axios.defaults.headers.common = axiosHeaders; 
      }, 300) 
     }, 

     // additional data 
     data: function() { 
      return { 

       // global param for parent 
       isLoaded: false, 

       // logic for the specific widget 
       hasBeenOpened: false, 
       content: 'Loaded and ready to go...', 
       image: { 
        src: false, 
        alt: null 
       } 
      }; 
     }, 

    } 
</script> 

_Widget.vue - 我們的基本的小工具,被延長

<template> 
    <div class="panel panel-default"> 
     <div class="panel-heading"> 
      <b><slot name="header">Slot header title</slot></b> 
      <div class="pull-right"> 
       <a href="#" v-on:click="toggleMinimized" 
        v-bind:title="(minimized ? 'Show widget' : 'Hide widget')"> 
        <i class="fa fa-fw" v-bind:class="minimized ? 'fa-plus' : 'fa-minus'"></i> 
       </a> 
      </div> 
     </div> 
     <div class="panel-body" v-if="!minimized"> 
      <div class="text-center text-muted" v-if="!isLoaded"> 
       <i class="fa fa-spin fa-circle-o-notch"></i><br /> 
       Loading... 
      </div> 
      <div v-else> 
       <slot name="content"></slot> 
      </div> 
     </div> 
    </div> 
</template> 

<script> 
    export default { 

     // get loaded state from our parent 
     computed: { 
      isLoaded: function() { 
       return this.$parent.isLoaded; 
      } 
     }, 

     // set our data element 
     data: function() { 
      return { 
       minimized: false 
      } 
     }, 

     // when the widget is mounted, trigger open state 
     mounted: function() { 
      this.minimized = this.$parent.minimized; 
      if(!this.minimized) this.opened(); 
     }, 

     // methods to manipulate our widget 
     methods: { 

      // save our widget state to database 
      storeWidgetState: function() { 

       // set our data to send 
       let data = { 
        'action' : 'toggleWidget', 
        'widget' : this.$parent.$options._componentTag, 
        'state' : !this.minimized 
       }; 

       // post this data to our endpoint 
       axios.post(axios.endpoint, data); 
      }, 

      // toggle our minimized state 
      toggleMinimized: function (e) { 

       // prevent default 
       e.preventDefault(); 

       // toggle our minimized state 
       this.minimized = !this.minimized; 

       // trigger opened if we aren't minimized 
       if(!this.minimized) this.opened(); 

       // save our widget state to database 
       this.storeWidgetState(); 
      }, 

      // when widget is opened, load content 
      opened: function() { 

       // make sure we have a valid loadContent method 
       if(typeof this.$parent.loadContent === "function") { 
        this.$parent.loadContent(); 
       } else { 
        console.log('You need to define a loadContent() method on the widget'); 
       } 
      } 
     } 
    } 
</script>